/*
 * Kneron system library test main code
 *
 * Copyright (C) 2019 Kneron, Inc. All rights reserved.
 *
 */

#include <string.h>
#include <stdlib.h>

#include "cmsis_os2.h"

#include "kdrv_usbd.h"
#include "kmdw_console.h"
#include "kmdw_memory.h"

#define FLAG_FOR_USB_EVENT 0x100

#define ENP_BULK_OUT 0x01     // bulk-out endpoint
#define ENP_BULK_IN 0x82      // bulk-in endpoint
#define ENP_INTERRUPT_IN 0x83 // interrupt-in endpoint

// endpoint 0x01, bulk-out
static kdrv_usbd_endpoint_descriptor_t endp_bulkOut_desc =
    {
        .bLength = 0x07,         // 7 bytes
        .bDescriptorType = 0x05, // Endpoint Descriptor
        .bEndpointAddress = ENP_BULK_OUT,
        .bmAttributes = 0x02,     // TransferType = Bulk
        .wMaxPacketSize = 0x0200, // max 512 bytes
        .bInterval = 0x00,        // never NAKs
};

// endpoint 0x81, bulk-in
static kdrv_usbd_endpoint_descriptor_t endp_bulkIn_desc =
    {
        .bLength = 0x07,         // 7 bytes
        .bDescriptorType = 0x05, // Endpoint Descriptor
        .bEndpointAddress = ENP_BULK_IN,
        .bmAttributes = 0x02,     // TransferType = Bulk
        .wMaxPacketSize = 0x0200, // max 512 bytes
        .bInterval = 0x00,        // never NAKs
};

// endpoint 0x82, interrupt-in
static kdrv_usbd_endpoint_descriptor_t endp_intIn_desc =
    {
        .bLength = 0x07,         // 7 bytes
        .bDescriptorType = 0x05, // Endpoint Descriptor
        .bEndpointAddress = ENP_INTERRUPT_IN,
        .bmAttributes = 0x03,     // TransferType = Interrupt
        .wMaxPacketSize = 0x0400, // max 1024 bytes
        .bInterval = 4,           // interval = 100 * frame period (125us) = 12 ms
};

static kdrv_usbd_interface_descriptor_t intf_desc =
    {
        .bLength = 0x9,          // 9 bytes
        .bDescriptorType = 0x04, // Inteface Descriptor
        .bInterfaceNumber = 0x0, // Interface Number
        .bAlternateSetting = 0x0,
        .bNumEndpoints = 3,      // 3 endpoints
        .bInterfaceClass = 0xFF, // Vendor specific
        .bInterfaceSubClass = 0x0,
        .bInterfaceProtocol = 0x0,
        .iInterface = 0x0, // No String Descriptor
        .endpoint[0] = &endp_bulkOut_desc,
        .endpoint[1] = &endp_bulkIn_desc,
        .endpoint[2] = &endp_intIn_desc,
};

static kdrv_usbd_config_descriptor_t confg_desc =
    {
        .bLength = 0x09,                                                       // 9 bytes
        .bDescriptorType = 0x02,                                               // Type: Configuration Descriptor
        .wTotalLength = (9 + 9 + 3 * sizeof(kdrv_usbd_endpoint_descriptor_t)), // stotal bytes including config/interface/endpoint descriptors
        .bNumInterfaces = 0x1,                                                 // Number of interfaces
        .bConfigurationValue = 0x1,                                            // Configuration number
        .iConfiguration = 0x0,                                                 // No String Descriptor
        .bmAttributes = 0xC0,                                                  // Self-powered, no Remote wakeup
        .MaxPower = 0x0,                                                       // 0 syould be ok for self-powered device
        .interface[0] = &intf_desc,
};

static kdrv_usbd_device_descriptor_t dev_desc =
    {
        .bLength = 0x12,          // 18 bytes
        .bDescriptorType = 0x01,  // Type : Device Descriptor
        .bcdUSB = 0x200,          // USB 2.0
        .bDeviceClass = 0x00,     // Device class, 0x0: defined by the interface descriptors
        .bDeviceSubClass = 0x00,  // Device sub-class
        .bDeviceProtocol = 0x00,  // Device protocol
        .bMaxPacketSize0 = 0x40,  // Max EP0 packet size: 64 bytes
        .idVendor = 0x3231,       // Vendor ID
        .idProduct = 0x0100,      // Product ID
        .bcdDevice = 0x0001,      // Device release number
        .iManufacturer = 0x00,    // Manufacture string index, FIXME
        .iProduct = 0x00,         // Product string index, FIXME
        .iSerialNumber = 0x0,     // Serial number string index
        .bNumConfigurations = 1,  // Number of configurations, FIXME
        .config[0] = &confg_desc, // configuration descriptor
};

