//*****************************************************************************
//
// usbhhid.c - This file contains the host HID driver.
//
// Copyright (c) 2008-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 "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/usbhid.h"
#include "usblib/host/usbhost.h"
#include "usblib/host/usbhhid.h"

static void * HIDDriverOpen(tUSBHostDevice *pDevice);
static void HIDDriverClose(void *pvInstance);

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

//*****************************************************************************
//
// If the user has not explicitly stated the maximum number of HID devices to
// support, we assume that we need to support up to the maximum number of USB
// devices that the build is configured for.
//
//*****************************************************************************
#ifndef MAX_HID_DEVICES
#define MAX_HID_DEVICES MAX_USB_DEVICES
#endif

//*****************************************************************************
//
// This is the structure that holds all of the data for a given instance of
// a HID device.
//
//*****************************************************************************
typedef struct
{
    //
    // Save the device instance.
    //
    tUSBHostDevice *pDevice;

    //
    // Used to save the callback.
    //
    tUSBCallback pfnCallback;

    //
    // Callback data provided by caller.
    //
    unsigned long ulCBData;

    //
    // Used to remember what type of device was registered.
    //
    tHIDSubClassProtocol eDeviceType;

    //
    // Interrupt IN pipe.
    //
    unsigned long ulIntInPipe;
}
tHIDInstance;

//*****************************************************************************
//
// The instance data storage for attached hid devices.
//
//*****************************************************************************
static tHIDInstance g_pHIDDevice[MAX_HID_DEVICES];

//*****************************************************************************
//
//! This constant global structure defines the HID Class Driver that is
//! provided with the USB library.
//
//*****************************************************************************
const tUSBHostClassDriver g_USBHIDClassDriver =
{
    USB_CLASS_HID,
    HIDDriverOpen,
    HIDDriverClose,
    0
};

//*****************************************************************************
//
//! This function is used to open an instance of a HID device.
//!
//! \param eDeviceType is the type of device that should be loaded for this
//! instance of the HID device.
//! \param pfnCallback is the function that will be called whenever changes
//! are detected for this device.
//! \param ulCBData is the data that will be returned in when the pfnCallback
//! function is called.
//!
//! This function creates an instance of an specific type of HID device.  The
//! \e eDeviceType parameter is one subclass/protocol values of the types
//! specified in enumerated types tHIDSubClassProtocol.  Only devices that
//! enumerate with this type will be called back via the \e pfnCallback
//! function.  The \e pfnCallback parameter is the callback function for any
//! events that occur for this device type.  The \e pfnCallback function must
//! point to a valid function of type \e tUSBCallback for this call to complete
//! successfully.  To release this device instance the caller of USBHHIDOpen()
//! should call USBHHIDClose() and pass in the value returned from the
//! USBHHIDOpen() call.
//!
//! \return This function returns and instance value that should be used with
//! any other APIs that require an instance value.  If a value of 0 is returned
//! then the device instance could not be created.
//
//*****************************************************************************
unsigned long
USBHHIDOpen(tHIDSubClassProtocol eDeviceType, tUSBCallback pfnCallback,
            unsigned long ulCBData)
{
    unsigned long ulLoop;

    //
    // Find a free device instance structure.
    //
    for(ulLoop = 0; ulLoop < MAX_HID_DEVICES; ulLoop++)
    {
        if(g_pHIDDevice[ulLoop].eDeviceType == USBH_HID_DEV_NONE)
        {
            //
            // Save the instance data for this device.
            //
            g_pHIDDevice[ulLoop].pfnCallback = pfnCallback;
            g_pHIDDevice[ulLoop].eDeviceType = eDeviceType;
            g_pHIDDevice[ulLoop].ulCBData = ulCBData;

            //
            // Return the device instance pointer.
            //
            return((unsigned long)&g_pHIDDevice[ulLoop]);
        }
    }

    //
    // If we get here, there are no space device slots so return NULL to
    // indicate a problem.
    //
    return(0);
}

//*****************************************************************************
//
//! This function is used to release an instance of a HID device.
//!
//! \param ulHIDInstance is the instance value for a HID device to release.
//!
//! This function releases an instance of a HID device that was created by a
//! call to USBHHIDOpen().  This call is required to allow other HID devices
//! to be enumerated after another HID device has been disconnected.  The
//! \e ulHIDInstance parameter should hold the value that was returned from the
//! previous call to USBHHIDOpen().
//!
//! \return None.
//
//*****************************************************************************
void
USBHHIDClose(unsigned long ulHIDInstance)
{
    tHIDInstance *pInst;

    //
    // Get our instance pointer.
    //
    pInst = (tHIDInstance *)ulHIDInstance;

    //
    // Disable any more notifications from the HID layer.
    //
    pInst->pfnCallback = 0;

    //
    // Mark this device slot as free.
    //
    pInst->eDeviceType = USBH_HID_DEV_NONE;
}

