//*****************************************************************************
//
// usbhaudio.c - USB host audio driver.
//
// Copyright (c) 2010-2020 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.2.0.295 of the Tiva USB Library.
//
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_types.h"
#include "driverlib/usb.h"
#include "usblib/usblib.h"
#include "usblib/usblibpriv.h"
#include "usblib/usbaudio.h"
#include "usblib/host/usbhost.h"
#include "usblib/host/usbhostpriv.h"
#include "usblib/host/usbhaudio.h"

//*****************************************************************************
//
//! \addtogroup usblib_host_class
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// These defines are used with the USBHostAudioFormatSet()
// USBHostAudioFormatGet() to parse out interface number and alternate
// setting number for an interface.
//
//*****************************************************************************
#define INTERFACE_NUM_M         0x000000FF
#define INTERFACE_ALTSETTING_M  0x0000FF00
#define INTERFACE_ALTSETTING_S  8

//*****************************************************************************
//
// Used to indicate an invalid interface descriptor number.
//
//*****************************************************************************
#define INVALID_INTERFACE       0xffffffff

//*****************************************************************************
//
// Forward declarations for the driver open and close calls.
//
//*****************************************************************************
static void *USBAudioOpen(tUSBHostDevice *psDevice);
static void USBAudioClose(void *pvInstance);

//*****************************************************************************
//
// This is the structure for an instance of a USB host audio driver.
//
//*****************************************************************************
struct tUSBHostAudioInstance
{
    //
    // Save the device instance.
    //
    tUSBHostDevice *psDevice;

    //
    // Used to save the call back.
    //
    tUSBHostAudioCallback pfnCallback;

    //
    // This is the control interface.
    //
    uint8_t ui8IControl;

    //
    // This is the output streaming interface.
    //
    uint8_t ui8OutInterface;

    //
    // This is the currently selected active output interface used with
    // ui8OutInterface interface.
    //
    uint8_t ui8OutAltSetting;

    //
    // This is the streaming interface.
    //
    uint8_t ui8InInterface;

    //
    // This is the currently selected active input interface used with
    // ui8InInterface interface.
    //
    uint8_t ui8InAltSetting;

    //
    // The Isochronous endpoint addresses.
    //
    uint8_t ui8IsochInAddress;
    uint8_t ui8IsochOutAddress;

    tACInputTerminal *psInTerminal;
    tACOutputTerminal *psOutTerminal;

    //
    // Holds the identifier for the Feature Unit for controlling volume.
    //
    uint8_t ui8VolumeID;

    tACFeatureUnit *psFeatureUnit;

    //
    // Holds what types of controls are enabled on the device.
    //
    uint16_t pui16Controls[3];

    //
    // Isochronous IN pipe.
    //
    uint32_t ui32IsochInPipe;
    uint16_t ui16PipeSizeIn;
    tUSBHostAudioCallback pfnInCallback;
    void *pvInBuffer;

    //
    // Isochronous OUT pipe.
    //
    uint32_t ui32IsochOutPipe;
    uint16_t ui16PipeSizeOut;
    tUSBHostAudioCallback pfnOutCallback;
    void *pvOutBuffer;

    //
    // State flags for this audio instance.
    //
    uint32_t ui32Flags;
};

//*****************************************************************************
//
// The internal flags for an audio interface.
//
//*****************************************************************************
#define AUDIO_FLAG_OUT_ACTIVE   1   // Audio output is active.
#define AUDIO_FLAG_IN_ACTIVE    2   // Audio input is active.

//*****************************************************************************
//
// The USB Host audio instance.
//
//*****************************************************************************
static tUSBHostAudioInstance g_sAudioDevice =
{
    0
};

//*****************************************************************************
//
//! This constant global structure defines the Audio Class Driver that is
//! provided with the USB library.
//
//*****************************************************************************
const tUSBHostClassDriver g_sUSBHostAudioClassDriver =
{
    USB_CLASS_AUDIO,
    USBAudioOpen,
    USBAudioClose,
    0
};

//*****************************************************************************
//
// This is the internal function that handles callbacks from the USB IN pipe.
//
//*****************************************************************************
static void
PipeCallbackIN(uint32_t ui32Pipe, uint32_t ui32Event)
{
    //
    // Only handle the data available callback and pass it on to the
    // application.
    //
    if(ui32Event == USB_EVENT_RX_AVAILABLE)
    {
        if(g_sAudioDevice.pfnInCallback)
        {
            g_sAudioDevice.pfnInCallback(&g_sAudioDevice,
                                         USB_EVENT_RX_AVAILABLE,
                                         USBHCDPipeTransferSizeGet(ui32Pipe),
                                         g_sAudioDevice.pvInBuffer);
        }
    }
}

//*****************************************************************************
//
// This is the internal function that handles callbacks from the USB OUT pipe.
//
//*****************************************************************************
static void
PipeCallbackOUT(uint32_t ui32Pipe, uint32_t ui32Event)
{
    //
    // Only handle the transmit complete callback and pass it on to the
    // application.
    //
    if(ui32Event == USB_EVENT_TX_COMPLETE)
    {
        if(g_sAudioDevice.pfnOutCallback)
        {
            g_sAudioDevice.pfnOutCallback(&g_sAudioDevice,
                                          USB_EVENT_TX_COMPLETE, 0,
                                          g_sAudioDevice.pvOutBuffer);
        }
    }
}

