//*****************************************************************************
//
// usbhhub.c - This file contains the host HID driver.
//
// Copyright (c) 2011-2013 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 2.0 of the Tiva USB Library.
//
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_types.h"
#include "inc/hw_ints.h"
#include "inc/hw_sysctl.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
//! @{
//
//*****************************************************************************

#ifdef ewarm
#pragma pack(1)
#endif

//*****************************************************************************
//
//! The USB standard hub descriptor structure.  Full documentation for the
//! contents of this structure can be found in chapter 11.23.2.1 of the USB
//! 2.0 specification.
//
//*****************************************************************************
typedef struct
{
    //
    //! The total number of bytes in the descriptor (including this field).
    //
    uint8_t bLength;

    //
    //! The descriptor type. For a hub descriptor, this will be USB_DTYPE_HUB
    //! (0x29 or 41 decimal).
    //
    uint8_t bDescType;

    //
    //! The number of downstream-facing ports that the hub supports.
    //
    uint8_t bNbrPorts;

    //
    //! Characteristics of the hub device including its power switching
    //! capabilities and over-current protection mode.
    //
    uint16_t wHubCharacteristics;

    //
    //! The time between the start of the power-on sequence for a port and
    //! the power to the port becoming stable.  This is expressed in 2mS units.
    //
    uint8_t bPwrOn2PwrGood;

    //
    //! The maximum current requirement for the hub circuitry in mA.
    //
    uint8_t bHubContrCurrent;

    //
    //! The last two fields in the structure are bit masks indicating which
    //! downstream ports support removable devices and, following this, another
    //! obsolete field from USB1.0 related to port power control.  Each field
    //! is byte aligned and contains a bit for each hub port.  This structure
    //! definition is set up with enough storage to handle ROOT_HUB_MAX_PORTS
    //! ports but beware that the actual size of each field is dependent upon
    //! the bNbrPorts field above.
    //
    uint8_t PortInfo[((ROOT_HUB_MAX_PORTS + 7) / 8) * 2];
}
PACKED tUsbHubDescriptor;

#ifdef ewarm
#pragma pack()
#endif

//*****************************************************************************
//
// This structure holds all data specific to a single hub port.
//
//*****************************************************************************
typedef struct
{
    //
    // The handle used by the HCD layer to identify this device.
    //
    uint32_t ui32DevHandle;

    //
    // The current state of the port.
    //
    volatile tHubPortState iState;

    //
    // General counter used in various states.
    //
    volatile uint32_t ui32Count;

    //
    // A flag used to indicate that the downstream device is a low speed
    // device.
    //
    bool bLowSpeed;

    //
    // The speed of the device on this port.
    //
    uint32_t ui32Speed;

    //
    // This flag is set if the hub reports that a change is pending on this
    // port.
    //
    volatile bool bChanged;
}
tHubPort;

//*****************************************************************************
//
// USB hub flags values for tHubInstance.ui32Flags.
//
//*****************************************************************************
#define USBLIB_HUB_ACTIVE       0x00000001
#define USBLIB_HUB_HS           0x00000002
#define USBLIB_HUB_MULTI_TT     0x00000004

//*****************************************************************************
//
// This is the structure that holds all of the data for a given instance of
// a Hub device.
//
//*****************************************************************************
struct tHubInstance
{
    //
    // Save the device instance.
    //
    tUSBHostDevice *psDevice;

    //
    // Used to save the callback function pointer.
    //
    tUSBHHubCallback pfnCallback;

    //
    // Callback data provided by caller.
    //
    uint32_t ui32CBData;

    //
    // Interrupt IN pipe.
    //
    uint32_t ui32IntInPipe;

    //
    // Hub characteristics as reported in the class-specific hub descriptor.
    //
    uint16_t ui16HubCharacteristics;

    //
    // The number of downstream-facing ports the hub supports.
    //
    uint8_t ui8NumPorts;