//*****************************************************************************
//
// This function handles callbacks for the interrupt IN endpoint.
//
//*****************************************************************************
static void
HIDIntINCallback(unsigned long ulPipe, unsigned long ulEvent)
{
    long lDev;

    switch (ulEvent)
    {
        //
        // Handles a request to schedule a new request on the interrupt IN
        // pipe.
        //
        case USB_EVENT_SCHEDULER:
        {
            USBHCDPipeSchedule(ulPipe, 0, 1);
            break;
        }
        //
        // Called when new data is available on the interrupt IN pipe.
        //
        case USB_EVENT_RX_AVAILABLE:
        {
            //
            // Determine which device this notification is intended for.
            //
            for(lDev = 0; lDev < MAX_HID_DEVICES; lDev++)
            {
                //
                // Does this device own the pipe we've been passed?
                //
                if(g_pHIDDevice[lDev].ulIntInPipe == ulPipe)
                {
                    //
                    // Yes - send the report data to the USB host HID device
                    // class driver.
                    //
                    g_pHIDDevice[lDev].pfnCallback(
                                          (void *)g_pHIDDevice[lDev].ulCBData,
                                          USB_EVENT_RX_AVAILABLE,
                                          ulPipe,
                                          0);
                }
            }

            break;
        }
    }
}

//*****************************************************************************
//
//! This function is used to open an instance of the HID driver.
//!
//! \param pDevice is a pointer to the device information structure.
//!
//! This function will attempt to open an instance of the HID driver based on
//! the information contained in the pDevice structure.  This call can fail if
//! there are not sufficient resources to open the device.  The function will
//! return a value that should be passed back into USBHIDClose() when the
//! driver is no longer needed.
//!
//! \return The function will return a pointer to a HID driver instance.
//
//*****************************************************************************
static void *
HIDDriverOpen(tUSBHostDevice *pDevice)
{
    long lIdx, lDev;
    tEndpointDescriptor *pEndpointDescriptor;
    tInterfaceDescriptor *pInterface;

    //
    // Get the interface descriptor.
    //
    pInterface = USBDescGetInterface(pDevice->pConfigDescriptor, 0, 0);

    //
    // Search the currently open instances for one that supports the protocol
    // of this device.
    //
    for(lDev = 0; lDev < MAX_HID_DEVICES; lDev++)
    {
        if(g_pHIDDevice[lDev].eDeviceType == pInterface->bInterfaceProtocol)
        {
            //
            // Save the device pointer.
            //
            g_pHIDDevice[lDev].pDevice = pDevice;

            for(lIdx = 0; lIdx < 3; lIdx++)
            {
                //
                // Get the first endpoint descriptor.
                //
                pEndpointDescriptor = USBDescGetInterfaceEndpoint(pInterface,
                                                                  lIdx, 256);

                //
                // If no more endpoints then break out.
                //
                if(pEndpointDescriptor == 0)
                {
                    break;
                }

                //
                // Interrupt
                //
                if((pEndpointDescriptor->bmAttributes & USB_EP_ATTR_TYPE_M) ==
                   USB_EP_ATTR_INT)
                {
                    //
                    // Interrupt IN.
                    //
                    if(pEndpointDescriptor->bEndpointAddress & USB_EP_DESC_IN)
                    {
                        g_pHIDDevice[lDev].ulIntInPipe = USBHCDPipeAlloc(0,
                                                          USBHCD_PIPE_INTR_IN,
                                                          pDevice,
                                                          HIDIntINCallback);
                        USBHCDPipeConfig(g_pHIDDevice[lDev].ulIntInPipe,
                                         pEndpointDescriptor->wMaxPacketSize,
                                         pEndpointDescriptor->bInterval,
                                         (pEndpointDescriptor->bEndpointAddress &
                                          USB_EP_DESC_NUM_M));
                    }
                }
            }

            //
            // If there is a callback function call it to inform the application that
            // the device has been enumerated.
            //
            if(g_pHIDDevice[lDev].pfnCallback != 0)
            {
                g_pHIDDevice[lDev].pfnCallback(
                                        (void *)g_pHIDDevice[lDev].ulCBData,
                                        USB_EVENT_CONNECTED,
                                        (unsigned long)&g_pHIDDevice[lDev], 0);
            }

            //
            // Save the device pointer.
            //
            g_pHIDDevice[lDev].pDevice = pDevice;

            return (&g_pHIDDevice[lDev]);
        }
    }

    //
    // If we get here, no user has registered an interest in this particular
    // HID device so we return an error.
    //
    return(0);
}

