/*
 *
 *     Module Name:  usbHandle.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 <string.h>
#include <errno.h>
#include "tx_api.h"
#include "bsp_drivers.h"
#include "bele.h"
#include "usbreg_def.h"
#include "usbDef.h"
#include "nammu.h"
#include "natimer.h"
#include "armutils.h"
#include "fwtypes.h"

#ifdef NET_OS_SPECIFIC
#include "minmax.h"
#endif


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

#if defined (NS9750)
/* Timer ticks for timer isr */
#define NA_USB_DEVICE_TIMER_TICKS_PER_SECOND    2 

static timerHandleType gUsbTimeHandle;
#endif

/* event threads definitions */
#define NA_USB_THREAD_STACK_SIZE (4096 + 1024)
#define USB_EVENT_THREAD_PRIORITY     3

/* English language id from usb.org */
#define US_ENGLISH_LANGID 0x0409
#define UK_ENGLISH_LANGID 0x0809
#define CA_ENGLISH_LANGID 0x1009

static char *event_thread_names[USB_DMA_COUNT] =
{
    "EP0 Out Event Thread",
    "EP0 IN Event Thread",
    "EP1 Event Thread",
    "EP2 Event Thread",
    "EP3 Event Thread",
    "EP4 Event Thread",
    "EP5 Event Thread",
    "EP6 Event Thread",
    "EP7 Event Thread",
    "EP8 Event Thread",
    "EP9 Event Thread",
    "EP10 Event Thread",
#if defined (NS9750) 
    "EP11 Event Thread"
#endif
};


/**
 * Build and send descriptor to the host via the given endpoint.
 *
 * This function allocates and formats a descriptor buffer with
 * given id and data specified in src.
 *
 * @category BSP:Device_Drivers:USB_DEVICE
 * @since 
 *
 * @param ep        Pointer to Netos endpoint structure
 * @param id        Id of the descriptor to be formatted
 * @param src       Pointer to the data 
 * @param length    Length of the src
 *
 * @return <0       Error (-Errno)
 * @return 0        Descriptor was successfully sent.
 *
 **/
static int usb_send_descriptor(NaUsbEndpoint_t *ep, int id, void * src, unsigned length)
{
    int             rc;
    char            * buffer;
    NaUsbDescriptorHeader_t * desc;
    unsigned        desc_length, total_length;

    desc_length = sizeof(NaUsbDescriptorHeader_t);
    total_length = desc_length + length;

    buffer = usb_device_alloc_buffer(ep, &total_length);

    if (buffer == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_send_descriptor: No buffer memory (size %d)\n", length);
        rc = -ENOMEM;
        goto _ret;
    }

    desc = (NaUsbDescriptorHeader_t *)buffer;
    desc->bLength = total_length;
    desc->bDescriptorType = id;

    buffer += desc_length;

    fmemcpy((void *)buffer, src, length);

    rc = naUsbDevEp0Send((void *)desc, total_length);
    if (rc != 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_send_descriptor: naUsbDevEp0Send failed %d\n", rc);
    }
    usb_device_free_buffer(ep, buffer, total_length);
    

_ret:
    return rc;
}

/**
 * Build and send string descriptor.
 *
 * It formats and sends the given descriptor.
 *
 * @category BSP:Device_Drivers:USB_DEVICE
 * @since 7.1
 *
 * @param ep    Pointer to netos endpoint structure
 * @param src   Pointer to string descriptor
 * @param length length of the src
 *
 * @return -ENOMEM  Cannot allocate memory
 * @return 0        Descriptor was successfully sent.
 *
 **/
