/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "usb_host_config.h"
#if ((defined USB_HOST_CONFIG_AUDIO) && (USB_HOST_CONFIG_AUDIO))
#include "usb_host.h"
#include "usb_host_audio.h"

/*******************************************************************************
 * Variables
 ******************************************************************************/

/* usb audio feature uint command request declaration */
static usb_audio_request_t s_usbAudioFuRequests[NUMBER_OF_FEATURE_COMMANDS] = {
    /* Mute command request  */
    {AUDIO_FU_MUTE_MASK, ITF_REQUEST, CUR_REQUEST, AUDIO_FU_MUTE, 1U},
    /* Volume command request  */
    {AUDIO_FU_VOLUME_MASK, ITF_REQUEST, CUR_REQUEST, AUDIO_FU_VOLUME, 2U},
    {AUDIO_FU_VOLUME_MASK, ITF_REQUEST, MIN_REQUEST, AUDIO_FU_VOLUME, 2U},
    {AUDIO_FU_VOLUME_MASK, ITF_REQUEST, MAX_REQUEST, AUDIO_FU_VOLUME, 2U},
    {AUDIO_FU_VOLUME_MASK, ITF_REQUEST, RES_REQUEST, AUDIO_FU_VOLUME, 2U},
};
/* USB audio endpoint command declaration */
static usb_audio_request_t s_usbAudioEpRequests[NUMBER_OF_ENDPOINT_COMMANDS] = {
    /* USB audio Pitch command request  */
    {AUDIO_PITCH_MASK, EP_REQUEST, CUR_REQUEST, AUDIO_PITCH_CONTROL, 1U},

    /* USB audio Sampling frequency command request  */
    {AUDIO_SAMPLING_FREQ_MASK, EP_REQUEST, CUR_REQUEST, AUDIO_SAMPLING_FREQ_CONTROL, 3U},
    {AUDIO_SAMPLING_FREQ_MASK, EP_REQUEST, MIN_REQUEST, AUDIO_SAMPLING_FREQ_CONTROL, 3U},
    {AUDIO_SAMPLING_FREQ_MASK, EP_REQUEST, MAX_REQUEST, AUDIO_SAMPLING_FREQ_CONTROL, 3U},
    {AUDIO_SAMPLING_FREQ_MASK, EP_REQUEST, RES_REQUEST, AUDIO_SAMPLING_FREQ_CONTROL, 3U},
};

/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief initialize the audio instance.
 *
 * This function allocate the resource for audio instance.
 *
 * @param deviceHandle       The device handle.
 * @param classHandlePtr return class handle.
 *
 * @retval kStatus_USB_Success        The device is initialized successfully.
 * @retval kStatus_USB_AllocFail      Allocate memory fail.
 */
usb_status_t USB_HostAudioInit(usb_device_handle deviceHandle, usb_host_class_handle *classHandlePtr)
{
    audio_instance_t *audioPtr = (audio_instance_t *)USB_OsaMemoryAllocate(sizeof(audio_instance_t));
    uint32_t info_value;

    if (audioPtr == NULL)
    {
        return kStatus_USB_AllocFail;
    }

    audioPtr->deviceHandle = deviceHandle;
    USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetHostHandle, &info_value);
    audioPtr->hostHandle = (usb_host_handle)info_value;
    USB_HostHelperGetPeripheralInformation(deviceHandle, kUSB_HostGetDeviceControlPipe, &info_value);
    audioPtr->controlPipe = (usb_host_pipe_handle)info_value;

    *classHandlePtr = audioPtr;
    return kStatus_USB_Success;
}

/*!
 * @brief de-initialize the audio instance.
 *
 * This function release the resource for audio instance.
 *
 * @param deviceHandle   the device handle.
 * @param classHandle the class handle.
 *
 * @retval kStatus_USB_Success        The device is de-initialized successfully.
 */