//*****************************************************************************
//
// Finds a given terminal and type in an audio configuration descriptor.
//
//*****************************************************************************
static tDescriptorHeader *
AudioTerminalGet(tConfigDescriptor *psConfigDesc, uint32_t ui32Terminal,
                 uint32_t ui32TerminalType)
{
    tACOutputTerminal *psOutput;
    tDescriptorHeader *psHeader;
    int32_t i32BytesRemaining;

    psHeader = (tDescriptorHeader *)psConfigDesc;
    i32BytesRemaining = psConfigDesc->wTotalLength;

    while(i32BytesRemaining > 0)
    {
        //
        // Output and input terminals are the same past the bDescriptorSubtype
        // and wTerminalType that are being searched for.
        //
        psOutput = (tACOutputTerminal *)psHeader;

        //
        // Only CS_INTERFACE descriptors can be a terminal.
        //
        if((psHeader->bDescriptorType == USB_DTYPE_CS_INTERFACE) &&
           (ui32Terminal == psOutput->bDescriptorSubtype))
        {
            if((psOutput->bDescriptorSubtype == USB_AI_OUTPUT_TERMINAL) ||
               (psOutput->bDescriptorSubtype == USB_AI_INPUT_TERMINAL))

            {
                //
                // If this was the terminal type that was requested, the
                // return it.
                //
                if(psOutput->wTerminalType == ui32TerminalType)
                {
                    return(psHeader);
                }
            }
            else if(psOutput->bDescriptorSubtype == USB_AI_FEATURE_UNIT)
            {
                return(psHeader);
            }
        }

        //
        // Decrease the bytes remaining by the size of this descriptor.
        //
        i32BytesRemaining -=  psHeader->bLength;

        //
        // Move the pointer to the next header.
        //
        psHeader = (tDescriptorHeader *)((uint32_t)psHeader +
                                         psHeader->bLength);
    }
    return((tDescriptorHeader *)0);
}

//*****************************************************************************
//
// This function returns the interface number for the control interface
// in the structure passed in the psConfigDesc.
//
// \param psConfigDescriptor is a pointer to the memory containing a valid
//        configuration descriptor for a device.
//
// This function searches a configuration descriptor for a control interface
// descriptor.  The function only search for the first descriptor and then
// returns when it finds one.
//
// \return The first control interface descriptor number for an audio device
//         or INVALID_INTERFACE if no control interface descriptor was found.
//
//*****************************************************************************
static uint32_t
AudioControlGet(tConfigDescriptor *psConfigDesc)
{
    tDescriptorHeader *psHeader;
    tInterfaceDescriptor *psInterface;
    uint32_t ui32Interface;
    int32_t i32Bytes;

    psHeader = (tDescriptorHeader *)psConfigDesc;
    i32Bytes = psConfigDesc->wTotalLength;

    //
    // Initialize the interface number to an invalid value.
    //
    ui32Interface = INVALID_INTERFACE;

    //
    // Search the whole configuration descriptor.
    //
    while(i32Bytes > 0)
    {
        //
        // Find an interface descriptor and see if it is a control interface.
        //
        if(psHeader->bDescriptorType == USB_DTYPE_INTERFACE)
        {
            psInterface = (tInterfaceDescriptor *)psHeader;

            //
            // If this is the control interface then return the value to the
            // caller.
            //
            if(psInterface->bInterfaceSubClass == USB_ASC_AUDIO_CONTROL)
            {
                ui32Interface = psInterface->bInterfaceNumber;

                break;
            }
        }

        //
        // Decrease the bytes remaining by the size of this descriptor.
        //
        i32Bytes -= psHeader->bLength;

        //
        // Move the pointer to the next header.
        //
        psHeader = (tDescriptorHeader*)((uint32_t)psHeader +
                                        psHeader->bLength);
    }
    return(ui32Interface);
}

