/*
* @file hv_drv_UsbGadgetBillboard.c
* @brief USB Billboard Gadget.
*
* @author HiView SoC Software Team
* @version 1.0.0
* @date 2022-06-15
*/


#include "hv_drv_UsbGadgetBillboard.h"
#include "hv_drv_UsbGadgetFuncBillboard.h"
#include "hv_drv_UsbDefaultConfig.h"
#include "hv_drv_UsbDefines.h"


#define CONFIG_USB_GADGET_MANUFACTURER      "Hi-image"
#define CONFIG_USB_GADGET_VENDOR_NUM        0x380C
#define CONFIG_USB_GADGET_PRODUCT_NUM       0x7315


/*
 * One needs to define the following:
 * CONFIG_USB_GADGET_VENDOR_NUM
 * CONFIG_USB_GADGET_PRODUCT_NUM
 * CONFIG_USB_GADGET_MANUFACTURER
 * at e.g. ./configs/<board>_defconfig
 */

#define STRING_MANUFACTURER     25
#define STRING_PRODUCT          2
/* Index of String Descriptor describing this configuration */
#define STRING_USBDOWN          4
/* Index of String serial */
#define STRING_SERIAL           3
#define MAX_STRING_SERIAL       256
/* Number of supported configurations */
#define CONFIGURATION_NUMBER    1

#define DRIVER_VERSION        "billboard 2.0"

static const char product[] = "USB 2.0 Billboard Device";
static char g_billboard_serial[MAX_STRING_SERIAL]="0000000000000001";
static const char manufacturer[] = CONFIG_USB_GADGET_MANUFACTURER;

void g_billboard_set_serialnumber(char *s)
{
    HV_MEMSET(g_billboard_serial, 0, MAX_STRING_SERIAL);
    strncpy(g_billboard_serial, s, MAX_STRING_SERIAL - 1);
}

static struct usb_device_descriptor device_desc = {
    .bLength = sizeof device_desc,
    .bDescriptorType = USB_DT_DEVICE,

    .bcdUSB = __constant_cpu_to_le16(0x0201),
    .bDeviceClass = USB_CLASS_BILLBOARD,
    .bDeviceSubClass = 0, 

    .idVendor = __constant_cpu_to_le16(CONFIG_USB_GADGET_VENDOR_NUM),
    .idProduct = __constant_cpu_to_le16(CONFIG_USB_GADGET_PRODUCT_NUM),
    /* .iProduct = DYNAMIC */
    /* .iSerialNumber = DYNAMIC */
    .bNumConfigurations = 1,
};

/*
 * static strings, in UTF-8
 * IDs for those strings are assigned dynamically at g_billboard_bind()
 */
static struct usb_string g_billboard_string_defs[] = {
    {.s = manufacturer},
    {.s = product},
    {.s = g_billboard_serial},
    { }        /* end of list */
};

static struct usb_gadget_strings g_billboard_string_tab = {
    .language = 0x0409, /* en-us */
    .strings = g_billboard_string_defs,
};

static struct usb_gadget_strings *g_billboard_composite_strings[] = {
    &g_billboard_string_tab,
    NULL,
};

static int g_billboard_unbind(struct usb_composite_dev *cdev)
{
    struct usb_gadget *gadget = cdev->gadget;

    debug("%s: calling usb_gadget_disconnect for "
            "controller '%s'\n", __func__, gadget->name);
    usb_gadget_disconnect(gadget);

    return 0;
}


static int g_billboard_do_config(struct usb_configuration *c)
{

    pr_debug("g_billboard_do_config\n");
    debug("%s: configuration: 0x%x composite dev: 0x%x\n",
          __func__, c, c->cdev);

    return billboard_add(c);
}

static int g_billboard_config_register(struct usb_composite_dev *cdev)
{
    struct usb_configuration *config;
    const char *name = "usb_billboard";

    config = USB_MALLOC(sizeof(*config));

    if (!config)
        return -ENOMEM;

    HV_MEMSET(config, 0, sizeof(*config));

    config->label = name;
    config->bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER;
    config->bConfigurationValue = CONFIGURATION_NUMBER;
    config->iConfiguration = STRING_USBDOWN;
    config->bind = g_billboard_do_config;

    return usb_add_config(cdev, config);
}

int board_usb_init(int index, enum usb_init_type init)
{
    return 0;
}

int board_usb_cleanup(int index, enum usb_init_type init)
{
    return 0;
}

int g_billboard_bind_fixup(struct usb_device_descriptor *dev, const char *name)
{
    return 0;
}

int g_billboard_get_board_bcd_device_number(int gcnum)
{
    return gcnum;
}

int g_billboard_board_usb_cable_connected(void)
{
    return -EOPNOTSUPP;
}


static int g_billboard_get_bcd_device_number(struct usb_composite_dev *cdev)
{
    struct usb_gadget *gadget = cdev->gadget;
    int gcnum;

    /* For "musb-hdrc", usb_gadget_controller_number() always return 0x15. 
       So the following code was removed for simplification.
    */
    /*gcnum = usb_gadget_controller_number(gadget);
    if (gcnum > 0)
        gcnum += 0x200;*/

    gcnum = 0x0215;
    return g_billboard_get_board_bcd_device_number(gcnum);
}


static int g_billboard_bind(struct usb_composite_dev *cdev)
{
    struct usb_gadget *gadget = cdev->gadget;
    int id, ret;
    int gcnum;

    id = usb_string_id(cdev);

    if (id < 0)
        return id;
    g_billboard_string_defs[0].id = id;
    device_desc.iManufacturer = id;

    id = usb_string_id(cdev);
    if (id < 0)
        return id;

    g_billboard_string_defs[1].id = id;
    device_desc.iProduct = id;

    g_billboard_bind_fixup(&device_desc, cdev->driver->name);

    if (strlen(g_billboard_serial)) {
        id = usb_string_id(cdev);
        if (id < 0)
            return id;

        g_billboard_string_defs[2].id = id;
        device_desc.iSerialNumber = id;
    }

    ret = g_billboard_config_register(cdev);
    if (ret)
        goto error;

    gcnum = g_billboard_get_bcd_device_number(cdev);
    if (gcnum >= 0)
        device_desc.bcdDevice = gcnum;
    else {
        debug("%s: controller '%s' not recognized\n",
            __func__, gadget->name);
        device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
    }

    usb_gadget_connect(gadget);

    return 0;

 error:
    g_billboard_unbind(cdev);
    return -ENOMEM;
}

static struct usb_composite_driver g_billboard_driver = {
    .name       = NULL,
    .dev        = &device_desc,
    .strings    = g_billboard_composite_strings,

    .bind       = g_billboard_bind,
    .unbind     = g_billboard_unbind,
};

/*
 * NOTICE:
 * Registering via USB function name won't be necessary after rewriting
 * g_dnl to support multiple USB functions.
 */
int g_billboard_register(const char *name)
{
    int ret;

    g_billboard_driver.name = name;

    ret = usb_composite_register(&g_billboard_driver);
    if (ret) {
        return ret;
    }
    return 0;
}

void g_billboard_unregister(void)
{
    usb_composite_unregister(&g_billboard_driver);
}