static int usb_send_string_descriptor(NaUsbEndpoint_t * ep, char * src, unsigned length)
{
    int             rc;
    char            * buf;
    NaUsbDescriptorHeader_t * desc;
    unsigned        i, desc_length, total_length;

    desc_length = sizeof(NaUsbDescriptorHeader_t);
    /* each char takes 2 bytes for string descriptor */
    total_length = desc_length + length * 2;

    desc = (NaUsbDescriptorHeader_t *)usb_device_alloc_buffer(ep, &total_length);
 

    if (desc == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_send_string_descriptor: No buffer memory for string descriptor (length %d)\n", length);
        rc = -ENOMEM;
        goto _ret;
    }
    desc->bLength = total_length;
    desc->bDescriptorType = USB_DT_STRING;

    buf = (char *)desc + desc_length;

    /* let's format the descriptor.
     * Format each character into 2 bytes.
     */
    for (i=0; i < length; i++)
    {
        *(buf++) = *(src+i);
        *(buf++) = 0;
    }

    rc = naUsbDevEp0Send((void *)desc, total_length);
    if (rc != 0)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_send_string_descriptor: naUsbDevEp0Send failed %d\n", rc);
    }

    usb_device_free_buffer(ep, desc, total_length);

_ret:
    return rc;
}

/**
 * Process get-descriptor request.
 *
 * @category BSP:Device_Drivers:USB_DEVICE
 * @since 7.1
 *
 * @param desc_type         Descriptor type value
 * @param desc_index        Index of the descriptor type
 * @param request_length    Length to be returned
 *
 **/
static void handle_get_descriptor(int desc_type, int desc_index, unsigned request_length)
{
    unsigned        sendLength;
    NaUsbString_t   * string;
    int             i;


    /* the descriptor type (DEVICE, CONFIGURATION, STRING, etc.)  */
    switch (desc_type)   
    {
        case USB_DT_DEVICE:  /* 0x01 DEVICE_DESCRIPTOR */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "---> Device-Desc\n");
            sendLength = sizeof(NaUsbDeviceDescriptor_t);
            /* "Get Device Descriptor" request */
            /* Beware if descriptor length goes over 255 */
            /* 1. Gets the pointer to the device descriptor */
            /* 2. Set up EP0 to return the minimum between the wLength and descriptor len */

            naUsbDevEp0Send((void *)gUsbDeviceDriver->dev->device_descriptor, 
                           min (sendLength, request_length));
            break;

        case USB_DT_CONFIG:   /* 0x02 CONFIGURATION_DESCRIPTOR */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Configuration-desc %d\n", request_length);
            /* "Get Configuration Descriptor" request */
            if (request_length > 0 && gUsbDeviceDriver->configuration_descriptor != NULL)
            {
                naUsbDevEp0Send((void *)gUsbDeviceDriver->configuration_descriptor, 
                                    min(gUsbDeviceDriver->configuration_descriptor_length, 
                                    (int)request_length));
            }
            break;
        case USB_DT_STRING:  /* 0x03 STRING_DESCRIPTOR */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "String-desc %d\n", desc_index);
            /* Get String descriptor 
             * 1. Check for wValue for
             * 0x00 ---------------- is for Language ID
             */
            if (desc_index == 0x00)
            {   /*  Request is for LanguageID string */
                sendLength = min (2, request_length);
                usb_send_descriptor(&gUsbDeviceDriver->ep0_in, USB_DT_STRING,
                               (void *)&gUsbDeviceDriver->dev->strings->language,
                               sendLength);
            }
            else
            {
                /* see which id is requested.
                 * If English language, we need to use UNICODE encoding.
                 * If not English language, we expect the string is already
                 * in string descriptor format with UNICODE encoding. 
                 */
                for (i = 0; gUsbDeviceDriver->dev->strings->strings[i].s != NULL; i++)
                {
                    string = &gUsbDeviceDriver->dev->strings->strings[i]; 
                    if (desc_index == string->id)
                    {
                        if (gUsbDeviceDriver->dev->strings->language == (unsigned short) TO_LE16(US_ENGLISH_LANGID) ||
                            gUsbDeviceDriver->dev->strings->language == (unsigned short) TO_LE16(UK_ENGLISH_LANGID) ||
                            gUsbDeviceDriver->dev->strings->language == (unsigned short) TO_LE16(CA_ENGLISH_LANGID))
                        {
                            sendLength = min (strlen((char *)string->s), request_length);
                            usb_send_string_descriptor(&gUsbDeviceDriver->ep0_in, 
                                                       string->s, sendLength);
                        }
                        else
                        {
                            /* if not English, directly send the data.
                             * User should encode the string descriptor
                             * if not English.
                             */
                            NaUsbDescriptorHeader_t * stringDesc;

                            stringDesc = (NaUsbDescriptorHeader_t *)string->s;
                            sendLength = min (stringDesc->bLength, request_length);
                            naUsbDevEp0Send((void *)string->s, sendLength);
                        }
                    }
                }
            }
            break;
    }
    return;
}