//*****************************************************************************
//
// If it exists, finds the correct audio interface for a given audio format.
//
//*****************************************************************************
static uint32_t
AudioGetInterface(tUSBHostAudioInstance *psAudioDevice, uint16_t ui16Format,
                  uint32_t ui32SampleRate, uint32_t ui32Bytes,
                  uint32_t ui32Channels, uint32_t ui32Flags)
{
    tDescriptorHeader *psHeader;
    tInterfaceDescriptor *psInterface;
    tEndpointDescriptor *pINEndpoint, *pOUTEndpoint;
    tACHeader *pACHeader;
    tACGeneral *pGeneral;
    tASFormat *pFormat;
    tEndpointDescriptor *pEndpoint;
    uint8_t *pui8Value;
    uint32_t ui32Value;
    int32_t i32Bytes, i32Idx;

    //
    // Initialize the Interface pointer to null.
    //
    psInterface = 0;
    pINEndpoint = 0;
    pOUTEndpoint = 0;

    //
    // Start at the top of the configuration descriptor.
    //
    psHeader = (tDescriptorHeader *)psAudioDevice->psDevice->psConfigDescriptor;

    i32Bytes = psAudioDevice->psDevice->psConfigDescriptor->wTotalLength;

    while(i32Bytes > 0)
    {
        if(psHeader->bDescriptorType == USB_DTYPE_INTERFACE)
        {
            //
            // If a new interface was found and the last one satisfied all
            // requirements then a valid interface was found so break out.
            //
            if(psInterface)
            {
                break;
            }

            //
            // Get the new interface pointer.
            //
            psInterface = (tInterfaceDescriptor *)psHeader;

            //
            // Reset the endpoints on finding a new interface descriptor.
            //
            pINEndpoint = 0;
            pOUTEndpoint = 0;

            //
            // If this is not a valid audio streaming interface then reset
            // the interface pointer to null.
            //
            if((psInterface->bNumEndpoints == 0) ||
               (psInterface->bInterfaceClass != USB_CLASS_AUDIO) ||
               (psInterface->bInterfaceSubClass != USB_ASC_AUDIO_STREAMING))
            {
                psInterface = 0;
            }
        }
        if((psInterface) &&
           (psHeader->bDescriptorType == USB_DTYPE_CS_INTERFACE))
        {
            pACHeader = (tACHeader *)psHeader;

            //
            // If this is a General descriptor the check if the format matches.
            //
            if(pACHeader->bDescriptorSubtype == USB_AS_GENERAL)
            {
                //
                // Just save the pointer to the format descriptor.
                //
                pGeneral = (tACGeneral *)psHeader;

                //
                // If this interface has the wrong format then set it to null
                // so that the rest of this interface is ignored.
                //
                if(pGeneral->wFormatTag != ui16Format)
                {
                    psInterface = 0;
                }
            }
            else if(pACHeader->bDescriptorSubtype == USB_AS_FORMAT_TYPE)
            {
                pFormat = (tASFormat *)psHeader;

                //
                // If the number of bytes per sample and number of channels do
                // not match then reset the interface pointer so that the rest
                // of this interface is ignored.
                //
                if((pFormat->bNrChannels != ui32Channels) ||
                   (pFormat->bSubFrameSize != ui32Bytes))
                {
                    psInterface = 0;
                }
                else
                {
                    pui8Value = &pFormat->tSamFreq;

                    //
                    // Attempt to find the sample rate in the sample rate
                    // table for this interface.
                    //
                    for(i32Idx = 0; i32Idx < pFormat->bSamFreqType; i32Idx++)
                    {
                        ui32Value = (*((uint32_t *)&pui8Value[i32Idx * 3]) &
                                    0xffffff);

                        if(ui32Value == ui32SampleRate)
                        {
                            break;
                        }
                    }

                    //
                    // If the sample rate was not found then set the interface
                    // pointer to null so that the rest of this interface is
                    // ignored.
                    //
                    if(i32Idx == pFormat->bSamFreqType)
                    {
                        psInterface = 0;
                    }
                }
            }
        }
        else if((psInterface) &&
                (psHeader->bDescriptorType == USB_DTYPE_ENDPOINT))
        {
            pEndpoint = (tEndpointDescriptor *)psHeader;

            //
            // See what direction is being requested.
            //
            if(ui32Flags & USBH_AUDIO_FORMAT_IN)
            {
                //
                // If this is an input endpoint and is just a feed back input
                // then ignore it.
                //
                if(pEndpoint->bEndpointAddress & USB_EP_DESC_IN)
                {
                    if((pEndpoint->bmAttributes & USB_EP_ATTR_USAGE_M)
                        == USB_EP_ATTR_USAGE_FEEDBACK)
                    {
                        psInterface = 0;
                    }
                    else
                    {
                        //
                        // Save this endpoint as a possible valid endpoint
                        //
                        pINEndpoint = pEndpoint;
                    }
                }
            }
            else
            {
                //
                // If this is an output endpoint and is just a feed back input
                // then ignore it.
                //
                if((pEndpoint->bEndpointAddress & USB_EP_DESC_IN) == 0)
                {
                    if((pEndpoint->bmAttributes & USB_EP_ATTR_USAGE_M)
                        == USB_EP_ATTR_USAGE_FEEDBACK)
                    {
                        psInterface = 0;
                    }
                    else
                    {
                        //
                        // Save this endpoint as a possible valid endpoint;
                        //
                        pOUTEndpoint = pEndpoint;
                    }
                }
            }
        }

        //
        // Decrease the bytes remaining by the size of this descriptor.
        //
        i32Bytes -= psHeader->bLength;

        //
        // Move the pointer to the next header.
        //
        psHeader = (tDescriptorHeader*)((uint32_t)psHeader +
                                        psHeader->bLength);
    }

    //
    // If there is still a valid interface then return the values.
    //
    if(psInterface)
    {
        //
        // Check a valid IN endpoint descriptor.
        //
        if(pINEndpoint)
        {
            //
            // Save the endpoint address.
            //
            g_sAudioDevice.ui8IsochInAddress = pINEndpoint->bEndpointAddress &
                                               USB_EP_DESC_NUM_M;

            //
            // If there is no current pipe then just allocate a new one with
            // the settings for this interface.
            //
            if(g_sAudioDevice.ui32IsochInPipe == 0)
            {
                //
                // Allocate the USB Pipe for this Isochronous IN end point.
                //
                g_sAudioDevice.ui32IsochInPipe =
                    USBHCDPipeAllocSize(0, USBHCD_PIPE_ISOC_IN_DMA,
                                        g_sAudioDevice.psDevice,
                                        pINEndpoint->wMaxPacketSize,
                                        PipeCallbackIN);
            }
            else if(g_sAudioDevice.ui16PipeSizeIn < pINEndpoint->wMaxPacketSize)
            {
                //
                // Free the old endpoint and allocate a new one.
                //
                USBHCDPipeFree(g_sAudioDevice.ui32IsochInPipe);

                //
                // Allocate the USB Pipe for this Isochronous IN end point.
                //
                g_sAudioDevice.ui32IsochInPipe =
                    USBHCDPipeAllocSize(0, USBHCD_PIPE_ISOC_IN_DMA,
                                        g_sAudioDevice.psDevice,
                                        pINEndpoint->wMaxPacketSize,
                                        PipeCallbackIN);

                //
                // Save the new size of the maximum packet size for this
                // USB pipe.
                //
                g_sAudioDevice.ui16PipeSizeIn = pINEndpoint->wMaxPacketSize;
            }

            //
            // Configure the USB pipe as a Isochronous IN end point.
            //
            USBHCDPipeConfig(g_sAudioDevice.ui32IsochInPipe,
                             pINEndpoint->wMaxPacketSize,
                             0,
                             g_sAudioDevice.ui8IsochInAddress);
        }

        //
        // Check a valid OUT endpoint descriptor.
        //
        if(pOUTEndpoint)
        {
            //
            // Save the endpoint address.
            //
            g_sAudioDevice.ui8IsochOutAddress =
                            pOUTEndpoint->bEndpointAddress & USB_EP_DESC_NUM_M;

            //
            // If there is no current pipe then just allocate a new one with
            // the settings for this interface.
            //
            if(g_sAudioDevice.ui32IsochOutPipe == 0)
            {
                //
                // Allocate the USB Pipe for this Isochronous OUT end point.
                //
                g_sAudioDevice.ui32IsochOutPipe =
                    USBHCDPipeAllocSize(0, USBHCD_PIPE_ISOC_OUT_DMA,
                                        g_sAudioDevice.psDevice,
                                        pOUTEndpoint->wMaxPacketSize,
                                        PipeCallbackOUT);
            }
            else if(g_sAudioDevice.ui16PipeSizeOut <
                    pOUTEndpoint->wMaxPacketSize)
            {
                //
                // Free the old endpoint and allocate a new one.
                //
                USBHCDPipeFree(g_sAudioDevice.ui32IsochOutPipe);

                //
                // Allocate the USB Pipe for this Isochronous OUT end point.
                //
                g_sAudioDevice.ui32IsochOutPipe =
                    USBHCDPipeAllocSize(0, USBHCD_PIPE_ISOC_OUT_DMA,
                                        g_sAudioDevice.psDevice,
                                        pOUTEndpoint->wMaxPacketSize,
                                        PipeCallbackOUT);

                //
                // Save the new size of the maximum packet size for this
                // USB pipe.
                //
                g_sAudioDevice.ui16PipeSizeOut = pOUTEndpoint->wMaxPacketSize;
            }

            //
            // Configure the USB pipe as a Isochronous OUT end point.
            //
            USBHCDPipeConfig(g_sAudioDevice.ui32IsochOutPipe,
                             pOUTEndpoint->wMaxPacketSize, 0,
                             g_sAudioDevice.ui8IsochOutAddress);
        }

        return(psInterface->bInterfaceNumber |
               (psInterface->bAlternateSetting << INTERFACE_ALTSETTING_S));
    }
    return(INVALID_INTERFACE);
}