usb_status_t USB_HostAudioDeinit(usb_device_handle deviceHandle, usb_host_class_handle classHandle)
{
    usb_status_t status;
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;

    if (deviceHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    if (classHandle != NULL)
    {
        if (audioPtr->isoInPipe != NULL)
        {
            status = USB_HostCancelTransfer(audioPtr->hostHandle, audioPtr->isoInPipe, NULL);
            status = USB_HostClosePipe(audioPtr->hostHandle, audioPtr->isoInPipe);

            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when close pipe\r\n");
#endif
            }
            audioPtr->isoInPipe = NULL;
        }
        if (audioPtr->isoOutPipe != NULL)
        {
            status = USB_HostCancelTransfer(audioPtr->hostHandle, audioPtr->isoOutPipe, NULL);
            status = USB_HostClosePipe(audioPtr->hostHandle, audioPtr->isoOutPipe);

            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when close pipe\r\n");
#endif
            }
            audioPtr->isoOutPipe = NULL;
        }
        USB_HostCloseDeviceInterface(deviceHandle, audioPtr->streamIntfHandle);

        if ((audioPtr->controlPipe != NULL) && (audioPtr->controlTransfer != NULL))
        {
            status = USB_HostCancelTransfer(audioPtr->hostHandle, audioPtr->controlPipe, audioPtr->controlTransfer);
        }
        USB_HostCloseDeviceInterface(deviceHandle, audioPtr->controlIntfHandle);
        USB_OsaMemoryFree(audioPtr);
    }
    else
    {
        USB_HostCloseDeviceInterface(deviceHandle, NULL);
    }

    return kStatus_USB_Success;
}

/*!
 * @brief audiostream iso in pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer     callback transfer.
 * @param status        transfer status.
 */
static void _USB_HostAudioStreamIsoInPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    audio_instance_t *audioPtr = (audio_instance_t *)param;

    if (audioPtr->inCallbackFn != NULL)
    {
        audioPtr->inCallbackFn(audioPtr->inCallbackParam, transfer->transferBuffer, transfer->transferSofar, status);
    }
    USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
}

/*!
 * @brief audiostream iso out pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer     callback transfer.
 * @param status        transfer status.
 */
static void _USB_HostAudioStreamIsoOutPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    audio_instance_t *audioPtr = (audio_instance_t *)param;

    if (audioPtr->outCallbackFn != NULL)
    {
        audioPtr->outCallbackFn(audioPtr->outCallbackParam, transfer->transferBuffer, transfer->transferSofar, status);
    }
    USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
}

/*!
 * @brief audiocontrol pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer     callback transfer.
 * @param status        transfer status.
 */
static void _USB_HostAudioControlCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    audio_instance_t *audioPtr = (audio_instance_t *)param;

    audioPtr->controlTransfer = NULL;
    if (audioPtr->controlCallbackFn != NULL)
    {
        audioPtr->controlCallbackFn(audioPtr->controlCallbackParam, transfer->transferBuffer, transfer->transferSofar,
                                    status);
    }
    USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
    audioPtr->isSetup = 0U;
}

/*!
 * @brief hid send control transfer common code.
 *
 * @param classHandle   the class handle.
 * @param typeRequest   setup packet request type.
 * @param request           setup packet request value.
 * @param wvalue            setup packet wvalue value.
 * @param windex            setup packet index value.
 * @param wlength           setup packet wlength value.
 * @param data                data buffer pointer will be transfer.
 * @param callbackFn       this callback is called after this function completes.
 * @param callbackParam the first parameter in the callback function.
 *
 * @return An error code or kStatus_USB_Success.
 */