/**
 * Process control endpoint OUT request.
 * This function handles standard control request.
 *
 * @external
 * @category BSP:Device_Drivers:USB_DEVICE
 * @include  <usbDevApi.h>  
 * @since 7.1
 *
 * @param ctrl_request   Pointer to control setup request data
 *
 * @return NA_USB_SUCCESS The control setup request is successfully processed.
 * @return NA_USB_NONE    The control setup request has not been processed.
 *
 **/
int usb_device_standard_request_handle(NaUsbCtrlRequest_t * ctrl_request)
{
    unsigned char   wValueLo, wValueHi;
    unsigned status, value;
    int      rc = NA_USB_SUCCESS;

    USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "usbDevice_HandleCtrlRequest: %02x %02x %04x %04x %04x --- ",
          ctrl_request->bmRequestType, ctrl_request->bRequest, ctrl_request->wValue,
          ctrl_request->wIndex, ctrl_request->wLength);
    
    switch (ctrl_request->bRequest)
    {
        case USB_REQ_CLEAR_FEATURE:    /* 0x1 CLEAR_FEATURE */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Clear-Feature\n");
#if 0
            
            if (ctrl_request->wValue == USB_DEVICE_REMOTE_WAKEUP)
            {
                if (ctrl_request->bmRequestType == USB_BM_HOST_TO_DEVICE | USB_BM_STANDARD | USB_BM_DEVICE)
                {
                }
            }
            else if (ctrl_request->wValue == USB_ENDPOINT_HALT)
            {
                if (ctrl_request->bmRequestType == USB_BM_HOST_TO_DEVICE | USB_BM_STANDARD | USB_BM_ENDPOINT)
                {
                }
            }
            else if (ctrl_request->wValue == USB_TEST_MODE)
            {
                if (ctrl_request->bmRequestType == USB_BM_HOST_TO_DEVICE | USB_BM_STANDARD | USB_BM_INTERFACE)
                {
                }
            }
#endif            
            rc = NA_USB_NONE;
            break;
        case USB_REQ_SET_FEATURE:      /* 0x3 SET_FEATURE */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Set-Feature\n");
            rc = NA_USB_NONE;
            break;

        case USB_REQ_SET_ADDRESS:      /* 0x5 SET_ADDRESS */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Set-Address\n");
            rc = NA_USB_NONE;
            break;

        case USB_REQ_SET_CONFIGURATION:        /* 0x9 SET_ADDRESS */
            gUsbDeviceDriver->iConfiguration = ctrl_request->wValue;
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "set-configuration %d\n", gUsbDeviceDriver->iConfiguration);
            break;
            
        case USB_REQ_SET_INTERFACE:    /* 0xB SET_INTERFACE */
            gUsbDeviceDriver->iInterface = ctrl_request->wIndex;
            gUsbDeviceDriver->iAltSetting = ctrl_request->wValue;
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "set-interface iInterface %d AltSetting %d\n", 
                    gUsbDeviceDriver->iInterface, gUsbDeviceDriver->iAltSetting);
            break;

        case USB_REQ_GET_STATUS:       /* 0x00 GET_STATUS */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Get-Status\n");
            /* Get Device Status - USB expects Little Endian status word containing:
               - Bit 0: Self Powered
               - Bit 1: Remote Wakeup enable
             */
            /* Manipulate NS9750 status bits into USB status positions */
            /* 1. Check for Remote Wake-up & Self Power */
            /* 2. Send the character string back through EP0. */
#if 1
            value = 0;

            status = narm_read_reg(NARM_USB_DCS_REG, NA_USB_DCSR_ADDRESS, self_pwr);

            if (status == 1)
            {
                value |= 0x1;
            }

            status = narm_read_reg(NARM_USB_GC_REG, NA_USB_GCSR_ADDRESS, wakeup);
            if (status == 1)
            {
                value |= 0x2;
            }
            naUsbDevEp0Send((void *)&value, ctrl_request->wLength);
            rc = NA_USB_SUCCESS;
