/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2007 by Christian Gmeiner
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include <string.h>
#include "usb_storage.h"
#include "usbstack/utils.h"

/*-------------------------------------------------------------------------*/

static struct usb_dcd_controller_ops* ops;

struct usb_device_driver usb_storage_driver = {
    .name =         "storage",
    .bind =         usb_storage_driver_bind,
    .unbind =       usb_storage_driver_unbind,
    .request =      usb_storage_driver_request,
    .suspend =      NULL,
    .resume =       NULL,
    .speed =        usb_storage_driver_speed,
};

/*-------------------------------------------------------------------------*/

#define PROTO_BULK 0x50    // Bulk only
#define SUBCL_SCSI 0x06    // Transparent SCSI

/* Bulk-only class specific requests */
#define USB_BULK_RESET_REQUEST          0xff
#define USB_BULK_GET_MAX_LUN_REQUEST    0xfe

/*-------------------------------------------------------------------------*/
/* usb descriptors */

#define MANUFACTURER_STR_ID  1
#define PRODUCT_STR_ID       2
#define SERIAL_STR_ID        3
#define CONFIG_STR_ID        4
#define DATA_STR_ID          5

/* static strings, in UTF-8 */
static struct usb_string strings[] = {
    { MANUFACTURER_STR_ID, "RockBox" },
    { PRODUCT_STR_ID, "RockBox Storage Driver" },
    { SERIAL_STR_ID, "0" },
    { CONFIG_STR_ID, "Storage Bulk" },
    { DATA_STR_ID, "Storage Data" },
};

static struct usb_device_descriptor storage_device_desc = {
    .bLength =              USB_DT_DEVICE_SIZE,
    .bDescriptorType =      USB_DT_DEVICE,
    .bcdUSB =               0x0200,
    .bDeviceClass =         0,
    .bDeviceSubClass =      0,
    .bDeviceProtocol =      0,
    .idVendor =             0xffff,
    .idProduct =            0x0001,
    .iManufacturer =        MANUFACTURER_STR_ID,
    .iProduct =             PRODUCT_STR_ID,
    .iSerialNumber =        SERIAL_STR_ID,
    .bNumConfigurations =   1,
};

static struct usb_config_descriptor storage_config_desc = {
    .bLength =              USB_DT_CONFIG_SIZE,
    .bDescriptorType =      USB_DT_CONFIG,

    .bNumInterfaces =       1,
    .bConfigurationValue =  1,
    .iConfiguration =       CONFIG_STR_ID,
    .bmAttributes =         USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
    .bMaxPower =            250, /* 500mA in 2mA units */
};

static struct usb_interface_descriptor storage_interface_desc = {
    .bLength =              USB_DT_INTERFACE_SIZE,
    .bDescriptorType =      USB_DT_INTERFACE,
    .bInterfaceNumber =     0,
    .bNumEndpoints =        2,
    .bInterfaceClass =      USB_CLASS_MASS_STORAGE,
    .bInterfaceSubClass =   SUBCL_SCSI,
    .bInterfaceProtocol =   PROTO_BULK,
    .iInterface =           DATA_STR_ID,
};

static struct usb_endpoint_descriptor storage_fs_bulk_in_desc = {
    .bLength =              USB_DT_ENDPOINT_SIZE,
    .bDescriptorType =      USB_DT_ENDPOINT,
    .bEndpointAddress =     USB_DIR_IN,
    .bmAttributes =         USB_ENDPOINT_XFER_BULK,
    .wMaxPacketSize =       64,
};

static struct usb_endpoint_descriptor storage_fs_bulk_out_desc = {
    .bLength =              USB_DT_ENDPOINT_SIZE,
    .bDescriptorType =      USB_DT_ENDPOINT,
    .bEndpointAddress =     USB_DIR_OUT,
    .bmAttributes =         USB_ENDPOINT_XFER_BULK,
    .wMaxPacketSize =       64,
};