static usb_status_t _USB_HostAudioControl(usb_host_class_handle classHandle,
                                          uint8_t typeRequest,
                                          uint8_t request,
                                          uint16_t wvalue,
                                          uint16_t windex,
                                          uint16_t wlength,
                                          uint8_t *data,
                                          transfer_callback_t callbackFn,
                                          void *callbackParam)
{
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    if (USB_HostMallocTransfer(audioPtr->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Error;
    }
    audioPtr->controlCallbackFn = callbackFn;
    audioPtr->controlCallbackParam = callbackParam;

    transfer->transferBuffer = data;
    transfer->transferLength = wlength;
    transfer->callbackFn = _USB_HostAudioControlCallback;
    transfer->callbackParam = audioPtr;
    transfer->setupPacket.bmRequestType = typeRequest;
    transfer->setupPacket.bRequest = request;
    transfer->setupPacket.wValue = USB_SHORT_TO_LITTLE_ENDIAN(wvalue);
    transfer->setupPacket.wIndex = USB_SHORT_TO_LITTLE_ENDIAN(windex);

    transfer->setupPacket.wLength = USB_SHORT_TO_LITTLE_ENDIAN(wlength);
    audioPtr->isSetup = 1;

    if (USB_HostSendSetup(audioPtr->hostHandle, audioPtr->controlPipe, transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("failt for USB_HostSendSetup\r\n");
#endif
        USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
        return kStatus_USB_Error;
    }
    audioPtr->controlTransfer = transfer;

    return kStatus_USB_Success;
}

/*!
 * @brief audio open interface.
 *
 * @param audioPtr     audio instance pointer.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t _USB_HostAudioOpenInterface(audio_instance_t *audioPtr)
{
    usb_status_t status;
    uint8_t ep_index = 0U;
    usb_host_pipe_init_t pipe_init;
    usb_descriptor_endpoint_t *ep_desc = NULL;
    usb_host_interface_t *interface_ptr;

    if (audioPtr->isoInPipe != NULL)
    {
        status = USB_HostClosePipe(audioPtr->hostHandle, audioPtr->isoInPipe);

        if (status != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when close pipe\r\n");
#endif
        }
        audioPtr->isoInPipe = NULL;
    }
    if (audioPtr->isoOutPipe != NULL)
    {
        status = USB_HostClosePipe(audioPtr->hostHandle, audioPtr->isoOutPipe);

        if (status != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when close pipe\r\n");
#endif
        }
        audioPtr->isoOutPipe = NULL;
    }

    /* open interface pipes */
    interface_ptr = (usb_host_interface_t *)audioPtr->streamIntfHandle;
    for (ep_index = 0U; ep_index < interface_ptr->epCount; ++ep_index)
    {
        ep_desc = interface_ptr->epList[ep_index].epDesc;
        if (((ep_desc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_MASK) ==
             USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_IN) &&
            ((ep_desc->bmAttributes & USB_DESCRIPTOR_ENDPOINT_ATTRIBUTE_TYPE_MASK) == USB_ENDPOINT_ISOCHRONOUS))
        {
            pipe_init.devInstance = audioPtr->deviceHandle;
            pipe_init.pipeType = USB_ENDPOINT_ISOCHRONOUS;
            pipe_init.direction = USB_IN;
            pipe_init.endpointAddress = (ep_desc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_NUMBER_MASK);
            pipe_init.interval = ep_desc->bInterval;
            pipe_init.maxPacketSize = (uint16_t)(USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(ep_desc->wMaxPacketSize) &
                                                 USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_SIZE_MASK);
            pipe_init.numberPerUframe = (USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(ep_desc->wMaxPacketSize) &
                                         USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_MASK);
            pipe_init.nakCount = USB_HOST_CONFIG_MAX_NAK;

            audioPtr->inPacketSize = pipe_init.maxPacketSize;
            audioPtr->isoEpNum = pipe_init.endpointAddress;
            status = USB_HostOpenPipe(audioPtr->hostHandle, &audioPtr->isoInPipe, &pipe_init);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("usb_host_audio_stream_set_interface fail to open pipe\r\n");
#endif
                return kStatus_USB_Error;
            }
        }
        else if (((ep_desc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_MASK) ==
                  USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_OUT) &&
                 ((ep_desc->bmAttributes & USB_DESCRIPTOR_ENDPOINT_ATTRIBUTE_TYPE_MASK) == USB_ENDPOINT_ISOCHRONOUS))
        {
            pipe_init.devInstance = audioPtr->deviceHandle;
            pipe_init.pipeType = USB_ENDPOINT_ISOCHRONOUS;
            pipe_init.direction = USB_OUT;
            pipe_init.endpointAddress = (ep_desc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_NUMBER_MASK);
            pipe_init.interval = ep_desc->bInterval;
            pipe_init.maxPacketSize = (uint16_t)(USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(ep_desc->wMaxPacketSize) &
                                                 USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_SIZE_MASK);
            pipe_init.numberPerUframe = (USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(ep_desc->wMaxPacketSize) &
                                         USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_MASK);
            pipe_init.nakCount = USB_HOST_CONFIG_MAX_NAK;

            audioPtr->outPacketSize = pipe_init.maxPacketSize;
            audioPtr->isoEpNum = pipe_init.endpointAddress;
            status = USB_HostOpenPipe(audioPtr->hostHandle, &audioPtr->isoOutPipe, &pipe_init);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("usb_host_audio_stream_set_interface fail to open pipe\r\n");
#endif
                return kStatus_USB_Error;
            }
        }
        else
        {
             return kStatus_USB_Error;
        }
    }

    return kStatus_USB_Success;
}