//*****************************************************************************
//
// This function is used to open an instance of the USB host audio driver.
//
// \param psDevice is a pointer to the device information structure.
//
// This function attempts to open an instance of the USB host audio driver
// based on the information contained in the psDevice structure.  This call
// fails if there are not sufficient resources to open the device.  The
// function returns a value that should be passed back into USBHostAudioClose()
// when the driver is no longer needed.
//
// \return The function returns a pointer to a USB host audio driver
// instance.
//
//*****************************************************************************
static void *
USBAudioOpen(tUSBHostDevice *psDevice)
{
    uint32_t ui32Temp;
    tConfigDescriptor *psConfigDesc;

    //
    // Don't allow the device to be opened without closing first.
    //
    if(g_sAudioDevice.psDevice)
    {
        return(0);
    }

    //
    // Save the Host device pointer.
    //
    g_sAudioDevice.psDevice = psDevice;

    //
    // Save a shorter name for the configuration descriptor.
    //
    psConfigDesc = psDevice->psConfigDescriptor;

    //
    // Find the input terminal.
    //
    g_sAudioDevice.psInTerminal =
        (tACInputTerminal *)AudioTerminalGet(psConfigDesc,
                                             USB_AI_INPUT_TERMINAL,
                                             USB_TTYPE_STREAMING);

    //
    // Find the output terminal.
    //
    g_sAudioDevice.psOutTerminal =
        (tACOutputTerminal *)AudioTerminalGet(psConfigDesc,
                                              USB_AI_OUTPUT_TERMINAL,
                                              USB_TTYPE_STREAMING);

    //
    // Find the feature unit.
    g_sAudioDevice.psFeatureUnit =
        (tACFeatureUnit *)AudioTerminalGet(psConfigDesc,
                                             USB_AI_FEATURE_UNIT, 0);

    //
    // Need some kind of terminal to send or receive audio from.
    //
    if((g_sAudioDevice.psOutTerminal == 0) &&
       (g_sAudioDevice.psInTerminal == 0))
    {
        return(0);
    }

    //
    // Find the Audio control interface.
    //
    ui32Temp = AudioControlGet(psConfigDesc);

    if(ui32Temp == INVALID_INTERFACE)
    {
        return(0);
    }

    //
    // Save the control interface index and increment the number
    // of interfaces that have been found.
    //
    g_sAudioDevice.ui8IControl = (uint8_t)ui32Temp;

    //
    // If the call back exists, call it with an Open event.
    //
    if(g_sAudioDevice.pfnCallback != 0)
    {
        g_sAudioDevice.pfnCallback(&g_sAudioDevice,
                                   USBH_AUDIO_EVENT_OPEN, 0, 0);
    }

    //
    // If a feature unit was found, save the ID
    //
    if(g_sAudioDevice.psFeatureUnit != 0)
    {
        g_sAudioDevice.ui8VolumeID = g_sAudioDevice.psFeatureUnit->bUnitID;
    }

    //
    // Allocate the USB Pipe for this Isochronous IN end point.
    //
    g_sAudioDevice.ui32IsochInPipe =
        USBHCDPipeAllocSize(0, USBHCD_PIPE_ISOC_IN_DMA,
                            g_sAudioDevice.psDevice, 256, PipeCallbackIN);
    g_sAudioDevice.ui16PipeSizeIn = 256;

    //
    // Allocate the USB Pipe for this Isochronous OUT end point.
    //
    g_sAudioDevice.ui32IsochOutPipe =
        USBHCDPipeAllocSize(0, USBHCD_PIPE_ISOC_OUT_DMA,
                            g_sAudioDevice.psDevice, 256, PipeCallbackOUT);
    g_sAudioDevice.ui16PipeSizeOut = 256;

    //
    // Clear the flags.
    //
    g_sAudioDevice.ui32Flags = 0;

    //
    // Return the only instance of this device.
    //
    return(&g_sAudioDevice);
}

