/*
 *
 *     Module Name:  usbDevice.c
 *         Version: 1.00
 *        Language: Ansi C
 * Compile Options:
 * Compile defines:
 *       Libraries:
 *    Link Options:
 *
 *    Entry Points:
 *
 *  Copyright (c) 1996-2006 Digi International Inc., All Rights Reserved
 *
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *
 *  Restricted Rights Legend
 *
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "bele.h"
#include "mc_isr.h"
#include "mcdma_def.h"
#include "nammu.h"
#include "usbreg_def.h"
#include "usbDevApi.h"
//#include "gpio.h"
#include "armutils.h"
#include "bsp_drivers.h"
#include "usbDef.h"
#include "usbDesc.h"
#include <bsp_api.h>

#if (defined BSP_USB_DEVICE_VERSION) && (BSP_USB_DEVICE_VERSION == BSP_USB_DRIVER_VERSION_1)

/* This is used in naUsbDevEp0Send. */
#define ALIGNED_32          0x20
#define IS_WORD_ALIGNED(x)  (((unsigned)(x) % 32) == 0)

TX_MUTEX gUsbDeviceMutex;

NaUsbNetos_t        * gUsbDeviceDriver = NULL;

#ifdef NETOS_GNU_TOOLS
NaUsbNetos_t        gUsbDevice __attribute__ ((aligned(32)));
static char         gZeroBuffer[2] __attribute__ ((aligned(32))) = {0, 0};
#else
#pragma alignvar (32)
NaUsbNetos_t        gUsbDevice ;
#pragma alignvar (32)
static char         gZeroBuffer[2] = {0, 0};
#endif


/**
 * Fill the given descriptors in the buffer.
 *
 * @category BSP:Device_Drivers:USB_DEVICE
 * @since 7.1
 *
 * @param buf       Pointer to memory buffer where descriptor will be written to.
 * @param buflen    Size of the buf
 * @param src       Pointer to a list of descriptors to be written in the buf.
 *
 * @return < 0     Error (-Errno)
 * @return > 0     Length of the current buf
 *
 **/
static int usb_fill_desc(void *buf, unsigned buflen, const NaUsbDescriptorHeader_t **src)
{
    unsigned char   *dest = buf;

    if (!src)
            return -EINVAL;

    /* fill buffer from src[] until null descriptor ptr */
    for (; *src != NULL; src++) {
        unsigned len = (*src)->bLength;

        if (len > buflen)
           return -ENOMEM;
        fmemcpy(dest, *src, len);
        buflen -= len;
        dest += len;
    }
    return dest - (unsigned char *)buf;
}

/** 
 * Build and format a complete configuration descriptor.
 *
 * This function is to construct configuration descriptor for get-descriptor request
 * for configuration. It formats given configuration descriptor (config) and a list 
 * of the descriptors given in the desc and writes to the given memory buffer.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>   
 * @since 7.1
 *
 * @param config    Point to configuration descriptor
 * @param desc      Point to a table that contains a list of descriptors
 * @param buf       Point to memory where a complete configuration
 *                  descriptor response to be written to.
 * @param length    Length of the buf.
 *
 * @return  > 0     Size of the configuration descriptor response
 * @return  -EINVAL Invalid length. The given length is not enough to fit the response.
 *
 **/
static int build_config_desc(const NaUsbConfigurationDescriptor_t * config, const NaUsbDescriptorHeader_t ** desc, void * buf, unsigned length)
{

#define USB_DT_CONFIG_SIZE  sizeof(NaUsbConfigurationDescriptor_t)
#define USB_USHORT_MAX      0xFFFF

        NaUsbConfigurationDescriptor_t  *cp = buf;
        int                             len;

        if (length < USB_DT_CONFIG_SIZE || !desc)
            return -EINVAL;

        /* config descriptor first */
        *cp = *config;

        /* then interface/endpoint/class/vendor/... */
        len = usb_fill_desc(USB_DT_CONFIG_SIZE + (unsigned char *)buf, 
                            length - USB_DT_CONFIG_SIZE, desc);
        if (len < 0)
            return len;
        len += USB_DT_CONFIG_SIZE;
        if (len > USB_USHORT_MAX)
            return -EINVAL;

        /* patch up the config descriptor */
        cp->bLength = USB_DT_CONFIG_SIZE;
        cp->bDescriptorType = USB_DT_CONFIG;
        cp->wTotalLength = TO_LE16(len);
        cp->bmAttributes |= USB_CONFIG_ATT_ONE;
        return len;
}

