/*
 * Copyright 2019 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "usb_host_config.h"
#if ((defined USB_HOST_CONFIG_VIDEO) && (USB_HOST_CONFIG_VIDEO))
#include "usb_host.h"
#include "usb_host_video.h"

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

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*!
 * @brief open video control interface.
 *
 * @param videoInstance     video instance pointer.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostVideoControlOpenInterface(usb_host_video_instance_struct_t *videoInstance);

/*!
 * @brief open video stream interface.
 *
 * @param videoInstance     video instance pointer.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostVideoStreamOpenInterface(usb_host_video_instance_struct_t *videoInstance);

/*!
 * @brief video control pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoSetControlInterfaceCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

/*!
 * @brief video set interface callback, open pipes.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoSetStreamInterfaceCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

/*!
 * @brief video control command transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoControlCommandCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

/*!
 * @brief video stream iso in pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoStreamIsoInPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

/*!
 * @brief video 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_HostVideoControl(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);

/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief open video control interface.
 *
 * @param videoInstance     video instance pointer.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostVideoControlOpenInterface(usb_host_video_instance_struct_t *videoInstance)
{
    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;
    void *temp;
    if (videoInstance->interruptPipe != NULL)
    {
        status = USB_HostClosePipe(videoInstance->hostHandle, videoInstance->interruptPipe);

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

    /* open interface pipes */
    temp          = (void *)videoInstance->controlIntfHandle;
    interface_ptr = (usb_host_interface_t *)temp;
    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_INTERRUPT))
        {
            pipe_init.devInstance     = videoInstance->deviceHandle;
            pipe_init.pipeType        = USB_ENDPOINT_INTERRUPT;
            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 = (uint8_t)((USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(ep_desc->wMaxPacketSize) &
                                                   USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_MASK));
            pipe_init.nakCount        = USB_HOST_CONFIG_MAX_NAK;

            videoInstance->interruptInPacketSize = pipe_init.maxPacketSize;
            videoInstance->interruptInEpNum      = pipe_init.endpointAddress;
            status = USB_HostOpenPipe(videoInstance->hostHandle, &videoInstance->interruptPipe, &pipe_init);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("usb_host_video_control_set_interface fail to open pipe\r\n");
#endif
                return kStatus_USB_Error;
            }
        }
        else
        {
        }
    }

    return kStatus_USB_Success;
}

/*!
 * @brief open video stream interface.
 *
 * @param videoInstance     video instance pointer.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostVideoStreamOpenInterface(usb_host_video_instance_struct_t *videoInstance)
{
    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;
    void *temp;

    if (videoInstance->streamIsoInPipe != NULL)
    {
        status = USB_HostClosePipe(videoInstance->hostHandle, videoInstance->streamIsoInPipe);

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

    /* open interface pipes */
    temp          = (void *)videoInstance->streamIntfHandle;
    interface_ptr = (usb_host_interface_t *)temp;
    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     = videoInstance->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 = (uint8_t)((USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(ep_desc->wMaxPacketSize) &
                                                   USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_MASK) >>
                                                  USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_SHFIT);
            pipe_init.nakCount        = USB_HOST_CONFIG_MAX_NAK;

            videoInstance->isoInPacketSize = pipe_init.maxPacketSize;
            videoInstance->isoInEpNum      = pipe_init.endpointAddress;
            status = USB_HostOpenPipe(videoInstance->hostHandle, &videoInstance->streamIsoInPipe, &pipe_init);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("usb_host_video_stream_set_interface fail to open pipe\r\n");
#endif
                return kStatus_USB_Error;
            }
        }
        else
        {
        }
    }

    return kStatus_USB_Success;
}

/*!
 * @brief video control pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoSetControlInterfaceCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)param;

    videoInstance->controlTransfer = NULL;
    if (status == kStatus_USB_Success)
    {
        status = USB_HostVideoControlOpenInterface(videoInstance);
    }

    if (videoInstance->controlCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in the _USB_HostAudioControl,
        USB_HostVideoStreamSetInterface
        or USB_HostVideoControlSetInterface, but is the same function */
        videoInstance->controlCallbackFn(videoInstance->controlCallbackParam, transfer->transferBuffer,
                                         transfer->transferSofar, status);
    }
    (void)USB_HostFreeTransfer(videoInstance->hostHandle, transfer);
}

