/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 2017-10-30     ZYH            the first version
 */

#include "drv_otghs_dev.h"
#include <rtthread.h>
#include <rtdevice.h>
#include "board.h"
#include "drv_usb_hw.h"

usb_core_driver gd_usbd;

static struct udcd _gd_udc;

#ifdef USE_USB_FS
static struct ep_id _ep_pool[] =
{
    {0x0,  USB_EP_ATTR_CONTROL,     USB_DIR_INOUT,  64, ID_ASSIGNED  },
    {0x1,  USB_EP_ATTR_BULK,        USB_DIR_IN,     64, ID_UNASSIGNED},
    {0x1,  USB_EP_ATTR_BULK,        USB_DIR_OUT,    64, ID_UNASSIGNED},
    {0x2,  USB_EP_ATTR_INT,         USB_DIR_IN,     64, ID_UNASSIGNED},
    {0x2,  USB_EP_ATTR_INT,         USB_DIR_OUT,    64, ID_UNASSIGNED},
    {0x3,  USB_EP_ATTR_BULK,        USB_DIR_IN,     64, ID_UNASSIGNED},
    {0x3,  USB_EP_ATTR_BULK,        USB_DIR_OUT,    64, ID_UNASSIGNED},
    {0xFF, USB_EP_ATTR_TYPE_MASK,   USB_DIR_MASK,   0,  ID_ASSIGNED  },
};
#elif defined USE_USB_HS
static struct ep_id _ep_pool[] =
{
    {0x0,  USB_EP_ATTR_CONTROL,     USB_DIR_INOUT,  64, ID_ASSIGNED  },
    {0x1,  USB_EP_ATTR_BULK,        USB_DIR_IN,     64, ID_UNASSIGNED},
    {0x1,  USB_EP_ATTR_BULK,        USB_DIR_OUT,    512, ID_UNASSIGNED},
    {0x2,  USB_EP_ATTR_INT,         USB_DIR_IN,     512, ID_UNASSIGNED},
    {0x2,  USB_EP_ATTR_INT,         USB_DIR_OUT,    512, ID_UNASSIGNED},
    {0x3,  USB_EP_ATTR_BULK,        USB_DIR_IN,     512, ID_UNASSIGNED},
    {0x3,  USB_EP_ATTR_BULK,        USB_DIR_OUT,    512, ID_UNASSIGNED},
    {0xFF, USB_EP_ATTR_TYPE_MASK,   USB_DIR_MASK,   0,  ID_ASSIGNED  },
};
#endif

void usbd_reset_callback(void)
{
    rt_usbd_reset_handler(&_gd_udc);
}

void usbd_setup_stage_callback(struct urequest *setup_packet)
{
    rt_usbd_ep0_setup_handler(&_gd_udc, setup_packet);
}

void usbd_data_in_stage_callback(uint8_t epnum, uint16_t data_count)
{
    if (epnum == 0) {
        rt_usbd_ep0_in_handler(&_gd_udc);
    } else {
        rt_usbd_ep_in_handler(&_gd_udc, 0x80 | epnum, data_count);
    }
}

void usbd_connect_callback(void)
{
    rt_usbd_connect_handler(&_gd_udc);
}

void usbd_sof_callback(void)
{
    rt_usbd_sof_handler(&_gd_udc);
}

void usbd_disconnect_callback(void)
{
    rt_usbd_disconnect_handler(&_gd_udc);
}

void usbd_data_out_stage_callback(uint8_t epnum, uint16_t data_count)
{
    if (epnum == 0) {
        rt_usbd_ep0_out_handler(&_gd_udc, data_count);
    } else {
        rt_usbd_ep_out_handler(&_gd_udc, epnum, data_count);
    }
}

void usbd_set_connection_state(uint8_t state)
{
    if (state == 1) {
        usb_dev_connect (&gd_usbd);

        usb_mdelay(3);
    } else {
        usb_dev_disconnect (&gd_usbd);

        usb_mdelay(3);
    }
}

static rt_err_t _ep_set_stall(rt_uint8_t address)
{
    usb_ep_stall(&gd_usbd, address);

    return RT_EOK;
}

static rt_err_t _ep_clear_stall(rt_uint8_t address)
{
    usb_ep_clrstall(&gd_usbd, address);

    return RT_EOK;
}

static rt_err_t _set_address(rt_uint8_t address)
{
    usb_devaddr_set(&gd_usbd, address);

    return RT_EOK;
}

static rt_err_t _set_config(rt_uint8_t address)
{
    return RT_EOK;
}

static rt_err_t _ep_enable(uep_t ep)
{
    RT_ASSERT(ep != RT_NULL);
    RT_ASSERT(ep->ep_desc != RT_NULL);

    usb_ep_active(&gd_usbd, 
                   ep->ep_desc->bEndpointAddress, 
                   ep->ep_desc->wMaxPacketSize, 
                   ep->ep_desc->bmAttributes);

    return RT_EOK;
}