static struct usb_qualifier_descriptor storage_qualifier_desc = {
    .bLength =             sizeof(struct usb_qualifier_descriptor),
    .bDescriptorType =     USB_DT_DEVICE_QUALIFIER,
    .bcdUSB =              0x0200,
    .bDeviceClass =        0,
    .bNumConfigurations =  1,
};

struct usb_descriptor_header *storage_fs_function[] = {
    (struct usb_descriptor_header *) &storage_interface_desc,
    (struct usb_descriptor_header *) &storage_fs_bulk_in_desc,
    (struct usb_descriptor_header *) &storage_fs_bulk_out_desc,
    NULL,
};

/* USB 2.0 */
static struct usb_endpoint_descriptor storage_hs_bulk_in_desc = {
    .bLength =              USB_DT_ENDPOINT_SIZE,
    .bDescriptorType =      USB_DT_ENDPOINT,
    .bEndpointAddress =     USB_DIR_IN,
    .bmAttributes =         USB_ENDPOINT_XFER_BULK,
    .wMaxPacketSize =       512,
};

static struct usb_endpoint_descriptor storage_hs_bulk_out_desc = {
    .bLength =              USB_DT_ENDPOINT_SIZE,
    .bDescriptorType =      USB_DT_ENDPOINT,
    .bEndpointAddress =     USB_DIR_OUT,
    .bmAttributes =         USB_ENDPOINT_XFER_BULK,
    .wMaxPacketSize =       512,
};

struct usb_descriptor_header *storage_hs_function[] = {
    (struct usb_descriptor_header *) &storage_interface_desc,
    (struct usb_descriptor_header *) &storage_hs_bulk_in_desc,
    (struct usb_descriptor_header *) &storage_hs_bulk_out_desc,
    NULL,
};

#define BUFFER_SIZE 100
uint8_t buf[BUFFER_SIZE];

struct usb_response res;

/* helper functions */
static int config_buf(uint8_t *buf, uint8_t type, unsigned index);
static int set_config(int config);
static int set_interface_alt_setting(int interface_alt_setting);

struct device {
    struct usb_ep* in;
    struct usb_ep* out;
    struct usb_ep* intr;
    uint32_t used_config;
    uint32_t used_interface_alt_setting;
    struct usb_descriptor_header** descriptors;
};

static struct device dev;

/*-------------------------------------------------------------------------*/

void usb_storage_driver_init(void)
{
    logf("usb storage: register");
    usb_device_driver_register(&usb_storage_driver);
}

/*-------------------------------------------------------------------------*/
/* device driver ops */

int usb_storage_driver_bind(void* controler_ops)
{
    ops = controler_ops;

    /* serach and asign endpoints */
    usb_ep_autoconfig_reset();

    dev.in = usb_ep_autoconfig(&storage_fs_bulk_in_desc);
    if (!dev.in) {
        goto autoconf_fail;
    }
    dev.in->claimed = true;
    logf("usb storage: in: %s", dev.in->name);

    dev.out = usb_ep_autoconfig(&storage_fs_bulk_out_desc);
    if (!dev.out) {
        goto autoconf_fail;
    }
    dev.out->claimed = true;
    logf("usb storage: out: %s", dev.out->name);

    /* update device decsriptor */
    storage_device_desc.bMaxPacketSize0 = ops->ep0->maxpacket;

    /* update hs descriptors as we asume that endpoints
       are the same for fs and hs */
    storage_hs_bulk_in_desc.bEndpointAddress = 
                                       storage_fs_bulk_in_desc.bEndpointAddress;
    storage_hs_bulk_out_desc.bEndpointAddress =
                                      storage_fs_bulk_out_desc.bEndpointAddress;

    return 0;

autoconf_fail:
    logf("failed to find endpoints");
    return -EOPNOTSUPP;
}

void usb_storage_driver_unbind(void)
{

    /* disable endpoints... */
}