//*****************************************************************************
//
// This function is used to release an instance of the USB host audio driver.
//
// \param pvAudioDevice is an instance pointer that needs to be released.
//
// This function frees up any resources in use by the USB host audio
// driver instance that is passed in.  The \e pvAudioDevice pointer should be a
// valid value that was returned from a call to USBHostAudioOpen().
//
// \return None.
//
//*****************************************************************************
static void
USBAudioClose(void *pvAudioDevice)
{
    tUSBHostAudioInstance *psAudioDevice;

    psAudioDevice = (tUSBHostAudioInstance *)pvAudioDevice;

    //
    // Do nothing if there is not a driver open.
    //
    if(psAudioDevice->psDevice == 0)
    {
        return;
    }

    //
    // Reset the device pointer.
    //
    psAudioDevice->psDevice = 0;

    //
    // Free the Isochronous IN pipe.
    //
    if(psAudioDevice->ui32IsochInPipe != 0)
    {
        USBHCDPipeFree(psAudioDevice->ui32IsochInPipe);
    }

    //
    // Free the Isochronous OUT pipe.
    //
    if(psAudioDevice->ui32IsochOutPipe != 0)
    {
        USBHCDPipeFree(psAudioDevice->ui32IsochOutPipe);
    }

    //
    // If the call back exists then call it.
    //
    if(psAudioDevice->pfnCallback != 0)
    {
        psAudioDevice->pfnCallback(psAudioDevice, USBH_AUDIO_EVENT_CLOSE, 0,
                                   0);
    }
}

//*****************************************************************************
//
//! This function should be called before any devices are present to enable
//! the host audio class driver.
//!
//! \param ui32Index is the audio device to open (currently only 0 is
//! supported).
//! \param pfnCallback is the driver call back for host audio events.
//!
//! This function is called to open an instance of a host audio device and
//! should provide a valid callback function for host audio events in the
//! \e pfnCallback parameter.  This function must be called before the USB
//! host code can successfully enumerate an audio device.
//!
//! \return This function returns the driver instance to use for the other
//! host audio functions.  If there is no instance available at the time of
//! this call, this function returns zero.
//
//*****************************************************************************
tUSBHostAudioInstance *
USBHostAudioOpen(uint32_t ui32Index, tUSBHostAudioCallback pfnCallback)
{
    //
    // Only one audio device is supported at this time and on one instance
    // is supported so if there is already a call back then fail.
    //
    if((ui32Index != 0) || (g_sAudioDevice.pfnCallback))
    {
        return(0);
    }

    //
    // Save the call back.
    //
    g_sAudioDevice.pfnCallback = pfnCallback;

    //
    // Return the requested device instance.
    //
    return(&g_sAudioDevice);
}

//*****************************************************************************
//
//! This function should be called to release an audio device instance.
//!
//! \param psAudioInstance is the device instance that is to be released.
//!
//! This function is called when a host audio device needs to be released.
//! This could be in preparation for shutdown or a switch to USB device mode,
//! for example.  Following this call, the audio device is available and can
//! be opened again using a call to USBHostAudioOpen().  After calling this
//! function, the host audio driver will no longer provide any callbacks or
//! accept calls to other audio driver APIs.
//!
//! \return None.
//
//*****************************************************************************
void
USBHostAudioClose(tUSBHostAudioInstance *psAudioInstance)
{
    //
    // Close the audio device.
    //
    USBAudioClose(psAudioInstance);

    //
    // Clear the call back indicating that the device is now closed.
    //
    psAudioInstance->pfnCallback = 0;
}

//*****************************************************************************
//
// This function is used to request settings from a given audio interface.
//
// \param psAudioDevice is the audio device instance to access.
// \param ui32Interface is the interface to access.
// \param ui32Channel is the channel number to access.
// \param ui32Request is the audio device request.
//
// This function is used to get volume control parameters from a given
// interface and on a given channel.  The \e ui32Interface is the interface to
// make the request specified by \e ui32Channel and \e ui32Request.  The
// \e ui32Request parameter must be one of the USB_AC_GET_* values.
//
// \return This function returns the requested value.
//
//*****************************************************************************
static uint32_t
VolumeSettingGet(tUSBHostAudioInstance *psAudioDevice, uint32_t ui32Interface,
                 uint32_t ui32Channel, uint32_t ui32Request)
{
    uint32_t ui32Value;
    tUSBRequest sSetupPacket;

    ui32Value = 0;

    //
    // This is a Class specific Interface IN request.
    //
    sSetupPacket.bmRequestType = USB_RTYPE_DIR_IN | USB_RTYPE_CLASS |
                                 USB_RTYPE_INTERFACE;

    //
    // Request a Device Descriptor.
    //
    sSetupPacket.bRequest = (ui32Request & 0xff);

    //
    // Request for a string descriptor.
    //
    sSetupPacket.wValue = VOLUME_CONTROL | (ui32Channel & 0xff);

    //
    // Set the language ID.
    //
    sSetupPacket.wIndex = (psAudioDevice->ui8VolumeID << 8) |
                          (ui32Interface & 0xff);

    //
    // Only request the space available.
    //
    sSetupPacket.wLength = 2;

    //
    // Put the setup packet in the buffer.
    //
    USBHCDControlTransfer(0, &sSetupPacket, psAudioDevice->psDevice,
                (uint8_t *)&ui32Value, 4,
                psAudioDevice->psDevice->sDeviceDescriptor.bMaxPacketSize0);

    return(ui32Value);
}

