/**
 * \file    main.c
 * \brief   main entry
 *
 * \par     Copyright (c) 2018 UWE Technologies Corp., Ltd. All Rights Reserved.
 * \author  James
 * \date    2018-03-05
 * \version 
 * 
 * \history 
 *  Date        Note
 *  2018-03-05  Original Creation
 * 
 */

#include <stdarg.h>
#include "libc.h"
#include "drivers.h"
#include "cli_engine.h"
#include "sys_gpio.h"
#include "sys_low_power.h"
#include "sys_task.h"
#include "sys_uart1.h"
#include "sys_i2c.h"
#include "sys_spi.h"
#include "sys_config.h"
#include "cli_cmd_usbpd.h"
#include "usbpd.h"
#if SYS_OLED_DISPLAY_ENABLED
#include "sys_oled_display.h"
#endif /* SYS_OLED_DISPLAY_ENABLED */
#include "sys_pd_hook.h"
#if SYS_SMG_DISPLAY_ENABLED
#include "sys_smg_display.h"
#endif /* SYS_SMG_DISPLAY_ENABLED */


#if (SYS_FREERTOS_ENABLED)

/* Kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"


/*
 * FreeRTOS hook for when malloc fails, enable in FreeRTOSConfig.
 */
void vApplicationMallocFailedHook( void );

/*
 * FreeRTOS hook for when freertos is idling, enable in FreeRTOSConfig.
 */
void vApplicationIdleHook( void );


/* The queue that is used to send message to vUartRxTask for display in the
terminal output window. */
QueueHandle_t xUartRxQueue = 0;

static void vUartRxTask( void *pvParameters )
{
    UART_RX_DATA_t rxData;
    u8_t i;

    /* Just to stop compiler warnings. */
    ( void ) pvParameters;

    for( ;; )
    {
        /* Wait for a message to arrive. */
        if ( xQueueReceive( xUartRxQueue, &rxData, portMAX_DELAY ) == pdPASS )
        {
            sys_lpm_activate();
#if 0  /* modified by James Gan - 2019-05-27 */
            switch (rxData.ucId) 
            {
                case DRV_UART_ID_0:
                    /* Parse the message and handle CLI command */
                    if (rxData.ucLength > UART_RX_FIFO_SIZE)
                        rxData.ucLength = UART_RX_FIFO_SIZE;
                    for (i = 0; i < rxData.ucLength; i++)
                    {
                        cli_do_work(rxData.ucData[i]);
                        sys_lpm_activate();
                    }
                    break;
                case DRV_UART_ID_1:
                    /* Parse the UART1 data and handle CLI command */
                    if (rxData.ucLength > UART_RX_FIFO_SIZE)
                        rxData.ucLength = UART_RX_FIFO_SIZE;
                    for (i = 0; i < rxData.ucLength; i++)
                    {
                        sys_uart1_handler(rxData.ucData[i]);
                        sys_lpm_activate();
                    }
                    break;
                default:
                    break;
            }
#else
            if (rxData.ucId == drv_uart_second_get())
            {
                /* Parse the UART1 data and handle CLI command */
                if (rxData.ucLength > UART_RX_FIFO_SIZE)
                    rxData.ucLength = UART_RX_FIFO_SIZE;
                for (i = 0; i < rxData.ucLength; i++)
                {
                    sys_uart1_handler(rxData.ucData[i]);
                    sys_lpm_activate();
                }
            }
            else {
                /* Parse the message and handle CLI command */
                if (rxData.ucLength > UART_RX_FIFO_SIZE)
                    rxData.ucLength = UART_RX_FIFO_SIZE;
                for (i = 0; i < rxData.ucLength; i++)
                {
                    cli_do_work(rxData.ucData[i]);
                    sys_lpm_activate();
                }
            }
#endif /* #if 0 */
        }
    }
}

QueueHandle_t xInterfaceQueue = 0;

#if ((I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) || (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED))
static void vInterfaceRxHandler( void *pvParameters )
{
    u32_t rxData;

    /* Just to stop compiler warnings. */
    ( void ) pvParameters;

    for( ;; )
    {
        if ( xQueueReceive( xInterfaceQueue, &rxData, portMAX_DELAY ) == pdPASS )
        {
            sys_lpm_activate();
#if CLI_ENABLED
            if (sys_cfg_dbg_get())
            {
                drv_uart_putstr("INTFRX=");
                drv_uart_putdec(rxData);
                drv_uart_putchar(UART_LF);
            }
#endif
            /* Parse the queue, only one word */
            switch (rxData)
            {
#if (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED)
                case IPIC_CFG_INTR_IDX_SPI_INT:
                    sys_spi_handler();
                    break;
#endif /* (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED) */
#if (I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED)
#if (!I2C_SLA_CB_ENABLED)
                case IPIC_CFG_INTR_IDX_SEC_I2C_INT:
                    sys_i2c_handler(DRV_I2C_ID_1);
                    break;
                case IPIC_CFG_INTR_IDX_I2C_INT:
                    sys_i2c_handler(DRV_I2C_ID_0);
                    break;
#endif /* (!I2C_SLA_CB_ENABLED) */
#endif /* (I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) */
                default:
                    break;
            }
        }
    }
}
#endif /* ((I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) || (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED)) */

