/*
 * usb_task
 *
 * Copyright (C) 2022 Texas Instruments Incorporated
 * 
 * 
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions 
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the   
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
*/

/******************************************************************************
 *
  * The vUSBTask first configures the USB peripheral of the TM4C123GH6PM MCU to
 * operate in USB device mode and enumerate as an USB CDC device.  Then the
 * UART0 peripheral is configured with both TX and RX interrupts enabled.
 * Then a mutex and a queue are created followed by the three application
 * tasks described below.  The queue is used for sending details on any errors
 * while the mutex is used to guard the UART TX process from being disrupted.
 *
 * The prvUARTtoUSBXferTask waits for a task notification from the UART RX
 * interrupt and when running it processes the latest UART message.  The task
 * reads out the available data in the UART RX buffer and transmits them over
 * the USB CDC interface.  When used with serial COM terminal software on a PC,
 * this means a message sent to the COM port connected to the UART0 interface
 * will display on the COM terminal for the USB CDC interface.
 *
 * The vUSBtoUARTXferTask waits for a task notification from the USB handler
 * when data has been received.  Once the notification is received, the mutex
 * is taken to prevent multiple USB interrupts from disrupting the UART output
 * process.  The USB buffer is read for the latest received data which is then
 * sent over UART with TX interrupts enabled.  This process will continue until
 * all bytes received are transmitted over UART  When used with serial COM
 * terminal software on a PC, this means a message sent to the COM port
 * connected to the USB CDC interface will display on the COM terminal for the
 * UART0 interface.
 *
 * The prvUARTErrorHandlerTask tracks any errors to send to the USB stack so
 * the bus is aware of the issue and can recover accordingly.  A queue is used
 * to send the error to this task and if no errors occur the task will just
 * block indefinitely.
 *
 */

/* Standard includes. */
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

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

/* Hardware includes. */
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_uart.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/usbcdc.h"
#include "usblib/usb-ids.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdcdc.h"
#include "usb_serial_structs.h"
/*-----------------------------------------------------------*/

/*
 * The system clock frequency.
 */
extern uint32_t g_ui32SysClock;

/*
 * Flag indicating whether or not the application is currently sending a
 * Break condition.
 */
extern bool g_bSendingBreak;

/*
 * The mutex used to guard the USB RX to UART TX resource between tasks.
 */
static SemaphoreHandle_t xMutex = NULL;

/*
 * Declare a variable that is used to hold the handle of the UART to USB
 * transfer task.
 */
static TaskHandle_t xUARTtoUSBTask = NULL;

/*
 * Declare a variable that is used to hold the handle of the USB to UART
 * transfer task.
 */
TaskHandle_t xUSBtoUARTTask = NULL;

/*
 * Queue used to send and receive UART errors.
 */
static QueueHandle_t xErrorQueue = NULL;

/*
 * The tasks as described in the comments at the top of this file.
 */
static void prvUARTtoUSBXferTask( void *pvParameters );
extern void vUSBtoUARTXferTask( void *pvParameters );
static void prvUARTErrorHandlerTask( void *pvParameters );

/*
 * Called by main() to create the USB application.
 */
void vUSBTask( void );

/* 
 * Configure the USB peripheral to function as a USB Device in Bulk mode.
 */
static void prvConfigureUSB( void );

/*
 * Configure the UART peripheral to send and receive data with TX and RX
 * interrupts enabled.
 */
static void prvConfigureUART( void );
/*-----------------------------------------------------------*/