/*!
 * @brief video set interface callback, open pipes.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoSetStreamInterfaceCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)param;

    videoInstance->controlTransfer = NULL;
    if (status == kStatus_USB_Success)
    {
        status = USB_HostVideoStreamOpenInterface(videoInstance);
    }

    if (videoInstance->controlCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in the _USB_HostAudioControl,
        USB_HostAudioStreamSetInterface
        or USB_HostAudioControlSetInterface, but is the same function */
        videoInstance->controlCallbackFn(videoInstance->controlCallbackParam, NULL, 0U, status);
    }
    (void)USB_HostFreeTransfer(videoInstance->hostHandle, transfer);
}

/*!
 * @brief video control command transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoControlCommandCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)param;

    if (videoInstance->controlCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in the USB_HostCdcControl,
        USB_HostCdcSetControlInterface
        or USB_HostCdcSetDataInterface, but is the same function */
        videoInstance->controlCallbackFn(videoInstance->controlCallbackParam, transfer->transferBuffer,
                                         transfer->transferSofar, status);
    }
    (void)USB_HostFreeTransfer(videoInstance->hostHandle, transfer);
}

/*!
 * @brief video stream iso in pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostVideoStreamIsoInPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)param;

    if (videoInstance->streamIsoInCallbackFn != NULL)
    {
        /* callback function is initialized in USB_HosVideoStreamRecv */
        videoInstance->streamIsoInCallbackFn(videoInstance->streamIsoInCallbackParam, transfer->transferBuffer,
                                             transfer->transferSofar, status);
    }
    (void)USB_HostFreeTransfer(videoInstance->hostHandle, transfer);
}

/*!
 * @brief video 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_HostVideoControl(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)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_transfer_t *transfer;

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

    /* malloc one transfer */
    if (USB_HostMallocTransfer(videoInstance->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Error;
    }
    /* save the application callback function */
    videoInstance->controlCallbackFn    = callbackFn;
    videoInstance->controlCallbackParam = callbackParam;

    transfer->transferBuffer             = data;
    transfer->transferLength             = wlength;
    transfer->callbackFn                 = USB_HostVideoControlCommandCallback;
    transfer->callbackParam              = videoInstance;
    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);

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

    return kStatus_USB_Success;
}

