/* Copyright (C) 2023 Alif Semiconductor - All Rights Reserved.
 * Use, distribution and modification of this code is permitted under the
 * terms stated in the Alif Semiconductor Software License Agreement
 *
 * You should have received a copy of the Alif Semiconductor Software
 * License Agreement with this file. If not, please write to:
 * contact@alifsemi.com, or visit: https://alifsemi.com/license
 */

/**************************************************************************//**
 * @file     I3C_Baremetal_masterside.c
 * @author   Prabhakar kumar
 * @email    prabhakar.kumar@alifsemi.com
 * @version  V1.0.0
 * @date     27-May-2023
 * @brief    Baremetal to verify master and slave loop back test
 *
 *           Master sends n-bytes of data to Slave.
 *            Slave sends back received data to Master.
 *            then Master will compare send and received data
 *            (Master will continue sending data in loop,
 *             Master will stop if send and received data does not match).
 *
 *           Hardware Setup:
 *            Required two boards one for Master and one for Slave
 *             (as there is only one i3c instance is available on ASIC).
 *
 *           Connect SDA to SDA and SCL to SCL and GND to GND.
 *            - SDA P7_6 -> SDA P7_6
 *            - SCL P7_7 -> SCL P7_7
 *            - GND      -> GND
 * @bug      None.
 * @Note     None.
 ******************************************************************************/


/* System Includes */
#include <stdio.h>
#include "string.h"

/* Project Includes */
/* I3C Driver */
#include "Driver_I3C.h"
#include "system_utils.h"

/* PINMUX Driver */
#include "pinconf.h"
#include "Driver_GPIO.h"

#include "RTE_Device.h"
#include "RTE_Components.h"
#if defined(RTE_Compiler_IO_STDOUT)
#include "retarget_stdout.h"
#endif  /* RTE_Compiler_IO_STDOUT */

/* i3c Driver instance 0 */
extern ARM_DRIVER_I3C Driver_I3C;
static ARM_DRIVER_I3C *I3Cdrv = &Driver_I3C;

/* I3C slave target address */
#define I3C_SLV_TAR           (0x48)

/* transmit buffer from i3c */
uint8_t __ALIGNED(4) tx_data[4] = {0x00, 0x01, 0x02, 0x03};

/* receive buffer from i3c */
uint8_t __ALIGNED(4) rx_data[4] = {0x00};

uint32_t tx_cnt = 0;
uint32_t rx_cnt = 0;

/* flag for callback event. */
volatile uint32_t cb_event;

void i3c_master_loopback_demo();

/* i3c callback events */
typedef enum _I3C_CB_EVENT{
    I3C_CB_EVENT_SUCCESS        = (1 << 0),
    I3C_CB_EVENT_ERROR          = (1 << 1)
}I3C_CB_EVENT;