void vUSBTask( void )
{
    /* Configure the USB peripheral and enumerate it as a CDC Device. */
    prvConfigureUSB();

    /* Configure the UART peripheral for transfer with interrupts enabled. */
    prvConfigureUART();

    /* Before a semaphore is used it must be explicitly created. In this
     * example a mutex type semaphore is created. */
    xMutex = xSemaphoreCreateMutex();

    /* Create a Queue that is one item long which will store the signed 32-bit
     * error value for UART receptions. */
    xErrorQueue = xQueueCreate(1, sizeof(int32_t));

    if( ( xMutex != NULL ) && ( xErrorQueue != NULL ) )
    {
        /* The xTaskCreate parameters in order are:
         *  - The function that implements the task.
         *  - The text name for the output task - for debug only as it is not
         *    used by the kernel.
         *  - The size of the stack to allocate to the task.
         *  - The parameter passed to the task - just to check the functionality.
         *  - The priority assigned to the task.
         *  - The task handle is not required, so NULL is passed. */
        xTaskCreate( prvUARTtoUSBXferTask,
                    "UART->USB",
                    configMINIMAL_STACK_SIZE,
                    NULL,
                    tskIDLE_PRIORITY + 2,
                    &xUARTtoUSBTask );

        /* The xTaskCreate parameters in order are:
         *  - The function that implements the task.
         *  - The text name for the output task - for debug only as it is not
         *    used by the kernel.
         *  - The size of the stack to allocate to the task.
         *  - The parameter passed to the task - just to check the functionality.
         *  - The priority assigned to the task.
         *  - The task handle is not required, so NULL is passed. */
        xTaskCreate( vUSBtoUARTXferTask,
                    "USB->UART",
                    configMINIMAL_STACK_SIZE,
                    NULL,
                    tskIDLE_PRIORITY + 2,
                    &xUSBtoUARTTask );

        /* The xTaskCreate parameters in order are:
         *  - The function that implements the task.
         *  - The text name for the output task - for debug only as it is not
         *    used by the kernel.
         *  - The size of the stack to allocate to the task.
         *  - The parameter passed to the task - just to check the functionality.
         *  - The priority assigned to the task.
         *  - The task handle is not required, so NULL is passed. */
        xTaskCreate( prvUARTErrorHandlerTask,
                    "UART Error",
                    configMINIMAL_STACK_SIZE,
                    NULL,
                    tskIDLE_PRIORITY + 3,
                    NULL );
    }

    /* Enable interrupts now that the application is ready to start. */
    IntEnable(INT_UART0);
}
/*-----------------------------------------------------------*/

static void prvUARTtoUSBXferTask( void *pvParameters )
{
unsigned long ulEventsToProcess;
int32_t i32Char, i32Errors;
uint8_t ucChar;
uint32_t ui32Space;

    for( ;; )
    {
        /* Wait to receive a notification sent directly to this task from the
         * interrupt service routine. */
        ulEventsToProcess = ulTaskNotifyTake( pdTRUE, portMAX_DELAY );

        if (ulEventsToProcess != 0)
        {
            /* Clear our error indicator. */
            i32Errors = 0;

            /* How much space do we have in the buffer? */
            ui32Space = USBBufferSpaceAvailable((tUSBBuffer *)&g_sTxBuffer);

            /* Read data from the UART FIFO until there is none left or we run
             * out of space in our receive buffer. */
            while(ui32Space && UARTCharsAvail(UART0_BASE))
            {
                /* Read a character from the UART FIFO into the ring buffer if
                 * no errors are reported. */
                i32Char = UARTCharGetNonBlocking(UART0_BASE);

                /* If the character did not contain any error notifications,
                 * copy it to the output buffer. */
                if(!(i32Char & ~0xFF))
                {
                    ucChar = (uint8_t)(i32Char & 0xFF);

                    USBBufferWrite((tUSBBuffer *)&g_sTxBuffer,
                                   (uint8_t *)&ucChar, 1);

                    /* Decrement the number of bytes we know the buffer can
                    accept. */
                    ui32Space--;
                }
                else
                {
                    /* Update our error accumulator. */
                    i32Errors |= i32Char;
                }
            }

            /* Check to see if we need to notify the host of any errors we just
             * detected. */
            if(i32Errors != 0)
            {
                /* Send the accumulated error indicators to the error handler
                 * task. */
                xQueueSend( xErrorQueue, ( void * )i32Errors, 0);
            }
        }
    }
}
/*-----------------------------------------------------------*/