/*!
 * @brief set video class stream interface.
 *
 * This function bind the interface with the video 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_HostVideoStreamSetInterface(usb_host_class_handle classHandle,
                                             usb_host_interface_handle interfaceHandle,
                                             uint8_t alternateSetting,
                                             transfer_callback_t callbackFn,
                                             void *callbackParam)
{
    usb_status_t status;
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *interface_ptr;
    usb_host_transfer_t *transfer;
    usb_host_video_descriptor_union_t descUnion;
    uint32_t length, ep = 0U;
    uint32_t descLength = 0;
    void *temp;

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

    videoInstance->streamIntfHandle = interfaceHandle;

    status = USB_HostOpenDeviceInterface(videoInstance->deviceHandle, interfaceHandle); /* save the application callback function */
    if (status != kStatus_USB_Success)
    {
        return status;
    }
    
    /* cancel transfers */
    if (videoInstance->streamIsoInPipe != NULL)
    {
        status = USB_HostCancelTransfer(videoInstance->hostHandle, videoInstance->streamIsoInPipe, 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;

    if (0U == alternateSetting)
    {
        descUnion.bufr = interface_ptr->interfaceExtension;
        length         = 0U;
        while (length < interface_ptr->interfaceExtensionLength)
        {
            if (descUnion.common->bDescriptorType == USB_HOST_DESC_CS_INTERFACE)
            {
                if (descUnion.common->bData[0] == USB_HOST_DESC_SUBTYPE_VS_INPUT_HEADER)
                {
                    temp                             = (void *)descUnion.bufr;
                    videoInstance->vsInputHeaderDesc = (usb_host_video_stream_input_header_desc_t *)temp;
                    break;
                }
            }
            length += descUnion.common->bLength;
            descUnion.bufr += descUnion.common->bLength;
        }
    }
    else
    {
        descUnion.bufr = interface_ptr->interfaceExtension;
        length         = 0U;
        while (length < interface_ptr->interfaceExtensionLength)
        {
            if ((descUnion.common->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE) &&
                (descUnion.interface->bAlternateSetting == alternateSetting))
            {
                interface_ptr->epCount = descUnion.interface->bNumEndpoints;
                break;
            }
            length += descUnion.common->bLength;
            descUnion.bufr += descUnion.common->bLength;
        }

        while (ep < interface_ptr->epCount)
        {
            if (descUnion.common->bDescriptorType == USB_DESCRIPTOR_TYPE_ENDPOINT)
            {
                temp                             = (void *)descUnion.bufr;
                interface_ptr->epList[ep].epDesc = (usb_descriptor_endpoint_t *)temp;
                descLength                       = descUnion.common->bLength;
                descUnion.bufr += descUnion.common->bLength;

                if (USB_HOST_DESC_CS_ENDPOINT == descUnion.common->bDescriptorType)
                {
                    interface_ptr->epList[ep].epExtension       = descUnion.bufr;
                    interface_ptr->epList[ep].epExtensionLength = descUnion.common->bLength;
                }
                else
                {
                    descUnion.bufr -= descLength;
                }
                ep++;
            }
            descUnion.bufr += descUnion.common->bLength;
        }
    }

    if (alternateSetting == 0U) /* open interface directly */
    {
        if (callbackFn != NULL)
        {
            status = USB_HostVideoStreamOpenInterface(videoInstance);
            callbackFn(callbackParam, NULL, 0U, kStatus_USB_Success);
        }
    }
    else /* send setup transfer */
    {
        /* malloc one transfer */
        if (USB_HostMallocTransfer(videoInstance->hostHandle, &transfer) != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error to get transfer\r\n");
#endif
            return kStatus_USB_Error;
        }
        /* save the application callback function */
        videoInstance->controlCallbackFn    = callbackFn;
        videoInstance->controlCallbackParam = callbackParam;
        /* initialize transfer */
        transfer->callbackFn                 = USB_HostVideoSetStreamInterfaceCallback;
        transfer->callbackParam              = videoInstance;
        transfer->setupPacket->bRequest      = USB_REQUEST_STANDARD_SET_INTERFACE;
        transfer->setupPacket->bmRequestType = USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
        transfer->setupPacket->wIndex        = USB_SHORT_TO_LITTLE_ENDIAN(
            ((usb_host_interface_t *)videoInstance->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(videoInstance->hostHandle, videoInstance->controlPipe, transfer);

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

    return status;
}

/*!
 * @brief set control interface.
 *
 * This function bind the control interface with the video instance.
 *
 * @param classHandle      the class handle.
 * @param interfaceHandle  the control 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_HostVideoControlSetInterface(usb_host_class_handle classHandle,
                                              usb_host_interface_handle interfaceHandle,
                                              uint8_t alternateSetting,
                                              transfer_callback_t callbackFn,
                                              void *callbackParam)
{
    usb_status_t status;
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *interface_ptr;
    usb_host_transfer_t *transfer;
    usb_host_video_descriptor_union_t desc;
    uint32_t length = 0U;
    void *temp;

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

    status = USB_HostOpenDeviceInterface(videoInstance->deviceHandle, interfaceHandle); /* notify host driver the interface is open */
    if (status != kStatus_USB_Success)
    {
        return status;
    }
    desc.bufr = interface_ptr->interfaceExtension;

    length = 0U;

    while (length < interface_ptr->interfaceExtensionLength)
    {
        if (((interface_ptr->interfaceDesc->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE) &&
             (interface_ptr->interfaceDesc->bAlternateSetting == alternateSetting)) ||
            ((desc.common->bDescriptorType == USB_DESCRIPTOR_TYPE_INTERFACE) &&
             (desc.interface->bAlternateSetting == alternateSetting)))
        {
            break;
        }
        length += desc.common->bLength;
        desc.bufr += desc.common->bLength;
    }
    while (length < interface_ptr->interfaceExtensionLength)
    {
        if (desc.common->bDescriptorType == USB_HOST_DESC_CS_INTERFACE)
        {
            temp = (void *)desc.bufr;
            if (desc.common->bData[0] == USB_HOST_DESC_SUBTYPE_VC_HEADER)
            {
                videoInstance->vcHeaderDesc = (usb_host_video_ctrl_header_desc_t *)temp;
            }
            else if (desc.common->bData[0] == USB_HOST_DESC_SUBTYPE_VC_INPUT_TERMINAL)
            {
                videoInstance->vcInputTerminalDesc = (usb_host_video_ctrl_it_desc_t *)temp;
            }
            else if (desc.common->bData[0] == USB_HOST_DESC_SUBTYPE_VC_OUTPUT_TERMINAL)
            {
                videoInstance->vcOutputTerminalDesc = (usb_host_video_ctrl_ot_desc_t *)temp;
            }
            else if (desc.common->bData[0] == USB_HOST_DESC_SUBTYPE_VC_PROCESSING_UNIT)
            {
                videoInstance->vcProcessingUnitDesc = (usb_host_video_ctrl_pu_desc_t *)temp;
            }
            else
            {
                /*no action*/
            }
        }
        length += desc.common->bLength;
        desc.bufr += desc.common->bLength;
    }

    if (alternateSetting == 0U) /* open interface directly */
    {
        if (callbackFn != NULL)
        {
            status = USB_HostVideoControlOpenInterface(videoInstance);
            callbackFn(callbackParam, NULL, 0U, kStatus_USB_Success);
        }
    }
    else /* send setup transfer */
    {
        /* malloc one transfer */
        if (USB_HostMallocTransfer(videoInstance->hostHandle, &transfer) != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error to get transfer\r\n");
#endif
            return kStatus_USB_Error;
        }
        /* save the application callback function */
        videoInstance->controlCallbackFn    = callbackFn;
        videoInstance->controlCallbackParam = callbackParam;
        /* initialize transfer */
        transfer->callbackFn                 = USB_HostVideoSetControlInterfaceCallback;
        transfer->callbackParam              = videoInstance;
        transfer->setupPacket->bRequest      = USB_REQUEST_STANDARD_SET_INTERFACE;
        transfer->setupPacket->bmRequestType = USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
        transfer->setupPacket->wIndex        = USB_SHORT_TO_LITTLE_ENDIAN(
            ((usb_host_interface_t *)videoInstance->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(videoInstance->hostHandle, videoInstance->controlPipe, transfer);

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

    return status;
}

/*!
 * @brief video stream receive data.
 *
 * This function implements video receiving 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_HosVideoStreamRecv(usb_host_class_handle classHandle,
                                    uint8_t *buffer,
                                    uint32_t bufferLen,
                                    transfer_callback_t callbackFn,
                                    void *callbackParam)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_transfer_t *transfer;

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

    if (videoInstance->streamIsoInPipe == NULL)
    {
        return kStatus_USB_Error;
    }

    /* malloc one transfer */
    if (USB_HostMallocTransfer(videoInstance->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Error;
    }
    /* save the application callback function */
    videoInstance->streamIsoInCallbackFn    = callbackFn;
    videoInstance->streamIsoInCallbackParam = callbackParam;
    transfer->transferBuffer                = buffer;
    transfer->transferLength                = bufferLen;
    transfer->callbackFn                    = USB_HostVideoStreamIsoInPipeCallback;
    transfer->callbackParam                 = videoInstance;

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

    return kStatus_USB_Success;
}

/*!
 * @brief initialize the video instance.
 *
 * This function allocate the resource for video instance.
 *
 * @param deviceHandle   the device handle.
 * @param classHandle    return class handle.
 *
 * @retval kStatus_USB_Success        The device is initialized successfully.
 * @retval kStatus_USB_AllocFail      Allocate memory fail.
 */
usb_status_t USB_HostVideoInit(usb_device_handle deviceHandle, usb_host_class_handle *classHandle)
{
    usb_host_video_instance_struct_t *videoInstance =
        (usb_host_video_instance_struct_t *)OSA_MemoryAllocate(sizeof(usb_host_video_instance_struct_t));
    uint32_t info_value;
    uint32_t *temp;
    if (videoInstance == NULL)
    {
        return kStatus_USB_AllocFail;
    }
    
    /* initialize video instance */
    videoInstance->deviceHandle      = deviceHandle;
    videoInstance->controlIntfHandle = NULL;
    videoInstance->streamIntfHandle  = NULL;
    (void)USB_HostHelperGetPeripheralInformation(deviceHandle, (uint32_t)kUSB_HostGetHostHandle, &info_value);
    temp                      = (uint32_t *)info_value;
    videoInstance->hostHandle = (usb_host_handle)temp;
    (void)USB_HostHelperGetPeripheralInformation(deviceHandle, (uint32_t)kUSB_HostGetDeviceControlPipe, &info_value);
    temp                       = (uint32_t *)info_value;
    videoInstance->controlPipe = (usb_host_pipe_handle)temp;

    *classHandle = videoInstance;
    return kStatus_USB_Success;
}

/*!
 * @brief de-initialize the video instance.
 *
 * This function release the resource for video 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_HostVideoDeinit(usb_device_handle deviceHandle, usb_host_class_handle classHandle)
{
    usb_status_t status;
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;

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

    if (classHandle != NULL)
    {
        /* cancel transfers */
        if (videoInstance->streamIsoInPipe != NULL)
        {
            status = USB_HostCancelTransfer(videoInstance->hostHandle, videoInstance->streamIsoInPipe, NULL);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when cancel pipe\r\n");
#endif
            }
            status = USB_HostClosePipe(videoInstance->hostHandle, videoInstance->streamIsoInPipe);

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

        (void)USB_HostCloseDeviceInterface(deviceHandle, videoInstance->streamIntfHandle);

        /* cancel transfers */
        if (videoInstance->interruptPipe != NULL)
        {
            status = USB_HostCancelTransfer(videoInstance->hostHandle, videoInstance->interruptPipe, NULL);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when cancel pipe\r\n");
#endif
            }
            status = USB_HostClosePipe(videoInstance->hostHandle, videoInstance->interruptPipe);

            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when close pipe\r\n");
#endif
            }
            videoInstance->interruptPipe = NULL;
        }
        /* cancel transfers */
        if ((videoInstance->controlPipe != NULL) && (videoInstance->controlTransfer != NULL))
        {
            status = USB_HostCancelTransfer(videoInstance->hostHandle, videoInstance->controlPipe,
                                            videoInstance->controlTransfer);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when cancel pipe\r\n");
#endif
            }
        }
        (void)USB_HostCloseDeviceInterface(deviceHandle, videoInstance->controlIntfHandle);
        OSA_MemoryFree(videoInstance);
    }
    else
    {
        (void)USB_HostCloseDeviceInterface(deviceHandle, NULL);
    }

    return kStatus_USB_Success;
}

/*!
 * @brief get video stream format descriptor.
 *
 * This function implements get video stream format descriptor.
 *
 * @param classHandle   The class handle.
 * @param subType       The descriptor subtype.
 * @param descriptor    The pointer of specific format descriptor.
 *
 * @retval kStatus_USB_Success            Get video stream format descriptor request successfully.
 * @retval kStatus_USB_InvalidHandle     The classHandle is NULL pointer.
 * @retval kStatus_USB_InvalidParameter  The descriptor is NULL pointer.
 *
 */
usb_status_t USB_HostVideoStreamGetFormatDescriptor(usb_host_class_handle classHandle,
                                                    uint8_t subType,
                                                    void **descriptor)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *interface_ptr;
    usb_host_video_descriptor_union_t descUnion;
    uint32_t length = 0U;

    if (NULL == classHandle)
    {
        return kStatus_USB_InvalidHandle;
    }
    
    /* get the steam interface handle */
    interface_ptr = (usb_host_interface_t *)videoInstance->streamIntfHandle;

    descUnion.bufr = interface_ptr->interfaceExtension;
    length         = 0U;
    while (length < interface_ptr->interfaceExtensionLength)
    {
        if (descUnion.common->bDescriptorType == USB_HOST_DESC_CS_INTERFACE)
        {
            if (descUnion.common->bData[0] == subType)
            {
                *descriptor = descUnion.bufr;
                return kStatus_USB_Success;
                ;
            }
        }
        length += descUnion.common->bLength;
        descUnion.bufr += descUnion.common->bLength;
    }

    return kStatus_USB_Error;
}

