/*
 * 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"

#define FLAG_FOR_USB_EVENT 0x100

#define ENP_BULK_OUT 0x01     // bulk-out endpoint
#define ENP_BULK_IN 0x81      // bulk-in endpoint
#define ENP_INTERRUPT_IN 0x82 // 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 = 0x0D7D,       // 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 void bulk_out_thread(void *argument)
{
    // use DDR for buffers
    uint32_t *buf = (uint32_t *)0x61000000;
    uint32_t blen = 1 * 1024 * 1024;
    kdrv_status_t status;

    kdrv_usbd_set_enable(true);

    kmdw_printf("waiting for device being configured\n");

    while (!kdrv_usbd_is_dev_configured())
        osDelay(500);

    kmdw_printf("device is enumerated & configured\n");

    while (1)
    {
        uint32_t txLen = blen;
        status = kdrv_usbd_bulk_receive(ENP_BULK_OUT, buf, &txLen, 0);
        if (status == KDRV_STATUS_OK)
        {
            kmdw_printf("endpoint 0x%x received %d bytes\n", ENP_BULK_OUT, txLen);
        }
        else
        {
            kmdw_printf("kdrv_usbd_bulk_receive(enp:0x%x) failed, error: %d\n", ENP_BULK_OUT, status);
        }
    }
}

static void bulk_in_thread(void *argument)
{
    // use DDR for buffers
    uint32_t *buf = (uint32_t *)(0x61000000 + 1 * 1024 * 1024);
    //uint32_t blen = 1 * 1024 * 1024;
    kdrv_status_t status;

    while (!kdrv_usbd_is_dev_configured())
        osDelay(500);

    osDelay(500);

    uint32_t sendBytes = 256;

    while (1)
    {
        status = kdrv_usbd_bulk_send(ENP_BULK_IN, buf, sendBytes, 0);
        if (status == KDRV_STATUS_OK)
        {
            kmdw_printf("endpoint 0x%x sent %d bytes\n", ENP_BULK_IN, sendBytes);
        }
        else
        {
            kmdw_printf("kdrv_usbd_bulk_send(enp:0x%x) failed, error: %d\n", ENP_BULK_IN, status);
        }

        if (sendBytes == 1 * 1024 * 1024)
            sendBytes = 256;
        else
            sendBytes += (1 + (rand() % 2048));

        if (sendBytes > 1 * 1024 * 1024)
            sendBytes = 1 * 1024 * 1024;
    }
}

// this thread send some data to the host via interrupt-in transfer per 2 sec
static void interrupt_in_thread(void *argument)
{
    while (!kdrv_usbd_is_dev_configured())
        osDelay(500);

    uint32_t count = 0;
    while (1)
    {
        kdrv_usbd_interrupt_send(ENP_INTERRUPT_IN, &count, sizeof(count), 100);
        osDelay(2000);
        ++count;
    }
}

void usbd_sync_example_init(void)
{
    kmdw_printf("==== Start to test USBD driver API ====\n");

    // create threads to handle usb transfers

    osThreadNew(bulk_out_thread, NULL, NULL);
    osThreadNew(bulk_in_thread, NULL, NULL);
    osThreadNew(interrupt_in_thread, NULL, NULL);

    kdrv_usbd_initialize();

    // 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 test\n", ENP_BULK_OUT);
    kmdw_printf("configure endpoint 0x%02x for bulk-in test\n", ENP_BULK_IN);
    kmdw_printf("configure endpoint 0x%02x for interrupt-in test\n", ENP_INTERRUPT_IN);
}