/**
  \fn          int32_t hardware_init(void)
  \brief       i3c hardware pin initialization:
                - PIN-MUX configuration
                - PIN-PAD configuration
  \param[in]   void
  \return      0:success; -1:failure
*/
int32_t hardware_init(void)
{
    /* for I3C_D(PORT_7 PIN_6(SDA)/PIN_7(SCL)) instance,
     *  for I3C in I3C mode (not required for I3C in I2C mode)
     *  GPIO voltage level(flex) has to be change to 1.8-V power supply.
     *
     *  GPIO_CTRL Register field VOLT:
     *   Select voltage level for the 1.8-V/3.3-V (flex) I/O pins
     *    0x0: I/O pin will be used with a 3.3-V power supply
     *    0x1: I/O pin will be used with a 1.8-V power supply
     */

    /* Configure GPIO flex I/O pins to 1.8-V:
     *  P7_6 and P7_7 pins are part of GPIO flex I/O pins,
     *   so we can use any one of the pin to configure flex I/O.
     */
#define GPIO7_PORT          7

    extern  ARM_DRIVER_GPIO ARM_Driver_GPIO_(GPIO7_PORT);
    ARM_DRIVER_GPIO *gpioDrv = &ARM_Driver_GPIO_(GPIO7_PORT);

    int32_t  ret = 0;
    uint32_t arg = 0;

    ret = gpioDrv->Initialize(PIN_6, NULL);
    if (ret != ARM_DRIVER_OK)
    {
        printf("ERROR: Failed to initialize GPIO \n");
        return ARM_DRIVER_ERROR;
    }

    ret = gpioDrv->PowerControl(PIN_6, ARM_POWER_FULL);
    if (ret != ARM_DRIVER_OK)
    {
        printf("ERROR: Failed to powered full GPIO \n");
        return ARM_DRIVER_ERROR;
    }

    /* select control argument as flex 1.8-V */
    arg = ARM_GPIO_FLEXIO_VOLT_1V8;
    ret = gpioDrv->Control(PIN_6, ARM_GPIO_CONFIG_FLEXIO, &arg);
    if (ret != ARM_DRIVER_OK)
    {
        printf("ERROR: Failed to control GPIO Flex \n");
        return ARM_DRIVER_ERROR;
    }

    /* I3C_SDA_D */
    pinconf_set(PORT_7, PIN_6, PINMUX_ALTERNATE_FUNCTION_6,
                PADCTRL_READ_ENABLE | PADCTRL_DRIVER_DISABLED_PULL_UP | \
                PADCTRL_OUTPUT_DRIVE_STRENGTH_4MA);

    /* I3C_SCL_D */
    pinconf_set(PORT_7, PIN_7, PINMUX_ALTERNATE_FUNCTION_6,
                PADCTRL_READ_ENABLE | PADCTRL_DRIVER_DISABLED_PULL_UP | \
                PADCTRL_OUTPUT_DRIVE_STRENGTH_4MA);

    return ARM_DRIVER_OK;
}

/**
  \fn          void I3C_callback(UINT event)
  \brief       i3c isr callback
  \param[in]   event: i3c Event
  \return      none
*/
static void I3C_callback(uint32_t event)
{
    if (event & ARM_I3C_EVENT_TRANSFER_DONE)
    {
        cb_event = I3C_CB_EVENT_SUCCESS;
    }
    if (event & ARM_I3C_EVENT_TRANSFER_ERROR)
    {
        cb_event = I3C_CB_EVENT_ERROR;
    }
}

