/*
 * 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 configures the USB peripheral of the TM4C123GH6PM MCU to
 * operate in USB device mode and enumerate as an HID Keyboard device.  Then
 * the UART0 peripheral is configured with RX interrupts and the SW1 & SW2
 * buttons of the EK-TM4C123GXL are configured to trigger interrupts when
 * pressed.
 *
 * The prvMessageChangeTask waits for a task notification from the UART RX
 * interrupt and when running it processes the latest UART message.  The
 * buffer size to hold UART messages is set by UART_BUFFER_SIZE.  There are
 * two buffers used so the current message is not overwritten until a complete
 * new message is received.  Once a complete message has been received, a queue
 * is used to send the updated pointer and message length information to the
 * USB HID task.
 *
 * For this example, a complete message is considered to be received by the
 * message being terminated by a CR-LF sequence.  If a message is sent that
 * exceeds the size of UART_BUFFER_SIZE, then the message will be wrapped to
 * the next buffer.  If three or more messages are sent sequentially without
 * printing any messages out, only the first two will be stored for display.
 *
 * The prvKeyboardOutputTask waits for a semaphore to be given from the ISR of
 * the push buttons.  Once received, it will check which button was pressed
 * and type out the corresponding message.  The message is typed out to
 * whatever program is currently selected and it is recommended to have a
 * notepad or word document program open when pressing the buttons to see the
 * full message output.
 *
 * The right button sends a default message that does not get overwritten at
 * any point.  The left button initially sends a default message but that
 * message will get overwritten as soon as the UART interface receives a valid
 * message and sends the message details via the queue.  Whenever the left
 * button is pressed, the task will look to see if a message has been received
 * in the queue and update the pointer and message length accordingly.
 *
 */

/* 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/usbhid.h"
#include "usblib/device/usbdevice.h"
#include "usblib/device/usbdhid.h"
#include "usblib/device/usbdhidkeyb.h"
#include "utils/ustdlib.h"
#include "drivers/rtos_hw_drivers.h"
#include "usb_keyb_structs.h"
#include "usb_keyb_codetable.h"
/*-----------------------------------------------------------*/

/*
 * Definition for the size of the message buffer that gets updated through the
 * UART interface.  Two buffers will be made using this so adjust the
 * parameter accordingly.
 */
#define UART_BUFFER_SIZE 255

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

/*
 * Time stamp global variable for switch debounce.
 */
volatile static uint32_t g_ui32TimeStamp = 0;

/*
 * Global variable to log the last GPIO button pressed.
 */
volatile static uint32_t g_pui32ButtonPressed = NULL;

/*
 *  This global indicates whether or not we are connected to a USB host.
 */
volatile bool g_bConnected = false;

/*
 * This global indicates whether or not the USB bus is currently in the
 * suspended state.
 */
volatile bool g_bSuspended = false;

/*
 * The number of items the queue can hold.  This is two as the UART task only
 * has two buffers setup.
 */
#define mainQUEUE_LENGTH                    ( 2 )

/*
 * The queue structure for transferring the data length and pointer to the
 * data buffer.
 */
struct AMessage
{
    uint32_t ui32MsgValue;
    uint32_t *pvMsgData;
} xMessage;

/*
 * The binary semaphore used to trigger USB data outputs via button presses.
 */
static SemaphoreHandle_t xButtonSemaphore = NULL;

/*
 * The queue used to transfer complete UART messages to the USB output task.
 */
static QueueHandle_t xStructQueue = NULL;

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

/*
 * Declare a variable that is used to hold the handle of the USB keyboard
 * output task.
 */
TaskHandle_t xUSBKeyboardTask = NULL;

/*
 * The tasks as described in the comments at the top of this file.
 */
static void prvMessageChangeTask( void *pvParameters );
static void prvKeyboardOutputTask( void *pvParameters );

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

/* 
 * Configure the USB peripheral to function as a USB Device in HID mode and
 * to enumerate as a USB keyboard device.
 */
static void prvConfigureUSB( void );

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

/*
 * Hardware configuration for the buttons SW1 and SW2 to generate interrupts.
 */