/**
 * Get the interface number for the given endpoint.
 * It searches the given endpoint descriptor from the given 
 * table which contains a list of descriptors. If the given 
 * endpoint descriptor is found, it locates and returns the
 * interface number.
 *
 * @category BSP:Device_Drivers:USB_DEVICE
 * @since 7.1
 *
 * @param header    Point to a table that contains a list of descriptors 
 *                  (includes interface descriptor).
 * @param epDesc    Point to endpoint descriptor 
 * @param interface Point to memory where interface number will be returned.
 *                  If not found, -1 will be returned.
 * @return  NA_SUCCESS    success
 *
 **/
static void get_configuration_interface_value(NaUsbDescriptorHeader_t ** header, NaUsbEndpointDescriptor_t * epDesc, int * interface)
{
    NaUsbDescriptorHeader_t         * pDescHeader;
    NaUsbInterfaceDescriptor_t      * pInterfaceDesc = NULL;
    int i;

    i = 0;

    *interface = -1;
        
    while (header[i] != NULL)
    {
        pDescHeader = header[i];
        /* locates interface descriptor */
        if (pDescHeader->bDescriptorType == USB_DT_INTERFACE)
        {
            pInterfaceDesc = (NaUsbInterfaceDescriptor_t *)pDescHeader;
            if (epDesc == NULL) 
            {   /* if no endpoint descriptor is given, just return
                 * the first on we found.
                 */
                *interface = (int)pInterfaceDesc->bInterfaceNumber;
                break;
            }
        }
        else if (pDescHeader == (NaUsbDescriptorHeader_t *)epDesc && pInterfaceDesc != NULL)
        {
            *interface = (int)pInterfaceDesc->bInterfaceNumber;
            break;
        }
        i++;
    }
}



#define _USB_ENDPOINT_ADDRESS_DIRECTION_MASK    (0x1 << 7)

/*ad*
 * Creates and configures an endpoint
 *
 * It initializes the endpoint and makes it usable with given 
 * endpoint descriptor. It returns the endpoint handler which 
 * an endpoint is created and configured.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param desc  Pointer to the endpoint descriptor
 *
 * @return >0   Endpoint Handle. The endpoint was successfully created
 *              and configured.
 * @return  0   Unable to create or intialize an endpoint
 *
 * @link naUsbDevCloseEndpoint
 *ad*/