/*!
 * @brief audio set interface callback, open pipes.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void _USB_HostAudioSetInterfaceCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    audio_instance_t *audioPtr = (audio_instance_t *)param;

    audioPtr->controlTransfer = NULL;
    if (status == kStatus_USB_Success)
    {
        status = _USB_HostAudioOpenInterface(audioPtr);
    }

    if (audioPtr->controlCallbackFn != NULL)
    {
        audioPtr->controlCallbackFn(audioPtr->controlCallbackParam, NULL, 0U, status);
    }
    USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
}

/*!
 * @brief set audioclass stream interface.
 *
 * This function bind the interface with the audio instance.
 *
 * @param classHandle        The class handle.
 * @param interfaceHandle          The interface handle.
 * @param alternateSetting  The alternate setting value.
 * @param callbackFn          This callback is called after this function completes.
 * @param callbackParam    The first parameter in the callback function.
 *
 * @retval kStatus_USB_Success         The device is initialized successfully.
 * @retval kStatus_USB_InvalidHandle  The classHandle is NULL pointer.
 * @retval kStatus_USB_Busy           There is no idle transfer.
 * @retval kStatus_USB_Error          send transfer fail, please reference to USB_HostSendSetup.
 * @retval kStatus_USB_Busy           callback return status, there is no idle pipe.
 * @retval kStatus_USB_TransferStall  callback return status, the transfer is stall by device.
 * @retval kStatus_USB_Error          callback return status, open pipe fail, please reference to USB_HostOpenPipe.
 */
usb_status_t USB_HostAudioStreamSetInterface(usb_host_class_handle classHandle,
                                             usb_host_interface_handle interfaceHandle,
                                             uint8_t alternateSetting,
                                             transfer_callback_t callbackFn,
                                             void *callbackParam)
{
    usb_status_t status;
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;
    usb_host_interface_t *interface_ptr;
    usb_host_transfer_t *transfer;
    audio_descriptor_union_t ptr1;
    uint32_t length = 0U, ep = 0U;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidParameter;
    }

    audioPtr->streamIntfHandle = interfaceHandle;

    status = USB_HostOpenDeviceInterface(audioPtr->deviceHandle, interfaceHandle);

    if (audioPtr->isoInPipe != NULL)
    {
        status = USB_HostCancelTransfer(audioPtr->hostHandle, audioPtr->isoInPipe, NULL);

        if (status != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when cancel pipe\r\n");
#endif
        }
    }

    if (audioPtr->isoOutPipe != NULL)
    {
        status = USB_HostCancelTransfer(audioPtr->hostHandle, audioPtr->isoOutPipe, NULL);

        if (status != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when cancel pipe\r\n");
#endif
        }
    }
    /* open interface pipes */
    interface_ptr = (usb_host_interface_t *)interfaceHandle;
    ptr1.bufr = interface_ptr->interfaceExtension;

    length = 0U;
    while (length < interface_ptr->interfaceExtensionLength)
    {
        if ((ptr1.common->bDescriptorType == 0x04U) && (ptr1.interface->bAlternateSetting == alternateSetting))
        {
            interface_ptr->epCount = ptr1.interface->bNumEndpoints;
            break;
        }
        ptr1.bufr += ptr1.common->bLength;
        length += ptr1.common->bLength;
    }
    while (ep < interface_ptr->epCount)
    {
        if (ptr1.common->bDescriptorType == 0x24U)
        {
            if (ptr1.common->bData[0] == USB_DESC_SUBTYPE_AS_CS_GENERAL)
            {
                audioPtr->asIntfDesc = (usb_audio_stream_spepific_as_intf_desc_t *)ptr1.bufr;
            }
            else if (ptr1.common->bData[0] == USB_DESC_SUBTYPE_AS_CS_FORMAT_TYPE)
            {
                audioPtr->formatTypeDesc = (usb_audio_stream_format_type_desc_t *)ptr1.bufr;
            }
            else
            {
            }
        }
        if (ptr1.common->bDescriptorType == 0x05U)
        {
            interface_ptr->epList[ep].epDesc = (usb_descriptor_endpoint_t *)ptr1.bufr;
            audioPtr->isoEndpDesc = (usb_audio_stream_specific_iso_endp_desc_t *)ptr1.bufr;
            ep++;
            ptr1.bufr += ptr1.common->bLength;
            interface_ptr->epList[ep].epExtension = ptr1.bufr;
            interface_ptr->epList[ep].epExtensionLength = ptr1.common->bLength;
            break;
        }
        ptr1.bufr += ptr1.common->bLength;
    }

    if (alternateSetting == 0U)
    {
        if (callbackFn != NULL)
        {
            status = _USB_HostAudioOpenInterface(audioPtr);
            callbackFn(callbackParam, NULL, 0U, kStatus_USB_Success);
        }
    }
    else
    {
        if (USB_HostMallocTransfer(audioPtr->hostHandle, &transfer) != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error to get transfer\r\n");
#endif
            return kStatus_USB_Error;
        }
        audioPtr->controlCallbackFn = callbackFn;
        audioPtr->controlCallbackParam = callbackParam;
        /* initialize transfer */
        transfer->callbackFn = _USB_HostAudioSetInterfaceCallback;
        transfer->callbackParam = audioPtr;
        transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_SET_INTERFACE;
        transfer->setupPacket.bmRequestType = USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
        transfer->setupPacket.wIndex = USB_SHORT_TO_LITTLE_ENDIAN(
            ((usb_host_interface_t *)audioPtr->streamIntfHandle)->interfaceDesc->bInterfaceNumber);
        transfer->setupPacket.wValue = USB_SHORT_TO_LITTLE_ENDIAN(alternateSetting);
        transfer->setupPacket.wLength = 0;
        transfer->transferBuffer = NULL;
        transfer->transferLength = 0;
        status = USB_HostSendSetup(audioPtr->hostHandle, audioPtr->controlPipe, transfer);

        if (status == kStatus_USB_Success)
        {
            audioPtr->controlTransfer = transfer;
        }
        else
        {
            USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
        }
    }

    return status;
}