int usb_storage_driver_request(struct usb_ctrlrequest* request)
{
    int ret = -EOPNOTSUPP;
    logf("usb storage: request");

    res.length = 0;
    res.buf = NULL;

    switch (request->bRequestType & USB_TYPE_MASK) {
    case USB_TYPE_STANDARD:

        switch (request->bRequest) {
        case USB_REQ_GET_DESCRIPTOR:

            switch (request->wValue >> 8) {
            case USB_DT_DEVICE:
                logf("usb storage: sending device desc");
                ret = MIN(sizeof(struct usb_device_descriptor),
                          request->wLength);
                res.buf = &storage_device_desc;
                break;

            case USB_DT_DEVICE_QUALIFIER:
                logf("usb storage: sending qualifier dec");
                ret = MIN(sizeof(struct usb_qualifier_descriptor),
                          request->wLength);
                res.buf = &storage_qualifier_desc;
                break;

            case USB_DT_OTHER_SPEED_CONFIG:
            case USB_DT_CONFIG:
                logf("usb storage: sending config desc");

                ret = config_buf(buf, request->wValue >> 8,
                                 request->wValue & 0xff);
                if (ret >= 0) {
                    logf("%d, vs %d", request->wLength, ret);
                    ret = MIN(request->wLength, (uint16_t)ret);
                }
                res.buf = buf;
                break;

            case USB_DT_STRING:
                logf("usb storage: sending string desc");
                ret = usb_stack_get_string(strings, request->wValue & 0xff,
                                           buf);
                ret = MIN(ret, request->wLength);
                res.buf = buf;
                break;
            }
            break;

        case USB_REQ_SET_CONFIGURATION:
            logf("usb storage: set configuration %d", request->wValue);
            ret = set_config(request->wValue);
            break;

        case USB_REQ_GET_CONFIGURATION:
            logf("usb storage: get configuration");
            ret = 1;
            res.buf = &dev.used_config;
            break;

        case USB_REQ_GET_INTERFACE:
            logf("usb storage: get interface");
            ret = 1;
            res.buf = &dev.used_interface_alt_setting;
            break;

        case USB_REQ_SET_INTERFACE:
            logf("usb storage: set interface");
            ret = set_interface_alt_setting(request->wValue);
            break;
        }

    case USB_TYPE_CLASS:

        switch (request->bRequest) {
        case USB_BULK_RESET_REQUEST:
            logf("usb storage: bulk reset");
            break;

        case USB_BULK_GET_MAX_LUN_REQUEST:
            logf("usb storage: get max lun");
            /* we support no LUNs (Logical Unit Number) */
            buf[0] = 0;
            ret = 1;
            break;
        }
        break;
    }

    if (ret >= 0) {
        res.length = ret;
        ret = ops->send(NULL, &res);
    }

    return ret;
}

void usb_storage_driver_speed(enum usb_device_speed speed)
{
    switch (speed) {
    case USB_SPEED_HIGH:
        logf("usb storage: using highspeed");
        dev.descriptors = storage_hs_function;
        break;
    default:
        logf("usb storage: using fullspeed");
        dev.descriptors = storage_fs_function;
        break;
    }
}

/*-------------------------------------------------------------------------*/
/* S/GET CONFIGURATION helpers */

static int config_buf(uint8_t *buf, uint8_t type, unsigned index)
{
    int len;

    (void)index;

    len = usb_stack_configdesc(&storage_config_desc, buf, BUFFER_SIZE,
                               dev.descriptors);
    logf("result %d", len);
    if (len < 0) {
        return len;
    }
    ((struct usb_config_descriptor *)buf)->bDescriptorType = type;
    return len;
}

static int set_config(int config)
{
    /* enable endpoints */
    logf("setup %s", dev.in->name);
    ops->enable(dev.in, (struct usb_endpoint_descriptor*)dev.descriptors[1]);
    logf("setup %s", dev.out->name);
    ops->enable(dev.out, (struct usb_endpoint_descriptor*)dev.descriptors[2]);

    dev.used_config = config;

    /* setup buffers */

    return 0;
}

static int set_interface_alt_setting(int interface_alt_setting)
{
    dev.used_interface_alt_setting = interface_alt_setting;

    return 0;
}

