//*****************************************************************************
//
// usbhhub.c - This file contains the host HID driver.
//
// Copyright (c) 2011-2012 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 9453 of the Stellaris USB Library.
//
//*****************************************************************************

#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "driverlib/usb.h"
#include "driverlib/interrupt.h"
#include "driverlib/rom_map.h"
#include "driverlib/rtos_bindings.h"
#include "usblib/usblib.h"
#include "usblib/host/usbhost.h"
#include "usblib/host/usbhostpriv.h"
#include "usblib/host/usbhhub.h"
#ifdef INCLUDE_DEBUG_OUTPUT
#include "utils/uartstdio.h"
#define DEBUG_OUTPUT UARTprintf
#else
#define DEBUG_OUTPUT while(0)((int (*)(char *, ...))0)
#endif

//*****************************************************************************
//
//! \addtogroup usblib_host_class
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
//! Forward references to the hub class driver functions.
//
//*****************************************************************************
static void *HubDriverOpen(tUSBHostDevice *pDevice);
static void HubDriverClose(void *pvInstance);

//*****************************************************************************
//
//! This constant global structure defines the Hub Class Driver that is
//! provided with the USB library.
//
//*****************************************************************************
const tUSBHostClassDriver g_USBHubClassDriver =
{
    USB_CLASS_HUB,
    HubDriverOpen,
    HubDriverClose,
    0
};

//*****************************************************************************
//
// The instance data storage for attached hub.
//
//*****************************************************************************
static tHubInstance *g_pRootHub;

//*****************************************************************************
//
// Hub and port state change flags as reported via the hub's IN endpoint.
//
//*****************************************************************************
static volatile unsigned long g_ulChangeFlags;

//
// Note: The following assumes ROOT_HUB_MAX_PORTS is less than 32!
//
static unsigned long g_ulHubChanges;

//*****************************************************************************
//
// This function is called to send a request to the hub to set a feature on
// a given port.
//
// \param ulInstance is the hub device instance.
// \param ucPort is the port number for this request.
// \param usFeature is one of the HUB_FEATURE_PORT_* values.
//
// This function will send the set feature request to the hub indicated by the
// \e ulInstance parameter.  The \e ucPort value indicates which port number
// to send this request to and can range from 0 to the number of valid ports
// on the given hub.  A \e ucPort value of 0 is an access to the hub itself and
// not one of the hub ports.  The \e usFeature is the feature request to set
// on the given port.  For example, a \e usFeature value of
// \e HUB_FEATURE_PORT_RESET and \e ucPort value of 1 will cause reset
// signaling to hub port 1.
//
// \return None.
//
//*****************************************************************************
static void
HubSetPortFeature(unsigned long ulInstance, unsigned char ucPort,
                  unsigned short usFeature)
{
    tUSBRequest SetupPacket;
    tHubInstance *pHubInstance;
    tUSBHostDevice *pDevice;

    //
    // Retrieve the hub instance and device pointer.
    //
    pHubInstance = (tHubInstance *)ulInstance;
    pDevice = pHubInstance->pDevice;

    //
    // This is a standard OUT request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS |
                                USB_RTYPE_OTHER;

    //
    // Set the field to clear the requested port feature.
    //
    SetupPacket.bRequest = USBREQ_SET_FEATURE;
    SetupPacket.wValue = usFeature;
    SetupPacket.wIndex = ucPort;
    SetupPacket.wLength = 0;

    //
    // Send the request.
    //
    USBHCDControlTransfer(0,
                          &SetupPacket,
                          pDevice,
                          0,
                          0,
                          pDevice->DeviceDescriptor.bMaxPacketSize0);
}

