/*
 * Copyright 2022 MindMotion Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "board_init.h"

#include "hal_usb.h"
#include "hal_usb_bdt.h"

/*
 * As some HID devices require additional configuration, this example cannot guarantee that each HID device can be read raw.
 *
 * you can download the following example to another board as usb device to connect the host:
 * Keyboard: MindSDK/application/demo_apps/tinyusb/tud_hid_keyboard.
 * Mouse:    MindSDK/application/demo_apps/tinyusb/tud_hid_mouse.
 */

/*
 * Macros.
 */

#define APP_USB_SETUP_PACKET_SIZE   8u
#define APP_USB_MAX_DEV_EP_BUF_SIZE 64u
#define APP_USB_DESP_BUF_SIZE       255u
#define APP_USB_SOF_THRESHOLD       255u
#define APP_USB_TIMEOUT             10000u

/*
 * Variables.
 */

/* usb buffer descriptor table. */
__ALIGNED(512) USB_BufDespTable_Type usb_bdt = {0u};
/* the buffer descriptor be used to send packet. */
USB_BufDesp_Type * usb_tx_bd = &usb_bdt.Table[0u][1u][0u];
/* the buffer descriptor be used to recv packet. */
USB_BufDesp_Type * usb_rx_bd = &usb_bdt.Table[0u][0u][0u];

/* the buffer be used for recv packet. */
uint8_t usb_desp_buf[APP_USB_DESP_BUF_SIZE] = {0u};
/* the desp size. */
uint32_t usb_desp_size = 0u;

/* the function of this packet is to get device descriptor from device. */
uint8_t usb_get_dev_desp [] = {0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00};
/* the function of this packet is to set device addr is 0x01. */
uint8_t usb_set_addr_desp[] = {0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
/* the function of this packet is to set configuration. */
uint8_t usb_set_cfg_desp [] = {0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
/* the function of this packet is to set idle. */
uint8_t usb_set_idle_desp[] = {0x21, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

/* task step. */
uint32_t usb_task_step = 0u;
/* get or set desp step. */
uint32_t usb_task_sub_step = 0u;
/* DATA0 or DATA1 packet to be xfer. */
uint32_t usb_data_n = 0u;

/*
 * Declerations.
 */

/* init usb host. */
void app_usb_init(void);
/* delay function. */
void app_delay(uint32_t delay);

/*
 * Functions.
 */
int main(void)
{
    BOARD_Init();

    printf("usbh_hid_raw example.\r\n");

    /* init USB Host. */
    app_usb_init();

    while (1)
    {
    }
}

/* init usb host. */
void app_usb_init(void)
{
    USB_Host_Init_Type usb_init;
    usb_init.BufDespTable_Addr = (uint32_t)(&usb_bdt); /* set usb bdt addr. */
    usb_init.SofThreshold      = APP_USB_SOF_THRESHOLD; /* set SOF threshold, make sure no data xfered when host send SOF token. */
    USB_InitHost(BOARD_USB_PORT, &usb_init);

    USB_EnableOddEvenReset(BOARD_USB_PORT, true); /* only use even buf desp, this example will not usb odd buf desp xfer data. */
    USB_SetDeviceAddr(BOARD_USB_PORT, 0x00); /* set usb addr is 0x00, to xfer data when device attached. */

    /* enable interrupt. */
    NVIC_ClearPendingIRQ(BOARD_USB_IRQn);
    USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_ATTACH, true); /* wait device attached. */
    NVIC_EnableIRQ(BOARD_USB_IRQn);
}

/* print the contents in buf in hex format. */
void app_print_buf(uint8_t * buf, uint32_t len)
{

    for(uint32_t i = 0u; i < len; i++)
    {
        if (i > 0u && i % 8u == 0u)
        {
            printf("\r\n      ");
        }
        printf("%02X ", buf[i]);
    }
    printf("\r\n");
}

/* delay function. */
void app_delay(uint32_t delay)
{
    for (uint32_t i = 0u; i < delay; i++)
    {
        for (uint32_t j = 0u; j < CLOCK_SYS_FREQ / 1000u; j++) /* at least 1ms. */
        {
            __NOP();
        }
    }
}

/* this function be use to get desp. */
bool usb_get_desp(uint8_t * setup_packet)
{
    bool ret = false; /* not done. */

    switch(usb_task_sub_step)
    {
        case 0u: /* send setup packet, this case will be operate by USB_INT_SOFTOK. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false); /* disable softok, until send setup packet done. */
            USB_BufDesp_Reset(usb_tx_bd);
            USB_BufDesp_Xfer(usb_tx_bd, 0u, setup_packet, APP_USB_SETUP_PACKET_SIZE);
            for(uint32_t i = 0; i < APP_USB_TIMEOUT; i++)
            {
                if (USB_SetToken(BOARD_USB_PORT, 0u, USB_TokenPid_SETUP))
                {
                    break;
                }
            }
            usb_task_sub_step++;
            break;
        case 1u: /* send setup packet done, this case will be operate by USB_INT_TOKENDONE. */
            usb_task_sub_step++;
            usb_data_n = 1u;
            usb_desp_size = 0u;
            USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true); /* prepare to recv desp. */
            break;
        case 2u: /* recv desp, this case will be operate by USB_INT_SOFTOK. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false);
            USB_BufDesp_Reset(usb_rx_bd);
            USB_BufDesp_Xfer(usb_rx_bd, usb_data_n, &usb_desp_buf[usb_desp_size], APP_USB_MAX_DEV_EP_BUF_SIZE);
            usb_data_n = !usb_data_n;
            for(uint32_t i = 0; i < APP_USB_TIMEOUT; i++)
            {
                if (USB_SetToken(BOARD_USB_PORT, 0u, USB_TokenPid_IN))
                {
                    break;
                }
            }
            usb_task_sub_step++;
            break;
        case 3u: /* loop to recv desp until recv done, this case will be operate by USB_INT_TOKENDONE. */
            usb_desp_size += USB_BufDesp_GetPacketSize(usb_rx_bd);
            if (usb_desp_buf[0u] > usb_desp_size) /* loop to recv. */
            {
                usb_task_sub_step--; /* back to last case. */
            }
            else /* send ZLP. */
            {
                usb_task_sub_step++;
            }
            USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true); /* prepare to xfer next data. */
            break;
        case 4u: /* send ZLP done, this case will be operate by USB_INT_SOFTOK. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false);
            USB_BufDesp_Reset(usb_tx_bd);
            USB_BufDesp_Xfer(usb_tx_bd, 1u, NULL, 0u);
            for(uint32_t i = 0; i < APP_USB_TIMEOUT; i++)
            {
                if (USB_SetToken(BOARD_USB_PORT, 0u, USB_TokenPid_OUT))
                {
                    break;
                }
            }
            usb_task_sub_step++;
            break;
        case 5u: /* send ZLP done, this case will be operate by USB_INT_TOKENDONE. */
            usb_task_sub_step = 0u;
            ret = true;
            usb_data_n = 0u;
            break;
        default:
            break;
    }
    return ret;
}