//*****************************************************************************
//
//! This function is used to get the current volume setting for a given
//! audio device.
//!
//! \param psAudioInstance is an instance of the USB audio device.
//! \param ui32Interface is the interface number to use to query the current
//! volume setting.
//! \param ui32Channel is the 0 based channel number to query.
//!
//! The function is used to retrieve the current volume setting for an audio
//! device on the channel specified by \e ui32Channel.  The \e ui32Interface is
//! ignored for now and should be set to 0 to access the default audio control
//! interface.  The \e ui32Channel value starts with 0 which is the master
//! audio volume control interface.  The remaining \e ui32Channel values
//! provide access to various other audio channels, with 1 and 2 being left and
//! right audio channels.
//!
//! \note On devices that do not support volume control interfaces, this
//! call returns 0, indicating a 0db setting.
//!
//! \return Returns the current volume setting for the requested interface.
//
//*****************************************************************************
uint32_t
USBHostAudioVolumeGet(tUSBHostAudioInstance *psAudioInstance,
                      uint32_t ui32Interface, uint32_t ui32Channel)
{
    return(VolumeSettingGet(psAudioInstance, ui32Interface, ui32Channel,
                            USB_AC_GET_CUR));
}

//*****************************************************************************
//
//! This function is used to get the maximum volume setting for a given
//! audio device.
//!
//! \param psAudioInstance is an instance of the USB audio device.
//! \param ui32Interface is the interface number to use to query the maximum
//! volume control value.
//! \param ui32Channel is the 0 based channel number to query.
//!
//! The function is used to retrieve the maximum volume setting for an audio
//! device on the channel specified by \e ui32Channel.  The \e ui32Interface is
//! ignored for now and should be set to 0 to access the default audio control
//! interface.  The \e ui32Channel value starts with 0 which is the master
//! audio volume control interface.  The remaining \e ui32Channel values
//! provide access to various other audio channels, with 1 and 2 being left and
//! right audio channels.
//!
//! \note On devices that do not support volume control interfaces, this
//! call returns 0, indicating a 0db setting.
//!
//! \return Returns the maximum volume setting for the requested interface.
//
//*****************************************************************************
uint32_t
USBHostAudioVolumeMaxGet(tUSBHostAudioInstance *psAudioInstance,
                         uint32_t ui32Interface, uint32_t ui32Channel)
{
    return(VolumeSettingGet(psAudioInstance, ui32Interface, ui32Channel,
                            USB_AC_GET_MAX));
}

//*****************************************************************************
//
//! This function is used to get the minimum volume setting for a given
//! audio device.
//!
//! \param psAudioInstance is an instance of the USB audio device.
//! \param ui32Interface is the interface number to use to query the minimum
//! volume control value.
//! \param ui32Channel is the 0 based channel number to query.
//!
//! The function is used to retrieve the minimum volume setting for an audio
//! device on the channel specified by \e ui32Channel.  The \e ui32Interface is
//! ignored for now and should be set to 0 to access the default audio control
//! interface.  The \e ui32Channel value starts with 0 which is the master
//! audio volume control interface.  The remaining \e ui32Channel values
//! provide access to various other audio channels, with 1 and 2 being left and
//! right audio channels.
//!
//! \note On devices that do not support volume control interfaces, this
//! call returns 0, indicating a 0db setting.
//!
//! \return Returns the minimum volume setting for the requested interface.
//
//*****************************************************************************
uint32_t
USBHostAudioVolumeMinGet(tUSBHostAudioInstance *psAudioInstance,
                         uint32_t ui32Interface, uint32_t ui32Channel)
{
    return(VolumeSettingGet(psAudioInstance, ui32Interface, ui32Channel,
                            USB_AC_GET_MIN));
}

//*****************************************************************************
//
//! This function is used to get the volume control resolution for a given
//! audio device.
//!
//! \param psAudioInstance is an instance of the USB audio device.
//! \param ui32Interface is the interface number to use to query the resolution
//! for the volume control.
//! \param ui32Channel is the 0 based channel number to query.
//!
//! The function is used to retrieve the volume control resolution for an audio
//! device on the channel specified by \e ui32Channel.  The \e ui32Interface is
//! ignored for now and should be set to 0 to access the default audio control
//! interface.  The \e ui32Channel value starts with 0 which is the master
//! audio volume control interface.  The remaining \e ui32Channel values
//! provide access to various other audio channels, with 1 and 2 being left and
//! right audio channels.
//!
//! \note On devices that do not support volume control interfaces, this
//! call returns 0, indicating a 0db setting.
//!
//! \return Returns the volume control resolution for the requested interface.
//
//*****************************************************************************
uint32_t
USBHostAudioVolumeResGet(tUSBHostAudioInstance *psAudioInstance,
                         uint32_t ui32Interface, uint32_t ui32Channel)
{
    return(VolumeSettingGet(psAudioInstance, ui32Interface, ui32Channel,
                            USB_AC_GET_RES));
}

//*****************************************************************************
//
//! This function is used to set the current volume setting for a given
//! audio device.
//!
//! \param psAudioInstance is an instance of the USB audio device.
//! \param ui32Interface is the interface number to use to set the current
//! volume setting.
//! \param ui32Channel is the 0 based channel number to query.
//! \param ui32Value is the value to write to the USB audio device.
//!
//! The function is used to set the current volume setting for an audio
//! device on the channel specified by \e ui32Channel.  The \e ui32Interface is
//! ignored for now and should be set to 0 to access the default audio control
//! interface.  The \e ui32Channel value starts with 0 which is the master
//! audio volume control interface.  The remaining \e ui32Channel values
//! provide access to various other audio channels, with 1 and 2 being left and
//! right audio channels.
//!
//! \note On devices that do not support volume control interfaces, this
//! call returns 0, indicating a 0db setting.
//!
//! \return None.
//
//*****************************************************************************
void
USBHostAudioVolumeSet(tUSBHostAudioInstance *psAudioInstance,
                      uint32_t ui32Interface, uint32_t ui32Channel,
                      uint32_t ui32Value)
{
    tUSBRequest sSetupPacket;

    //
    // This is a Class specific Interface OUT request.
    //
    sSetupPacket.bmRequestType = USB_RTYPE_DIR_OUT | USB_RTYPE_CLASS |
                                 USB_RTYPE_INTERFACE;

    //
    // Request is to set the current value.
    //
    sSetupPacket.bRequest = USB_AC_SET_CUR;

    //
    // Request the volume control.
    //
    sSetupPacket.wValue = VOLUME_CONTROL | (ui32Channel & 0xff);

    //
    // Set Volume control ID and interface to 0.
    //
    sSetupPacket.wIndex = psAudioInstance->ui8VolumeID << 8;

    //
    // Only request the space available.
    //
    sSetupPacket.wLength = 2;

    //
    // Put the setup packet in the buffer.
    //
    USBHCDControlTransfer(0, &sSetupPacket, psAudioInstance->psDevice,
            (uint8_t *)&ui32Value, 2,
            psAudioInstance->psDevice->sDeviceDescriptor.bMaxPacketSize0);
}