static rt_err_t _ep_disable(uep_t ep)
{
    RT_ASSERT(ep != RT_NULL);
    RT_ASSERT(ep->ep_desc != RT_NULL);

    usb_ep_deactivate(&gd_usbd, ep->ep_desc->bEndpointAddress);

    return RT_EOK;
}

static rt_size_t _ep_read(rt_uint8_t address, void *buffer)
{
    rt_size_t size = 0;
    RT_ASSERT(buffer != RT_NULL);

    return size;
}

static rt_size_t _ep_read_prepare(rt_uint8_t address, void *buffer, rt_size_t size)
{
    usb_ep_outxfer(&gd_usbd, address, buffer, size);

    return size;
}

static rt_size_t _ep_write(rt_uint8_t address, void *buffer, rt_size_t size)
{
    usb_ep_inxfer(&gd_usbd, address, buffer, size);

    return size;
}

static rt_err_t _ep0_send_status(void)
{
    usb_ep_inxfer(&gd_usbd, 0, NULL, 0);

    usb_ctlep_startout(&gd_usbd);

    return RT_EOK;
}

static rt_err_t _suspend(void)
{
    return RT_EOK;
}

static rt_err_t _wakeup(void)
{
    return RT_EOK;
}

static rt_err_t _init(rt_device_t device)
{
    usb_rcu_config();

    usb_timer_init();

#ifdef USE_USBHS0

#ifdef USE_USB_FS
    usb_para_init(&gd_usbd, USBHS0, USB_SPEED_FULL);
#endif

#ifdef USE_USB_HS
    usb_para_init(&gd_usbd, USBHS0, USB_SPEED_HIGH);
#endif

#endif /* USE_USBHS0 */

#ifdef USE_USBHS1

#ifdef USE_USB_FS
    usb_para_init(&gd_usbd, USBHS1, USB_SPEED_FULL);
#endif

#ifdef USE_USB_HS
    usb_para_init(&gd_usbd, USBHS1, USB_SPEED_HIGH);
#endif

#endif /* USE_USBHS1 */

    /* configure USB capabilites */
    usb_basic_init(&gd_usbd.bp, &gd_usbd.regs);

    usb_globalint_disable(&gd_usbd.regs);

    /* initailizes the USB core*/
    usb_core_init(gd_usbd.bp, &gd_usbd.regs);

    /* set device disconnect */
    usbd_set_connection_state(0);

    usb_curmode_set(&gd_usbd.regs, DEVICE_MODE);

    /* initailizes device mode */
    usb_devcore_init(&gd_usbd);

    usb_globalint_enable(&gd_usbd.regs);

    /* set device connect */
    usbd_set_connection_state(1);

#ifdef USE_USB_HS
    #ifdef USE_USBHS0
        pllusb_rcu_config(USBHS0);
    #endif

    #ifdef USE_USBHS1
        pllusb_rcu_config(USBHS1);
    #endif
#endif /* USE_USB_HS */

    usb_intr_config();

    return RT_EOK;
}

const static struct udcd_ops _udc_ops =
{
    _set_address,
    _set_config,
    _ep_set_stall,
    _ep_clear_stall,
    _ep_enable,
    _ep_disable,
    _ep_read_prepare,
    _ep_read,
    _ep_write,
    _ep0_send_status,
    _suspend,
    _wakeup,
};

int gd_usbd_register(void)
{
    rt_memset((void *)&_gd_udc, 0, sizeof(struct udcd));
    _gd_udc.parent.type = RT_Device_Class_USBDevice;
    _gd_udc.parent.init = _init;
    _gd_udc.parent.user_data = NULL;
    _gd_udc.ops = &_udc_ops;
    /* Register endpoint infomation */
    _gd_udc.ep_pool = _ep_pool;
    _gd_udc.ep0.id = &_ep_pool[0];
    rt_device_register((rt_device_t)&_gd_udc, "usbd", 0);
    rt_usb_device_init();
    return RT_EOK;
}

INIT_DEVICE_EXPORT(gd_usbd_register);

#ifdef RT_USB_DEVICE_HID

void usbd_hid_send(uint8_t *buffer, uint16_t size)
{
    struct hid_s
    {
        struct rt_device parent;
        struct ufunction *func;
        uep_t ep_in;
        uep_t ep_out;
        int status;
        rt_uint16_t protocol;
        rt_uint8_t report_buf[64];
        struct rt_messagequeue hid_mq;
    };

    udevice_t device = RT_NULL;
    uintf_t intf;
    ufunction_t func;

    struct hid_s *hiddev;

    device = rt_usbd_find_device(&_gd_udc);

    intf = rt_usbd_find_interface(device, 0, &func);

    if (RT_NULL != intf) {
        hiddev = (struct hid_s *)func->user_data;

        hiddev->parent.write(&hiddev->parent, 0, buffer, size);
    }
}

#endif