//*****************************************************************************
//
// This function is called to send a request to the hub to clear a feature on
// a given port.
//
// \param ulInstance is the hub device instance.
// \param ucPort is the port number for this request.
// \param usFeature is one of the HUB_FEATURE_PORT_* values.
//
// This function will send the clear feature request to the hub indicated by
// the \e ulInstance parameter.  The \e ucPort value indicates which port
// number to send this request to and can range from 0 to the number of valid
// ports on the given hub.  A \e ucPort value of 0 is an access to the hub
// itself and not one of the hub ports.  The \e usFeature is the feature
// request to clear on the given port.  For example, a \e usFeature value of
// \e HUB_FEATURE_C_PORT_RESET and \e ucPort value of 1 will clear the reset
// complete signaling on hub port 1.  Values like the reset feature will
// remain set until actively cleared by this function.
//
// \return None.
//
//*****************************************************************************
static void
HubClearPortFeature(unsigned long ulInstance, unsigned char ucPort,
                    unsigned short usFeature)
{
    tUSBRequest SetupPacket;
    tHubInstance *pHubInstance;
    tUSBHostDevice *pDevice;

    //
    // Retrieve the hub instance and device pointer.
    //
    pHubInstance = (tHubInstance *)ulInstance;
    pDevice = pHubInstance->pDevice;

    //
    // This is a standard OUT request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS |
                                USB_RTYPE_OTHER;

    //
    // Set the field to clear the requested port feature.
    //
    SetupPacket.bRequest = USBREQ_CLEAR_FEATURE;
    SetupPacket.wValue = usFeature;
    SetupPacket.wIndex = ucPort;
    SetupPacket.wLength = 0;

    //
    // Send the request.
    //
    USBHCDControlTransfer(0,
                          &SetupPacket,
                          pDevice,
                          0,
                          0,
                          pDevice->DeviceDescriptor.bMaxPacketSize0);
}

//*****************************************************************************
//
// This function is used to retrieve the current status of a port on the
// hub.
//
// \param ulInstance is the hub device instance.
// \param ucPort is the port number for this request.
// \param pusPortStatus is a pointer to the memory to store the current status
// of the port.
// \param pusPortChange is a pointer to the memory to store the current change
// status of the ports.
//
// This function is used to retrieve the current overall status and change
// status for the port given in the \e ucPort parameter.  The \e ucPort value
// indicates which port number to send this request to and can range from 0 to
// the number of valid ports on the given hub.  A \e ucPort value of 0 is an
// access to the hub itself and not one of the hub ports.
//
// \return None.
//
//*****************************************************************************
static tBoolean
HubGetPortStatus(unsigned long ulInstance, unsigned char ucPort,
                 unsigned short *pusPortStatus, unsigned short *pusPortChange)
{
    unsigned long ulData, ulRead;
    tUSBRequest SetupPacket;
    tHubInstance *pHubInstance;
    tUSBHostDevice *pDevice;

    //
    // Retrieve the hub instance and device pointer.
    //
    pHubInstance = (tHubInstance *)ulInstance;
    pDevice = pHubInstance->pDevice;

    //
    // This is a standard OUT request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_IN | USB_RTYPE_CLASS |
                                USB_RTYPE_OTHER;

    //
    // Set the fields to get the hub status.
    //
    SetupPacket.bRequest = USBREQ_GET_STATUS;
    SetupPacket.wValue = 0;
    SetupPacket.wIndex = (unsigned short)ucPort;
    SetupPacket.wLength = 4;

    //
    // Send the request.
    //
    ulRead = USBHCDControlTransfer(0, &SetupPacket,
                                   pDevice,
                                   (unsigned char *)&ulData, 4,
                                   pDevice->DeviceDescriptor.bMaxPacketSize0);

    //
    // Check that we received the correct number of bytes.
    //
    if(ulRead != 4)
    {
        return(false);
    }
    else
    {
        //
        // We got 4 bytes from the device. Now translate these into the 2
        // unsigned shorts we pass back to the caller.
        //
        *pusPortStatus = (unsigned short)(ulData & 0xFFFF);
        *pusPortChange = (unsigned short)(ulData >> 16);

        DEBUG_OUTPUT("Port %d, status 0x%04x, change 0x%04x\n", ucPort,
                     *pusPortStatus, *pusPortChange);
    }

    //
    // All is well.
    //
    return(true);
}

//*****************************************************************************
//
// This function handles callbacks for the interrupt IN endpoint for the hub
// device.
//
//*****************************************************************************
static void
HubIntINCallback(unsigned long ulPipe, unsigned long ulEvent)
{
    switch (ulEvent)
    {
        //
        // Handles a request to schedule a new request on the interrupt IN
        // pipe.
        //
        case USB_EVENT_SCHEDULER:
        {
            //
            // Set things up to read the next change indication from the hub.
            //
            USBHCDPipeSchedule(ulPipe, (unsigned char *)&g_ulHubChanges,
                               (unsigned long)g_pRootHub->ucReportSize);
            break;
        }

        //
        // Called when new data is available on the interrupt IN pipe.
        //
        case USB_EVENT_RX_AVAILABLE:
        {
            //
            // For data transfers on INT IN endpoints, we need to acknowledge
            // the data from this callback.
            //
            USBHCDPipeDataAck(ulPipe);

            //
            // Update our global "ports needing service" flags with the latest
            // information we've just received.
            //
            g_ulChangeFlags |= g_ulHubChanges;

            //
            // Send the report data to the USB host hub device class driver if
            // we have been given a callback function.
            //
            if(g_pRootHub->pfnCallback)
            {
                g_pRootHub->pfnCallback((void *)g_pRootHub->ulCBData,
                                        USB_EVENT_RX_AVAILABLE,
                                        ulPipe,
                                        &g_ulHubChanges);
            }

            break;
        }
        case USB_EVENT_ERROR:
        {
            break;
        }
    }
}