//*****************************************************************************
//
//! This function is called to determine if an audio format is supported by the
//! connected USB Audio device.
//!
//! \param psAudioInstance is the device instance for this call.
//! \param ui32SampleRate is the sample rate of the audio stream.
//! \param ui32Bits is the number of bits per sample in the audio stream.
//! \param ui32Channels is the number of channels in the audio stream.
//! \param ui32Flags is a set of flags to determine what type of interface to
//!        retrieve.
//!
//! This function is called when an application needs to determine which audio
//! formats are supported by a USB audio device that has been connected.  The
//! \e psAudioInstance value that is used with this call is the value that was
//! returned from the USBHostAudioOpen() function.  This call checks the
//! USB audio device to determine if it can support the values provided in the
//! \e ui32SampleRate, \e ui32Bits, and \e ui32Channels values.  The
//! \e ui32Flags currently only supports either the \b USBH_AUDIO_FORMAT_IN or
//! \b USBH_AUDIO_FORMAT_OUT values that indicates if a request is for an
//! audio input and an audio output.  If the format is supported this
//! function returns zero, and this function returns a non-zero value if the
//! format is not supported.  This function does not set the current output or
//! input format.
//!
//! \return A value of zero indicates the supplied format is supported and
//! a non-zero value indicates that the format is not supported.
//
//*****************************************************************************
uint32_t
USBHostAudioFormatGet(tUSBHostAudioInstance *psAudioInstance,
                      uint32_t ui32SampleRate, uint32_t ui32Bits,
                      uint32_t ui32Channels, uint32_t ui32Flags)
{
    //
    // Look for the requested format.
    //
    if(AudioGetInterface(psAudioInstance, USB_ADF_PCM, ui32SampleRate,
                         ui32Bits >> 3, ui32Channels, ui32Flags) !=
       INVALID_INTERFACE)
    {
        return(0);
    }
    return(1);
}

//*****************************************************************************
//
//! This function is called to set the current sample rate on an audio
//! interface.
//!
//! \param psAudioInstance specifies the device instance for this call.
//! \param ui32SampleRate is the sample rate in Hz.
//! \param ui32Bits is the number of bits per sample.
//! \param ui32Channels is then number of audio channels.
//! \param ui32Flags is a set of flags that determine the access type.
//!
//! This function is called when to set the current audio output or input
//! format for a USB audio device.  The \e psAudioInstance value that is used
//! with this call is the value that was returned from the USBHostAudioOpen()
//! function.  The application can use this call to insure that the audio
//! format is supported and set the format at the same time.  If the
//! application is just checking for supported rates, then it should call the
//! USBHostAudioFormatGet().
//!
//! \note This function must be called before attempting to send or receive
//! audio with the USBHostAudioPlay() or USBHostAudioRecord() functions.
//!
//! \return A non-zero value indicates the supplied format is not supported and
//! a zero value indicates that the format was supported and has been
//! configured.
//
//*****************************************************************************
uint32_t
USBHostAudioFormatSet(tUSBHostAudioInstance *psAudioInstance,
                      uint32_t ui32SampleRate, uint32_t ui32Bits,
                      uint32_t ui32Channels, uint32_t ui32Flags)
{
    uint32_t ui32Interface;

    //
    // Look for the requested format.
    //
    ui32Interface = AudioGetInterface(psAudioInstance, USB_ADF_PCM,
                                      ui32SampleRate, ui32Bits >> 3,
                                      ui32Channels, ui32Flags);

    if(ui32Interface == INVALID_INTERFACE)
    {
        return(1);
    }

    //
    // Determine if this is an input or output request.
    //
    if(ui32Flags & USBH_AUDIO_FORMAT_IN)
    {
        //
        // Get the active interface number and alternate setting for this
        // format.
        //
        psAudioInstance->ui8InInterface =
            (uint8_t)(ui32Interface & INTERFACE_NUM_M);
        psAudioInstance->ui8InAltSetting =
            (uint8_t)((ui32Interface & INTERFACE_ALTSETTING_M) >>
            INTERFACE_ALTSETTING_S);
    }
    else
    {
        //
        // Get the active interface number and alternate setting for this
        // format.
        //
        psAudioInstance->ui8OutInterface =
            (uint8_t)(ui32Interface & INTERFACE_NUM_M);
        psAudioInstance->ui8OutAltSetting =
            (uint8_t)((ui32Interface & INTERFACE_ALTSETTING_M) >>
            INTERFACE_ALTSETTING_S);
    }
    return(0);
}