/*!
 * @brief get specific video stream frame descriptor.
 *
 * This function implements get specific video stream frame descriptor.
 *
 * @param classHandle        The class handle.
 * @param formatDescriptor   The frame descriptor pointer.
 * @param index              The specific frame descriptor id
 * @param descriptor         The pointer of specific frame descriptor.
 *
 * @retval kStatus_USB_Success           Get video stream frame descriptor request successfully.
 * @retval kStatus_USB_InvalidHandle     The classHandle is NULL pointer.
 * @retval kStatus_USB_InvalidParameter  The descriptor is NULL pointer.
 *
 */
usb_status_t USB_HostVideoStreamGetFrameDescriptor(
    usb_host_class_handle classHandle, void *formatDescriptor, uint8_t subType, uint8_t frameIndex, void **descriptor)
{
    usb_host_video_stream_payload_format_common_desc_t *formatDesc =
        (usb_host_video_stream_payload_format_common_desc_t *)formatDescriptor;
    usb_host_video_descriptor_union_t desc;
    uint32_t i         = 0U;
    uint8_t frameCount = 0U;

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

    if ((formatDesc == NULL) || (formatDesc->bDescriptorType != USB_HOST_DESC_CS_INTERFACE))
    {
        return kStatus_USB_InvalidParameter;
    }

    frameCount = formatDesc->bNumFrameDescriptors;
    desc.bufr  = (void *)formatDesc;
    desc.bufr += desc.common->bLength;

    while (i <= frameCount)
    {
        if ((desc.video_frame_common->bDescriptorType == USB_HOST_DESC_CS_INTERFACE) &&
            (desc.video_frame_common->bDescriptorSubtype == subType))
        {
            if (desc.video_frame_common->bFrameIndex == frameIndex)
            {
                *descriptor = (void *)desc.bufr;
                return kStatus_USB_Success;
            }
        }
        i++;
        desc.bufr += desc.common->bLength;
    }

    return kStatus_USB_Error;
}