static void prvConfigureButton( void );
/*-----------------------------------------------------------*/

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

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

    /* Configure the button to generate interrupts. */
    prvConfigureButton();

    /* Create the binary semaphore used to synchronize the button ISR and the
     * USB Keyboard task. */
    xButtonSemaphore = xSemaphoreCreateBinary();

    /* Create a Queue that is two items long and will store the message
     * information for the next USB transaction. */
    xStructQueue = xQueueCreate(mainQUEUE_LENGTH, sizeof(xMessage));

    if( ( xStructQueue != NULL ) && ( xButtonSemaphore != 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( prvMessageChangeTask,
                     "UART RX",
                     configMINIMAL_STACK_SIZE + UART_BUFFER_SIZE*2,
                     NULL,
                     tskIDLE_PRIORITY + 2,
                     &xUARTReceiveTask );

        /* 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( prvKeyboardOutputTask,
                     "USB TX",
                     configMINIMAL_STACK_SIZE + STORED_MESSAGE_SIZE,
                     NULL,
                     tskIDLE_PRIORITY + 2,
                     &xUSBKeyboardTask );
    }
}
/*-----------------------------------------------------------*/

static void prvMessageChangeTask( void *pvParameters )
{
unsigned long ulEventsToProcess;
struct AMessage xQueueMessage;
uint32_t pui32PrimaryBuffer[UART_BUFFER_SIZE] = {'0'};
uint32_t pui32SecondaryBuffer[UART_BUFFER_SIZE] = {'0'};
uint32_t ui32PrimaryBufferLength;
uint32_t ui32SecondaryBufferLength;
bool bFillPrimary;

    /* Initialize local variables. */
    ui32PrimaryBufferLength = 0;
    ui32SecondaryBufferLength = 0;
    xQueueMessage.pvMsgData = &pui32PrimaryBuffer[0];
    xQueueMessage.ui32MsgValue = 0;
    bFillPrimary = true;

    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 (uxQueueMessagesWaiting( xStructQueue ) == 2)
            {
                /* The queue is full, discard the current message. */
                while(UARTCharsAvail(UART0_BASE))
                {
                    /* Read a character from the UART FIFO. */
                    UARTCharGet(UART0_BASE);
                }
            }
            else
            {
                if (bFillPrimary)
                {
                    /* Read data from the UART FIFO until there is none left
                     * or we run out of space in our receive buffer. */
                    while((ui32PrimaryBufferLength < UART_BUFFER_SIZE) &&
                            UARTCharsAvail(UART0_BASE))
                    {
                        /* Read a character from the UART FIFO into the ring
                         * buffer if no errors are reported. */
                        pui32PrimaryBuffer[ui32PrimaryBufferLength++] =
                                UARTCharGet(UART0_BASE);
                    }

                    /* If the message length has reached the end of the buffer
                     * or the terminating character sequence of CR-LF has been
                     * received, then toggle to the secondary buffer and send a
                     * queue message to let the USB task know a new completed
                     * message has been received. */
                    if ((ui32PrimaryBufferLength == UART_BUFFER_SIZE) ||
                        (pui32PrimaryBuffer[ui32PrimaryBufferLength-1] == 0x0A) ||
                        (pui32PrimaryBuffer[ui32PrimaryBufferLength-2] == 0x0D))
                    {
                        /* Toggle to secondary buffer. */
                        xQueueMessage.pvMsgData = &pui32PrimaryBuffer[0];
                        xQueueMessage.ui32MsgValue = ui32PrimaryBufferLength;
                        ui32SecondaryBufferLength = 0;
                        bFillPrimary = false;

                        /* Send the entire structure by value to the queue. */
                        xQueueSend( xStructQueue,
                                    ( void * ) &xQueueMessage,
                                    ( TickType_t ) 0 );
                    }
                }
                else
                {
                    /* Read data from the UART FIFO until there is none left
                     * or we run out of space in our receive buffer. */
                    while((ui32SecondaryBufferLength < UART_BUFFER_SIZE) &&
                            UARTCharsAvail(UART0_BASE))
                    {
                        /* Read a character from the UART FIFO into the ring
                         * buffer if no errors are reported. */
                        pui32SecondaryBuffer[ui32SecondaryBufferLength++] =
                                UARTCharGet(UART0_BASE);
                    }

                    /* If the message length has reached the end of the buffer
                     * or the terminating character sequence of CR-LF has been
                     * received, then toggle to the primary buffer and send a
                     * queue message to let the USB task know a new completed
                     * message has been received. */
                    if ((ui32SecondaryBufferLength == UART_BUFFER_SIZE) ||
                        (pui32SecondaryBuffer[ui32SecondaryBufferLength-1] == 0x0A) ||
                        (pui32SecondaryBuffer[ui32SecondaryBufferLength-2] == 0x0D))
                    {
                        /* Toggle to primary buffer. */
                        xQueueMessage.pvMsgData = &pui32SecondaryBuffer[0];
                        xQueueMessage.ui32MsgValue = ui32SecondaryBufferLength;
                        ui32PrimaryBufferLength = 0;
                        bFillPrimary = true;

                        /* Send the entire structure by value to the queue. */
                        xQueueSend( xStructQueue,
                                    ( void * ) &xQueueMessage,
                                    ( TickType_t ) 0 );
                    }
                }
            }
        }
    }
}
/*-----------------------------------------------------------*/