//*****************************************************************************
//
// Query the class-specific hub descriptor.
//
//*****************************************************************************
static tBoolean
GetHubDescriptor(tUsbHubDescriptor *psDesc)
{
    unsigned long ulRead;
    tUSBRequest SetupPacket;
    tUSBHostDevice *pDevice;

    //
    // Retrieve the device pointer.
    //
    pDevice = g_pRootHub->pDevice;

    //
    // This is a standard OUT request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_IN | USB_RTYPE_CLASS |
                                USB_RTYPE_DEVICE;

    //
    // Set the fields to get the hub descriptor.  Initially, we request only
    // the first 4 bytes of the descriptor.  This will give us the size which
    // we use to determine how many bytes to read to get the full descriptor.
    // This is necessary since we don't know how many ports the hub can support
    // and we only support up to MAX_USB_DEVICES.
    //
    SetupPacket.bRequest = USBREQ_GET_DESCRIPTOR;
    SetupPacket.wValue = (USB_DTYPE_HUB << 8);
    SetupPacket.wIndex = 0;
    SetupPacket.wLength = sizeof(tUsbHubDescriptor);

    //
    // Send the request.
    //
    ulRead = USBHCDControlTransfer(0, &SetupPacket,
                                   pDevice,
                                   (void *)psDesc, sizeof(tUsbHubDescriptor),
                                   pDevice->DeviceDescriptor.bMaxPacketSize0);

     //
     // Make sure we got at least some data.
     //
     if(ulRead == 0)
     {
         return(false);
     }

    //
    // All is well.
    //
    return(true);
}