#endif
            break;

        case USB_REQ_GET_CONFIGURATION:        /* 0x08 GET_CONFIGURATION */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Get-Configuration\n");
            /* Return the current configuration number */
            /* 1. get the current configuration number */
            /* 2. send it back through EP0 */
            naUsbDevEp0Send((void *)&gUsbDeviceDriver->iConfiguration, ctrl_request->wLength);
            rc = NA_USB_SUCCESS;
            break;

        case USB_REQ_GET_DESCRIPTOR:   /* 0x06 GET_DESCRIPTOR */
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Get-Descriptor -- %d\n", ctrl_request->wLength);
            /* Descriptor Type (HiByte)and Descriptor Index (LowByte) */
            wValueHi = (ctrl_request->wValue & 0x0ff); /* get the higher byte value */
            wValueLo = (ctrl_request->wValue & 0xff00) >> 8;   /* get the lower byte value */
            handle_get_descriptor(wValueHi, wValueLo, ctrl_request->wLength);
            rc = NA_USB_SUCCESS;
            break;
        default:
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_INFO, "Unknown\n");
            rc = NA_USB_NONE;
            break;
    }

    return rc;
}


static void usb_event_handler(ULONG thread_input)
{
    unsigned long   fifo_event, dma_event, other_event;
    unsigned long   actual_events;
    int             ccode;
    NaUsbEndpoint_t  * ep;

    TX_INTERRUPT_SAVE_AREA

    ep = (NaUsbEndpoint_t *)thread_input;

    if (ep == NULL) goto _ret;

    fifo_event = (USB_DEVICE_FIFO1_EVENT << ep->dma_channel);
    dma_event = (USB_DEVICE_DMA1_EVENT  << ep->dma_channel);
    other_event = 0;

    if (ep->dma_channel == 0)
    {
        other_event = (USB_DEVICE_SUSPEND_EVENT | USB_DEVICE_SET_CONFIG_EVENT |
                      USB_DEVICE_SET_IFACE_EVENT);
    }
    for (;;)
    {
        /* process event */
        ccode = tx_event_flags_get(ep->event, (fifo_event | dma_event | other_event), 
                                   TX_OR_CLEAR, &actual_events, 10 /* TX_WAIT_FOREVER */);

        if (ccode == TX_SUCCESS)
        {

            TX_DISABLE
#if 1
            if (actual_events & fifo_event)
            {
                usbFifo_Process(ep);
                *(unsigned long *) NA_USB_GIE_ADDRESS |= USB_GIS_FIFO;
            }
#endif
            if (actual_events & dma_event)
            {
                usbDma_Process(ep);
            }

            if (actual_events & other_event)
            {
                if (actual_events & USB_DEVICE_SUSPEND_EVENT)
                {
                    if (gUsbDeviceDriver->dev->suspend_callback)
                    {
                        gUsbDeviceDriver->dev->suspend_callback(gUsbDeviceDriver->dev);
                    }
                    narm_write_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, urst, 1);
                }
                if (actual_events & USB_DEVICE_SET_CONFIG_EVENT)
                {
                    /* if supported, add code here to handle set-configuration */
                }
                if (actual_events & USB_DEVICE_SET_IFACE_EVENT)
                {
                    /* if supported, add code here to handle set-interface */
                }
            }
            TX_RESTORE

        }
#if 0
        else if (ep->dma_channel != 0)
        {
            if (ep->dma_buffer_desc_active_count != 0)
            {
                usbDma_EnableEndpoint(ep);
            }
            
        }
#endif  
    }
_ret:
    return;
}