/* this func be use to set desp. */
bool usb_set_desp(const uint8_t * setup_packet)
{
    bool ret = false; /* not recv desp. */
    switch(usb_task_sub_step)
    {
        case 0u: /* send setup packet, this case will be operate by USB_INT_SOFTOK. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false); /* disable softok, until send setup packet done. */
            USB_BufDesp_Reset(usb_tx_bd);
            USB_BufDesp_Xfer(usb_tx_bd, 0, (uint8_t *)setup_packet, APP_USB_SETUP_PACKET_SIZE);
            for(uint32_t i = 0; i < APP_USB_TIMEOUT; i++)
            {
                if (USB_SetToken(BOARD_USB_PORT, 0u, USB_TokenPid_SETUP))
                {
                    break;
                }
            }
            usb_task_sub_step++;
            break;
        case 1u: /* send setup packet done, this case will be operate by USB_INT_TOKENDONE. */
            usb_task_sub_step++;
            USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            break;
        case 2u: /* recv ZLP, this case will be operate by USB_INT_SOFTOK. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false); /* disable softok, until send setup packet done. */
            USB_BufDesp_Reset(usb_rx_bd);
            USB_BufDesp_Xfer(usb_rx_bd, 1, NULL, 0u);
            for(uint32_t i = 0; i < APP_USB_TIMEOUT; i++)
            {
                if (USB_SetToken(BOARD_USB_PORT, 0u, USB_TokenPid_IN))
                {
                    break;
                }
            }
            usb_task_sub_step++;
            break;
        case 3u: /* recv ZLP done, this case will be operate by USB_INT_TOKENDONE. */
            usb_task_sub_step = 0;
            ret = true;
            break;
        default:
            break;
    }
    return ret;
}

/* this func be use to get hid report. */
bool usb_get_report(void)
{
    bool ret = false; /* not recv desp. */
    switch(usb_task_sub_step)
    {
        case 0u: /* send setup packet, this case will be operate by USB_INT_SOFTOK. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false); /* disable softok, until send setup packet done. */
            USB_BufDesp_Reset(usb_rx_bd);
            USB_BufDesp_Xfer(usb_rx_bd, 0, (uint8_t *)usb_desp_buf, APP_USB_MAX_DEV_EP_BUF_SIZE);
            for(uint32_t i = 0; i < APP_USB_TIMEOUT; i++)
            {
                if (USB_SetToken(BOARD_USB_PORT, 1u, USB_TokenPid_IN))
                {
                    break;
                }
            }
            usb_task_sub_step++;
            break;
        case 1u: /* send setup packet done, this case will be operate by USB_INT_TOKENDONE. */
            usb_desp_size = USB_BufDesp_GetPacketSize(usb_rx_bd);
            printf("recv: ");
            app_print_buf(usb_desp_buf, usb_desp_size);
            USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            usb_task_sub_step = 0;
            ret = true;
            break;
        default:
            break;
    }
    return ret;
}