//*****************************************************************************
//
// Open an instance of the hub driver.  This is called when the USB host
// has enumerated a new hub device.
//
//*****************************************************************************
static void *
HubDriverOpen(tUSBHostDevice *pDevice)
{
    tEndpointDescriptor *pEndpointDescriptor;
    tInterfaceDescriptor *pInterface;
    tUsbHubDescriptor  sHubDesc;
    tBoolean bRetcode;
    unsigned long ulLoop;

    //
    // If we are already talking to a hub, fail the call.  We only support
    // a single hub.
    //
    if(g_pRootHub->bHubActive)
    {
        return(0);
    }

    //
    // Get pointers to the device descriptors we need to look at.
    //
    pInterface = USBDescGetInterface(pDevice->pConfigDescriptor, 0, 0);
    pEndpointDescriptor = USBDescGetInterfaceEndpoint(pInterface, 0,
                                      pDevice->ulConfigDescriptorSize);

    //
    // If there are no endpoints, something is wrong since a hub must have
    // a single INT endpoint for signaling.
    //
    if(pEndpointDescriptor == 0)
    {
        return 0;
    }

    //
    // Make sure we really are talking to a hub.
    //
    if((pInterface->bInterfaceClass != USB_CLASS_HUB) ||
       pInterface -> bInterfaceSubClass || pInterface -> bInterfaceProtocol)
    {
        //
        // Something is wrong - this isn't a hub or, if it is, we don't
        // understand the protocol it is using.
        //
        return(0);
    }

    //
    // Remember the device information for later.
    //
    g_pRootHub->pDevice = pDevice;

    //
    // A hub must support an interrupt endpoint so check this.
    //
    if((pEndpointDescriptor->bmAttributes & USB_EP_ATTR_TYPE_M) ==
       USB_EP_ATTR_INT)
    {
        //
        // The endpoint is the correct type. Is it an IN endpoint?
        //
        if(pEndpointDescriptor->bEndpointAddress & USB_EP_DESC_IN)
        {
            //
            // Yes - all is well with the hub endpoint so allocate a pipe to
            // handle traffic from the hub.
            //
            g_pRootHub->ulIntInPipe = USBHCDPipeAlloc(0,USBHCD_PIPE_INTR_IN,
                                                      pDevice,
                                                      HubIntINCallback);
            USBHCDPipeConfig(g_pRootHub->ulIntInPipe,
                             pEndpointDescriptor->wMaxPacketSize,
                             pEndpointDescriptor->bInterval,
                             pEndpointDescriptor->bEndpointAddress &
                             USB_EP_DESC_NUM_M);
        }
    }

    //
    // Did we allocate the endpoint successfully?
    //
    if(!g_pRootHub->ulIntInPipe)
    {
        //
        // No - return an error.
        //
        return 0;
    }

    //
    // Assuming we have a callback, call it to tell the owner that a hub is
    // now connected.
    //
    if(g_pRootHub->pfnCallback != 0)
    {
        g_pRootHub->pfnCallback((void *)g_pRootHub->ulCBData,
                                USB_EVENT_CONNECTED,
                                (unsigned long)g_pRootHub, 0);
    }

    //
    // Get the hub descriptor and store information we'll need for later.
    //
    bRetcode = GetHubDescriptor(&sHubDesc);
    if(bRetcode)
    {

        //
        // We read the descriptor successfully so extract the parts we need.
        //
        g_pRootHub->ucNumPorts = sHubDesc.bNbrPorts;
        g_pRootHub->usHubCharacteristics = sHubDesc.wHubCharacteristics;
        g_pRootHub->ucNumPortsInUse = (sHubDesc.bNbrPorts > MAX_USB_DEVICES) ?
                                      MAX_USB_DEVICES : sHubDesc.bNbrPorts;

        //
        // The size of the status change report that the hub sends is dependent
        // upon the number of ports that the hub supports.  Calculate this by
        // adding 1 to the number of ports (bit 0 of the report is the hub
        // status, higher bits are one per port) then dividing by 8 (bits per
        // byte) and rounding up.
        //
        g_pRootHub->ucReportSize = ((sHubDesc.bNbrPorts + 1) + 7) / 8;

        //
        // Enable power to all ports on the hub.
        //
        for(ulLoop = 1; ulLoop <= sHubDesc.bNbrPorts; ulLoop++)
        {
            //
            // Turn on power to this port.
            //
            HubSetPortFeature((unsigned long )g_pRootHub, ulLoop,
                              HUB_FEATURE_PORT_POWER);
        }

        //
        // Clear out our port state structures.
        //
        for(ulLoop = 0; ulLoop < MAX_USB_DEVICES; ulLoop++)
        {
            g_pRootHub->psPorts[ulLoop].bChanged = false;
            g_pRootHub->psPorts[ulLoop].sState = PORT_IDLE;
        }
    }
    else
    {
        //
        // Oops - we can't read the hub descriptor!  Tidy up and return
        // an error.
        //
        USBHCDPipeFree(g_pRootHub->ulIntInPipe);
        g_pRootHub->pfnCallback = 0;
        g_pRootHub->bHubActive = false;
        return(0);
    }

    //
    // If we get here, all is well so remember that the hub is connected and
    // active.
    //
    g_pRootHub->bHubActive = true;

    //
    // Return our instance data pointer to the caller to use as a handle.
    //
    return((void *)g_pRootHub);
}

//*****************************************************************************
//
// Close an instance of the hub driver.
//
//*****************************************************************************
static void
HubDriverClose(void *pvInstance)
{
    unsigned long ulLoop;

    //
    // No device so just exit.
    //
    if(g_pRootHub->pDevice == 0)
    {
        return;
    }

    //
    // Disconnect any devices that are currently connected to the hub.
    //
    for(ulLoop = 0; ulLoop < MAX_USB_DEVICES; ulLoop++)
    {
        //
        // Does this port have a device connected to it that we have previously
        // reported to the host control layer?
        //
        if((g_pRootHub->psPorts[ulLoop].sState == PORT_ACTIVE) ||
           (g_pRootHub->psPorts[ulLoop].sState == PORT_RESET_WAIT) ||
           (g_pRootHub->psPorts[ulLoop].sState == PORT_ENUMERATED) ||
           (g_pRootHub->psPorts[ulLoop].sState == PORT_ERROR))
        {
            //
            // Yes - tell the host controller to disconnect the device.
            //
            USBHCDHubDeviceDisconnected(0,
                g_pRootHub->psPorts[ulLoop].ulDevHandle);

        }

        //
        // Make sure that the state returns to idle.
        //
        g_pRootHub->psPorts[ulLoop].sState = PORT_IDLE;

    }

    //
    // Reset the device pointer.
    //
    g_pRootHub->pDevice = 0;

    //
    // Mark the hub as absent.
    //
    g_pRootHub->bHubActive = false;

    //
    // Note that we are not in the middle of enumerating anything.
    //
    g_pRootHub->bEnumerationBusy = false;

    //
    // Free the Interrupt IN pipe.
    //
    if(g_pRootHub->ulIntInPipe != 0)
    {
        USBHCDPipeFree(g_pRootHub->ulIntInPipe);
    }

    //
    // If the callback exists, call it with a DISCONNECTED event.
    //
    if(g_pRootHub->pfnCallback != 0)
    {
        g_pRootHub->pfnCallback((void *)g_pRootHub->ulCBData,
                                USB_EVENT_DISCONNECTED,
                                (unsigned long)g_pRootHub, 0);
    }
}