void usb_device_isr(int intrSource)
{
    unsigned long irqStatus, enStatus;
    unsigned long status;
    int suspendStatus;
/*    BYTE altInterface, iInterface; */

    NaUsbEndpoint_t      * ep;

    int channel = 0;
    unsigned fifoStatus;

    TX_INTERRUPT_SAVE_AREA

    TX_DISABLE

    if (intrSource != 2) goto _ret;

//    enStatus = *(unsigned long *) NA_USB_GIE_ADDRESS;       /* Global Interrupt Status Reg */
//    irqStatus = *(unsigned long *) NA_USB_GIS_ADDRESS;       /* Global Interrupt Status Reg */
    enStatus = narm_read_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, all);
    irqStatus = narm_read_reg(NA_USB_GIS_REG, NA_USB_GIS_ADDRESS, all);
//    suspendStatus = narm_read_reg(NARM_USB_GC_REG, NA_USB_GCSR_ADDRESS, susp);

    if ((enStatus & irqStatus) == 0) goto _ret;

    if (irqStatus & USB_GIS_SUSPEND)
    {
        suspendStatus = narm_read_reg(NARM_USB_GC_REG, NA_USB_GCSR_ADDRESS, susp);
        if (suspendStatus)
        {
            /* disable usb device - suspend interrupt 
             * This prevents getting the suspend interrut again.
             */
            narm_write_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, suspend, 0);
            /* channel 0 (EP0) will handle this */
            ep = usb_channel_get_netos_endpoint(0);
            if (ep != NULL)
            {
                tx_event_flags_set(ep->event, USB_DEVICE_SUSPEND_EVENT, TX_OR);
            }
        }
    }

    if (irqStatus & USB_GIS_SET_CONFIG)
    {
        /* channel 0 (EP0) will handle this */
        ep = usb_channel_get_netos_endpoint(0);
        if (ep != NULL)
        {
            tx_event_flags_set(ep->event, USB_DEVICE_SET_CONFIG_EVENT, TX_OR);
        }
    }
    if (irqStatus & USB_GIS_SET_INTERFACE)
    {
        /* channel 0 (EP0) will handle this */
        ep = usb_channel_get_netos_endpoint(0);
        if (ep != NULL)
        {
            tx_event_flags_set(ep->event, USB_DEVICE_SET_IFACE_EVENT, TX_OR);
        }

    }

    if (irqStatus & USB_GIS_FIFO)
    {
//        *(unsigned long *) NA_USB_GIE_ADDRESS &= ~USB_GIS_FIFO;  
        status = narm_read_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, all);
        status &= ~USB_GIS_FIFO;
        narm_write_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, all, status);

        for (channel = 0; channel < USB_DMA_COUNT; channel++)
        {
            ep = usb_channel_get_netos_endpoint(channel);
            if (ep != NULL)
            {
                usb_get_fifo_status(channel, &fifoStatus);

                if ((fifoStatus & _USB_FIFO_IRQ_ALL_BITS))  
                {
/*                    usbFifo_Process(ep); */
                    tx_event_flags_set(ep->event, (USB_DEVICE_FIFO1_EVENT << channel), TX_OR);
                }
            }
        }
    }

    if (irqStatus & USB_GIS_GBL_DMA)
    {
//        *(unsigned long *) NA_USB_GIE_ADDRESS &= ~(irqStatus & USB_GIS_DMA_CHANNELS);
        status = narm_read_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, all);
        status &= ~(irqStatus & USB_GIS_DMA_CHANNELS);
        narm_write_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, all, status);
                    
        for (channel=0; channel < USB_DMA_COUNT; channel++)
        {
            if (irqStatus & (USB_GIS_DMA_CHANNEL_1 << channel))
            {
                ep = usb_channel_get_netos_endpoint(channel);
                
                if (ep != NULL)
                {
                    tx_event_flags_set(ep->event, (USB_DEVICE_DMA1_EVENT << channel), TX_OR);
                    /* enable usb device - suspend interrupt 
                     * since usb starts working.
                     */
                    narm_write_reg(NARM_USB_GIE_REG, NA_USB_GIE_ADDRESS, suspend, 1);
                }
            }
        }
    }

//    *(unsigned long *) NA_USB_GIS_ADDRESS |= irqStatus;
    narm_write_reg(NA_USB_GIS_REG, NA_USB_GIS_ADDRESS, all, irqStatus);

_ret:
    TX_RESTORE

    return;
}