/*!
 * @brief set audioclass control interface.
 *
 * This function bind the interface with the audio instance.
 *
 * @param classHandle        The class handle.
 * @param interfaceHandle          The interface handle.
 * @param alternateSetting  The alternate setting value.
 * @param callbackFn          This callback is called after this function completes.
 * @param callbackParam    The first parameter in the callback function.
 *
 * @retval kStatus_USB_Success         The device is initialized successfully.
 * @retval kStatus_USB_InvalidHandle  The classHandle is NULL pointer.
 * @retval kStatus_USB_Busy           There is no idle transfer.
 * @retval kStatus_USB_Error          send transfer fail, please reference to USB_HostSendSetup.
 * @retval kStatus_USB_Busy           callback return status, there is no idle pipe.
 * @retval kStatus_USB_TransferStall  callback return status, the transfer is stall by device.
 * @retval kStatus_USB_Error          callback return status, open pipe fail, please reference to USB_HostOpenPipe.
 */
usb_status_t USB_HostAudioControlSetInterface(usb_host_class_handle classHandle,
                                              usb_host_interface_handle interfaceHandle,
                                              uint8_t alternateSetting,
                                              transfer_callback_t callbackFn,
                                              void *callbackParam)
{
    usb_status_t status;
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;
    usb_host_interface_t *interface_ptr;
    usb_host_transfer_t *transfer;
    audio_descriptor_union_t ptr1;
    uint32_t length = 0U;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidParameter;
    }
    audioPtr->controlIntfHandle = interfaceHandle;
    interface_ptr = (usb_host_interface_t *)interfaceHandle;

    status = USB_HostOpenDeviceInterface(audioPtr->deviceHandle, interfaceHandle);
    ptr1.bufr = interface_ptr->interfaceExtension;

    length = 0U;

    while (length < interface_ptr->interfaceExtensionLength)
    {
        if (((interface_ptr->interfaceDesc->bDescriptorType == 0x04U) &&
             (interface_ptr->interfaceDesc->bAlternateSetting == alternateSetting)) ||
            ((ptr1.common->bDescriptorType == 0x04U) && (ptr1.interface->bAlternateSetting == alternateSetting)))
        {
            break;
        }

        ptr1.bufr += ptr1.common->bLength;
        length += ptr1.common->bLength;
    }
    while (length < interface_ptr->interfaceExtensionLength)
    {
        if (ptr1.common->bDescriptorType == 0x24U)
        {
            if (ptr1.common->bData[0] == USB_DESC_SUBTYPE_AUDIO_CS_HEADER)
            {
                audioPtr->headerDesc = (usb_audio_ctrl_header_desc_t *)ptr1.bufr;
            }
            else if (ptr1.common->bData[0] == USB_DESC_SUBTYPE_AUDIO_CS_IT)
            {
                audioPtr->itDesc = (usb_audio_ctrl_it_desc_t *)ptr1.bufr;
            }
            else if (ptr1.common->bData[0] == USB_DESC_SUBTYPE_AUDIO_CS_OT)
            {
                audioPtr->otDesc = (usb_audio_ctrl_ot_desc_t *)ptr1.bufr;
            }
            else if (ptr1.common->bData[0] == USB_DESC_SUBTYPE_AUDIO_CS_FU)
            {
                audioPtr->fuDesc = (usb_audio_ctrl_fu_desc_t *)ptr1.bufr;
            }
            else
            {
            }
        }
        ptr1.bufr += ptr1.common->bLength;
        length += ptr1.common->bLength;
    }

    if (alternateSetting == 0U)
    {
        if (callbackFn != NULL)
        {
            callbackFn(callbackParam, NULL, 0U, kStatus_USB_Success);
        }
    }
    else
    {
        if (USB_HostMallocTransfer(audioPtr->hostHandle, &transfer) != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error to get transfer\r\n");
#endif
            return kStatus_USB_Error;
        }
        audioPtr->controlCallbackFn = callbackFn;
        audioPtr->controlCallbackParam = callbackParam;
        /* initialize transfer */
        transfer->callbackFn = _USB_HostAudioControlCallback;
        transfer->callbackParam = audioPtr;
        transfer->setupPacket.bRequest = USB_REQUSET_STANDARD_SET_INTERFACE;
        transfer->setupPacket.bmRequestType = USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
        transfer->setupPacket.wIndex = USB_SHORT_TO_LITTLE_ENDIAN(
            ((usb_host_interface_t *)audioPtr->controlIntfHandle)->interfaceDesc->bInterfaceNumber);
        transfer->setupPacket.wValue = USB_SHORT_TO_LITTLE_ENDIAN(alternateSetting);
        transfer->setupPacket.wLength = 0;
        transfer->transferBuffer = NULL;
        transfer->transferLength = 0;
        status = USB_HostSendSetup(audioPtr->hostHandle, audioPtr->controlPipe, transfer);

        if (status == kStatus_USB_Success)
        {
            audioPtr->controlTransfer = transfer;
        }
        else
        {
            USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
        }
    }

    return status;
}