//*****************************************************************************
//
// Perform any processing required as a result of a change in the reset
// signaling for a given port.
//
//*****************************************************************************
static void
HubDriverReset(unsigned char ucPort, tBoolean bResetActive)
{
    //
    // Did the reset sequence end or begin?
    //
    if(!bResetActive)
    {
        //
        // The reset ended.  Now wait for at least 10ms before signaling
        // USB enumeration code that a new device is waiting to be enumerated.
        //
        g_pRootHub->psPorts[ucPort].sState = PORT_RESET_WAIT;

        //
        // Set the wait to 10ms (10 frames) from now.
        //
        g_pRootHub->psPorts[ucPort].ulCount = 10;
    }
    else
    {
        //
        // Was this device previously active?
        //
        if(g_pRootHub->psPorts[ucPort].sState == PORT_ACTIVE)
        {
            USBHCDHubDeviceDisconnected(0,
                g_pRootHub->psPorts[ucPort].ulDevHandle);
        }

        //
        // The reset is active so mark our port as in reset.
        //
        g_pRootHub->psPorts[ucPort].sState = PORT_RESET_ACTIVE;
    }
}

//*****************************************************************************
//
// Start the process of enumerating a new device by issuing a reset to the
// appropriate downstream port.
//
//*****************************************************************************
static void
HubDriverDeviceReset(unsigned char ucPort)
{
    DEBUG_OUTPUT("Starting enumeration for port %d\n", ucPort);

    //
    // Record the fact that we are in the process of enumerating a device.
    //
    g_pRootHub->bEnumerationBusy = true;

    //
    // Save the port that is being enumerated.
    //
    g_pRootHub->ucEnumIdx = ucPort;

    //
    // Mark the port as being reset.
    //
    g_pRootHub->psPorts[ucPort].sState = PORT_RESET_ACTIVE;

    //
    // Initiate a reset on the relevant port to start the enumeration process.
    //
    HubSetPortFeature((unsigned long)g_pRootHub,
                      ucPort,
                      HUB_FEATURE_PORT_RESET);
}

//*****************************************************************************
//
// A new device has been connected to the hub.  Allocate resources to manage
// it and pass details back to the main USB host enumeration code to have the
// device enumerated.
//
//*****************************************************************************
static void
HubDriverDeviceConnect(unsigned char ucPort, tBoolean bLowSpeed)
{
    DEBUG_OUTPUT("HubDriverDeviceConnect\n");

    //
    // We've allocated a port table entry so fill it in then initiate a reset
    // on the device.
    //
    g_pRootHub->psPorts[ucPort].bChanged = false;
    g_pRootHub->psPorts[ucPort].bLowSpeed = bLowSpeed;

    //
    // Mark the port as having a device present but not enumerated.
    //
    DEBUG_OUTPUT("Deferring enumeration for port %d\n", ucPort);
    g_pRootHub->psPorts[ucPort].sState = PORT_CONNECTED;

    //
    // Wait 100ms to reset the device.
    //
    g_pRootHub->psPorts[ucPort].ulCount = 100;
}

//*****************************************************************************
//
// An existing device has been removed from the hub.  Tidy up and let the main
// USB host code know so that it can free device resources.
//
//*****************************************************************************
static void
HubDriverDeviceDisconnect(unsigned char ucPort)
{
    //
    // This is a device we are currently managing.  Have we already informed
    // the host controller that it is present?
    //
    if((g_pRootHub->psPorts[ucPort].sState == PORT_ACTIVE) ||
       (g_pRootHub->psPorts[ucPort].sState == PORT_RESET_WAIT) ||
       (g_pRootHub->psPorts[ucPort].sState == PORT_ENUMERATED) ||
       (g_pRootHub->psPorts[ucPort].sState == PORT_ERROR))
    {
        //
        // Yes - tell the host controller that the device is not longer
        // connected.
        //
        USBHCDHubDeviceDisconnected(0, g_pRootHub->psPorts[ucPort].ulDevHandle);
    }

    //
    // If the device was being enumerated, make sure we clear the flag
    // indicating that an enumeration is still ongoing.
    //
    if((g_pRootHub->psPorts[ucPort].sState == PORT_RESET_ACTIVE) ||
       (g_pRootHub->psPorts[ucPort].sState == PORT_RESET_WAIT) ||
       (g_pRootHub->psPorts[ucPort].sState == PORT_ACTIVE))
    {
        g_pRootHub->bEnumerationBusy = false;
    }

    //
    // Free up the port state structure.
    //
    g_pRootHub->psPorts[ucPort].sState = PORT_IDLE;
}