unsigned long naUsbDevOpenEndpoint(NaUsbEndpointDescriptor_t * desc)
{
    int             rc = 0;
    int             iConfiguration, iInterface;
    unsigned short  max, type;

    NaUsbEndpoint_t  * netos_ep = NULL;

    unsigned long direction;
    unsigned long ep_number;
    int           i;

    USB_LOCK;

    NA_ASSERT(gUsbDeviceDriver != NULL);
    NA_ASSERT(desc != NULL);

    if (desc == NULL || gUsbDeviceDriver == NULL ||
        gUsbDeviceDriver->dev == NULL ||
        gUsbDeviceDriver->ep_count >= USB_ENDPOINT_COUNT ||
        desc->bDescriptorType != USB_DT_ENDPOINT)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "naUsbDevOpenEndpoint: Invalid\n");
        rc = -EINVAL;
        goto _ret;
    }

    iConfiguration = gUsbDeviceDriver->dev->configuration_descriptor->bConfigurationValue;
    /* get the interface number for this endpoint.
     * The descriptor_header should contain the interface descriptor for this endpoing descriptor.
     */
    get_configuration_interface_value(gUsbDeviceDriver->dev->descriptor_header, desc, &iInterface);
    if (iConfiguration < 0 || iInterface < 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "naUsbDevOpenEndpoint: get_configuration_interface_value fails\n");
        rc = -EINVAL;
        goto _ret;
    }

    ep_number = (desc->bEndpointAddress & 0xF);
    /* the endpoint descriptor doesn't specify the endpoint number.
     * We need to assign one.
     */
    if (ep_number == 0)
    {
        for (i=1; i < USB_ENDPOINT_COUNT; i++)
        {
            if ((gUsbDeviceDriver->ep_mask & (0x1 << i)) == 0)
            {
                ep_number = i;
                desc->bEndpointAddress |= (ep_number & 0x0F);
                break;
            }
        }
    }

    gUsbDeviceDriver->ep_mask |= (0x1 << ep_number);
    gUsbDeviceDriver->ep_count++;

    if (ep_number == 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevOpenEndpoint: Unable to obtain endpoint number\n");
        rc = -EINVAL;
        goto _ret;
    }

    /* new endpoint */
    netos_ep = usb_new_netos_endpoint(ep_number);
    if (netos_ep == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevOpenEndpoint: Unable to get a new endpoint\n");
        rc = -EINVAL;
        goto _ret;
    }

    /* intialize USB registers */
    max = FROM_LE16(desc->wMaxPacketSize);
    max &= 0x3ff;
    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), maxPacketSize, max);

    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), altEp, 0);
    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), interfaceEp, iInterface);
    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), configEp, iConfiguration);

    type = (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK);

    switch (type)
    {
        case USB_ENDPOINT_XFER_CONTROL:
        case USB_ENDPOINT_XFER_ISOC:
        case USB_ENDPOINT_XFER_BULK:
        case USB_ENDPOINT_XFER_INT:
            break;
        default:
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevOpenEndpoint: invalid bmAttributes 0x%X\n", (unsigned)desc->bmAttributes);
            rc = -EINVAL;
            goto _ret;
    }
    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), epType, type);

    if (desc->bEndpointAddress & _USB_ENDPOINT_ADDRESS_DIRECTION_MASK)
    {
        direction = USB_DIR_IN;
    }               
    else
    {
        direction = USB_DIR_OUT;
    }
    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), epDir, direction);
    narm_write_reg(NARM_USB_IP_EP_REG, NA_USB_PED_ADDRESS(ep_number), epNum, ep_number);

    netos_ep->type = type;
    netos_ep->desc = desc;
    netos_ep->dir = direction; 
    netos_ep->max_packet_size = max;
    

    /* initialize memory for the endpoint */
    rc = usb_setup_endpoint_memory(netos_ep, ep_number+1, USB_DMA_DESCRIPTOR_COUNT, USB_DMA_DESCRIPTOR_COUNT);
    if (rc != 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevOpenEndpoint: Unable to set endpoint memory\n");
        goto _ret;
    }

    /* setup DMA  */
    rc = usbDma_InitEndpoint(netos_ep);
    if (rc != 0)
    {
        goto _ret;
    }

    /* start endpoint thread */
    /* start endpoint thread */
    if (type == USB_ENDPOINT_XFER_INT)
    {   /* interrupt endpoint */
        rc = usb_device_endpoint_thread_init(netos_ep, USB_EVENT_THREAD2_PRIORITY, USB_EVENT_THREAD2_STACK_SIZE);
    }
    else
    {   /* bulk data and iso endpoint */
        rc = usb_device_endpoint_thread_init(netos_ep, USB_EVENT_THREAD3_PRIORITY, USB_EVENT_THREAD3_STACK_SIZE);
    }

    if (rc != 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevOpenEndpoint: Unable to initialize endpoint thread\n");
    }
_ret:
    if (rc != 0 && netos_ep != NULL)
    {
        naUsbDevCloseEndpoint((unsigned long)netos_ep);
        netos_ep = NULL;
    }
    USB_UNLOCK;

    return (unsigned long)netos_ep;    
}

/*ad*
 * Stops and free an endpoint
 *
 * It stops the endpoint and the endpoint is no longer usable.
 * You must call usbDevice_enableEndpoint to re-initialize
 * the endpoint to become available again.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include  <usbDevApi.h>  
 * @since 7.1
 *
 * @param ep_handle    Endpoint handle associated with an endpoint through 
 *                     the naUsbDevOpenEndpoint
 *
 * @return  0       The Endpoint was successfully disabled
 * @return < 0      -Errno. Error is encountered.
 *
 * @link naUsbDevOpenEndpoint
 *ad*/
int naUsbDevCloseEndpoint(unsigned long ep_handle)
{
    int                     rc = -EINVAL;
    NaUsbEndpoint_t          * ep = (NaUsbEndpoint_t *)ep_handle;
    unsigned                i;

    USB_LOCK;

    NA_ASSERT(gUsbDeviceDriver != NULL);
    NA_ASSERT(ep != NULL);

    /* if this is already initialized, fails */    
    if (usb_is_netos_endpoint(ep) == TRUE)
    {
        /* stop DMA */
        usbDma_DisableEndpoint(ep);
    
        /* Return all buffers back to user */
        for (i=0; i < ep->request_count; i++)
        {
            if (ep->request[i].status == USB_REQUEST_ALLOCATED)
            {
                usb_ep_complete_request(ep, &ep->request[i], ep->request[i].buf, ep->request[i].length, NA_USB_REQUEST_CLOSED);
            }

        }

        /* free any endpoint's thread */
        usb_device_endpoint_thread_free(ep);

        /* free all endpoint's memory */
        usb_free_endpoint_memory(ep);

        /* return the endpoint number */
        if (ep->dma_channel > 1)
        {
            gUsbDeviceDriver->ep_count--;
            gUsbDeviceDriver->ep_mask &= ~(0x1 << (ep->dma_channel - 1));
        }
        /* free netos endpoint */
        usb_free_netos_endpoint(ep);

        rc = 0;

    }

    USB_UNLOCK;

    return rc;
}