/* detach process. */
void usb_detach_process(void)
{
    printf("detached.\r\n");
    usb_task_step = 0u; /* clear usb task step. */
    usb_task_sub_step = 0; /* clear sub step. */
    usb_data_n = 0; /* clear data_n. */
    usb_get_dev_desp[6] = 0x40; /* recorvery dev desp packet. */
    USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_ATTACH, true); /* wait device attached. */
    USB_ClearInterruptStatus(USB, USB_INT_ATTACH); /* clear last attached interrupt status. */
    USB_Enable(BOARD_USB_PORT, false); /* stop send SOF token. */
    USB_SetDeviceAddr(BOARD_USB_PORT, 0x00); /* set usb addr is 0x00, to xfer data when device attached. */
    USB_EnableEndPoint(BOARD_USB_PORT, 0, USB_EndPointMode_NULL, false); /* disable ep0. */
    USB_EnableLowSpeed(BOARD_USB_PORT, false);
}

/* attached process. */
void usb_attach_process()
{
    printf("attached.\r\n");
    USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_ATTACH, false);
    USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_RESET);

    /* get speed. */
    if (USB_BusSignalStatus_J == USB_GetBusSignalStatus(BOARD_USB_PORT) ) /* full speed. */
    {
        USB_EnableLowSpeed(BOARD_USB_PORT, false);
    }
    else /* low speed. */
    {
        USB_EnableLowSpeed(BOARD_USB_PORT, true);
    }

    /* reset bus. */
    USB_Enable(BOARD_USB_PORT, false);
    USB_EnableBusReset(BOARD_USB_PORT, true);
    app_delay(10); /* delay 10 ms. */
    USB_EnableBusReset(BOARD_USB_PORT, false);
    USB_Enable(BOARD_USB_PORT, true);

    /* start enum. */
    USB_EnableEndPoint(BOARD_USB_PORT, 0, USB_EndPointMode_Control, true);
    USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK | USB_INT_TOKENDONE | USB_INT_RESET, true);
}

/* xfer process, implement enum and get HID report function. */
void xfer_process(void)
{
    switch(usb_task_step)
    {
        case 0: /* get device descriptor. */
            if (usb_get_desp(usb_get_dev_desp))
            {
                usb_get_dev_desp[6] = usb_desp_buf[0]; /* modify dev_desp size. */
                usb_task_step++;
                USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
                USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            }
            break;
        case 1: /* reset bus. */
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, false);
            USB_Enable(BOARD_USB_PORT, false);
            BOARD_USB_PORT->FSCTL |= USB_FSCTL_RESET_MASK;
            app_delay(10); /* delay 10 ms. */
            BOARD_USB_PORT->FSCTL &= ~USB_FSCTL_RESET_MASK;
            USB_Enable(BOARD_USB_PORT, true);
            usb_task_step++;
            USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
            USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            break;
        case 2: /* set address @0x01. */
            if (usb_set_desp(usb_set_addr_desp))
            {
                USB_SetDeviceAddr(BOARD_USB_PORT, 0x01); /* set device descriptor @0x01. */
                usb_task_step++;
                USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
                USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            }
            break;
        case 3: /* get device descriptor again. */
            if (usb_get_desp(usb_get_dev_desp))
            {
                usb_get_dev_desp[6] = 0x40;
                usb_task_step++;
                USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
                USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            }
            break;
        case 4: /* set configuration. */
            if (usb_set_desp(usb_set_cfg_desp))
            {
                usb_task_step++;
                USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
                USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            }
            break;
        case 5: /* set idle. */
            if (usb_set_desp(usb_set_idle_desp))
            {
                usb_task_step++;
                USB_ClearInterruptStatus(BOARD_USB_PORT, USB_INT_SOFTOK);
                USB_EnableInterrupts(BOARD_USB_PORT, USB_INT_SOFTOK, true);
            }
            break;
        case 6: /* enum done. */
            usb_get_report();
            break;
        default:
            break;
    }
}

void BOARD_USB_IRQHandler(void)
{
    uint32_t flag = USB_GetInterruptStatus(BOARD_USB_PORT);
    flag &= USB_GetEnabledInterrupts(BOARD_USB_PORT);

    if (0 != (flag & USB_INT_RESET) ) /* detach. */
    {
        usb_detach_process();
    }

    if (0 != (flag & USB_INT_ATTACH) ) /* attach. */
    {
        usb_attach_process();
    }

    if (0 != (flag & USB_INT_SOFTOK) ) /* sof token. */
    {
        xfer_process();
    }

    if (0 != (flag & USB_INT_TOKENDONE) ) /* token done. */
    {
        xfer_process();
    }

    USB_ClearInterruptStatus(BOARD_USB_PORT, flag);
}

/* EOF. */