static void vUSBPDTask( void *pvParameters )
{
    TickType_t xDelayTicks = 1;
    /* Just to stop compiler warnings. */
    ( void ) pvParameters;

    for( ;; )
    {
        /* call dpm main handler */
        usbpd_main();
        vTaskDelay(xDelayTicks);
    }
}

/*-----------------------------------------------------------*/

void vApplicationMallocFailedHook( void )
{
    /* vApplicationMallocFailedHook() will only be called if
    configUSE_MALLOC_FAILED_HOOK is set to 1 in FreeRTOSConfig.h.  It is a hook
    function that will get called if a call to pvPortMalloc() fails.
    pvPortMalloc() is called internally by the kernel whenever a task, queue,
    timer or semaphore is created.  It is also called by various parts of the
    demo application.  If heap_1.c or heap_2.c are used, then the size of the
    heap available to pvPortMalloc() is defined by configTOTAL_HEAP_SIZE in
    FreeRTOSConfig.h, and the xPortGetFreeHeapSize() API function can be used
    to query the size of free heap space that remains (although it does not
    provide information on how the remaining heap might be fragmented). */
    drv_uart_puts("\nMalloc failed, halt.");
    taskDISABLE_INTERRUPTS();
    for( ;; );
}
/*-----------------------------------------------------------*/

void vApplicationIdleHook( void )
{
    /* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set
    to 1 in FreeRTOSConfig.h.  It will be called on each iteration of the idle
    task.  It is essential that code added to this hook function never attempts
    to block in any way (for example, call xQueueReceive() with a block time
    specified, or call vTaskDelay()).  If the application makes use of the
    vTaskDelete() API function (as this demo application does) then it is also
    important that vApplicationIdleHook() is permitted to return to its calling
    function, because it is the responsibility of the idle task to clean up
    memory allocated by the kernel to any task that has since been deleted. */

    //vInterfaceRxHandler(NULL);

    sys_state_check_and_sleep();

#if ((SPI_MODULE_ENABLED)&&(SPI_MODULE_SLAVE_ENABLED)&&(SPI_MODULE_SLAVE_DMA_ENABLED))
    sys_spi_dma_handler();
#endif /* ((SPI_MODULE_ENABLED)&&(SPI_MODULE_SLAVE_ENABLED)&&(SPI_MODULE_SLAVE_DMA_ENABLED)) */

#if (SYS_OLED_DISPLAY_ENABLED)
    /* call system oled handler */
    sys_oled_handler();
#endif /* (SYS_OLED_DISPLAY_ENABLED) */
}
/*-----------------------------------------------------------*/

#endif /* if (SYS_FREERTOS_ENABLED) */


void vApplicationStackOverflowHook( void *pxTask, char *pcTaskName )
{
    ( void ) pcTaskName;
    ( void ) pxTask;

    /* Run time stack overflow checking is performed if
    configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2.  This hook
    function is called if a stack overflow is detected. */
    drv_uart_putstr("\nStack overflow [");
    drv_uart_putstr(pcTaskName);
    drv_uart_puts("], halt.");
    //taskDISABLE_INTERRUPTS();
    for( ;; );
}

/**
 * Main entry
 *
 * \return 0 success, other failure
 */