/**
 * Setup wakeup mode.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param state TRUE - enable wakeup mode
 *              FALSE - disable wakeup mode
 *
 **/
void usb_device_wakeup(BOOLEAN state)
{
    narm_write_reg(NARM_USB_GC_REG, NA_USB_GCSR_ADDRESS, wakeup, state);

}

/**
 * Setup self powered
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param value value of the self-powered
 *
 * @return  0        It's successfully setup the wakeup mode.
 * @return -ENOTSUP  Not Supported
 *
 **/
int usb_device_selfpowered(int value)
{
    int rc = 0;

#ifdef NS9750
    if (value == 0)
    {
        rc = -ENOTSUP;
    }
    else
#endif
    narm_write_reg(NARM_USB_DCS_REG, NA_USB_DCSR_ADDRESS, self_pwr, value);

    return rc;
}

/*ad*
 * Starts an IO request to an endpoint.
 *
 * This function sets up the USB device controller for transferring data 
 * through an endpoint. If an endpoint is OUT endpoint, this function
 * sets up the buffer waiting for data from the host. If an endpoint is 
 * IN endpoint, this function sets up the controller for sending data
 * in the buffer to the host.
 *
 * When the transfer completes, the completion callback function is called 
 * to return the buffer back to the caller. Any endpoint may have more than 
 * one transfer request queued; they complete in FIFO order. 
 * The given buffer may not be examined or modified until it is given back 
 * to the caller through the completion callback.
 * The buffer must be given in 32-byte aligned.
 *
 * For IN endpoint (sending data to the host), if the length is multiple of 
 * wMaxPacketSize (see @link NaUsbEndpointDescriptor_t) of an endpoint, 
 * caller can add an extra byte with null data in the buffer or send 
 * a zero-length packet to complete the transfer.
 *
 * If specified buffer is NULL, the callback must be specified and 
 * the callback will be called when the host is requesting data from the device.
 *
 * For 0-length packet, user must specify the buffer with length set to 0
 * and must ensure no other IO request is pending to the endpoint.
 *
 * @note This function should be called to set up multiple buffers so that 
 * the device controller is able to move data continuously from the host for
 * OUT endpoint.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param ep_handle    Endpoint handle associated with an endpoint through 
 *                     the naUsbDevOpenEndpoint
 * @param buffer   Pointer to memory where data will be written to for IN endpoint.
 *                 Pointer to data which will be sent to the host for OUT endpoint.
 *                 This buffer must be at cache-line aligned (32-byte align).
 * @param length   Length of the buffer.
 * @param callback  User's completion callback function. 
 * @param context   Pointer to user's context which will be passed to the 
 *                  completion callback function.
 *
 * @return  0   An IO request was successfully submitted to an endpoint.
 * @return <0   -Errno. Error is encountered.
 *
 * 
 *ad*/
int naUsbDevQueue(unsigned long ep_handle, void * buffer, unsigned length, NaUsbDevCompleteCallback_t callback, void * context)
{
    int             rc = 0;
    NaUsbEndpoint_t  * ep = (NaUsbEndpoint_t *)ep_handle;
    NaUsbDeviceReq_t    * req;

    NA_ASSERT(gUsbDeviceDriver != NULL);
    NA_ASSERT(ep != NULL);

    if (gUsbDeviceDriver == NULL || ep == NULL || (buffer == NULL && length > 0) || length > USB_DMA_BUFFER_SIZE_MAX)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: Invalid parameters\n");
        rc = -EINVAL;
        goto _ret;
    }

    if (buffer == NULL)
    {
        if (ep->dir == USB_DIR_OUT)
        {
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: buffer is NULL for out endpoint.\n");
            rc = -EINVAL;
            goto _ret;
        }
        else if (callback == NULL)
        {
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: buffer and callback are NULL.\n");
            rc = -EINVAL;
            goto _ret;
        }
    }

    if (!IS_WORD_ALIGNED(buffer)) 
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: buffer (0x%x) is not word-aligned\n", (unsigned)buffer);
        rc = -EFAULT;
        goto _ret;
    }

    /* get the netos endpoint data structure */
    if (usb_is_netos_endpoint(ep) == FALSE)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: Unknown endpoint handle\n");
        rc = -EINVAL;
        goto _ret;
    }
    /* create a request */
    req = usb_new_request(ep, callback, context);

    if (req == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: request full (no request buffer available)\n");
        rc = -ENOMEM;
        goto _ret;
    }

    req->buf = buffer;
    req->length = length;
    req->bytes_transfered = 0;


    if (buffer == NULL)
    {
        /* just save the callback (no need to set up the DMA).
         * callback will be called when NACK is set.
         * This is used when caller wants to be notified if
         * the host is requesting data.
         */
         goto _ret;
    }

    if (ep->dir == USB_DIR_IN)
    {   /* set up dma to send data out */
        rc = usbDma_SetupInPacket(ep, req);

    }
    else
    {   /* set up dma to receive data */
        rc = usbDma_SetupOutPacket(ep, req);
    }

    if (rc == 0)
    {
        ep->dma_buffer_desc_active_count++;
        if (ep->dma_buffer_desc_active_count == 1)
        {
            rc = usbDma_EnableEndpoint(ep);
            if (rc != 0)
            {
                USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: Could not enable DMA (channel %d), error %d\n", ep->dma_channel, rc);
            }
        }
    }
    else
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevQueue: Unable to setup DMA for DMA channel %d (error %d)", ep->dma_channel, rc);
        usb_free_request(ep, req);
    }