void vUSBtoUARTXferTask( void *pvParameters )
{
unsigned long ulEventsToProcess;
uint32_t ui32Read, ui32Ints;
uint8_t ucChar;

    for( ;; )
    {
        /* Wait to receive a notification sent directly to this task from the
         * interrupt service routine. */
        ulEventsToProcess = ulTaskNotifyTake( pdTRUE, portMAX_DELAY );

        if (ulEventsToProcess != 0)
        {
            /* If we are currently sending a break condition, don't receive any
             * more data. We will resume transmission once the break is turned
             * off. */
            if(!g_bSendingBreak)
            {
                /* The mutex is created before the scheduler is started, so
                 * already exists by the time this task executes.  Attempt to
                 * take the mutex, blocking indefinitely to wait for the mutex
                 * if it is not available right away. */
                if ( xSemaphoreTake( xMutex, portMAX_DELAY ) == pdTRUE )
                {
                    /* If there is space in the UART FIFO, try to read some
                     * characters from the receive buffer to fill it again. */
                    while(UARTSpaceAvail(UART0_BASE))
                    {
                        /* Get a character from the buffer. */
                        ui32Read = USBBufferRead((tUSBBuffer *)&g_sRxBuffer,
                                                 &ucChar, 1);

                        /* Did we get a character? */
                        if(ui32Read)
                        {
                            /* Place the character in the UART TX FIFO. */
                            UARTCharPut(UART0_BASE, ucChar);
                        }
                        else
                        {
                            /* We ran out of characters so check is a UART TX
                             * interrupt is pending. */
                            ui32Ints = UARTIntStatus(UART0_BASE, true);
                            if (ui32Ints & UART_INT_TX)
                            {
                                /* Do nothing, let ISR get processed. */
                            }
                            else
                            {
                                /* Verify the output buffer is empty and then
                                 * turn off the transmit interrupt. */
                                if(!USBBufferDataAvailable(&g_sRxBuffer))
                                {
                                    UARTIntDisable(UART0_BASE, UART_INT_TX);
                                }
                            }

                            /* Break from the while() loop. */
                            break;
                        }
                    }
                }

                /* The mutex MUST be given back. */
                xSemaphoreGive( xMutex );
            }
        }
    }
}
/*-----------------------------------------------------------*/

static void prvUARTErrorHandlerTask( void *pvParameters )
{
unsigned short usSerialState;
int32_t i32Errors;

    for (;;)
    {
        if( xQueueReceive(xErrorQueue, &( i32Errors ), portMAX_DELAY))
        {
            /* Clear our USB serial state.  Since we are faking the handshakes,
             * always set the TXCARRIER (DSR) and RXCARRIER (DCD) bits. */
            usSerialState = USB_CDC_SERIAL_STATE_TXCARRIER |
                            USB_CDC_SERIAL_STATE_RXCARRIER;

            /* Are any error bits set? */
            if(i32Errors)
            {
                /* At least one error is being notified so translate from our
                 * hardware error bits into the correct state markers for the
                 * USB notification. */
                if(i32Errors & UART_DR_OE)
                {
                    usSerialState |= USB_CDC_SERIAL_STATE_OVERRUN;
                }

                if(i32Errors & UART_DR_PE)
                {
                    usSerialState |= USB_CDC_SERIAL_STATE_PARITY;
                }

                if(i32Errors & UART_DR_FE)
                {
                    usSerialState |= USB_CDC_SERIAL_STATE_FRAMING;
                }

                if(i32Errors & UART_DR_BE)
                {
                    usSerialState |= USB_CDC_SERIAL_STATE_BREAK;
                }

                /* Call the CDC driver to notify the state change. */
                USBDCDCSerialStateChange((void *)&g_sCDCDevice, usSerialState);
            }
        }
    }
}
/*-----------------------------------------------------------*/