//*****************************************************************************
//
// This function is called periodically by USBHCDMain().  We use it to handle
// the hub port state machine.
//
//*****************************************************************************
void
USBHHubMain(void)
{
    unsigned short usStatus, usChanged;
    unsigned char ucPort;
    tBoolean bRetcode;

    //
    // If the hub isn't present, just return.
    //
    if((g_pRootHub == 0) || (!g_pRootHub->bHubActive))
    {
        return;
    }

    //
    // Initialize the status variables.
    //
    usStatus = 0;
    usChanged = 0;

    //
    // The hub is active and something changed. Check to see which port changed
    // state and handle as necessary.
    //
    for(ucPort = 0; ucPort <= g_pRootHub->ucNumPortsInUse; ucPort++)
    {
        //
        // Decrement any wait counter if there is one present.
        //
        if(g_pRootHub->psPorts[ucPort].ulCount != 0)
        {
            g_pRootHub->psPorts[ucPort].ulCount--;
        }

        //
        // Is this port waiting to be enumerated and is the last device
        // enumeration finished?
        //
        if((g_pRootHub->psPorts[ucPort].sState == PORT_CONNECTED) &&
           (!g_pRootHub->bEnumerationBusy) &&
           (g_pRootHub->psPorts[ucPort].ulCount == 0))
        {
            //
            // Yes - start the enumeration processing for this device.
            //
            HubDriverDeviceReset(ucPort);
        }

        //
        // If the state is PORT_RESET_WAIT then the hub is waiting before
        // accessing device as the USB 2.0 specification requires.
        //
        if((g_pRootHub->psPorts[ucPort].sState == PORT_RESET_WAIT) &&
           (g_pRootHub->psPorts[ucPort].ulCount == 0))
        {
            //
            // Start the enumeration process if the timeout has passed and
            // the hub is waiting to start enumerating the device.
            //
            g_pRootHub->psPorts[ucPort].sState = PORT_ACTIVE;

            //
            // Call the main host controller layer to have it enumerate the newly
            // connected device.
            //
            g_pRootHub->psPorts[ucPort].ulDevHandle =
                        USBHCDHubDeviceConnected(0, 1, ucPort,
                                     g_pRootHub->psPorts[ucPort].bLowSpeed,
                                     g_pRootHub->psPorts[ucPort].pucConfigDesc,
                                     g_pRootHub->psPorts[ucPort].ulConfigSize);
        }

        //
        // If an enumeration is in progress and the loop is not on the port
        // being enumerated then skip the port.
        //
        if(g_pRootHub->bEnumerationBusy && (g_pRootHub->ucEnumIdx != ucPort))
        {
            continue;
        }

        //
        // Did something change for this particular port?
        //
        if(g_ulChangeFlags & (1 << ucPort))
        {
            //
            // Yes - query the port status.
            //
            bRetcode = HubGetPortStatus((unsigned long)g_pRootHub, ucPort,
                                        &usStatus, &usChanged);

            //
            // Clear this change with the USB interrupt temporarily disabled to
            // ensure that we do not clear a flag that the interrupt routine
            // has just set.
            //
            OS_INT_DISABLE(INT_USB0);
            g_ulChangeFlags &= ~(1 << ucPort);
            OS_INT_ENABLE(INT_USB0);

            //
            // If there was an error, go on and look at the next bit.
            //
            if(!bRetcode)
            {
                continue;
            }

            //
            // Now consider what changed and handle it as necessary.
            //

            //
            // Was a device connected to or disconnected from the port?
            //
            if(usChanged & HUB_PORT_CHANGE_DEVICE_PRESENT)
            {
                DEBUG_OUTPUT("Connection change on port %d\n", ucPort);

                //
                // Clear the condition.
                //
                HubClearPortFeature((unsigned long)g_pRootHub, ucPort,
                                    HUB_FEATURE_C_PORT_CONNECTION);

                //
                // Was a device connected or disconnected?
                //
                if(usStatus & HUB_PORT_STATUS_DEVICE_PRESENT)
                {
                    DEBUG_OUTPUT("Connected\n");

                    //
                    // A device was connected.
                    //
                    HubDriverDeviceConnect(ucPort,
                            ((usStatus & HUB_PORT_STATUS_LOW_SPEED) ?
                             true : false));
                }
                else
                {
                    DEBUG_OUTPUT("Disconnected\n");

                    //
                    // A device was disconnected.
                    //
                    HubDriverDeviceDisconnect(ucPort);
                }
            }

            //
            // Did a reset on the port complete?
            //
            if(usChanged & HUB_PORT_CHANGE_RESET)
            {
                //
                // Clear the condition.
                //
                HubClearPortFeature((unsigned long)g_pRootHub, ucPort,
                                    HUB_FEATURE_C_PORT_RESET);

                //
                // Yes - query the port status.
                //
                bRetcode = HubGetPortStatus((unsigned long)g_pRootHub, ucPort,
                                            &usStatus, &usChanged);

                DEBUG_OUTPUT("Reset %s for port %d\n",
                        ((usStatus & HUB_PORT_STATUS_RESET) ? "asserted" :
                        "deasserted"), ucPort);

                //
                // Handle the reset case.
                //
                HubDriverReset(ucPort, (usStatus & HUB_PORT_STATUS_RESET) ?
                               true : false);
            }

            //
            // Did an over-current reset on the port complete?
            //
            if(usChanged & HUB_PORT_CHANGE_OVER_CURRENT)
            {
                DEBUG_OUTPUT("Port %d over current.\n", ucPort);

                //
                // Currently we ignore this and just clear the condition.
                //
                HubClearPortFeature((unsigned long)g_pRootHub, ucPort,
                                    HUB_FEATURE_C_PORT_OVER_CURRENT);
            }

            //
            // Has the port been enabled or disabled?
            //
            if(usChanged & HUB_PORT_CHANGE_ENABLED)
            {
                DEBUG_OUTPUT("Enable change for port %d.\n", ucPort);

                //
                // Currently we ignore this and just clear the condition.
                //
                HubClearPortFeature((unsigned long)g_pRootHub, ucPort,
                                    HUB_FEATURE_C_PORT_ENABLE);
            }

            //
            // Has the port been suspended or resumed?
            //
            if(usChanged & HUB_PORT_CHANGE_SUSPENDED)
            {
                DEBUG_OUTPUT("Suspend change for port %d.\n", ucPort);

                //
                // Currently we ignore this and just clear the condition.
                //
                HubClearPortFeature((unsigned long)g_pRootHub, ucPort,
                                    HUB_FEATURE_C_PORT_SUSPEND);
            }
        }
    }
}