//*****************************************************************************
//
//! This function is called to send an audio buffer to the USB audio device.
//!
//! \param psAudioInstance specifies the device instance for this call.
//! \param pvBuffer is the audio buffer to send.
//! \param ui32Size is the size of the buffer in bytes.
//! \param pfnCallback is a pointer to a callback function that is called
//! when the buffer can be used again.
//!
//! This function is called when an application needs to schedule a new buffer
//! for output to the USB audio device.  Since this call schedules the transfer
//! and returns immediately, the application should provide a \e pfnCallback
//! function to be notified when the buffer can be used again by the
//! application.  The \e pfnCallback function provided is called with the
//! \e pvBuffer parameter set to the \e pvBuffer provided by this call, the
//! \e ui32Param can be ignored and the \e ui32Event parameter is
//! \b USB_EVENT_TX_COMPLETE.
//!
//! \return This function returns the number of bytes that were scheduled
//! to be sent.  If this function returns zero then there was no USB audio
//! device present or the request could not be satisfied at this time.
//
//*****************************************************************************
int32_t
USBHostAudioPlay(tUSBHostAudioInstance *psAudioInstance, void *pvBuffer,
                 uint32_t ui32Size, tUSBHostAudioCallback pfnCallback)
{
    uint32_t ui32Bytes;

    //
    // Make sure that there is a device present.
    //
    if(psAudioInstance->psDevice == 0)
    {
        return(0);
    }

    //
    // If the audio output interface is not active then select the current
    // active audio interface.
    //
    if(HWREGBITW(&psAudioInstance->ui32Flags, AUDIO_FLAG_OUT_ACTIVE) == 0)
    {
        //
        // Indicate the active audio interface has been selected.
        //
        HWREGBITW(&psAudioInstance->ui32Flags, AUDIO_FLAG_OUT_ACTIVE) = 1;

        //
        // Configure the USB audio device to use the selected audio interface.
        //
        USBHCDSetInterface(0, (uint32_t)psAudioInstance->psDevice,
                           psAudioInstance->ui8OutInterface,
                           psAudioInstance->ui8OutAltSetting);
    }

    //
    // Save the callback function and the buffer pointer.
    //
    psAudioInstance->pfnOutCallback = pfnCallback;
    psAudioInstance->pvOutBuffer = (void *)pvBuffer;

    //
    // Schedule the data to be written out to the FIFO.
    //
    ui32Bytes = USBHCDPipeSchedule(psAudioInstance->ui32IsochOutPipe, pvBuffer,
                                   ui32Size);

    //
    // Return the number of bytes scheduled to be sent.
    //
    return(ui32Bytes);
}

//*****************************************************************************
//
//! This function is called to provide an audio buffer to the USB audio device
//! for audio input.
//!
//! \param psAudioInstance specifies the device instance for this call.
//! \param pvBuffer is the audio buffer to send.
//! \param ui32Size is the size of the buffer in bytes.
//! \param pfnCallback is a pointer to a callback function that is called
//! when the buffer has been filled.
//!
//! This function is called when an application needs to schedule a new buffer
//! for input from the USB audio device.  Since this call schedules the
//! transfer and returns immediately, the application should provide a
//! \e pfnCallback function to be notified when the buffer has been filled with
//! audio data.  When the \e pfnCallback function is called, the \e pvBuffer
//! parameter is set to \e pvBuffer provided in this call, the \e ui32Param is
//! the number of valid bytes in the pvBuffer and the \e ui32Event is set to
//! \b USB_EVENT_RX_AVAILABLE.
//!
//! \return This function returns the number of bytes that were scheduled
//! to be sent.  If this function returns zero then there was no USB audio
//! device present or the device does not support audio input.
//
//*****************************************************************************
int32_t
USBHostAudioRecord(tUSBHostAudioInstance *psAudioInstance, void *pvBuffer,
                   uint32_t ui32Size, tUSBHostAudioCallback pfnCallback)
{
    uint32_t ui32Bytes;

    //
    // Make sure that there is a device present.
    //
    if(psAudioInstance->psDevice == 0)
    {
        return(0);
    }

    //
    // If the audio input interface is not active then select the current
    // active audio interface.
    //
    if(HWREGBITW(&psAudioInstance->ui32Flags, AUDIO_FLAG_IN_ACTIVE) == 0)
    {
        //
        // Indicate the active audio interface has been selected.
        //
        HWREGBITW(&psAudioInstance->ui32Flags, AUDIO_FLAG_IN_ACTIVE) = 1;

        //
        // Configure the USB audio device to use the selected audio interface.
        //
        USBHCDSetInterface(0, (uint32_t)psAudioInstance->psDevice,
                        psAudioInstance->ui8InInterface,
                        psAudioInstance->ui8InAltSetting);
    }

    //
    // Save the callback function and the buffer pointer.
    //
    psAudioInstance->pfnInCallback = pfnCallback;
    psAudioInstance->pvInBuffer = (void *)pvBuffer;

    //
    // Schedule the data to be read from the FIFO.
    //
    ui32Bytes = USBHCDPipeSchedule(psAudioInstance->ui32IsochInPipe, pvBuffer,
                                   ui32Size);

    //
    // Return the number of bytes scheduled to be sent.
    //
    return(ui32Bytes);
}

//*****************************************************************************
//
//! This function forwards an LPM request for a device to enter L1 sleep state.
//!
//! \param psAudioInstance is the audio device instance that was returned
//! from the call to USBHostAudioOpen().
//!
//! This function forwards a request from an application to the audio 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:
//! - \b USBHCD_LPM_AVAIL - The transition to L1 state is scheduled to be sent.
//! - \b USBHCD_LPM_PENDING - There is already an LPM request pending.
//
//*****************************************************************************
uint32_t
USBHostAudioLPMSleep(tUSBHostAudioInstance *psAudioInstance)
{
    //
    // Call the host controller function to send the sleep command.
    //
    return(USBHCDLPMSleep(psAudioInstance->psDevice));
}

//*****************************************************************************
//
//! This function returns the current status of an LPM request.
//!
//! \param psAudioInstance is the audio device instance that was returned
//! from the call to USBHostAudioOpen().
//!
//! 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
USBHostAudioLPMStatus(tUSBHostAudioInstance *psAudioInstance)
{
    //
    // Call the host controller function to get the current LPM status.
    //
    return(USBHCDLPMStatus(psAudioInstance->psDevice));
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************