// Device Qualifier Descriptor, plz see USB 2.0 SPEC
static kdrv_usbd_device_qualifier_descriptor_t dev_qual_desc =
    {
        .bLength = 0xA,
        .bDescriptorType = 0x06,
        .bcdUSB = 0x200,
        .bDeviceClass = 0x0,
        .bDeviceSubClass = 0x0,
        .bDeviceProtocol = 0x0,
        .bMaxPacketSize0 = 0x40,
        .bNumConfigurations = 0x1,
        .bReserved = 0x0,
};

static uint32_t sendBytes;

static void handle_vendor_cmd(kdrv_usbd_setup_packet_t setup_packet)
{
    kmdw_printf("setup packet: bmRequestType 0x%x bRequest 0x%x wValue 0x%x wIndex 0x%x wLength %d\n",
                setup_packet.bmRequestType, setup_packet.bRequest,
                setup_packet.wValue, setup_packet.wIndex, setup_packet.wLength);

    int isDirIn = setup_packet.bmRequestType & 0x80;
    int isDirOut = !isDirIn;

    // handle only sepcific commands
    if (isDirOut &&
        setup_packet.bRequest == 0x1 &&
        setup_packet.wValue == 0x2 &&
        setup_packet.wIndex == 0x3 &&
        setup_packet.wLength > 0)
    {
        kdrv_status_t status;
        uint32_t data_size = setup_packet.wLength;
        uint8_t *data = (uint8_t *)malloc(data_size);

        status = kdrv_usbd_control_receive(data, &data_size, 1000);
        if (status == KDRV_STATUS_OK)
        {
            uint32_t sum = 0;
            for (int i = 0; i < data_size; i++)
                sum += data[i];

            kmdw_printf("cx cmd is done, received %d bytes, sum = 0x%x, respond OK to host\n", data_size, sum);

            kdrv_usbd_control_respond(KDRV_USBD_RESPOND_OK);
        }
        else
        {
            kmdw_printf("kdrv_usbd_control_receive() failed, received %d bytes, err = %d\n", data_size, status);
            kdrv_usbd_control_respond(KDRV_USBD_RESPOND_ERROR);
        }

        free(data);
    }
    else if (isDirIn &&
             setup_packet.bRequest == 0x2 &&
             setup_packet.wValue == 0x4 &&
             setup_packet.wIndex == 0x6 &&
             setup_packet.wLength > 0)
    {
        kdrv_status_t status;
        uint32_t data_size = setup_packet.wLength;
        uint8_t *data = (uint8_t *)malloc(data_size);

        uint32_t sum = 0;
        for (int i = 0; i < data_size; i++)
        {
            data[i] = rand() % 0x100;
            sum += data[i];
        }

        kmdw_printf("cx transfer data %d bytes, sum = 0x%x to host\n", data_size, sum);

        status = kdrv_usbd_control_send(data, data_size, 1000);
        if (status == KDRV_STATUS_OK)
        {
            kmdw_printf("cx cmd is done, sent %d bytes, respond OK to host\n", data_size);
            kdrv_usbd_control_respond(KDRV_USBD_RESPOND_OK);
        }
        else
        {
            kmdw_printf("kdrv_usbd_control_receive() failed, err = %d\n", status);
            kdrv_usbd_control_respond(KDRV_USBD_RESPOND_ERROR);
        }

        free(data);
    }
    else
    {
        kmdw_printf("cx cmd is not supported\n");
        kdrv_usbd_control_respond(KDRV_USBD_RESPOND_ERROR);
    }
}