int usb_device_endpoint_thread_init(NaUsbEndpoint_t * ep, int priority, int stack_size)
{

    int rc = 0;

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

    ep->event_thread = (TX_THREAD *)malloc(sizeof(TX_THREAD));
    if (ep->event_thread == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_device_endpoint_thread_init:  malloc TX_THREAD fails %d.", rc);
        rc = -ENOMEM;
        goto _ret;
    }

    memset(ep->event_thread, 0, sizeof(TX_THREAD));

    ep->event_thread_stack = (char *)malloc(stack_size);
    if (ep->event_thread_stack == NULL)
    {
        USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_device_endpoint_thread_init: malloc TX_THREAD stack size %d fails %d.", stack_size, rc);
        rc = -ENOMEM;
        goto _ret;
    }

    memset(ep->event_thread_stack, 0, stack_size);
    rc = tx_thread_create (ep->event_thread,      /* control block for thread */
                           event_thread_names[ep->dma_channel],       /* thread name              */
                           usb_event_handler,               /* entry function           */
                           (unsigned long)ep,                             /* parameter                */
                           ep->event_thread_stack,                         /* start of stack           */
                           stack_size, /* size of stack            */
                           priority,   /* priority                 */
                           priority,   /* preemption threshold     */
                           1,                          /* time slice threshold     */
                           TX_AUTO_START);             /* start immediately        */
    if (rc != TX_SUCCESS)
    {
        if (ep->event_thread != NULL) 
        {
            free(ep->event_thread);
            ep->event_thread = NULL;
        }
        if (ep->event_thread_stack != NULL)
        {
            free(ep->event_thread_stack);
            ep->event_thread_stack = NULL;
        }
        rc = -ENOMEM;
        goto _ret;
    }
_ret:
    return rc;
}    
int usb_device_endpoint_thread_free(NaUsbEndpoint_t * ep)
{

    int rc = -EINVAL;

    if (ep != NULL && ep->event_thread != NULL)
    {
        rc = tx_thread_terminate(ep->event_thread);
        if (rc != TX_SUCCESS)
        {
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_device_endpoint_thread_free: tx_thread_terminate fails %d\n", rc);
        }

        rc = tx_thread_delete(ep->event_thread);
        if (rc != TX_SUCCESS)
        {
            USB_DEBUG(USB_DEVICE_TRACE, TRACE_CRITICAL, "usb_device_endpoint_thread_free: tx_thread_delete fails %d\n", rc);
        }

        free(ep->event_thread);
        ep->event_thread = NULL;
        if (ep->event_thread_stack != NULL)
        {
            free(ep->event_thread_stack);
            ep->event_thread_stack = NULL;
        }
        rc = 0;
    }

    return rc;
}
int usb_device_done(void)
{
    int             rc = 0;
    int             i;
    NaUsbEndpoint_t  * ep;

    USB_LOCK;

    if (gUsbDeviceDriver != NULL)
    {

        for (i=0; i < USB_DMA_COUNT; i++)
        {
            if (i == 0)
            {
                ep = &gUsbDeviceDriver->ep0_out;
            }
            else if (i == 1)
            {
                ep = &gUsbDeviceDriver->ep0_in;
                tx_event_flags_delete(&gUsbEp0Event);
            }
            else
            {
                ep = gUsbDeviceDriver->ep[i-1];
            }

            if (ep != NULL)
            {

                rc = naUsbDevCloseEndpoint((unsigned long)ep);
            }
        }

        if (gUsbDeviceDriver->configuration_descriptor != NULL)
        {
            usb_kfree(gUsbDeviceDriver->configuration_descriptor);
        }
    }

#if 0
    if (gUsbDeviceDriver != NULL)
    {
        for (i=0; i < USB_DMA_COUNT; i++)
        {
			if ((ep = usb_channel_get_netos_endpoint(i)) != NULL)
                rc = naUsbDevCloseEndpoint((unsigned long)ep);
        }

        if (gUsbDeviceDriver->configuration_descriptor != NULL)
            usb_kfree(gUsbDeviceDriver->configuration_descriptor);
    }
#endif
    USB_UNLOCK;

    USB_DELETE_LOCK;

    return rc;
    
}