/*!
 * @brief video set probe.
 *
 * This function implements the Video class-specific request (set probe).
 *
 * @param classHandle    the class handle.
 * @param request        setup packet request value.
 * @param probe          video probe data
 * @param callbackFn     this callback is called after this function completes.
 * @param callbackParam  the first parameter in the callback function.
 *
 * @retval kStatus_USB_Success           Request successful.
 * @retval kStatus_USB_InvalidHandle     The classHandle is NULL pointer.
 * @retval kStatus_USB_InvalidParameter  The interface descriptor is NULL pointer.
 */
usb_status_t USB_HostVideoSetProbe(usb_host_class_handle classHandle,
                                   uint8_t request,
                                   uint8_t *probe,
                                   transfer_callback_t callbackFn,
                                   void *callbackParam)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *streamInterface;
    usb_status_t status;
    streamInterface = (usb_host_interface_t *)videoInstance->streamIntfHandle;

    if (NULL == streamInterface)
    {
        return kStatus_USB_InvalidHandle;
    }
    if (NULL == streamInterface->interfaceDesc)
    {
        return kStatus_USB_InvalidParameter;
    }
    status = USB_HostVideoControl(
        classHandle, USB_REQUEST_TYPE_DIR_OUT | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        request, (uint16_t)(USB_HOST_VS_PROBE_CONTROL << 8UL), streamInterface->interfaceDesc->bInterfaceNumber, 26U,
        probe, callbackFn, callbackParam);
    return status;
}