static void usbd_test_thread(void *argument)
{
    // use DDR for buffers
    uint32_t buf_size = 4 * 1024 * 1024; // 4MB
    uint32_t *buf_bout = (uint32_t *)kmdw_ddr_reserve(buf_size);
    uint32_t *buf_bin = (uint32_t *)kmdw_ddr_reserve(buf_size);

    kdrv_status_t status;

    kdrv_usbd_set_enable(true);

    srand(rand());

    while (1)
    {
        uint32_t flags = osThreadFlagsWait(FLAG_FOR_USB_EVENT, osFlagsWaitAny, osWaitForever);

        // receiving an usb event, and handle it if interested
        kdrv_usbd_event_t uevent;
        while (KDRV_STATUS_OK == kdrv_usbd_get_event(&uevent))
        {
            switch (uevent.ename)
            {
            case KDRV_USBD_EVENT_BUS_RESET:
                kmdw_printf("bus reset\n");
                break;

            case KDRV_USBD_EVENT_BUS_SUSPEND:
                kmdw_printf("bus suspend\n");
                break;

            case KDRV_USBD_EVENT_BUS_RESUME:
                kmdw_printf("bus resume\n");
                break;

            case KDRV_USBD_EVENT_SETUP_PACKET:
            {
                handle_vendor_cmd(uevent.setup);
                break;
            }
            case KDRV_USBD_EVENT_DEV_CONFIGURED:
            {
                kmdw_printf("\n==========================================\n");
                kmdw_printf("device is enumerated & configured\n");

                // prepare some data on this endpoint for bulk-in

                for (int i = 0; i < 1 * 1024 * 1024; i++)
                    ((uint8_t *)buf_bin)[i] = (uint8_t)(i & 0xff);

                sendBytes = 5; // at first queue some bytes into the bulk-in buffer
                status = kdrv_usbd_bulk_send_async(ENP_BULK_IN, buf_bin, sendBytes);
                if (status != KDRV_STATUS_OK)
                    kmdw_printf("kdrv_usbd_bulk_send_async() failed, error: %d\n", status);

                break;
            }

            case KDRV_USBD_EVENT_TRANSFER_OUT:
            {
                uint32_t endpoint = uevent.data1;

                if (endpoint == ENP_BULK_OUT)
                {
                    // just receive some bytes from host
                    status = kdrv_usbd_bulk_receive_async(endpoint, buf_bout, buf_size);
                    if (status != KDRV_STATUS_OK)
                    {
                        kmdw_printf("kdrv_usbd_bulk_receive_async() failed, error: %d\n", status);
                        return;
                    }
                }
                break;
            }

            case KDRV_USBD_EVENT_TRANSFER_DONE:
            {
                uint32_t endpoint = uevent.data1;
                static uint32_t txfer_size = 0;

                if (endpoint == ENP_BULK_OUT)
                {
                    txfer_size = uevent.data2;
                    kmdw_printf("bulk-out transferred %d bytes\n", txfer_size);
                }
                else if (endpoint == ENP_BULK_IN)
                {
                    sendBytes += 5;
                    if (sendBytes > 4 * 1024 * 1024)
                        sendBytes = 5;

                    kmdw_printf("bulk-in sending %d bytes\n", sendBytes);

                    status = kdrv_usbd_bulk_send_async(ENP_BULK_IN, buf_bin, sendBytes);
                    if (status != KDRV_STATUS_OK)
                        kmdw_printf("kdrv_usbd_bulk_send_async() failed, error: %d\n", status);
                }

                break;
            }

            case KDRV_USBD_EVENT_TRANSFER_TERMINATED:
            {
                uint32_t endpoint = uevent.data1;
                kmdw_printf("warning : endpoint 0x%x is terminated\n", endpoint);

                if (endpoint == ENP_BULK_IN)
                {
                    sendBytes = 5; // queue some bytes into the bulk-in buffer
                    status = kdrv_usbd_bulk_send_async(ENP_BULK_IN, buf_bin, sendBytes);
                    if (status != KDRV_STATUS_OK)
                        kmdw_printf("kdrv_usbd_bulk_send_async() failed, error: %d\n", status);
                }

                break;
            }

            default:
                kmdw_printf("got an unhandled event %d\n", uevent.ename);
                break;
            }
        }
    }
}

void usbd_async_example_init(void)
{
    kmdw_printf("Testing async USBD API\n");

    // create threads and start to listen USB events

    osThreadId_t tid_bulk = osThreadNew(usbd_test_thread, NULL, NULL);

    kdrv_usbd_initialize();

    kdrv_usbd_register_thread_notification(tid_bulk, FLAG_FOR_USB_EVENT);

    // set up with one configuration / one interface / two endpoints
    kdrv_usbd_set_device_descriptor(KDRV_USBD_HIGH_SPEED, &dev_desc);
    kdrv_usbd_set_device_qualifier_descriptor(KDRV_USBD_HIGH_SPEED, &dev_qual_desc);

    kmdw_printf("configure endpoint 0x%02x for bulk-out transfer\n", ENP_BULK_OUT);
    kmdw_printf("configure endpoint 0x%02x for bulk-in transfer\n", ENP_BULK_IN);
    kmdw_printf("configure endpoint 0x%02x for interrupt-in transfer\n", ENP_INTERRUPT_IN);
}