_ret:
    return rc;
}


/*ad*
 * Parses and formats the given buffer into control request form.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param request_buffer  Pointer to buffer that contains control request data.
 * @param ctrl_request  Pointer to memory where control data to be written.
 *
 *ad*/
void naUsbDevParseCtrlRequest(char * request_buffer, NaUsbCtrlRequest_t * ctrl_request)
{
    unsigned char wLengthLo, wLengthHi;
    unsigned short length = 0;

    NA_ASSERT(request_buffer != NULL);
    NA_ASSERT(ctrl_request != NULL);

    if (request_buffer != NULL && ctrl_request != NULL)
    {
        ctrl_request->bmRequestType = (unsigned char) request_buffer[0];
        ctrl_request->bRequest = (unsigned char) request_buffer[1];
        /* copy two bytes into the (USHORT) pointer */
        memcpy (&ctrl_request->wValue, &request_buffer[2], 2);

        /* copy two bytes into the (USHORT) pointer */
        memcpy (&ctrl_request->wIndex, &request_buffer[4], 2);
        /* copy two bytes into the (USHORT) pointer */
        memcpy (&ctrl_request->wLength, &request_buffer[6], 2);
        wLengthHi = (ctrl_request->wLength & 0x00ff);
        wLengthLo = (ctrl_request->wLength & 0xff00) >> 8;
        length = (wLengthHi == 0) ? (unsigned short) wLengthLo : (unsigned short) ((wLengthHi << 8) + wLengthLo);
        ctrl_request->wLength = length;
    }
    else
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevParseCtrlRequest: invalid ctrl request");
    }
}

/*ad*
 * Sets up control endpoint to send data to a host.
 *
 * This function copies the data into the internal buffer and starts
 * transferring. 
 * 
 * USB_EP0_OUT_DMA_DESCRIPTOR_COUNT is used to define number of internal buffers 
 * and it's pre-allocated during initialization.
 * The internal buffer should be available for sending data to the host. If not,
 * this function will returns -ENOMEM and the host may not be able to receive
 * control endpoint response.
 * Set USB_EP0_IN_TRANSFER_WAIT to one to ensure the transfer is completed
 * when this function returns.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param buf       Pointer to data to be sent
 * @param length    Length of the data
 *
 * @return  0       Control endpoint was successfully set up the data to be sent.
 * @return <0       -Errno. Error is encountered. 
 *                  The Control endpoint was not setup to send data to the host.
 *
 * @link USB_EP0_IN_TRANSFER_WAIT
 * @link USB_EP0_OUT_DMA_DESCRIPTOR_COUNT
 * @link usbEp0MaxPacketSize
 *ad*/
int naUsbDevEp0Send(void * buf, unsigned length)
{
    int                     rc = -EINVAL;
    NaUsbEndpoint_t         * ep = NULL;
    NaUsbDeviceReq_t        * req = NULL;
    void                    * buffer = NULL;
    unsigned                size;
    unsigned long volatile  * addr;
    unsigned long           status;

#if (USB_EP0_IN_TRANSFER_WAIT == 1)
    unsigned long   actual_event;
    int             req_status;
#endif

    NA_ASSERT(gUsbDeviceDriver != NULL);

    if (gUsbDeviceDriver == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevEp0Send: Usb driver is not initialized.\n");
        goto _ret;
    }

    ep = &gUsbDeviceDriver->ep0_in;

    addr = NA_USB_DEVICE_DMA_CCR (ep->dma_channel);       /* get DMA Chan Control Reg */
    status = *addr;
    if (status & DMA_BDR_MASK)      /* if BDR is still set, return */
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDma_SetupInPacket: DMA_BDR_MASK\n");
        rc = -EAGAIN;
        goto _ret;
	}