/*!
 * @brief video get probe.
 *
 * This function implements the Video class-specific request (get probe).
 *
 * @param classHandle   the class handle.
 * @param request       setup packet request value.
 * @param probe         video probe data
 * @param callbackFn    this callback is called after this function completes.
 * @param callbackParam the first parameter in the callback function.
 *
 * @retval kStatus_USB_Success           Request successful.
 * @retval kStatus_USB_InvalidHandle     The classHandle is NULL pointer.
 * @retval kStatus_USB_InvalidParameter  The interface descriptor is NULL pointer.
 */
usb_status_t USB_HostVideoGetProbe(usb_host_class_handle classHandle,
                                   uint8_t request,
                                   uint8_t *probe,
                                   transfer_callback_t callbackFn,
                                   void *callbackParam)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *streamInterface;
    usb_status_t status;
    streamInterface = (usb_host_interface_t *)videoInstance->streamIntfHandle;
    if (NULL == streamInterface)
    {
        return kStatus_USB_InvalidHandle;
    }
    if (NULL == streamInterface->interfaceDesc)
    {
        return kStatus_USB_InvalidParameter;
    }
    status = USB_HostVideoControl(
        classHandle, USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        request, (uint16_t)(USB_HOST_VS_PROBE_CONTROL << 8UL), streamInterface->interfaceDesc->bInterfaceNumber, 26U,
        probe, callbackFn, callbackParam);
    return status;
}