#if defined (NS9750)
timerIsrCcodeType usb_device_timer_isr(timerHandleType const handle, void * context)
{ 
#define _USB_DMA_CR_STATE_MASK      0x0000fc00
#define _USB_DMA_SR_LENGTH_MASK     0xffff

    unsigned long volatile *addr;
    unsigned long srStatus, crStatus, status; 
    unsigned long spaceAvailable, dataAvailable;    

    NaUsbEndpoint_t      * ep;
    NaUsbDeviceReq_t    * req;
    int                 ep_index;
    int                 descIndex;
    
    TX_INTERRUPT_SAVE_AREA 
    
    NA_UNUSED_PARAM(context);

    TX_DISABLE

    NATimerInterruptAck(handle);

    for (ep_index = 1; ep_index < (int)gUsbDeviceDriver->ep_count; ep_index++)
    {
        ep = gUsbDeviceDriver->ep[ep_index];
        if ((ep != NULL) && (ep->max_packet_size != 0) && (ep->dir == USB_DIR_OUT))
        {
            usb_get_fifo_status(ep->dma_channel, (unsigned *)&status);

            if (status & 0x80)   
            {   /* if the ack exist, channel is still active. */
                usb_reset_fifo_status(ep->dma_channel);
            }     
            else
            {
                addr = NA_USB_DEVICE_DMA_CCR(ep->dma_channel);     
                crStatus = *addr;

                 /* If not idle and not aborted */
                if ((crStatus & _USB_DMA_CR_STATE_MASK) != 0 && (crStatus & DMA_CHANNEL_ABORT) == 0) 
                {
                    *addr = (crStatus & ~DMA_CHANNEL_ENABLE);

                    addr = NA_USB_DEVICE_DMA_SIER (ep->dma_channel);
                    srStatus = *addr;

                    /* get the length in the register */
                    spaceAvailable = srStatus & _USB_DMA_SR_LENGTH_MASK;   
                  
                    descIndex = ep->dma_buffer_desc_process_index; 
                    req = usb_get_request(ep, &ep->dma[descIndex]);

                    if (req == NULL)
                    {
                        USB_DEBUG(USB_DEVICE_TRACE, TRACE_WARNING, "usb_device_timer_isr: no request in process index %i, channel %i\n", descIndex, ep->dma_channel);
                        continue;
                    }
                    dataAvailable =  req->length - spaceAvailable;   
  
                    if (dataAvailable == 0) 
                    {
                        addr = NA_USB_DEVICE_DMA_CCR(ep->dma_channel);     
                        *addr = crStatus | DMA_CHANNEL_ENABLE;
                    }
                    else
                    {
                        if (ep->dir == USB_DIR_OUT)
                        {
                            ep->dma[descIndex].flag |= DMA_FULL_MASK;
                        }
                        else
                        {
                            ep->dma[descIndex].flag &= ~DMA_FULL_MASK;
                        }

                        ep->dma[descIndex].buf_len =  req->length - spaceAvailable;   

                        tx_event_flags_set(ep->event, (USB_DEVICE_DMA1_EVENT << ep->dma_channel), TX_OR);
	                }
	    
                }
            }   
        }
    }

    TX_RESTORE
    return ackAndContinue;
}

void usb_device_timer_start(void)
{

    timerConfigType config = 
    {
        timerFrequency,                 /* specify config as a frequency*/
        {NA_USB_DEVICE_TIMER_TICKS_PER_SECOND}, /* frequency set in usb.c */
        notGated,                       /* system clock is not gated */
        FALSE,                          /* nor is it a one shot */
        usb_device_timer_isr,                  /* pointer to ISR for timer */
        NULL,                           /* ISR context (not used by threadX*)*/
        FALSE                           /* don't want timer to halt in debugger*/


    };

    NATimerOpen(TIMER_NEXT_AVAILABLE, &gUsbTimeHandle, timerNaturalSize);
    NATimerConfigure(gUsbTimeHandle,  &config);
    NATimerInterruptEnable(gUsbTimeHandle);
    NATimerStart(gUsbTimeHandle);
}

#endif /* (NS9750) */

#endif /* BSP_USB_DEVICE_VERSION */