    //
    // The number of ports on the hub that we can actually talk to.  This will
    // be the smaller of the number of ports on the hub and MAX_USB_DEVICES.
    //
    uint8_t ui8NumPortsInUse;

    //
    // The size of a status change packet sent by the hub.  This is determined
    // from the number of ports supported by the hub.
    //
    uint8_t ui8ReportSize;

    //
    // Flags indicating whether the hub is connected.
    //
    uint32_t ui32Flags;

    //
    // Flag indicating that a device is currently in process of being
    // enumerated.
    //
    volatile bool bEnumerationBusy;

    //
    // This is valid if bEnumerationBusy is set and indicates the port
    // that is in the process of enumeration.
    //
    uint8_t ui8EnumIdx;

    //
    // The state of each of the ports we support on the hub.
    //
    tHubPort psPorts[MAX_USB_DEVICES];

    //
    // The interrupt number for this instance.
    //
    uint32_t ui32IntNum;
};

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

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

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

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

//
// Note: The following assumes ROOT_HUB_MAX_PORTS is less than 32!
//
static uint32_t g_ui32HubChanges;

//*****************************************************************************
//
// This function is called to set the operating speed of a given port.
//
// \param ui8Port is the port number for this request.
// \param ui32Speed is one of the HUB_FEATURE_PORT_* values.
//
// This function sets the operating speed of the hub port specified in the
// \e ui8Port parameter.  A \e ui8Port value of 0 is an access to the hub
// itself and not one of the hub ports.  The \e ui32Speed value is one of the
// \b USB_EP_SPEED_ values.
//
// \return None.
//
//*****************************************************************************
static void
USBHubPortSpeedSet(uint8_t ui8Port, uint32_t ui32Speed)
{
    g_sRootHub.psPorts[ui8Port].ui32Speed = ui32Speed;
}

//*****************************************************************************
//
// This function is called to send a request to the hub to set a feature on
// a given port.
//
// \param psHubInstance is the hub device instance.
// \param ui8Port is the port number for this request.
// \param ui16Feature is one of the HUB_FEATURE_PORT_* values.
//
// This function will send the set feature request to the hub indicated by the
// \e psHubInstance parameter.  The \e ui8Port 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 ui8Port value of 0 is an access to the hub
// itself and not one of the hub ports.  The \e ui16Feature is the feature
//  request toset on the given port.  For example, a \e ui16Feature value of
// \e HUB_FEATURE_PORT_RESET and \e ui8Port value of 1 will cause reset
// signaling to hub port 1.
//
// \return None.
//
//*****************************************************************************
static void
HubSetPortFeature(tHubInstance *psHubInstance, uint8_t ui8Port,
                  uint16_t ui16Feature)
{
    tUSBRequest sSetupPacket;
    tUSBHostDevice *psDevice;

    //
    // Retrieve the hub instance and device pointer.
    //
    psDevice = psHubInstance->psDevice;

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

    //
    // Set the field to clear the requested port feature.
    //
    sSetupPacket.bRequest = USBREQ_SET_FEATURE;
    sSetupPacket.wValue = ui16Feature;
    sSetupPacket.wIndex = ui8Port;
    sSetupPacket.wLength = 0;

    //
    // Send the request.
    //
    USBHCDControlTransfer(0, &sSetupPacket, psDevice, 0, 0,
                          psDevice->sDeviceDescriptor.bMaxPacketSize0);
}