//*****************************************************************************
//
//! This function is used to release an instance of the HID driver.
//!
//! \param pvInstance is an instance pointer that needs to be released.
//!
//! This function will free up any resources in use by the HID driver instance
//! that is passed in.  The \e pvInstance pointer should be a valid value that
//! was returned from a call to USBHIDOpen().
//!
//! \return None.
//
//*****************************************************************************
static void
HIDDriverClose(void *pvInstance)
{
    tHIDInstance *pInst;

    //
    // Get our instance pointer.
    //
    pInst = (tHIDInstance *)pvInstance;

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

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

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

//*****************************************************************************
//
//! This function is used to set the idle timeout for a HID device.
//!
//! \param ulInstance is the value that was returned from the call to
//! USBHHIDOpen().
//! \param ucDuration is the duration of the timeout in milliseconds.
//! \param ucReportID is the report identifier to set the timeout on.
//!
//! This function will send the Set Idle command to a HID device to set the
//! idle timeout for a given report.  The length of the timeout is specified
//! by the \e ucDuration parameter and the report the timeout for is in the
//! \e ucReportID value.
//!
//! \return Always returns 0.
//
//*****************************************************************************
unsigned long
USBHHIDSetIdle(unsigned long ulInstance, unsigned char ucDuration,
               unsigned char ucReportID)
{
    tUSBRequest SetupPacket;
    tHIDInstance *pHIDInstance;

    pHIDInstance = (tHIDInstance *)ulInstance;

    //
    // This is a Class specific interface OUT request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS
                    | USB_RTYPE_INTERFACE;

    //
    // Request a Device Descriptor.
    //
    SetupPacket.bRequest = USBREQ_SET_IDLE;
    SetupPacket.wValue = (ucDuration << 8) | ucReportID;

    //
    // Set this on interface 1.
    //
    SetupPacket.wIndex = 0;

    //
    // This is always 0 for this request.
    //
    SetupPacket.wLength = 0;

    //
    // Put the setup packet in the buffer.
    //
    return(USBHCDControlTransfer(0, &SetupPacket, pHIDInstance->pDevice,
                                 0, 0, MAX_PACKET_SIZE_EP0));
}

//*****************************************************************************
//
//! This function can be used to retrieve the report descriptor for a given
//! device instance.
//!
//! \param ulInstance is the value that was returned from the call to
//! USBHHIDOpen().
//! \param pucBuffer is the memory buffer to use to store the report
//! descriptor.
//! \param ulSize is the size in bytes of the buffer pointed to by
//! \e pucBuffer.
//!
//! This function is used to return a report descriptor from a HID device
//! instance so that it can determine how to interpret reports that are
//! returned from the device indicated by the \e ulInstance parameter.
//! This call is blocking and will return the number of bytes read into the
//! \e pucBuffer.
//!
//! \return Returns the number of bytes read into the \e pucBuffer.
//
//*****************************************************************************
unsigned long
USBHHIDGetReportDescriptor(unsigned long ulInstance, unsigned char *pucBuffer,
                           unsigned long ulSize)
{
    tUSBRequest SetupPacket;
    unsigned long ulBytes;
    tHIDInstance *pHIDInstance;

    pHIDInstance = (tHIDInstance *)ulInstance;

    //
    // This is a Standard Device IN request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_IN | USB_RTYPE_STANDARD
                    | USB_RTYPE_INTERFACE;

    //
    // Request a Report Descriptor.
    //
    SetupPacket.bRequest = USBREQ_GET_DESCRIPTOR;
    SetupPacket.wValue = USB_HID_DTYPE_REPORT << 8;

    //
    // Index is always 0 for device requests.
    //
    SetupPacket.wIndex = 0;

    //
    // All devices must have at least an 8 byte max packet size so just ask
    // for 8 bytes to start with.
    //
    SetupPacket.wLength = ulSize;

    //
    // Now get the full descriptor now that the actual maximum packet size
    // is known.
    //
    ulBytes = USBHCDControlTransfer(
                0,
                &SetupPacket,
                pHIDInstance->pDevice,
                pucBuffer,
                ulSize,
                pHIDInstance->pDevice->DeviceDescriptor.bMaxPacketSize0);

    return(ulBytes);
}

//*****************************************************************************
//
//! This function is used to set or clear the boot protocol state of a device.
//!
//! \param ulInstance is the value that was returned from the call to
//! USBHHIDOpen().
//! \param ulBootProtocol is either zero or non-zero to indicate which protocol
//! to use for the device.
//!
//! A USB host device can use this function to set the protocol for a connected
//! HID device.  This is commonly used to set keyboards and mice into their
//! simplified boot protocol modes to fix the report structure to a know
//! state.
//!
//! \return This function returns 0.
//
//*****************************************************************************
unsigned long
USBHHIDSetProtocol(unsigned long ulInstance, unsigned long ulBootProtocol)
{
    tUSBRequest SetupPacket;
    tHIDInstance *pHIDInstance;

    pHIDInstance = (tHIDInstance *)ulInstance;

    //
    // This is a Standard Device IN request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS
                    | USB_RTYPE_INTERFACE;

    //
    // Request a Report Descriptor.
    //
    SetupPacket.bRequest = USBREQ_SET_PROTOCOL;

    if(ulBootProtocol)
    {
        //
        // Boot Protocol.
        //
        SetupPacket.wValue = 0;
    }
    else
    {
        //
        // Report Protocol.
        //
        SetupPacket.wValue = 1;
    }

    //
    // Index is always 0 for device requests.
    //
    SetupPacket.wIndex = 0;

    //
    // Always 0.
    //
    SetupPacket.wLength = 0;

    //
    // Now get the full descriptor now that the actual maximum packet size
    // is known.
    //
    USBHCDControlTransfer(
        0,
        &SetupPacket,
        pHIDInstance->pDevice,
        0,
        0,
        pHIDInstance->pDevice->DeviceDescriptor.bMaxPacketSize0);

    return(0);
}

//*****************************************************************************
//
//! This function is used to retrieve a report from a HID device.
//!
//! \param ulInstance is the value that was returned from the call to
//! USBHHIDOpen().
//! \param ulInterface is the interface to retrieve the report from.
//! \param pucData is the memory buffer to use to store the report.
//! \param ulSize is the size in bytes of the buffer pointed to by
//! \e pucBuffer.
//!
//! This function is used to retrieve a report from a USB pipe.  It is usually
//! called when the USB HID layer has detected a new data available in a USB
//! pipe.  The USB HID host device code will receive a
//! \b USB_EVENT_RX_AVAILABLE event when data is available, allowing the
//! callback function to retrieve the data.
//!
//! \return Returns the number of bytes read from report.
//
//*****************************************************************************
unsigned long
USBHHIDGetReport(unsigned long ulInstance,
                 unsigned long ulInterface,
                 unsigned char *pucData,
                 unsigned long ulSize)
{
    tHIDInstance *pHIDInstance;

    //
    // Cast the instance pointer to the correct type for ease of use.
    //
    pHIDInstance = (tHIDInstance *)ulInstance;

    //
    // Read the Data out.
    //
    ulSize = USBHCDPipeReadNonBlocking(pHIDInstance->ulIntInPipe, pucData,
                                       ulSize);

    //
    // Return the number of bytes read from the interrupt in pipe.
    //
    return(ulSize);
}

//*****************************************************************************
//
//! This function is used to send a report to a HID device.
//!
//! \param ulInstance is the value that was returned from the call to
//! USBHHIDOpen().
//! \param ulInterface is the interface to send the report to.
//! \param pucData is the memory buffer to use to store the report.
//! \param ulSize is the size in bytes of the buffer pointed to by
//! \e pucBuffer.
//!
//! This function is used to send a report to a USB HID device.  It can be
//! only be called from outside the callback context as this function will not
//! return from the call until the data has been sent successfully.
//!
//! \return Returns the number of bytes sent to the device.
//
//*****************************************************************************
unsigned long
USBHHIDSetReport(unsigned long ulInstance, unsigned long ulInterface,
                 unsigned char *pucData, unsigned long ulSize)
{
    tUSBRequest SetupPacket;
    tHIDInstance *pHIDInstance;

    pHIDInstance = (tHIDInstance *)ulInstance;

    //
    // This is a Standard Device IN request.
    //
    SetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS
                    | USB_RTYPE_INTERFACE;

    //
    // Request a Report Descriptor.
    //
    SetupPacket.bRequest = USBREQ_SET_REPORT;
    SetupPacket.wValue = USB_HID_REPORT_OUTPUT << 8;

    //
    // Index is always 0 for device requests.
    //
    SetupPacket.wIndex = (unsigned short)ulInterface;

    //
    // Always 0.
    //
    SetupPacket.wLength = ulSize;

    //
    // Now get the full descriptor now that the actual maximum packet size
    // is known.
    //
    USBHCDControlTransfer(0, &SetupPacket, pHIDInstance->pDevice,
            pucData, ulSize,
            pHIDInstance->pDevice->DeviceDescriptor.bMaxPacketSize0);

    return(ulSize);
}

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