#if (USB_EP0_IN_TRANSFER_WAIT == 1)
        req = usb_new_request(ep, (NaUsbDevCompleteCallback_t) usb_ep0_in_callback, (void *)&req_status);
#else
        req = usb_new_request(ep, NULL, NULL);
#endif
        if (req == NULL)
        {
        /* No dma available. Is something wrong?
         * if we wait 2 times calling this function and it still fails,
         * we reset the dma for other IN packet.
         */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevEp0Send: request full (no request buffer available)\n");

            ep->dma_fails++;

            if (ep->dma_fails > 2)
            {
                usbDma_Refetch(ep->dma_channel);
                USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevEp0Send: usbDma_Refetch channel %d\n", ep->dma_channel);

    #if 0
                usb_ep_complete_request(ep, req, req->buf, 0, NA_USB_REQUEST_CLOSED);

                // if no completion function was specified, free the buffer here
                if (req->complete == NULL)
                {
                    usb_device_free_buffer(ep, req->buf, req->length);
                }

                ep->dma[bufIndex].src_addr = 0x0;
                ep->dma[bufIndex].buf_len = 0x0;
                ep->dma[bufIndex].flag = 0;

                usb_free_request(ep, req);
    #endif
                status = FLY_BY_READ | BURST_OPERS_4 | DMA_CHANNEL_ENABLE;
                addr = NA_USB_DEVICE_DMA_CCR (ep->dma_channel);
                *addr = status;           /* start transmitting */ 
                ep->dma_fails = 0;
            }
            rc = -ENOMEM;
            goto _ret;
        }

    size = length;

    if (size > 0)
    {
        /* if the length is at the max_packet_size boundary,
         * we need to add additional byte for status stage.
         * 
    	 * Copy the user data into our buffer. 
    	 */
        if ((size % ep->max_packet_size) == 0)
        {
            size++;
        }

    	    buffer = usb_device_alloc_buffer(ep, &size);
		    if (buffer == NULL)
		    {
			    USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "naUsbDevEp0Send: No buffer memory, channel %i, buffer length %i\n",(int)ep->dma_channel, (int)length);
                ep->dma_fails++;
                if (ep->dma_fails > 2)
                {
                    usbDma_Refetch(ep->dma_channel);
                    USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevEp0Send: usbDma_Refetch channel %d\n", ep->dma_channel);

        #if 0
                    usb_ep_complete_request(ep, req, req->buf, 0, NA_USB_REQUEST_CLOSED);

                    // if no completion function was specified, free the buffer here
                    if (req->complete == NULL)
                    {
                        usb_device_free_buffer(ep, req->buf, req->length);
                    }

                    ep->dma[bufIndex].src_addr = 0x0;
                    ep->dma[bufIndex].buf_len = 0x0;
                    ep->dma[bufIndex].flag = 0;

                    usb_free_request(ep, req);
        #endif
                    status = FLY_BY_READ | BURST_OPERS_4 | DMA_CHANNEL_ENABLE;
                    addr = NA_USB_DEVICE_DMA_CCR (ep->dma_channel);
                    *addr = status;           /* start transmitting */ 
                    ep->dma_fails = 0;
                }
                rc = -ENOMEM;
                goto _ret;
    		}

		memcpy((char *)buffer,(char *)buf,(int)size);
    }
    else 
    {
        buffer = gZeroBuffer;
    }


    req->buf = buffer;
    req->length = size;
    req->bytes_transfered = 0;

    rc = usbDma_SetupInPacket(ep, req);

    if (rc == 0)
    {
        USB_LOCK;
        if (ep->dma_buffer_desc_active_count == 0)
        {
            rc = usbDma_EnableEndpoint(ep);
            if (rc != 0)
            {
                USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "naUsbDevEp0Send: error usbDma_EnableEndpoint fails %d\n", rc);
            }
        }
        ep->dma_buffer_desc_active_count++;
        USB_UNLOCK;

    }
    else
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevEp0Send: error usbDma_SetupInPacket fails %d\n", rc);
    }

#if (USB_EP0_IN_TRANSFER_WAIT == 1)
    if (rc == 0)
    {
        USB_LOCK;
        gUsbDeviceDriver->ep0_state = eUsbEp_Sending;
        USB_UNLOCK;

        rc = tx_event_flags_get(&gUsbEp0Event, USB_EP_REQ_DONE, TX_OR_CLEAR, &actual_event, TX_WAIT_FOREVER);
        if (rc != TX_SUCCESS)
        {
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "naUsbDevEp0Send: tx_event_flags_get fails %d\n", rc);
        }
        if (req_status != NA_USB_REQUEST_COMPLETED)
        {
            rc = -EIO;
        }
        else
        {
            rc = 0;
        }
    }