//*****************************************************************************
//
// This function is called to send a request to the hub to clear a feature on
// a given port.
//
// \param psHubInstance is the hub device instance.
// \param ui8Port is the port number for this request.
// \param ui16Feature is one of the HUB_FEATURE_PORT_* values.
//
// This function will send the clear feature request to the hub indicated by
// the \e psHubInstance parameter.  The \e ui8Port 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 ui8Port value of 0 is an access to the hub
// itself and not one of the hub ports.  The \e ui16Feature is the feature
// request to clear on the given port.  For example, a \e ui16Feature value of
// \e HUB_FEATURE_C_PORT_RESET and \e ui8Port 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(tHubInstance *psHubInstance, uint8_t ui8Port,
                    uint16_t ui16Feature)
{
    tUSBRequest sSetupPacket;
    tUSBHostDevice *psDevice;

    //
    // Retrieve the hub instance and device pointer.
    //
    psDevice = psHubInstance->psDevice;

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

    //
    // Set the field to clear the requested port feature.
    //
    sSetupPacket.bRequest = USBREQ_CLEAR_FEATURE;
    sSetupPacket.wValue = ui16Feature;
    sSetupPacket.wIndex = ui8Port;
    sSetupPacket.wLength = 0;

    //
    // Send the request.
    //
    USBHCDControlTransfer(0, &sSetupPacket, psDevice, 0, 0,
                          psDevice->sDeviceDescriptor.bMaxPacketSize0);
}