//*****************************************************************************
//
//! Informs the hub class driver that a downstream device has been enumerated.
//!
//! \param ucHub is the address of the hub to which the downstream device
//! is attached.
//! \param ucPort is the port on the hub to which the downstream device is
//! attached.
//!
//! This function is called by the host controller driver to inform the hub
//! class driver that a downstream device has been enumerated successfully.
//! The hub driver then moves on and continues enumeration of any other newly
//! connected devices.
//!
//! \return None.
//
//*****************************************************************************
void
USBHHubEnumerationComplete(unsigned char ucHub, unsigned char ucPort)
{
    DEBUG_OUTPUT("Enumeration complete for hub %d, port %d\n", ucHub, ucPort);

    //
    // Record the fact that the device is up and running.
    //
    g_pRootHub->psPorts[ucPort].sState = PORT_ENUMERATED;

    //
    // Clear the flag we use to defer further enumerations.  This will cause
    // the next connected device (if any) to start enumeration on the next
    // call to USBHHubMain().
    //
    g_pRootHub->bEnumerationBusy = false;
}

//*****************************************************************************
//
//! Informs the hub class driver that a downstream device failed to enumerate.
//!
//! \param ucHub is the address of the hub to which the downstream device
//! is attached.
//! \param ucPort is the port on the hub to which the downstream device is
//! attached.
//!
//! This function is called by the host controller driver to inform the hub
//! class driver that an attempt to enumerate a downstream device has failed.
//! The hub driver then cleans up and continues enumeration of any other newly
//! connected devices.
//!
//! \return None.
//
//*****************************************************************************
void
USBHHubEnumerationError(unsigned char ucHub, unsigned char ucPort)
{
    DEBUG_OUTPUT("Enumeration error for hub %d, port %d\n", ucHub, ucPort);

    //
    // Record the fact that the device is not working correctly.
    //
    g_pRootHub->psPorts[ucPort].sState = PORT_ERROR;

    //
    // Clear the flag we use to defer further enumerations.  This will cause
    // the next connected device (if any) to start enumeration on the next
    // call to USBHHubMain().
    //
    g_pRootHub->bEnumerationBusy = false;
}