/*!
 * @brief video get commit.
 *
 * This function implements the Video class-specific request (get commit).
 *
 * @param classHandle    the class handle.
 * @param request        setup packet request value.
 * @param probe          video probe data
 * @param callbackFn     this callback is called after this function completes.
 * @param callbackParam  the first parameter in the callback function.
 *
 * @retval kStatus_USB_Success           Request successful.
 * @retval kStatus_USB_InvalidHandle     The classHandle is NULL pointer.
 * @retval kStatus_USB_InvalidParameter  The interface descriptor is NULL pointer.
 */
usb_status_t USB_HostVideoGetCommit(usb_host_class_handle classHandle,
                                    uint8_t brequest,
                                    uint8_t *probe,
                                    transfer_callback_t callbackFn,
                                    void *callbackParam)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *streamInterface;
    usb_status_t status;
    streamInterface = (usb_host_interface_t *)videoInstance->streamIntfHandle;
    if (NULL == streamInterface)
    {
        return kStatus_USB_InvalidHandle;
    }
    if (NULL == streamInterface->interfaceDesc)
    {
        return kStatus_USB_InvalidParameter;
    }
    status = USB_HostVideoControl(
        classHandle, USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        brequest, (uint16_t)(USB_HOST_VS_COMMIT_CONTROL << 8UL), streamInterface->interfaceDesc->bInterfaceNumber, 26U,
        probe, callbackFn, callbackParam);
    return status;
}

/*!
 * @brief video set commit.
 *
 * This function implements the Video class-specific request (set commit).
 *
 * @param classHandle    the class handle.
 * @param request        setup packet request value.
 * @param probe          video probe data
 * @param callbackFn     this callback is called after this function completes.
 * @param callbackParam  the first parameter in the callback function.
 *
 * @retval kStatus_USB_Success           Request successful.
 * @retval kStatus_USB_InvalidHandle     The classHandle is NULL pointer.
 * @retval kStatus_USB_InvalidParameter  The interface descriptor is NULL pointer.
 */
usb_status_t USB_HostVideoSetCommit(usb_host_class_handle classHandle,
                                    uint8_t brequest,
                                    uint8_t *probe,
                                    transfer_callback_t callbackFn,
                                    void *callbackParam)
{
    usb_host_video_instance_struct_t *videoInstance = (usb_host_video_instance_struct_t *)classHandle;
    usb_host_interface_t *streamInterface;
    usb_status_t status;
    streamInterface = (usb_host_interface_t *)videoInstance->streamIntfHandle;
    if (NULL == streamInterface)
    {
        return kStatus_USB_InvalidHandle;
    }
    if (NULL == streamInterface->interfaceDesc)
    {
        return kStatus_USB_InvalidParameter;
    }
    status = USB_HostVideoControl(
        classHandle, USB_REQUEST_TYPE_DIR_OUT | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        brequest, (uint16_t)(USB_HOST_VS_COMMIT_CONTROL << 8UL), streamInterface->interfaceDesc->bInterfaceNumber, 26U,
        probe, callbackFn, callbackParam);
    return status;
}

#endif