int main (int argc, char *argv[])
{
    //Restore program address offset to 0
    DRV_REG_SET(PRG_ADDR_OFFSET_REG, 0);
    sys_gpio_uart0_disable();

    // If FreeRTOS is enabled, need to set system init flag
    sync_sys_init();

    //Init interrtupt
    drv_intr_init();

    //Init driver level PD interrupt handler
    drv_pd_intr_init(TRUE);

    //Init software timer
    drv_timer_init();

    //Init driver level GPIO interrupt handler
    drv_gpio_intr_handler_init();

    if (!drv_chip_wkup_flg())
    {
        //Init system level GPIOs
        sys_gpio_init();
    }

    //Init uart
    drv_uart_default_set(DRV_UART_ID_1);
    drv_uart_init(); 

#if SYS_WDT_ENABLED
    //Init watchdog, system need to feed watchdog at regular intervals
    drv_wdt_init(60000U, TRUE);
    volatile drv_timer_t wdt_timer = {0,1,0,0,0,0,0};
    drv_timer_start(10000U, &wdt_timer, (void *)drv_wdt_feed);
#endif /* SYS_WDT_ENABLED */

#if I2C_MODULE_ENABLED
    //Init I2C
    drv_i2c_init();

    //Init I2C 0
    drv_i2c_master_freq_set(DRV_I2C_ID_0, I2C_FREQ_400K);
    drv_i2c_open(DRV_I2C_ID_0, SLAVE_ADDRESS_0);
#if 0  /* deleted by James Gan - 2019-09-03 */
    //Init I2C 1
    drv_i2c_master_freq_set(DRV_I2C_ID_1, I2C_FREQ_400K);
    drv_i2c_open(DRV_I2C_ID_1, SLAVE_ADDRESS_1);
#endif /* #if 0 */
#if (I2C_MODULE_SLA_ENABLED && I2C_SLA_CB_ENABLED)
    drv_i2c_sla_rx_handler_set(DRV_I2C_ID_0, sys_i2c_rx_handler);
    drv_i2c_sla_tx_handler_set(DRV_I2C_ID_0, sys_i2c_tx_handler);
    drv_i2c_sla_rx_handler_set(DRV_I2C_ID_1, sys_i2c_rx_handler);
    drv_i2c_sla_tx_handler_set(DRV_I2C_ID_1, sys_i2c_tx_handler);
#endif /* I2C_SLA_CB_ENABLED */
#endif /* I2C_MODULE_ENABLED */

    // Init task
    sys_task_init();

    // load configuration from flash
    sys_cfg_load();
    // Load PD configuration
    sys_pd_cfg_load();

    //Print brand information
#if CLI_ENABLED
    if (sys_cfg_dbg_get())
    {
        if (!drv_chip_wkup_flg())
        {
            drv_uart_putchar(UART_LF);
            cli_print_brand(TRUE);
        }
        else {
            drv_uart_puts("wakeup!");
        }
    }
#endif /* CLI_ENABLED */

    //Init CLI engine
#if CLI_ENABLED
    cli_init();
    //Init PD CLI commands
    cli_cmd_usbpd_init();
#endif

    //Init PD module

    drv_adc_current_sense_init(DRV_ADC_CURRENT_SENSE_GAIN_15);

    usbpd_init();
    sys_pd_hook_init();
    sys_task_create(usbpd_main, 0);

#if SYS_OLED_DISPLAY_ENABLED
    sys_oled_control(usbpd_cfg_oled_display_mode_get());
#endif

    //Configure on sleep and wakeup handlers
    sys_lpm_on_sleep_handler_set(dpm_on_sleep_handler);
    sys_lpm_on_wakeup_handler_set(dpm_on_wakeup_handler);
    //Low power module initialization, include wake up restore
    sys_lpm_init();

#if (SYS_FREERTOS_ENABLED)

#if ((I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) || (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED))
    xInterfaceQueue = xQueueCreate( SYS_INTF_QUEUE_LENGTH, SYS_INTF_QUEUE_SIZE );
#endif /* ((I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) || (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED)) */

    /* Create the queue used to pass message to vUartRxTask. */
    xUartRxQueue = xQueueCreate( UART_RX_QUEUE_LENGTH, UART_RX_QUEUE_ITEM_SIZE );

    xTaskCreate( vUartRxTask, "URX", SYS_UART_TASK_STACK_SIZE, NULL, SYS_UART_TASK_PRIORITY, NULL );

#if ((I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) || (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED))
    xTaskCreate( vInterfaceRxHandler, "INT", SYS_INTF_TASK_STACK_SIZE, NULL, SYS_INTF_TASK_PRIORITY, NULL );
#endif /* ((I2C_MODULE_ENABLED && I2C_MODULE_SLA_ENABLED) || (SPI_MODULE_ENABLED && SPI_MODULE_SLAVE_ENABLED)) */

    xTaskCreate( vUSBPDTask, "PD", SYS_USBPD_TASK_STACK_SIZE, NULL, SYS_USBPD_TASK_PRIORITY, NULL );

#endif /* #if (SYS_FREERTOS_ENABLED) */

#if SYS_SMG_DISPLAY_ENABLED
    volatile drv_timer_t smg_display_timer = {0,1,0,0,0,0,0};
    drv_timer_start(1, &smg_display_timer, (void *)sys_smg_display_scan);
#endif /* SYS_SMG_DISPLAY_ENABLED */

    //Enable modules interrupt state
    drv_pd_intr_en_set(TRUE);
    drv_uart_common_intr_set(drv_uart_default_get(), TRUE);

    if (usbpd_cfg_test_mode_get())
    {
        volatile drv_timer_t uart0_en_timer = {0,0,0,0,0,0,0};
        drv_timer_start(5000, &uart0_en_timer, (void *)sys_gpio_uart0_enable);
    }
    sync_sys_init_done();

    sys_task_start_scheduler();

    return 0;
}