void prvKeyboardOutputTask( void *pvParameters )
{
struct AMessage xQueueMessage;
uint32_t *pui32QueueBuffer;
uint32_t *pui32OutputBuffer;
uint32_t pui32IntroMessageBuffer[STORED_MESSAGE_SIZE];
uint32_t ui32QueueMessageSize;
uint32_t ui32OutputMessageSize;
uint32_t ui32Count;
uint32_t ui32Char;

    /* Fill Default Buffer with message from usb_keyb_codetable.h. */
    for (ui32Count = 0; ui32Count < STORED_MESSAGE_SIZE; ui32Count++)
    {
        pui32IntroMessageBuffer[ui32Count] = g_pui32DefaultMessages[ui32Count];
    }

    /* Point the queue buffer to initial data from the intro message buffer. */
    pui32QueueBuffer = &pui32IntroMessageBuffer[INTRO_MESSAGE_SIZE];
    ui32QueueMessageSize = OVERWRITE_MESSAGE_SIZE;

    for( ;; )
    {
        /* Block until the ISR gives the semaphore. */
        if( xSemaphoreTake(xButtonSemaphore, portMAX_DELAY) == pdPASS)
        {
            /* Determine which button was pressed. */
            if(g_pui32ButtonPressed == USR_SW2)
            {
                /* Check if a message has been received in the Queue. */
                if( xQueueReceive( xStructQueue, &( xQueueMessage ), 0) == pdPASS )
                {
                    /* Update the queue buffer pointer and message size based
                     * on data received from the queue. This is done separately
                     * as the output buffer details change depend on the button
                     * pressed. */
                    pui32QueueBuffer = xQueueMessage.pvMsgData;
                    ui32QueueMessageSize = xQueueMessage.ui32MsgValue;
                }

                /* Set the output buffer to print out data based on the queue
                 * buffer. */
                pui32OutputBuffer = pui32QueueBuffer;
                ui32OutputMessageSize = ui32QueueMessageSize;
            }
            else if (g_pui32ButtonPressed == USR_SW1)
            {
                /* Set the output buffer to print out data based on the default
                 * buffer. */
               pui32OutputBuffer = &pui32IntroMessageBuffer[0];
                ui32OutputMessageSize = INTRO_MESSAGE_SIZE;
            }

            /* Loop through the entire buffer printing out each character via
             * the USB keyboard interface. */
            for (ui32Count = 0; ui32Count < ui32OutputMessageSize; ui32Count++)
            {
                /* Get the next character. */
                ui32Char = pui32OutputBuffer[ui32Count];

                /* Skip this character if it is a non-printable character. */
                if((ui32Char < ' ') || (ui32Char > '~'))
                {
                    /* Allow LF to work with this example. */
                    if (ui32Char != '\n')
                    {
                        continue;
                    }
                }

                /* Check for LF and if there is one, assign the table value.
                 * Otherwise, convert the character per the keyboard table. */
                if (ui32Char == '\n')
                {
                    ui32Char = 0x5f;
                }
                else
                {
                    /* Convert the character into an index into the keyboard
                     * usage code table. */
                    ui32Char -= ' ';
                }

                /* Send the key press message with the USB HID Keyboard
                 * interface. */
                if(USBDHIDKeyboardKeyStateChange((void *)&g_sKeyboardDevice,
                                                 g_ppi8KeyUsageCodes[ui32Char][0],
                                                 g_ppi8KeyUsageCodes[ui32Char][1],
                                                 true) != KEYB_SUCCESS)
                {
                    break;
                }

                /* Wait for USB TX to notify the data was sent.
                 * Break from the loop if it fails. */
                if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY) == pdFALSE)
                {
                    g_bConnected = false;
                    break;
                }

                /* Send the key release message with the USB HID Keyboard
                 * interface. */
                if(USBDHIDKeyboardKeyStateChange((void *)&g_sKeyboardDevice, 0,
                                                 g_ppi8KeyUsageCodes[ui32Char][1],
                                                 false) != KEYB_SUCCESS)
                {
                    break;
                }

                /* Wait for USB TX to notify the data was sent.
                 * Break from the loop if it fails. */
                if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY) == pdFALSE)
                {
                    g_bConnected = false;
                    break;
                }
            }
        }
    }
}
/*-----------------------------------------------------------*/