/**
  \fn          void i3c_master_loopback_demo(void)
  \brief       TestApp to verify i3c master mode loopback
               This demo does:
                 - initialize i3c driver;
                 - 1 byte of data transmitted from master and slave receive 1 byte and
                   same 1 byte of data received by slave is transmitted through slave
                   transmit and master receive 1byte.
  \return      none
*/
void i3c_master_loopback_demo(void)
{
    uint32_t   ret    = 0;
    uint32_t   len    = 0;
    int32_t    cmp    = 0;
    uint8_t    slave_addr = 0x00;

    /* I3C CCC (Common Command Codes) */
    ARM_I3C_CMD i3c_cmd;

    ARM_DRIVER_VERSION version;

    printf("\r\n \t\t >>> Master loop back demo starting up!!! <<< \r\n");

    /* Get i3c driver version. */
    version = I3Cdrv->GetVersion();
    printf("\r\n i3c version api:0x%X driver:0x%X \r\n",
                           version.api, version.drv);

    if((version.api < ARM_DRIVER_VERSION_MAJOR_MINOR(7U, 0U))       ||
       (version.drv < ARM_DRIVER_VERSION_MAJOR_MINOR(7U, 0U)))
    {
        printf("\r\n Error: >>>Old driver<<< Please use new one \r\n");
        return;
    }

    /* Initialize i3c hardware pins using PinMux Driver. */
    ret = hardware_init();
    if(ret != 0)
    {
        printf("\r\n Error: i3c hardware_init failed.\r\n");
        return;
    }

#if RTE_I3C_BLOCKING_MODE_ENABLE
    /* Initialize I3C driver */
    ret = I3Cdrv->Initialize(NULL);
#else
    /* Initialize I3C driver */
    ret = I3Cdrv->Initialize(I3C_callback);
#endif
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: I3C Initialize failed.\r\n");
        return;
    }

    /* Power up I3C peripheral */
    ret = I3Cdrv->PowerControl(ARM_POWER_FULL);
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: I3C Power Up failed.\r\n");
        goto error_uninitialize;
    }

    /* Initialize I3C master */
    ret = I3Cdrv->Control(I3C_MASTER_INIT, 0);
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: Master Init control failed.\r\n");
        goto error_uninitialize;
    }

    /* i3c Speed Mode Configuration: Bus mode slow  */
    ret = I3Cdrv->Control(I3C_MASTER_SET_BUS_MODE,
                          I3C_BUS_SLOW_MODE);

    /* Reject Hot-Join request */
    ret = I3Cdrv->Control(I3C_MASTER_SETUP_HOT_JOIN_ACCEPTANCE, 0);
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: Hot Join control failed.\r\n");
        goto error_uninitialize;
    }

    /* Reject Master request */
    ret = I3Cdrv->Control(I3C_MASTER_SETUP_MR_ACCEPTANCE, 0);
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: Master Request control failed.\r\n");
        goto error_uninitialize;
    }

    /* Reject Slave Interrupt request */
    ret = I3Cdrv->Control(I3C_MASTER_SETUP_SIR_ACCEPTANCE, 0);
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: Slave Interrupt Request control failed.\r\n");
        goto error_uninitialize;
    }

    sys_busy_loop_us(1000);

    /* Assign Dynamic Address to i3c slave */
    printf("\r\n >> i3c: Get dynamic addr for static addr:0x%X.\r\n",I3C_SLV_TAR);

    /* clear callback event flag. */
    cb_event = 0;

    i3c_cmd.rw            = 0U;
    i3c_cmd.cmd_id        = I3C_CCC_SETDASA;
    i3c_cmd.len           = 1U;
    /* Assign Slave's Static address */
    i3c_cmd.addr          = I3C_SLV_TAR;
    i3c_cmd.data          = NULL;
    i3c_cmd.def_byte      = 0U;

    ret = I3Cdrv->MasterAssignDA(&i3c_cmd);
    if(ret != ARM_DRIVER_OK)
    {
#if !RTE_I3C_BLOCKING_MODE_ENABLE
        printf("\r\n Error: I3C MasterAssignDA failed.\r\n");
        goto error_poweroff;
#endif
    }

#if !RTE_I3C_BLOCKING_MODE_ENABLE
    /* wait for callback event. */
    while(!((cb_event == I3C_CB_EVENT_SUCCESS) ||
            (cb_event == I3C_CB_EVENT_ERROR)));