/*!
 * @brief get pipe max packet size.
 *
 * @param classHandle the class handle.
 * @param pipeType    It's value is USB_ENDPOINT_CONTROL, USB_ENDPOINT_ISOCHRONOUS, USB_ENDPOINT_BULK or
 * USB_ENDPOINT_INTERRUPT.
 *                     Please reference to usb_spec.h
 * @param direction    pipe direction.
 *
 * @retval 0        The classHandle is NULL.
 * @retval max packet size.
 */
uint16_t USB_HostAudioPacketSize(usb_host_class_handle classHandle, uint8_t pipeType, uint8_t direction)
{
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;
    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    if (pipeType == USB_ENDPOINT_ISOCHRONOUS)
    {
        if (direction == USB_IN)
        {
            return audioPtr->inPacketSize;
        }
        else
        {
            return audioPtr->outPacketSize;
        }
    }
    return 0U;
}

/*!
 * @brief audio stream receive data.
 *
 * This function implements audioreceiving data.
 *
 * @param classHandle      The class handle.
 * @param buffer                The buffer pointer.
 * @param bufferLen          The buffer length.
 * @param callbackFn         This callback is called after this function completes.
 * @param callbackParam  The first parameter in the callback function.
 *
 * @retval kStatus_USB_Success          Receive request successfully.
 * @retval kStatus_USB_InvalidHandle  The classHandle is NULL pointer.
 * @retval kStatus_USB_Busy               There is no idle transfer.
 * @retval kStatus_USB_Error              pipe is not initialized.
 *                                                       Or, send transfer fail, please reference to USB_HostRecv.
 */