//*****************************************************************************
//
// This function is used to retrieve the current status of a port on the
// hub.
//
// \param psHubInstance is the hub device instance.
// \param ui8Port is the port number for this request.
// \param pui16PortStatus is a pointer to the memory to store the current
// status of the port.
// \param pui16PortChange 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 ui8Port parameter.  The \e ui8Port 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 ui8Port value of 0 is an
// access to the hub itself and not one of the hub ports.
//
// \return None.
//
//*****************************************************************************
static bool
HubGetPortStatus(tHubInstance *psHubInstance, uint8_t ui8Port,
                 uint16_t *pui16PortStatus, uint16_t *pui16PortChange)
{
    uint32_t ui32Data, ui32Read;
    tUSBRequest sSetupPacket;
    tUSBHostDevice *psDevice;

    //
    // Retrieve the device pointer.
    //
    psDevice = psHubInstance->psDevice;

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

    //
    // Set the fields to get the hub status.
    //
    sSetupPacket.bRequest = USBREQ_GET_STATUS;
    sSetupPacket.wValue = 0;
    sSetupPacket.wIndex = (uint16_t)ui8Port;
    sSetupPacket.wLength = 4;

    //
    // Send the request.
    //
    ui32Read = USBHCDControlTransfer(0, &sSetupPacket, psDevice,
                                (uint8_t *)&ui32Data, 4,
                                psDevice->sDeviceDescriptor.bMaxPacketSize0);

    //
    // Check that we received the correct number of bytes.
    //
    if(ui32Read != 4)
    {
        return(false);
    }
    else
    {
        //
        // We got 4 bytes from the device. Now translate these into the 2
        // 16-bit values we pass back to the caller.
        //
        *pui16PortStatus = (uint16_t)(ui32Data & 0xFFFF);
        *pui16PortChange = (uint16_t)(ui32Data >> 16);

        DEBUG_OUTPUT("Port %d, status 0x%04x, change 0x%04x\n", ui8Port,
                     *pui16PortStatus, *pui16PortChange);
    }

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

//*****************************************************************************
//
// This function handles callbacks for the interrupt IN endpoint for the hub
// device.
//
//*****************************************************************************
static void
HubIntINCallback(uint32_t ui32Pipe, uint32_t ui32Event)
{
    switch (ui32Event)
    {
        //
        // 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(ui32Pipe, (uint8_t *)&g_ui32HubChanges,
                               (uint32_t)g_sRootHub.ui8ReportSize);
            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(ui32Pipe);

            //
            // Update our global "ports needing service" flags with the latest
            // information we have just received.
            //
            g_ui32ChangeFlags |= g_ui32HubChanges;

            //
            // Send the report data to the USB host hub device class driver if
            // we have been given a callback function.
            //
            if(g_sRootHub.pfnCallback)
            {
                g_sRootHub.pfnCallback((void *)g_sRootHub.ui32CBData,
                                       USB_EVENT_RX_AVAILABLE,
                                       ui32Pipe, &g_ui32HubChanges);
            }

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

//*****************************************************************************
//
// Query the class-specific hub descriptor.
//
//*****************************************************************************
static bool
GetHubDescriptor(tUsbHubDescriptor *psDesc)
{
    uint32_t ui32Read;
    tUSBRequest sSetupPacket;
    tUSBHostDevice *psDevice;

    //
    // Retrieve the device pointer.
    //
    psDevice = g_sRootHub.psDevice;

    //
    // This is a standard OUT request.
    //
    sSetupPacket.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.
    //
    sSetupPacket.bRequest = USBREQ_GET_DESCRIPTOR;
    sSetupPacket.wValue = (USB_DTYPE_HUB << 8);
    sSetupPacket.wIndex = 0;
    sSetupPacket.wLength = sizeof(tUsbHubDescriptor);

    //
    // Send the request.
    //
    ui32Read = USBHCDControlTransfer(0, &sSetupPacket, psDevice,
                                (void *)psDesc, sizeof(tUsbHubDescriptor),
                                psDevice->sDeviceDescriptor.bMaxPacketSize0);

     //
     // Make sure we got at least some data.
     //
     if(ui32Read == 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 *psDevice)
{
    tEndpointDescriptor *psEndpointDescriptor;
    tInterfaceDescriptor *psInterface;
    tUsbHubDescriptor sHubDesc;
    bool bRetcode;
    uint32_t ui32Loop;

    //
    // If we are already talking to a hub, fail the call.  We only support
    // a single hub.
    //
    if(g_sRootHub.ui32Flags & USBLIB_HUB_ACTIVE)
    {
        return(0);
    }

    //
    // Get pointers to the device descriptors we need to look at.
    //
    psInterface = USBDescGetInterface(psDevice->psConfigDescriptor, 0, 0);
    psEndpointDescriptor = USBDescGetInterfaceEndpoint(psInterface, 0,
                                      psDevice->ui32ConfigDescriptorSize);

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

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

    //
    // Remember that this is a high speed hub with either single or multiple
    // transaction translators.
    //
    if(psInterface->bInterfaceProtocol == USB_HUB_PROTOCOL_SINGLE)
    {
        g_sRootHub.ui32Flags |= USBLIB_HUB_HS;
    }
    else if(psInterface->bInterfaceProtocol == USB_HUB_PROTOCOL_MULTI)
    {
        g_sRootHub.ui32Flags |= USBLIB_HUB_HS | USBLIB_HUB_MULTI_TT;
    }

    //
    // Remember the device information for later.
    //
    g_sRootHub.psDevice = psDevice;

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

    //
    // Did we allocate the endpoint successfully?
    //
    if(!g_sRootHub.ui32IntInPipe)
    {
        //
        // 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_sRootHub.pfnCallback != 0)
    {
        g_sRootHub.pfnCallback((void *)g_sRootHub.ui32CBData,
                               USB_EVENT_CONNECTED, (uint32_t)&g_sRootHub, 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_sRootHub.ui8NumPorts = sHubDesc.bNbrPorts;
        g_sRootHub.ui16HubCharacteristics = sHubDesc.wHubCharacteristics;
        g_sRootHub.ui8NumPortsInUse =
            (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_sRootHub.ui8ReportSize = ((sHubDesc.bNbrPorts + 1) + 7) / 8;

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

        //
        // Clear out our port state structures.
        //
        for(ui32Loop = 0; ui32Loop < MAX_USB_DEVICES; ui32Loop++)
        {
            g_sRootHub.psPorts[ui32Loop].bChanged = false;
            g_sRootHub.psPorts[ui32Loop].iState = ePortIdle;
        }
    }
    else
    {
        //
        // Oops - we can't read the hub descriptor!  Tidy up and return
        // an error.
        //
        USBHCDPipeFree(g_sRootHub.ui32IntInPipe);
        g_sRootHub.pfnCallback = 0;
        g_sRootHub.ui32Flags &= ~USBLIB_HUB_ACTIVE;
        return(0);
    }

    //
    // If we get here, all is well so remember that the hub is connected and
    // active.
    //
    g_sRootHub.ui32Flags |= USBLIB_HUB_ACTIVE;

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

//*****************************************************************************
//
// Close an instance of the hub driver.
//
//*****************************************************************************
static void
HubDriverClose(void *pvHubDevice)
{
    uint32_t ui32Loop;

    //
    // No device so just exit.
    //
    if(g_sRootHub.psDevice == 0)
    {
        return;
    }

    //
    // Disconnect any devices that are currently connected to the hub.
    //
    for(ui32Loop = 0; ui32Loop < MAX_USB_DEVICES; ui32Loop++)
    {
        //
        // Does this port have a device connected to it that we have previously
        // reported to the host control layer?h
        //
        if((g_sRootHub.psPorts[ui32Loop].iState == ePortActive) ||
           (g_sRootHub.psPorts[ui32Loop].iState == ePortResetWait) ||
           (g_sRootHub.psPorts[ui32Loop].iState == ePortEnumerated) ||
           (g_sRootHub.psPorts[ui32Loop].iState == ePortError))
        {
            //
            // Yes - tell the host controller to disconnect the device.
            //
            USBHCDHubDeviceDisconnected(0,
                g_sRootHub.psPorts[ui32Loop].ui32DevHandle);

        }

        //
        // Make sure that the state returns to idle.
        //
        g_sRootHub.psPorts[ui32Loop].iState = ePortIdle;

    }

    //
    // Reset the device pointer.
    //
    g_sRootHub.psDevice = 0;

    //
    // Mark the hub as absent.
    //
    g_sRootHub.ui32Flags &= ~USBLIB_HUB_ACTIVE;

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

    //
    // Free the Interrupt IN pipe.
    //
    if(g_sRootHub.ui32IntInPipe != 0)
    {
        USBHCDPipeFree(g_sRootHub.ui32IntInPipe);
    }

    //
    // If the callback exists, call it with a DISCONNECTED event.
    //
    if(g_sRootHub.pfnCallback != 0)
    {
        g_sRootHub.pfnCallback((void *)g_sRootHub.ui32CBData,
                               USB_EVENT_DISCONNECTED, (uint32_t)&g_sRootHub,
                               0);
    }
}

//*****************************************************************************
//
// Perform any processing required as a result of a change in the reset
// signaling for a given port.
//
//*****************************************************************************
static void
HubDriverReset(uint8_t ui8Port, bool 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_sRootHub.psPorts[ui8Port].iState = ePortResetWait;

        //
        // Set the wait to 10ms (10 frames) from now.
        //
        g_sRootHub.psPorts[ui8Port].ui32Count = 10;
    }
    else
    {
        //
        // Was this device previously active?
        //
        if(g_sRootHub.psPorts[ui8Port].iState == ePortActive)
        {
            USBHCDHubDeviceDisconnected(0,
                g_sRootHub.psPorts[ui8Port].ui32DevHandle);
        }

        //
        // The reset is active so mark our port as in reset.
        //
        g_sRootHub.psPorts[ui8Port].iState = ePortResetActive;
    }
}

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

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

    //
    // Save the port that is being enumerated.
    //
    g_sRootHub.ui8EnumIdx = ui8Port;

    //
    // Mark the port as being reset.
    //
    g_sRootHub.psPorts[ui8Port].iState = ePortResetActive;

    //
    // Initiate a reset on the relevant port to start the enumeration process.
    //
    HubSetPortFeature(&g_sRootHub, ui8Port, 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(uint8_t ui8Port)
{
    DEBUG_OUTPUT("HubDriverDeviceConnect\n");

    //
    // We've allocated a port table entry so fill it in then initiate a reset
    // on the device.
    //
    g_sRootHub.psPorts[ui8Port].bChanged = false;

    //
    // Mark the port as having a device present but not enumerated.
    //
    DEBUG_OUTPUT("Deferring enumeration for port %d\n", ui8Port);
    g_sRootHub.psPorts[ui8Port].iState = ePortConnected;

    //
    // Wait 100ms to reset the device.
    //
    g_sRootHub.psPorts[ui8Port].ui32Count = 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(uint8_t ui8Port)
{
    //
    // This is a device we are currently managing.  Have we already informed
    // the host controller that it is present?
    //
    if((g_sRootHub.psPorts[ui8Port].iState == ePortActive) ||
       (g_sRootHub.psPorts[ui8Port].iState == ePortResetWait) ||
       (g_sRootHub.psPorts[ui8Port].iState == ePortEnumerated) ||
       (g_sRootHub.psPorts[ui8Port].iState == ePortError))
    {
        //
        // Yes - tell the host controller that the device is not longer
        // connected.
        //
        USBHCDHubDeviceDisconnected(0,
                                g_sRootHub.psPorts[ui8Port].ui32DevHandle);
    }

    //
    // If the device was being enumerated, make sure we clear the flag
    // indicating that an enumeration is still ongoing.
    //
    if((g_sRootHub.psPorts[ui8Port].iState == ePortResetActive) ||
       (g_sRootHub.psPorts[ui8Port].iState == ePortResetWait) ||
       (g_sRootHub.psPorts[ui8Port].iState == ePortActive))
    {
        g_sRootHub.bEnumerationBusy = false;
    }

    //
    // Free up the port state structure.
    //
    g_sRootHub.psPorts[ui8Port].iState = ePortIdle;
}

//*****************************************************************************
//
// This function is called periodically by USBHCDMain().  We use it to handle
// the hub port state machine.
//
//*****************************************************************************
void
USBHHubMain(void)
{
    uint16_t ui16Status, ui16Changed;
    uint_fast8_t ui8Port;
    bool bRetcode;

    //
    // If the hub is not present, just return.
    //
    if((g_sRootHub.ui32Flags & USBLIB_HUB_ACTIVE) == 0)
    {
        return;
    }

    //
    // Initialize the status variables.
    //
    ui16Status = 0;
    ui16Changed = 0;

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

        //
        // Is this port waiting to be enumerated and is the last device
        // enumeration finished?
        //
        if((g_sRootHub.psPorts[ui8Port].iState == ePortConnected) &&
           (!g_sRootHub.bEnumerationBusy) &&
           (g_sRootHub.psPorts[ui8Port].ui32Count == 0))
        {
            //
            // Yes - start the enumeration processing for this device.
            //
            HubDriverDeviceReset(ui8Port);
        }

        //
        // If the state is ePortResetWait then the hub is waiting before
        // accessing device as the USB 2.0 specification requires.
        //
        if((g_sRootHub.psPorts[ui8Port].iState == ePortResetWait) &&
           (g_sRootHub.psPorts[ui8Port].ui32Count == 0))
        {
            //
            // Start the enumeration process if the timeout has passed and
            // the hub is waiting to start enumerating the device.
            //
            g_sRootHub.psPorts[ui8Port].iState = ePortActive;

            //
            // Call the main host controller layer to have it enumerate the
            // newly connected device.
            //
            g_sRootHub.psPorts[ui8Port].ui32DevHandle =
                USBHCDHubDeviceConnected(0, 1, ui8Port,
                                        g_sRootHub.psPorts[ui8Port].ui32Speed);
        }

        //
        // If an enumeration is in progress and the loop is not on the port
        // being enumerated then skip the port.
        //
        if(g_sRootHub.bEnumerationBusy &&
           (g_sRootHub.ui8EnumIdx != ui8Port))
        {
            continue;
        }

        //
        // Did something change for this particular port?
        //
        if(g_ui32ChangeFlags & (1 << ui8Port))
        {
            //
            // Yes - query the port status.
            //
            bRetcode = HubGetPortStatus(&g_sRootHub, ui8Port, &ui16Status,
                                        &ui16Changed);

            //
            // 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(g_sRootHub.ui32IntNum);
            g_ui32ChangeFlags &= ~(1 << ui8Port);
            OS_INT_ENABLE(g_sRootHub.ui32IntNum);

            //
            // 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(ui16Changed & HUB_PORT_CHANGE_DEVICE_PRESENT)
            {
                DEBUG_OUTPUT("Connection change on port %d\n", ui8Port);

                //
                // Clear the condition.
                //
                HubClearPortFeature(&g_sRootHub, ui8Port,
                                    HUB_FEATURE_C_PORT_CONNECTION);

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

                    //
                    // A device was connected.
                    //
                    HubDriverDeviceConnect(ui8Port);
                }
                else
                {
                    DEBUG_OUTPUT("Disconnected\n");

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

            //
            // Did a reset on the port complete?
            //
            if(ui16Changed & HUB_PORT_CHANGE_RESET)
            {
                //
                // Clear the condition.
                //
                HubClearPortFeature(&g_sRootHub, ui8Port,
                                    HUB_FEATURE_C_PORT_RESET);

                //
                // Yes - query the port status.
                //
                bRetcode = HubGetPortStatus(&g_sRootHub, ui8Port,
                                            &ui16Status, &ui16Changed);

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

                //
                // Handle the reset case.
                //
                HubDriverReset(ui8Port, (ui16Status & HUB_PORT_STATUS_RESET) ?
                               true : false);

                //
                // A device was connected.
                //
                if(ui16Status & HUB_PORT_STATUS_LOW_SPEED)
                {
                    USBHubPortSpeedSet(ui8Port, USB_EP_SPEED_LOW);
                }
                else if(ui16Status & HUB_PORT_STATUS_HIGH_SPEED)
                {
                    USBHubPortSpeedSet(ui8Port, USB_EP_SPEED_HIGH);
                }
                else
                {
                    USBHubPortSpeedSet(ui8Port, USB_EP_SPEED_FULL);
                }
            }

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

                //
                // Currently we ignore this and just clear the condition.
                //
                HubClearPortFeature(&g_sRootHub, ui8Port,
                                    HUB_FEATURE_C_PORT_OVER_CURRENT);
            }

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

                //
                // Currently we ignore this and just clear the condition.
                //
                HubClearPortFeature(&g_sRootHub, ui8Port,
                                    HUB_FEATURE_C_PORT_ENABLE);
            }

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

                //
                // Currently we ignore this and just clear the condition.
                //
                HubClearPortFeature(&g_sRootHub, ui8Port,
                                    HUB_FEATURE_C_PORT_SUSPEND);
            }
        }
    }
}

//*****************************************************************************
//
//! Informs the hub class driver that a downstream device has been enumerated.
//!
//! \param ui8Hub is the address of the hub to which the downstream device
//! is attached.
//! \param ui8Port 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(uint8_t ui8Hub, uint8_t ui8Port)
{
    DEBUG_OUTPUT("Enumeration complete for hub %d, port %d\n", ui8Hub, ui8Port);

    //
    // Record the fact that the device is up and running.
    //
    g_sRootHub.psPorts[ui8Port].iState = ePortEnumerated;

    //
    // 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_sRootHub.bEnumerationBusy = false;
}

//*****************************************************************************
//
//! Informs the hub class driver that a downstream device failed to enumerate.
//!
//! \param ui8Hub is the address of the hub to which the downstream device
//! is attached.
//! \param ui8Port 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(uint8_t ui8Hub, uint8_t ui8Port)
{
    DEBUG_OUTPUT("Enumeration error for hub %d, port %d\n", ui8Hub, ui8Port);

    //
    // Record the fact that the device is not working correctly.
    //
    g_sRootHub.psPorts[ui8Port].iState = ePortError;

    //
    // 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_sRootHub.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.
//!
//! 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 pui8HubPool is memory provided to the hub class to
//! manage the devices that are connected to the hub.  The \e ui32PoolSize 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 ui32NumHubs 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.
//
//*****************************************************************************
tHubInstance *
USBHHubOpen(tUSBHHubCallback pfnCallback)
{
    //
    // Only one hub is supported.
    //
    if(g_sRootHub.pfnCallback)
    {
        DEBUG_OUTPUT("USBHHubOpen failed - already connected.\n");
        return(0);
    }

    //
    // Save the instance data for this device.
    //
    g_sRootHub.pfnCallback = pfnCallback;

    DEBUG_OUTPUT("USBHHubOpen completed.\n");

    //
    // Return the device instance pointer.
    //
    return(&g_sRootHub);
}

//*****************************************************************************
//
//! This function is used to release a hub device instance.
//!
//! \param psHubInstance 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(tHubInstance *psHubInstance)
{
    //
    // Forget the instance pointer and callback.
    //
    psHubInstance->psDevice = 0;
    psHubInstance->pfnCallback = 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_ui32ChangeFlags = 0;
    g_ui32HubChanges = 0;

    if(g_sRootHub.psDevice != 0)
    {
        //
        // Save the USB interrupt number.
        //
        g_sRootHub.ui32IntNum = INT_USB0_BLIZZARD;

        //
        // These devices have a different USB interrupt number.
        //
        if(CLASS_IS_SNOWFLAKE)
        {
            g_sRootHub.ui32IntNum = INT_USB0_SNOWFLAKE;
        }
    }
}

//*****************************************************************************
//
//! This function forwards an LPM request for a device to enter L1 sleep state.
//!
//! \param psHubInstance is the hub device instance that was returned
//! from the call to USBHHubOpen().
//!
//! This function forwards a request from an application to the hub device
//! class to request that a device enter the LPM L1 sleep state.  The
//! caller must check the return value to see if the request can be
//! attempted at this time.  If another LPM transaction is busy on this or
//! another device, then this function returns \b USBHCD_LPM_PENDING.  If
//! the LPM request was scheduled to be sent the function returns
//! \b USBHCD_LPM_AVAIL.  The caller should check the USBHCDLPMStatus()
//! function to determine if the request completed successfully or if there
//! was an error.
//!
//! \return This function returns the following values:
//! - USBHCD_LPM_AVAIL - The transition to L1 state is scheduled to be sent.
//! - USBHCD_LPM_PENDING - There is already an LPM request pending.
//
//*****************************************************************************
uint32_t
USBHHubLPMSleep(tHubInstance *psHubInstance)
{
    //
    // Call the host controller function to send the sleep command.
    //
    return(USBHCDLPMSleep(psHubInstance->psDevice));
}

//*****************************************************************************
//
//! This function returns the current status of an LPM request.
//!
//! \param psHubInstance is the hub device instance that was returned
//! from the call to USBHHubOpen().
//!
//! This function returns the current status of LPM requests for a given
//! device.  This is called to determine if a previous request completed
//! successfully or if there was an error.
//!
//! \return This function returns the following values:
//! - \b USBHCD_LPM_AVAIL - There are no pending LPM requests on this specific
//!   device or the last request completed successfully.
//! - \b USBHCD_LPM_ERROR - The last LPM request for this device did not
//!   complete successfully.
//! - \b USBHCD_LPM_PENDING - The last LPM request has not completed.
//
//*****************************************************************************
uint32_t
USBHHubLPMStatus(tHubInstance *psHubInstance)
{
    //
    // Call the host controller function to get the current LPM status.
    //
    return(USBHCDLPMStatus(psHubInstance->psDevice));
}

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