#endif

    if((cb_event == I3C_CB_EVENT_ERROR) ||
       ((RTE_I3C_BLOCKING_MODE_ENABLE && (ret != ARM_DRIVER_OK))))
    {
        printf("\r\n Error: First attempt failed. retrying \r\n");
        /* Delay */
        sys_busy_loop_us(1000);

        /* clear callback event flag. */
        cb_event = 0;

        /* Observation:
         *  Master needs to send "MasterAssignDA" two times,
         *  First time slave is not giving ACK.
         */

        /* Assign Dynamic Address to i3c slave */
        ret = I3Cdrv->MasterAssignDA(&i3c_cmd);
        if(ret != ARM_DRIVER_OK)
        {
            printf("\r\n Error: I3C MasterAssignDA failed.\r\n");
            goto error_poweroff;
        }

#if !RTE_I3C_BLOCKING_MODE_ENABLE
        /* wait for callback event. */
        while(!((cb_event == I3C_CB_EVENT_SUCCESS) ||
                (cb_event == I3C_CB_EVENT_ERROR)));

        if(cb_event == I3C_CB_EVENT_ERROR)
        {
            printf("\nError: I3C MasterAssignDA failed\n");
            while(1);
        }
#endif
    }

    /* Get assigned dynamic address for the static address */
    ret = I3Cdrv->GetSlaveDynAddr(I3C_SLV_TAR, &slave_addr);
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: I3C Failed to get Dynamic Address.\r\n");
        goto error_poweroff;
    }
    else
    {
        printf("\r\n >> i3c: Rcvd dyn_addr:0x%X for static addr:0x%X\r\n",
                slave_addr,I3C_SLV_TAR);
    }

    /* i3c Speed Mode Configuration: Normal I3C mode */
    ret = I3Cdrv->Control(I3C_MASTER_SET_BUS_MODE,
                          I3C_BUS_NORMAL_MODE);

    sys_busy_loop_us(1000);

    while(1)
    {
        len = 4;

        /* Delay */
        sys_busy_loop_us(100);

        /* fill any random TX data. */
        tx_data[0] += 1;
        tx_data[1] += 1;
        tx_data[2] += 1;
        tx_data[3] += 1;

        /* clear callback event flag. */
        cb_event = 0;

        /* Master transmit */
        ret = I3Cdrv->MasterTransmit(slave_addr, tx_data, len);
        if(ret != ARM_DRIVER_OK)
        {
            printf("\r\n Error: I3C Master Transmit failed. \r\n");
            goto error_poweroff;
        }
#if !RTE_I3C_BLOCKING_MODE_ENABLE
        /* wait for callback event. */
        while(!((cb_event == I3C_CB_EVENT_SUCCESS) ||
                (cb_event == I3C_CB_EVENT_ERROR)));

        if(cb_event == I3C_CB_EVENT_ERROR)
        {
            printf("\nError: I3C Master transmit Failed\n");
            while(1);
        }
#endif
        tx_cnt += 1;

        /* Delay */
        sys_busy_loop_us(1000);

        /* clear rx_data buffer */
        rx_data[0] = 0x00;
        rx_data[1] = 0x00;
        rx_data[2] = 0x00;
        rx_data[3] = 0x00;

        /* clear callback event flag. */
        cb_event = 0;

        /* Master receive */
        ret = I3Cdrv->MasterReceive(slave_addr, rx_data, len);
        if(ret != ARM_DRIVER_OK)
        {
            printf("\r\n Error: I3C Master Receive failed. \r\n");
            goto error_poweroff;
        }

#if !RTE_I3C_BLOCKING_MODE_ENABLE
        /* wait for callback event. */
        while(!((cb_event == I3C_CB_EVENT_SUCCESS) ||
                (cb_event == I3C_CB_EVENT_ERROR)));

        if(cb_event == I3C_CB_EVENT_ERROR)
        {
            printf("\nError: I3C Master Receive failed.\n");
            while(1);
        }
#endif
        rx_cnt += 1;

        /* compare tx and rx data, stop if data does not match */
        cmp = memcmp(tx_data, rx_data, len);
        if(cmp != 0)
        {
           printf("\nError: TX and RX data mismatch.\n");
           while(1);
        }
}
error_poweroff:

    /* Power off I3C peripheral */
    ret = I3Cdrv->PowerControl(ARM_POWER_OFF);
    if(ret != ARM_DRIVER_OK)
    {
         printf("\r\n Error: I3C Power OFF failed.\r\n");
    }

error_uninitialize:

    /* Un-initialize I3C driver */
    ret = I3Cdrv->Uninitialize();
    if(ret != ARM_DRIVER_OK)
    {
        printf("\r\n Error: I3C Uninitialize failed.\r\n");
    }

    printf("\r\n I3C demo exiting...\r\n");
}

/* Define main entry point.  */
int main()
{
    #if defined(RTE_Compiler_IO_STDOUT_User)
    int32_t ret;
    ret = stdout_init();
    if(ret != ARM_DRIVER_OK)
    {
        while(1)
        {
        }
    }
    #endif
    /* Enter the ThreadX kernel.  */
    i3c_master_loopback_demo();
}

/************************ (C) COPYRIGHT ALIF SEMICONDUCTOR *****END OF FILE****/