static void prvConfigureUSB( void )
{
uint32_t ui32PLLRate;

    /* Enable the peripherals used by this application. */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);

    /* Initialize the transmit and receive buffers. */
    USBBufferInit(&g_sTxBuffer);
    USBBufferInit(&g_sRxBuffer);

    /* Tell the USB library the CPU clock and the PLL frequency.  This is a
     * new requirement for TM4C129 devices. */
    SysCtlVCOGet(SYSCTL_XTAL_25MHZ, &ui32PLLRate);
    USBDCDFeatureSet(0, USBLIB_FEATURE_CPUCLK, &g_ui32SysClock);
    USBDCDFeatureSet(0, USBLIB_FEATURE_USBPLL, &ui32PLLRate);

    /* Forcing device mode so that the VBUS and ID pins are not used or
     * monitored by the USB controller. For USB OTG, this function should
     * not be called.  If the USB Host will supply power, and the LaunchPad
     * power jumper is set to "OTG", this function should not be called. */
    USBStackModeSet(0, eUSBModeForceDevice, 0);

    /* Pass the device information to the USB library and place the device
     * on the bus. */
    USBDCDCInit(0, &g_sCDCDevice);
}
/*-----------------------------------------------------------*/

static void prvConfigureUART( void )
{
    /* Enable the peripherals used by this application. */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);

    /* Set the default UART configuration to 115200 baud and 8-N-1. */
    UARTConfigSetExpClk(UART0_BASE, g_ui32SysClock, 115200,
                        (UART_CONFIG_WLEN_8 | UART_CONFIG_PAR_NONE |
                         UART_CONFIG_STOP_ONE));

    /* Set the UART FIFO levels so interrupts can fire when either the TX or
     * RX is half-full. */
    UARTFIFOLevelSet(UART0_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8);

    /* Configure and enable UART interrupts for FIFO TX & RX interrupts based
     * on fill as well as detected errors. */
    UARTIntClear(UART0_BASE, UARTIntStatus(UART0_BASE, false));
    UARTIntEnable(UART0_BASE, (UART_INT_OE | UART_INT_BE | UART_INT_PE |
                  UART_INT_FE | UART_INT_RT | UART_INT_TX | UART_INT_RX));
}
/*-----------------------------------------------------------*/

void xUSBUARTIntHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ui32Ints;

    /* Get and clear the current interrupt source(s). */
    ui32Ints = UARTIntStatus(UART0_BASE, true);
    UARTIntClear(UART0_BASE, ui32Ints);

    /* Are we being interrupted because the TX FIFO has space available? */
    if(ui32Ints & UART_INT_TX)
    {
        /* If the output buffer is empty, turn off the transmit interrupt. */
        if(!USBBufferDataAvailable(&g_sRxBuffer))
        {
            UARTIntDisable(UART0_BASE, UART_INT_TX);
        }
        else
        {
            /* The xHigherPriorityTaskWoken parameter must be initialized to
             * pdFALSE as it will get set to pdTRUE inside the interrupt safe
             * API function if a context switch is required. */
            xHigherPriorityTaskWoken = pdFALSE;

            /* Defer the interrupt processing to a Task to minimize time spent
             * within the hardware interrupt service routine.  Send a
             * notification directly to the task to which interrupt processing
             * is being deferred. */
            vTaskNotifyGiveFromISR( xUSBtoUARTTask, &xHigherPriorityTaskWoken );

            /* This FreeRTOS API call will handle the context switch if it is
             * required or have no effect if that is not needed. */
            portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
        }
    }

    /* Handle receive interrupts. */
    if(ui32Ints & (UART_INT_RX | UART_INT_RT))
    {
        /* The xHigherPriorityTaskWoken parameter must be initialized to
         * pdFALSE as it will get set to pdTRUE inside the interrupt safe
         * API function if a context switch is required. */
        xHigherPriorityTaskWoken = pdFALSE;

        /* Defer the interrupt processing to a Task to minimize time spent
         * within the hardware interrupt service routine.  Send a notification
         * directly to the task to which interrupt processing is being
         * deferred. */
        vTaskNotifyGiveFromISR( xUARTtoUSBTask, &xHigherPriorityTaskWoken );

        /* This FreeRTOS API call will handle the context switch if it is
         * required or have no effect if that is not needed. */
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
}
/*-----------------------------------------------------------*/

void vApplicationTickHook( void )
{
    /* This function will be called by each tick interrupt if
        configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h.  User code can be
        added here, but the tick hook is called from an interrupt context, so
        code must not attempt to block, and only the interrupt safe FreeRTOS API
        functions can be used (those that end in FromISR()). */

    /* Only the full demo uses the tick hook so there is no code is
        executed here. */
}