static void prvConfigureUSB( void )
{
uint32_t ui32PLLRate;

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

    /* 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 our device information to the USB HID device class driver,
     * initialize the USB controller, and connect the device to the bus. */
    USBDHIDKeyboardInit(0, &g_sKeyboardDevice);
}
/*-----------------------------------------------------------*/

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 the FIFO RX interrupt based
     * on fill as well as detected errors. */
    UARTIntClear(UART0_BASE, UARTIntStatus(UART0_BASE, false));
    UARTIntEnable(UART0_BASE, ( UART_INT_RT |  UART_INT_RX ));

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

static void prvConfigureButton( void )
{
    /* Initialize the LaunchPad Buttons. */
    ButtonsInit();

    /* Configure both switches to trigger an interrupt on a falling edge. */
    GPIOIntTypeSet(BUTTONS_GPIO_BASE, ALL_BUTTONS, GPIO_FALLING_EDGE);

    /* Enable the interrupt for LaunchPad GPIO Port in the GPIO peripheral. */
    GPIOIntEnable(BUTTONS_GPIO_BASE, ALL_BUTTONS);

    /* Enable the Port J interrupt in the NVIC. */
    IntEnable(INT_GPIOJ);
}
/*-----------------------------------------------------------*/

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

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

    /* 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( xUARTReceiveTask, &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 xButtonsHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ui32Status;

    /* If the USB bus is not connected, clear the flag but don't process
     * anything further about the interrupt. */
    if (!g_bConnected)
    {
        GPIOIntClear(BUTTONS_GPIO_BASE, GPIOIntStatus(BUTTONS_GPIO_BASE, true));

        return;
    }

    if (g_bSuspended)
    {
        /* If the bus is suspended then resume it and continue execution.*/
        USBDHIDKeyboardRemoteWakeupRequest((void *)&g_sKeyboardDevice);
    }

    /* Initialize the xLEDTaskWoken as pdFALSE.  This is required as the
     * FreeRTOS interrupt safe API will change it if needed should a
     * context switch be required. */
    xHigherPriorityTaskWoken = pdFALSE;

    /* Read the buttons interrupt status to find the cause of the interrupt. */
    ui32Status = GPIOIntStatus(BUTTONS_GPIO_BASE, true);

    /* Clear the interrupt. */
    GPIOIntClear(BUTTONS_GPIO_BASE, ui32Status);

    /* Debounce the input with 200ms filter */
    if ((xTaskGetTickCount() - g_ui32TimeStamp ) > 200)
    {
        /* Log which button was pressed to trigger the ISR. */
        if ((ui32Status & USR_SW1) == USR_SW1)
        {
            g_pui32ButtonPressed = USR_SW1;
        }
        else if ((ui32Status & USR_SW2) == USR_SW2)
        {
            g_pui32ButtonPressed = USR_SW2;
        }

        /* Give the semaphore to unblock prvProcessSwitchInputTask.  */
        xSemaphoreGiveFromISR( xButtonSemaphore, &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 );
    }

    /* Update the time stamp. */
    g_ui32TimeStamp = xTaskGetTickCount();
}
/*-----------------------------------------------------------*/

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. */
}