#endif

_ret:
    if (rc != 0)
    {
        if (buffer != NULL) usb_device_free_buffer(ep, buffer, size);
        if (req != NULL) usb_free_request(ep, req);
    }
    return rc;
}

/*ad*
 * Starts USB device controller
 * 
 * This function enables USB device controller waiting for any IO activities.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @return  0           The usb device controller was successfully started.
 * @return <0           Error is encountered. The usb device controller was not
 *                      started.
 *
 * @link naUsbDevStop
 *ad*/
int naUsbDevStart(void)
{
    int     rc = 0;
    int     value;
    char    * buffer;
    int     buf_length;
    int     i;

    USB_LOCK;

    NA_ASSERT(gUsbDeviceDriver != NULL);

    if (gUsbDeviceDriver == NULL)
    {   /* not initialize yet */
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "naUsbDevStart: driver is not initialized yet\n");
        rc = -ENODEV;
        goto _ret;
    }

    /* Let's build configuration descriptor.
     * Let's start allocate 64-byte buffer. If not enough, 
     * increase 64 bytes each time until it fits the whole 
     * configuration descriptor
     */
    i = 1;
    do {
        
        buf_length = 64 * i;
        /* allocate buffer for the configuration descriptor */
        buffer = (char *)usb_kmalloc(buf_length, ALIGNED_32, 0);
        if (buffer == NULL)
        {
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usbDevice_start: alloc_buffer fails\n");
            rc = -ENOMEM;
            goto _ret;
        }
      
        value = build_config_desc((const NaUsbConfigurationDescriptor_t *)gUsbDeviceDriver->dev->configuration_descriptor,
                         (const NaUsbDescriptorHeader_t **)gUsbDeviceDriver->dev->descriptor_header, 
                          buffer,
                          buf_length);
        if (value > 0)
        {   /* fit the whole configuration descriptor */
            gUsbDeviceDriver->configuration_descriptor = buffer;
            gUsbDeviceDriver->configuration_descriptor_length = value;
        }
        else
        {   /* doesn't fits. free the current buffer and allocate bigger size */
            usb_kfree(buffer);
        }
        i++;
    } while (value < 0);


#if defined (NS9750)
    usb_device_timer_start();
#endif

    tx_thread_sleep(NS_MILLISECONDS_TO_TICKS(1000));

    rc = naIsrInstall (USB_DEVICE_INTERRUPT_LEVEL, (MC_ISR_HANDLER) usb_device_isr, (void *) 2);
    if (rc != SUCCESS)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usbDevice_start: naIsrInstall fails %d\n", rc);
    }

    /* start plug & play */
    usb_start_plug_and_play();

_ret:    
    USB_UNLOCK;
    return rc;
}

/*ad*
 * Initializes USB device controller
 * 
 * This function takes device descriptor, configuration descriptor, 
 * interface descriptors, endpoint descriptors and string descriptors
 * to configure the USB device controller.
 * 
 * It also accepts control endpoint callback and suspension callback.
 * The control endpoint callback is a callback function to handle and process
 * any device class specific requests.
 * The suspension callback is a callback function to notify user that
 * usb device controller is in suspended state.
 *
 * The control endpoint callback is called after the usb device controller
 * recovers from suspended state. 
 * 
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param dev   Pointer to the device data which including device descriptor,
 *              configuration descriptor, interface descriptors, endpoint 
 *              descriptors, string descriptors, control endpoint callback,
 *              and suspension callback.
 *
 * @return  0   The device controller was successfully initialized.
 * @return <0   -Errno. The device controller was not initialized.
 *
 *ad*/
int naUsbDevInit(const NaUsbDevice_t * dev)
{
    int rc = -EINVAL;
    int value;
    int     maxPacketSize;
    int     iInterface = 0;

    NaUsbConfigurationDescriptor_t  * pConfigDesc;

    NA_ASSERT(dev != NULL);

    memset(&gUsbDeviceMutex, 0, sizeof gUsbDeviceMutex);
    if (USB_CREATE_LOCK != TX_SUCCESS)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDevice_init: USB_CREATE_LOCK fails\n");
        return rc;
    }


    USB_LOCK;

    if (dev == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDevice_init: Invalid parameter\n");
        goto _ret;
    }

    if (gUsbDeviceDriver != NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDevice_init: driver is already intialized.\n");
        rc = -EBUSY;
        goto _ret;
    }

    if ((dev->descriptor_header == NULL) || (*dev->descriptor_header == NULL))
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDevice_init: Invalid Descriptor header specified.\n");
        goto _ret;
    }

    gUsbDeviceDriver = &gUsbDevice;