usb_status_t USB_HostAudioStreamRecv(usb_host_class_handle classHandle,
                                     uint8_t *buffer,
                                     uint32_t bufferLen,
                                     transfer_callback_t callbackFn,
                                     void *callbackParam)
{
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    if (audioPtr->isoInPipe == NULL)
    {
        return kStatus_USB_Error;
    }

    if (USB_HostMallocTransfer(audioPtr->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Error;
    }
    audioPtr->inCallbackFn = callbackFn;
    audioPtr->inCallbackParam = callbackParam;
    transfer->transferBuffer = buffer;
    transfer->transferLength = bufferLen;
    transfer->callbackFn = _USB_HostAudioStreamIsoInPipeCallback;
    transfer->callbackParam = audioPtr;

    if (USB_HostRecv(audioPtr->hostHandle, audioPtr->isoInPipe, transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("failt to USB_HostRecv\r\n");
#endif
        USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
        return kStatus_USB_Error;
    }

    return kStatus_USB_Success;
}

/*!
 * @brief audio stream send data.
 *
 * This function implements audio sending data.
 *
 * @param classHandle      The class handle.
 * @param buffer                The buffer pointer.
 * @param bufferLen          The buffer length.
 * @param callbackFn         This callback is called after this function completes.
 * @param callbackParam  The first parameter in the callback function.
 *
 * @retval kStatus_USB_Success          Receive request successfully.
 * @retval kStatus_USB_InvalidHandle  The classHandle is NULL pointer.
 * @retval kStatus_USB_Busy               There is no idle transfer.
 * @retval kStatus_USB_Error              pipe is not initialized.
 *                                                       Or, send transfer fail, please reference to USB_HostSend.
 */
usb_status_t USB_HostAudioStreamSend(usb_host_class_handle classHandle,
                                     uint8_t *buffer,
                                     uint32_t bufferLen,
                                     transfer_callback_t callbackFn,
                                     void *callbackParam)
{
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    if (audioPtr->isoOutPipe == NULL)
    {
        return kStatus_USB_Error;
    }

    if (USB_HostMallocTransfer(audioPtr->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Error;
    }
    audioPtr->outCallbackFn = callbackFn;
    audioPtr->outCallbackParam = callbackParam;
    transfer->transferBuffer = buffer;
    transfer->transferLength = bufferLen;
    transfer->callbackFn = _USB_HostAudioStreamIsoOutPipeCallback;
    transfer->callbackParam = audioPtr;

    if (USB_HostSend(audioPtr->hostHandle, audioPtr->isoOutPipe, transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("failt to USB_HostSend\r\n");
#endif
        USB_HostFreeTransfer(audioPtr->hostHandle, transfer);
        return kStatus_USB_Error;
    }

    return kStatus_USB_Success;
}

/*!
 * @brief get audio stream current altsetting descriptor.
 *
 * This function implements get audio stream current altsetting descriptor.
 *
 * @param classHandle               The class handle.
 * @param asIntfDesc                 The pointer of class specific AS interface descriptor.
 * @param formatTypeDesc       The pointer of format type descriptor.
 * @param isoEndpDesc    The pointer of specific iso endp descriptor.
 *
 * @retval kStatus_USB_Success          Get audio stream current altsetting descriptor request successfully.
 * @retval kStatus_USB_InvalidHandle  The classHandle is NULL pointer.
 *
 */
usb_status_t USB_HostAudioStreamGetCurrentAltsettingDescriptors(

    usb_host_class_handle classHandle,
    usb_audio_stream_spepific_as_intf_desc_t **asIntfDesc,
    usb_audio_stream_format_type_desc_t **formatTypeDesc,
    usb_audio_stream_specific_iso_endp_desc_t **isoEndpDesc)
{
    audio_instance_t *audioPtr = (audio_instance_t *)classHandle;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }
    *asIntfDesc = audioPtr->asIntfDesc;
    *formatTypeDesc = audioPtr->formatTypeDesc;
    *isoEndpDesc = audioPtr->isoEndpDesc;

    return kStatus_USB_Success;
}

/*!
 * @brief usb audio feature unit request.
 *
 * This function implements usb audio feature unit request.
 *
 * @param classHandle      The class handle.
 * @param channelNo        The channel number of audio feature unit.
 * @param buf                    The feature unit request buffer pointer.
 * @param cmdCode          The feature unit command code, for example USB_AUDIO_GET_CUR_MUTE etc.
 * @param callbackFn         This callback is called after this function completes.
 * @param callbackParam  The first parameter in the callback function.
 *
 * @retval kStatus_USB_Success            Feature unit request successfully.
 * @retval kStatus_USB_InvalidHandle    The classHandle is NULL pointer.
 * @retval kStatus_USB_Busy                There is no idle transfer.
 * @retval kStatus_USB_Error                Send transfer fail, please reference to USB_HostSendSetup.
 *
 */
usb_status_t USB_HostAudioFeatureUnitRequest(usb_host_class_handle classHandle,
                                             uint8_t channelNo,
                                             void *buf,
                                             uint32_t cmdCode,
                                             transfer_callback_t callbackFn,
                                             void *callbackParam)
{ /* Body */
    uint16_t windex;
    uint16_t request_value;
    audio_instance_t *if_ptr;
    usb_audio_request_t *p_feature_request;
    uint8_t *bmacontrols = NULL;
    uint8_t atribute_index;
    usb_status_t status = kStatus_USB_Error;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }

    if_ptr = (audio_instance_t *)classHandle;

    /* pointer to command */
    p_feature_request = &(s_usbAudioFuRequests[cmdCode & 0xfU]);
    /* get request value */
    request_value = (uint16_t)((uint16_t)((uint16_t)p_feature_request->requestValue << 8U) | channelNo);

    /* Check whether this attribute valid or not */
    if (if_ptr->fuDesc == NULL)
    {
        return kStatus_USB_Error;
    }
    windex = (uint16_t)((uint16_t)((uint16_t)(if_ptr->fuDesc->bunitid) << 8U) | (if_ptr->streamIfnum));
    atribute_index = if_ptr->fuDesc->bcontrolsize * channelNo;

    if (atribute_index < (if_ptr->fuDesc->blength - 7))
    {
        bmacontrols = &(if_ptr->fuDesc->bcontrolsize);
    }

    if (bmacontrols == NULL)
    {
        return kStatus_USB_Error;
    }

    bmacontrols++;
    if (bmacontrols[atribute_index] & p_feature_request->controlMask)
    {
        status = kStatus_USB_Success;
    }

    if (kStatus_USB_Success == status)
    {
        status = _USB_HostAudioControl(classHandle, (p_feature_request->typeRequest | (cmdCode & 0x80U)),
                                       (p_feature_request->codeRequest | (cmdCode & 0x80U)), request_value, windex,
                                       p_feature_request->length, (uint8_t *)buf, callbackFn, callbackParam);
    }
    return status;
}

/*!
 * @brief usb audio endpoint request.
 *
 * This function implements usb audio endpoint request.
 *
 * @param classHandle      The class handle.
 * @param buf                    The feature unit buffer pointer.
 * @param cmdCode          The feature unit command code, for example USB_AUDIO_GET_CUR_PITCH etc .
 * @param callbackFn         This callback is called after this function completes.
 * @param callbackParam   The first parameter in the callback function.
 *
 * @retval kStatus_USB_Success          Endpoint request successfully.
 * @retval kStatus_USB_InvalidHandle  The classHandle is NULL pointer.
 * @retval kStatus_USB_Busy               There is no idle transfer.
 * @retval kStatus_USB_Error               Send transfer fail, please reference to USB_HostSendSetup.
 *
 */
usb_status_t USB_HostAudioEndpointRequest(
    usb_host_class_handle classHandle, void *buf, uint32_t cmdCode, transfer_callback_t callbackFn, void *callbackParam)
{
    uint8_t endp_num;
    usb_status_t status = kStatus_USB_Error;
    uint16_t request_value;
    usb_audio_request_t *p_endpoint_request;
    audio_instance_t *audioPtr;

    if (classHandle == NULL)
    {
        return kStatus_USB_InvalidHandle;
    }
    audioPtr = (audio_instance_t *)classHandle;

    /* pointer to command */
    p_endpoint_request = &(s_usbAudioEpRequests[cmdCode & 0xfU]);
    /* get request value */
    request_value = (uint16_t)((uint16_t)((uint16_t)p_endpoint_request->requestValue << 8U));

    /* Check whether this attribute valid or not */
    if (audioPtr->isoEndpDesc == NULL)
    {
        return kStatus_USB_Error;
    }
    if ((audioPtr->isoEndpDesc->bmattributes) && (p_endpoint_request->controlMask))
    {
        status = kStatus_USB_Success;
    }
    else
    {
        status = kStatus_USB_InvalidRequest;
    }

    if (kStatus_USB_Success == status)
    {
        /* Any isochronous pipe is supported? */
        if ((NULL == audioPtr->isoInPipe) && (NULL == audioPtr->isoOutPipe))
        {
            return kStatus_USB_InvalidParameter;
        }
        else if (NULL != audioPtr->isoInPipe)
        {
            endp_num = (audioPtr->isoEpNum | 0x80U);
        }
        else
        {
            endp_num = audioPtr->isoEpNum;
        } /* Endif */

        status = _USB_HostAudioControl(classHandle, (p_endpoint_request->typeRequest | (cmdCode & 0x80U)),
                                       (p_endpoint_request->codeRequest | (cmdCode & 0x80U)), request_value, endp_num,
                                       p_endpoint_request->length, (uint8_t *)buf, callbackFn, callbackParam);
    }

    return status;
}
#endif /* USB_HOST_CONFIG_AUDIO */
