
#include <stdlib.h>
#include "cmsis_os2.h" // CMSIS RTOS header file

#include "rl_usb.h" // Keil.MDK-Pro::USB:CORE

// Interface class, subclass and protocol of the device that is supported
#define CUSTOM_CLASS_IF_CLASS USB_DEVICE_CLASS_VENDOR_SPECIFIC
#define CUSTOM_CLASS_IF_SUBCLASS 0
#define CUSTOM_CLASS_IF_PROTOCOL 0

static osThreadId_t usbh_app_thread;
static USBH_PIPE_HANDLE bulkout_pipe, bulkin_pipe, intin_pipe;

extern void kmdw_printf(const char *f, ...);
extern uint32_t kmdw_ddr_reserve(uint32_t numbyte);

/*----------------------------------------------------------------------------
*      Thread for usb host examples
*---------------------------------------------------------------------------*/

void usbh_cmsis_custom_thread(void *argument); // thread function

void usbh_cmsis_example_init(void)
{
    usbh_app_thread = osThreadNew(usbh_cmsis_custom_thread, NULL, NULL);
}

void usbh_cmsis_custom_thread(void *argument)
{
    usbStatus usb_status; // USB status

    uint8_t *bfptr = (uint8_t *)kmdw_ddr_reserve(4 * 1024 * 1024);

    kmdw_printf("bfptr = 0x%p\n");

    // init USB host through MDK middleware
    usb_status = USBH_Initialize(0U);
    if (usb_status != usbOK)
    {
        kmdw_printf("USBH_Initialize() failed\n");
    }

    kmdw_printf("USBH_Initialize() OK\n");

    kmdw_printf("waiting for usb device enumeration\n");

    // wait for device init done
    osThreadFlagsWait(0x01U, osFlagsWaitAny, osWaitForever);

    int test_run = 100;

    // below test bulk-out
    {
        kmdw_printf("\n==== starting bulk-out test ====\n\n");

        uint32_t total_send = 1024;

        for (int i = 0; i < test_run; i++)
        {
            total_send += (1 + (rand() % 4000));
            if (total_send > 1024 * 1024) // make it <= 1 MB
                total_send = (1 + (rand() % (512 * 1024)));

            uint32_t wanted_bytes = total_send;

            kmdw_printf("... bulk-out transferred %d bytes\n", wanted_bytes);

            while (1)
            {
                uint32_t sendBytes = 16 * 1024; // set a max pipe send

                if (wanted_bytes < sendBytes)
                    sendBytes = wanted_bytes;

                // NOTE: for now sendBytes MAX = 20 KB due to host driver implementation
                usbStatus sts = USBH_PipeSend(bulkout_pipe, bfptr, sendBytes);
                if (sts != usbOK)
                {
                    kmdw_printf("... bulk-out transferred %d bytes failed, sts = %d\n", sendBytes, sts);
                    goto Test_Failed;
                }

                sendBytes = USBH_PipeSendGetResult(bulkout_pipe);

                wanted_bytes -= sendBytes;

                if (wanted_bytes == 0)
                {
                    if ((sendBytes % 512) == 0)
                    {
                        // zero-length packet
                        //kmdw_printf("\nsending zero-length packet ~\n");
                        usbStatus sts = USBH_PipeSend(bulkout_pipe, bfptr, 0);
                        if (sts != usbOK)
                        {
                            kmdw_printf("... bulk-out transferring ZLP failed, sts = %d\n", sts);
                            goto Test_Failed;
                        }
                    }
                    break;
                }
            }
        }
        kmdw_printf("\nbulk-out test done ~\n");
    }

    // below test bulk-in
    {
        kmdw_printf("\n==== starting bulk-in test ====\n\n");
        for (int i = 0; i < test_run; i++)
        {
            uint32_t total_recvd = 0;

            while (1)
            {
                uint32_t recvBytes = 16384; // however only 16KB for one transfer at maximum

                // NOTE: for now recvBytes MAX = 20 KB due to host driver implementation
                usbStatus sts = USBH_PipeReceive(bulkin_pipe, bfptr, recvBytes);
                if (sts != usbOK)
                {
                    kmdw_printf("... bulk-in transfer failed, sts = %d\n", sts);
                    goto Test_Failed;
                }

                recvBytes = USBH_PipeReceiveGetResult(bulkin_pipe);

                total_recvd += recvBytes;

                if (recvBytes == 0 || (recvBytes % 512) != 0)
                    break;
            }

            kmdw_printf("... bulk-in transferred %d bytes\n", total_recvd);
        }
        kmdw_printf("\nbulk-in test done ~\n");
    }

    // below test interrupt-in
    {
        kmdw_printf("\n==== starting interrupt-in test ====\n\n");
        for (int i = 0; i < test_run; i++)
        {
            uint32_t data = 0;
            usbStatus sts = USBH_PipeReceive(intin_pipe, (uint8_t *)&data, 4);
            if (sts != usbOK)
            {
                kmdw_printf("... interrupt-in transfer failed, sts = %d\n", sts);
                goto Test_Failed;
            }

            kmdw_printf("... interrupt-in get data = 0x%x\n", data);
        }
        kmdw_printf("\ninterrupt-in test done ~\n");
    }

    kmdw_printf("\nAll tests on control/bulk/interrupt transfer are passed ~\n");
    return;

Test_Failed:

    kmdw_printf("\nSome tests are failing !!\n");
}

/************************** Class Driver Functions ****************************/

uint8_t USBH_CustomClass_Configure(uint8_t device, const USB_DEVICE_DESCRIPTOR *ptr_dev_desc, const USB_CONFIGURATION_DESCRIPTOR *ptr_cfg_desc)
{

    kmdw_printf("%s()\n", __FUNCTION__);

    if (ptr_dev_desc->idVendor != 0xD7D || ptr_dev_desc->idProduct != 0x100)
    {
        kmdw_printf("Cannot recognize the device!\n");
        return 255U; // value 255 : configuration failed
    }

    //usb_device = device;

    kmdw_printf("device = %u\n", device);

    // we do the hard-code way to create endpoint pipes for code size reduction
    bulkout_pipe = USBH_PipeCreate(device, 0x01, USB_ENDPOINT_TYPE_BULK, 512, 0);
    bulkin_pipe = USBH_PipeCreate(device, 0x81, USB_ENDPOINT_TYPE_BULK, 512, 0);
    intin_pipe = USBH_PipeCreate(device, 0x82, USB_ENDPOINT_TYPE_INTERRUPT, 1024, 4);

    return 0;
}

usbStatus USBH_CustomClass_Initialize(uint8_t instance)
{
    //  Add code for initializing device

    kmdw_printf("%s()\n", __FUNCTION__);

    osThreadFlagsSet(usbh_app_thread, 0x01U);

    return usbOK;
}

usbStatus USBH_CustomClass_Uninitialize(uint8_t instance)
{
    // Add code for de-initializing device

    kmdw_printf("%s()\n", __FUNCTION__);

    return usbOK;
}