/*    gUsbDeviceDriver = (NaUsbNetos_t *)NA_UNCACHED(gUsbDeviceDriver); */

    memset(gUsbDeviceDriver, 0, sizeof(NaUsbNetos_t));

    gUsbDeviceDriver->dev = (NaUsbDevice_t *)dev;

    gUsbDeviceDriver->ep0_state = eUsbEp_Setup;

    /* initialize usb hardware */
    USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDevice_init: calling usb_init_hardware without locks.\n");
    usb_init_hardware();

    pConfigDesc = dev->configuration_descriptor;
    value = (pConfigDesc->bmAttributes && USB_CONFIG_ATT_WAKEUP);
    usb_device_wakeup((BOOLEAN)value);

    value = (pConfigDesc->bmAttributes && USB_CONFIG_ATT_SELFPOWER);
    rc = usb_device_selfpowered(value);
    if (rc != 0) goto _ret;

    get_configuration_interface_value(dev->descriptor_header, NULL, &iInterface);
    gUsbDeviceDriver->iConfiguration = pConfigDesc->bConfigurationValue;
    gUsbDeviceDriver->iInterface = iInterface;
    gUsbDeviceDriver->iAltSetting = 0;
    
    if (dev->speed == USB_SPEED_LOW)
    {
        narm_write_reg (NA_BUM_UCR_REG, NA_BUM_UCR_ADDRESS, speed, 0);
        maxPacketSize = USB_EP0_MAX_PACKET_SIZE_LOW_SPEED;
    }
    else
    {
        narm_write_reg (NA_BUM_UCR_REG, NA_BUM_UCR_ADDRESS, speed, 1);  /* Full speed */
        maxPacketSize = USB_EP0_MAX_PACKET_SIZE_FULL_SPEED;
    }

    if (dev->device_descriptor->bMaxPacketSize0 == 8 ||
        dev->device_descriptor->bMaxPacketSize0 == 16 ||
        dev->device_descriptor->bMaxPacketSize0 == 32 ||
        dev->device_descriptor->bMaxPacketSize0 == 64)
    {
        maxPacketSize = dev->device_descriptor->bMaxPacketSize0;
    }

    /*---------------  Endpoint0 OUT -------------- */
    rc = usb_init_ep0_out(&gUsbDeviceDriver->ep0_out, maxPacketSize);

    if (rc != 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usbDevice_init: Could not initialize endpoint 0 out (error %d)\n", rc);
        goto _ret;
    }

    /*---------------  Endpoint0 IN -------------- */
    rc = usb_init_ep0_in(&gUsbDeviceDriver->ep0_in, maxPacketSize);
    
    if (rc != 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usbDevice_init: Could not initialize endpoint 0 in (error %d)\n", rc);
        goto _ret;
    }

    gUsbDeviceDriver->ep0_state = eUsbEp_Waiting;

_ret:
    if (rc != 0 && rc != -EBUSY) 
    {              
        gUsbDeviceDriver = NULL;
    }

    USB_UNLOCK;

    return rc;
}



/*ad*
 * Stops USB device controller
 *
 * This function terminates USB process.
 * 
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include <usbDevApi.h>
 * @since 7.1
 *
 * @param   dev       Pointer to the device parameter given in naUsbDevInit
 * @return  0         The device controller was successfully stopped.
 * @return <0         Error is encountered. The device controller was not stopped.
 *
 * @link naUsbDevInit
 * @link naUsbDevStart
 *ad*/
int naUsbDevStop(const NaUsbDevice_t * dev)
{
    int rc = 0;

    if (dev == NULL)
    {
        rc = -EINVAL;
        goto _ret;
    }

    NA_ASSERT(dev != NULL);
    NA_ASSERT(gUsbDeviceDriver->dev == dev);

    if (gUsbDeviceDriver == NULL)
    {   /* not initialize yet */
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usbDevice_init: driver was not intialized.\n");
        rc = -ENODEV;
        goto _ret;
    }

    USB_LOCK;

    if (gUsbDeviceDriver->dev == dev)
    {
        naIsrUninstall(USB_DEVICE_INTERRUPT_LEVEL);

		usb_stop_plug_and_play();

        usb_device_done();

        gUsbDeviceDriver = NULL;
    }

    USB_UNLOCK;
_ret:
    return rc;
}

#endif /* BSP_USB_DEVICE_VERSION */