//*****************************************************************************
//
//! This function is used to enable the host hub class driver before any
//! devices are present.
//!
//! \param pfnCallback is the driver call back for host hub events.
//! \param pucHubPool is the memory pool allocated to the USB hub class.
//! \param ulPoolSize is the size in bytes of the memory pool provided by the
//! \e pucHubPool parameter.
//! \param psHubInstance is a pointer to an instance of the private hub data.
//! \param ulNumHubs is the number of hubs to support.
//!
//! This function is called to open an instance of a host hub device and
//! provides a valid callback function for host hub events in the
//! \e pfnCallback parameter.  This function must be called before the USB
//! host code can successfully enumerate a hub device or any devices attached
//! to the hub.  The \e pucHubPool is memory provided to the hub class to
//! manage the devices that are connected to the hub.  The \e ulPoolSize is
//! the number of bytes and should be at least 32 bytes per device including
//! the hub device itself.  A simple formula for providing memory to the hub
//! class is \b MAX_USB_DEVICES * 32 bytes of data to allow for proper
//! enumeration of connected devices.  The value for \b MAX_USB_DEVICES is
//! defined in the usblib.h file and controls the number of devices
//! supported by the USB library.  The \e ulNumHubs parameter
//! defaults to one and only one buffer of size tHubInstance is required to
//! be passed in the \e psHubInstance parameter.
//!
//! \note Changing the value of \b MAX_USB_DEVICES requires a rebuild of the
//! USB library to have an effect on the library.
//!
//! \return This function returns the driver instance to use for the other
//! host hub functions.  If there is no instance available at the time of
//! this call, this function returns zero.
//
//*****************************************************************************
unsigned long
USBHHubOpen(tUSBCallback pfnCallback, unsigned char *pucHubPool,
            unsigned long ulPoolSize, tHubInstance *psHubInstance,
            unsigned long ulNumHubs)
{
    unsigned long ulLoop, ulBlockSize;

    //
    // Only one hub is supported.
    //
    if(g_pRootHub)
    {
        DEBUG_OUTPUT("USBHHubOpen failed - already connected.\n");
        return(0);
    }

    //
    // Save this instance.
    //
    g_pRootHub = psHubInstance;

    //
    // Save the instance data for this device.
    //
    g_pRootHub->pfnCallback = pfnCallback;

    //
    // Divide the pool up into blocks, one for each supported port.  We make
    // sure that each block is a multiple of 4 bytes.
    //
    ulBlockSize = (ulPoolSize / MAX_USB_DEVICES) & ~3;
    for(ulLoop = 0; ulLoop < MAX_USB_DEVICES; ulLoop++)
    {
        g_pRootHub->psPorts[ulLoop].pucConfigDesc = (pucHubPool +
                                                  (ulLoop * ulBlockSize));
        g_pRootHub->psPorts[ulLoop].ulConfigSize = ulBlockSize;
    }

    DEBUG_OUTPUT("USBHHubOpen completed.\n");

    //
    // Return the device instance pointer.
    //
    return((unsigned long)g_pRootHub);
}

//*****************************************************************************
//
//! This function is used to release a hub device instance.
//!
//! \param ulInstance is the hub device instance that is to be released.
//!
//! This function is called when an instance of the hub device must be
//! released.  This function is typically made in preparation for shutdown or a switch
//! to function as a USB device when in OTG mode.  Following this call, the hub device is
//! no longer available, but it can be opened again using a call to
//! USBHHubOpen().  After calling USBHHubClose(), the host hub driver no
//! longer provides any callbacks or accepts calls to other hub driver APIs.
//!
//! \return None.
//
//*****************************************************************************
void
USBHHubClose(unsigned long ulInstance)
{
    //
    // Forget the instance pointer.
    //
    g_pRootHub = 0;

    DEBUG_OUTPUT("USBHHubClose completed.\n");
}

//*****************************************************************************
//
// This function is used to initialize the Hub driver.  This is an internal
// function that should not be called by the application.
//
//*****************************************************************************
void
USBHHubInit(void)
{
    //
    // Initialize Hub state.
    //
    g_pRootHub = 0;
    g_ulChangeFlags = 0;
    g_ulHubChanges = 0;
}

//*****************************************************************************
//
//! @}
//
//*****************************************************************************
