/*
 * Copyright (c) 2015 - 2016, Freescale Semiconductor, Inc.
 * Copyright 2016,2019 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "usb_host_config.h"
#if ((defined USB_HOST_CONFIG_HID) && (USB_HOST_CONFIG_HID))
#include "usb_host.h"
#include "usb_host_hid.h"

/*******************************************************************************
 * API
 ******************************************************************************/

/*!
 * @brief hid in pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostHidInPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

/*!
 * @brief hid out pipe transfer callback.
 *
 * @param param       callback parameter.
 * @param transfer    callback transfer.
 * @param status      transfer status.
 */
static void USB_HostHidOutPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status);

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

/*!
 * @brief hid open interface. It is called when set interface request success or open alternate setting 0 interface.
 *
 * @param hidInstance     hid instance pointer.
 *
 * @return kStatus_USB_Success or error codes.
 */
static usb_status_t USB_HostHidOpenInterface(usb_host_hid_instance_t *hidInstance);

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

/*!
 * @brief hid send control transfer common code.
 *
 * @param classHandle    the class handle.
 * @param requestType    setup packet request type.
 * @param request        setup packet request value.
 * @param wvalueL        setup packet wvalue low byte.
 * @param wvalueH        setup packet wvalue high byte.
 * @param wlength        setup packet wlength value.
 * @param data           data buffer pointer
 * @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_HostHidControl(usb_host_class_handle classHandle,
                                       uint8_t requestType,
                                       uint8_t request,
                                       uint8_t wvalueL,
                                       uint8_t wvalueH,
                                       uint16_t wlength,
                                       uint8_t *data,
                                       transfer_callback_t callbackFn,
                                       void *callbackParam);

/*******************************************************************************
 * Definitions
 ******************************************************************************/
/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*******************************************************************************
 * Variables
 ******************************************************************************/
/*******************************************************************************
 * Code
 ******************************************************************************/

#if ((defined USB_HOST_CONFIG_CLASS_AUTO_CLEAR_STALL) && USB_HOST_CONFIG_CLASS_AUTO_CLEAR_STALL)

static void USB_HostHidClearInHaltCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)param;

    hidInstance->controlTransfer = NULL;
    if (hidInstance->inCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in the USB_HostHidRecv */
        hidInstance->inCallbackFn(hidInstance->inCallbackParam, hidInstance->stallDataBuffer,
                                  hidInstance->stallDataLength, kStatus_USB_TransferStall);
    }
    (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
}

static void USB_HostHidClearOutHaltCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)param;

    hidInstance->controlTransfer = NULL;
    if (hidInstance->outCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in USB_HostHidSend */
        hidInstance->outCallbackFn(hidInstance->outCallbackParam, hidInstance->stallDataBuffer,
                                   hidInstance->stallDataLength, kStatus_USB_TransferStall);
    }
    (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
}

static usb_status_t USB_HostHidClearHalt(usb_host_hid_instance_t *hidInstance,
                                         usb_host_transfer_t *stallTransfer,
                                         host_inner_transfer_callback_t callbackFn,
                                         uint8_t endpoint)
{
    usb_status_t status;
    usb_host_transfer_t *transfer;

    /* malloc one transfer */
    status = USB_HostMallocTransfer(hidInstance->hostHandle, &transfer);
    if (status != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("allocate transfer error\r\n");
#endif
        return status;
    }
    hidInstance->stallDataBuffer = stallTransfer->transferBuffer;
    hidInstance->stallDataLength = stallTransfer->transferSofar;
    /* save the application callback function */
    hidInstance->controlCallbackFn    = NULL;
    hidInstance->controlCallbackParam = NULL;
    /* initialize transfer */
    transfer->callbackFn                 = callbackFn;
    transfer->callbackParam              = hidInstance;
    transfer->transferBuffer             = NULL;
    transfer->transferLength             = 0;
    transfer->setupPacket->bRequest      = USB_REQUEST_STANDARD_CLEAR_FEATURE;
    transfer->setupPacket->bmRequestType = USB_REQUEST_TYPE_RECIPIENT_ENDPOINT;
    transfer->setupPacket->wValue  = USB_SHORT_TO_LITTLE_ENDIAN(USB_REQUEST_STANDARD_FEATURE_SELECTOR_ENDPOINT_HALT);
    transfer->setupPacket->wIndex  = USB_SHORT_TO_LITTLE_ENDIAN(endpoint);
    transfer->setupPacket->wLength = 0;
    status                         = USB_HostSendSetup(hidInstance->hostHandle, hidInstance->controlPipe, transfer);

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

    return status;
}
#endif

static void USB_HostHidInPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)param;

#if ((defined USB_HOST_CONFIG_CLASS_AUTO_CLEAR_STALL) && USB_HOST_CONFIG_CLASS_AUTO_CLEAR_STALL)
    if (status == kStatus_USB_TransferStall)
    {
        if (USB_HostHidClearHalt(hidInstance, transfer, USB_HostHidClearInHaltCallback,
                                 (USB_REQUEST_TYPE_DIR_IN |
                                  ((usb_host_pipe_t *)hidInstance->inPipe)->endpointAddress)) == kStatus_USB_Success)
        {
            (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
            return;
        }
    }
#endif
    if (hidInstance->inCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in the USB_HostHidRecv */
        hidInstance->inCallbackFn(hidInstance->inCallbackParam, transfer->transferBuffer, transfer->transferSofar,
                                  status);
    }
    (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
}

static void USB_HostHidOutPipeCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)param;

#if ((defined USB_HOST_CONFIG_CLASS_AUTO_CLEAR_STALL) && USB_HOST_CONFIG_CLASS_AUTO_CLEAR_STALL)
    if (status == kStatus_USB_TransferStall)
    {
        if (USB_HostHidClearHalt(hidInstance, transfer, USB_HostHidClearOutHaltCallback,
                                 (USB_REQUEST_TYPE_DIR_OUT |
                                  ((usb_host_pipe_t *)hidInstance->outPipe)->endpointAddress)) == kStatus_USB_Success)
        {
            (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
            return;
        }
    }
#endif
    if (hidInstance->outCallbackFn != NULL)
    {
        /* callback to application, callback function is initialized in USB_HostHidSend */
        hidInstance->outCallbackFn(hidInstance->outCallbackParam, transfer->transferBuffer, transfer->transferSofar,
                                   status); /* callback to application */
    }
    (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
}

static void USB_HostHidControlCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)param;

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

static usb_status_t USB_HostHidOpenInterface(usb_host_hid_instance_t *hidInstance)
{
    usb_status_t status;
    uint8_t epIndex = 0;
    usb_host_pipe_init_t pipeInit;
    usb_descriptor_endpoint_t *epDesc = NULL;
    usb_host_interface_t *interfacePointer;

    if (hidInstance->inPipe != NULL) /* close interrupt in pipe if it is open */
    {
        status = USB_HostClosePipe(hidInstance->hostHandle, hidInstance->inPipe);

        if (status != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when close pipe\r\n");
#endif
        }
        hidInstance->inPipe = NULL;
    }
    if (hidInstance->outPipe != NULL) /* close interrupt out pipe if it is open */
    {
        status = USB_HostClosePipe(hidInstance->hostHandle, hidInstance->outPipe);

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

    /* open interface pipes */
    interfacePointer = (usb_host_interface_t *)hidInstance->interfaceHandle;
    for (epIndex = 0; epIndex < interfacePointer->epCount; ++epIndex)
    {
        epDesc = interfacePointer->epList[epIndex].epDesc;
        if (((epDesc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_MASK) ==
             USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_IN) &&
            ((epDesc->bmAttributes & USB_DESCRIPTOR_ENDPOINT_ATTRIBUTE_TYPE_MASK) == USB_ENDPOINT_INTERRUPT))
        {
            pipeInit.devInstance     = hidInstance->deviceHandle;
            pipeInit.pipeType        = USB_ENDPOINT_INTERRUPT;
            pipeInit.direction       = USB_IN;
            pipeInit.endpointAddress = (epDesc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_NUMBER_MASK);
            pipeInit.interval        = epDesc->bInterval;
            pipeInit.maxPacketSize   = (uint16_t)((USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(epDesc->wMaxPacketSize) &
                                                 USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_SIZE_MASK));
            pipeInit.numberPerUframe = (uint8_t)((USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(epDesc->wMaxPacketSize) &
                                                  USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_MASK));
            pipeInit.nakCount        = USB_HOST_CONFIG_MAX_NAK;

            hidInstance->inPacketSize = pipeInit.maxPacketSize;

            status = USB_HostOpenPipe(hidInstance->hostHandle, &hidInstance->inPipe, &pipeInit);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("USB_HostHidSetInterface fail to open pipe\r\n");
#endif
                return kStatus_USB_Error;
            }
        }
        else if (((epDesc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_MASK) ==
                  USB_DESCRIPTOR_ENDPOINT_ADDRESS_DIRECTION_OUT) &&
                 ((epDesc->bmAttributes & USB_DESCRIPTOR_ENDPOINT_ATTRIBUTE_TYPE_MASK) == USB_ENDPOINT_INTERRUPT))
        {
            pipeInit.devInstance     = hidInstance->deviceHandle;
            pipeInit.pipeType        = USB_ENDPOINT_INTERRUPT;
            pipeInit.direction       = USB_OUT;
            pipeInit.endpointAddress = (epDesc->bEndpointAddress & USB_DESCRIPTOR_ENDPOINT_ADDRESS_NUMBER_MASK);
            pipeInit.interval        = epDesc->bInterval;
            pipeInit.maxPacketSize   = (uint16_t)((USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(epDesc->wMaxPacketSize) &
                                                 USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_SIZE_MASK));
            pipeInit.numberPerUframe = (uint8_t)((USB_SHORT_FROM_LITTLE_ENDIAN_ADDRESS(epDesc->wMaxPacketSize) &
                                                  USB_DESCRIPTOR_ENDPOINT_MAXPACKETSIZE_MULT_TRANSACTIONS_MASK));
            pipeInit.nakCount        = USB_HOST_CONFIG_MAX_NAK;

            hidInstance->outPacketSize = pipeInit.maxPacketSize;

            status = USB_HostOpenPipe(hidInstance->hostHandle, &hidInstance->outPipe, &pipeInit);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("USB_HostHidSetInterface fail to open pipe\r\n");
#endif
                return kStatus_USB_Error;
            }
        }
        else
        {
        }
    }

    return kStatus_USB_Success;
}

static void USB_HostHidSetInterfaceCallback(void *param, usb_host_transfer_t *transfer, usb_status_t status)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)param;

    hidInstance->controlTransfer = NULL;
    if (status == kStatus_USB_Success)
    {
        status = USB_HostHidOpenInterface(hidInstance); /* hid open interface */
    }

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

usb_status_t USB_HostHidInit(usb_device_handle deviceHandle, usb_host_class_handle *classHandle)
{
    uint32_t infoValue;
    uint32_t *temp;
    usb_host_hid_instance_t *hidInstance =
        (usb_host_hid_instance_t *)OSA_MemoryAllocate(sizeof(usb_host_hid_instance_t)); /* malloc hid class instance */

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

    /* initialize hid instance */
    hidInstance->deviceHandle    = deviceHandle;
    hidInstance->interfaceHandle = NULL;
    (void)USB_HostHelperGetPeripheralInformation(deviceHandle, (uint32_t)kUSB_HostGetHostHandle, &infoValue);
    temp                    = (uint32_t *)infoValue;
    hidInstance->hostHandle = (usb_host_handle)temp;
    (void)USB_HostHelperGetPeripheralInformation(deviceHandle, (uint32_t)kUSB_HostGetDeviceControlPipe, &infoValue);
    temp                     = (uint32_t *)infoValue;
    hidInstance->controlPipe = (usb_host_pipe_handle)temp;

    *classHandle = hidInstance;
    return kStatus_USB_Success;
}

usb_status_t USB_HostHidSetInterface(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_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

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

    hidInstance->interfaceHandle = interfaceHandle;
    status                       = USB_HostOpenDeviceInterface(hidInstance->deviceHandle,
                                         interfaceHandle); /* notify host driver the interface is open */
    if (status != kStatus_USB_Success)
    {
        return status;
    }

    /* cancel transfers */
    if (hidInstance->inPipe != NULL)
    {
        status = USB_HostCancelTransfer(hidInstance->hostHandle, hidInstance->inPipe, NULL);

        if (status != kStatus_USB_Success)
        {
#ifdef HOST_ECHO
            usb_echo("error when cancel pipe\r\n");
#endif
        }
    }
    if (hidInstance->outPipe != NULL)
    {
        status = USB_HostCancelTransfer(hidInstance->hostHandle, hidInstance->outPipe, NULL);

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

    if (alternateSetting == 0U) /* open interface directly */
    {
        if (callbackFn != NULL)
        {
            status = USB_HostHidOpenInterface(hidInstance);
            callbackFn(callbackParam, NULL, 0, status);
        }
    }
    else /* send setup transfer */
    {
        /* malloc one transfer */
        if (USB_HostMallocTransfer(hidInstance->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 */
        hidInstance->controlCallbackFn    = callbackFn;
        hidInstance->controlCallbackParam = callbackParam;
        /* initialize transfer */
        transfer->callbackFn                 = USB_HostHidSetInterfaceCallback;
        transfer->callbackParam              = hidInstance;
        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 *)hidInstance->interfaceHandle)->interfaceDesc->bInterfaceNumber);
        transfer->setupPacket->wValue  = USB_SHORT_TO_LITTLE_ENDIAN(alternateSetting);
        transfer->setupPacket->wLength = 0U;
        transfer->transferBuffer       = NULL;
        transfer->transferLength       = 0U;
        status                         = USB_HostSendSetup(hidInstance->hostHandle, hidInstance->controlPipe, transfer);

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

    return status;
}

usb_status_t USB_HostHidDeinit(usb_device_handle deviceHandle, usb_host_class_handle classHandle)
{
    usb_status_t status;
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)classHandle;

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

    if (classHandle != NULL) /* class instance has initialized */
    {
        if (hidInstance->inPipe != NULL)
        {
            status = USB_HostCancelTransfer(hidInstance->hostHandle, hidInstance->inPipe, NULL); /* cancel pipe */
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when cancel pipe\r\n");
#endif
            }
            status = USB_HostClosePipe(hidInstance->hostHandle, hidInstance->inPipe); /* close pipe */

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

            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when close pipe\r\n");
#endif
            }
            hidInstance->outPipe = NULL;
        }
        if ((hidInstance->controlPipe != NULL) &&
            (hidInstance->controlTransfer != NULL)) /* cancel control transfer if there is on-going control transfer */
        {
            status =
                USB_HostCancelTransfer(hidInstance->hostHandle, hidInstance->controlPipe, hidInstance->controlTransfer);
            if (status != kStatus_USB_Success)
            {
#ifdef HOST_ECHO
                usb_echo("error when cancel pipe\r\n");
#endif
            }
        }
        (void)USB_HostCloseDeviceInterface(
            deviceHandle, hidInstance->interfaceHandle); /* notify host driver the interface is closed */
        OSA_MemoryFree(hidInstance);
    }
    else
    {
        (void)USB_HostCloseDeviceInterface(deviceHandle, NULL);
    }

    return kStatus_USB_Success;
}

uint16_t USB_HostHidGetPacketsize(usb_host_class_handle classHandle, uint8_t pipeType, uint8_t direction)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)classHandle;
    if (classHandle == NULL)
    {
        return 0U;
    }

    if (pipeType == USB_ENDPOINT_INTERRUPT)
    {
        if (direction == USB_IN)
        {
            return hidInstance->inPacketSize;
        }
        else
        {
            return hidInstance->outPacketSize;
        }
    }

    return 0;
}

usb_status_t USB_HostHidRecv(usb_host_class_handle classHandle,
                             uint8_t *buffer,
                             uint32_t bufferLength,
                             transfer_callback_t callbackFn,
                             void *callbackParam)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

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

    if (hidInstance->inPipe == NULL)
    {
        return kStatus_USB_Error;
    }

    /* malloc one transfer */
    if (USB_HostMallocTransfer(hidInstance->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Busy;
    }
    /* save the application callback function */
    hidInstance->inCallbackFn    = callbackFn;
    hidInstance->inCallbackParam = callbackParam;
    /* initialize transfer */
    transfer->transferBuffer = buffer;
    transfer->transferLength = bufferLength;
    transfer->callbackFn     = USB_HostHidInPipeCallback;
    transfer->callbackParam  = hidInstance;

    if (USB_HostRecv(hidInstance->hostHandle, hidInstance->inPipe, transfer) !=
        kStatus_USB_Success) /* call host driver api */
    {
#ifdef HOST_ECHO
        usb_echo("failed to USB_HostRecv\r\n");
#endif
        (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
        return kStatus_USB_Error;
    }

    return kStatus_USB_Success;
}

usb_status_t USB_HostHidSend(usb_host_class_handle classHandle,
                             uint8_t *buffer,
                             uint32_t bufferLength,
                             transfer_callback_t callbackFn,
                             void *callbackParam)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

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

    if (hidInstance->outPipe == NULL)
    {
        return kStatus_USB_Error;
    }

    /* malloc one transfer */
    if (USB_HostMallocTransfer(hidInstance->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 */
    hidInstance->outCallbackFn    = callbackFn;
    hidInstance->outCallbackParam = callbackParam;
    /* initialize transfer */
    transfer->transferBuffer = buffer;
    transfer->transferLength = bufferLength;
    transfer->callbackFn     = USB_HostHidOutPipeCallback;
    transfer->callbackParam  = hidInstance;

    if (USB_HostSend(hidInstance->hostHandle, hidInstance->outPipe, transfer) !=
        kStatus_USB_Success) /* call host driver api */
    {
#ifdef HOST_ECHO
        usb_echo("failed to USB_HostSend\r\n");
#endif
        (void)USB_HostFreeTransfer(hidInstance->hostHandle, transfer);
        return kStatus_USB_Error;
    }

    return kStatus_USB_Success;
}

static usb_status_t USB_HostHidControl(usb_host_class_handle classHandle,
                                       uint8_t requestType,
                                       uint8_t request,
                                       uint8_t wvalueL,
                                       uint8_t wvalueH,
                                       uint16_t wlength,
                                       uint8_t *data,
                                       transfer_callback_t callbackFn,
                                       void *callbackParam)
{
    usb_host_hid_instance_t *hidInstance = (usb_host_hid_instance_t *)classHandle;
    usb_host_transfer_t *transfer;

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

    /* malloc one transfer */
    if (USB_HostMallocTransfer(hidInstance->hostHandle, &transfer) != kStatus_USB_Success)
    {
#ifdef HOST_ECHO
        usb_echo("error to get transfer\r\n");
#endif
        return kStatus_USB_Busy;
    }
    /* save the application callback function */
    hidInstance->controlCallbackFn    = callbackFn;
    hidInstance->controlCallbackParam = callbackParam;
    /* initialize transfer */
    transfer->transferBuffer             = data;
    transfer->transferLength             = wlength;
    transfer->callbackFn                 = USB_HostHidControlCallback;
    transfer->callbackParam              = hidInstance;
    transfer->setupPacket->bmRequestType = requestType;
    transfer->setupPacket->bRequest      = request;
    transfer->setupPacket->wValue        = USB_SHORT_TO_LITTLE_ENDIAN(wvalueL | (uint16_t)((uint16_t)wvalueH << 8));
    transfer->setupPacket->wIndex        = USB_SHORT_TO_LITTLE_ENDIAN(
        ((usb_host_interface_t *)hidInstance->interfaceHandle)->interfaceDesc->bInterfaceNumber);
    transfer->setupPacket->wLength = USB_SHORT_TO_LITTLE_ENDIAN(wlength);

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

    return kStatus_USB_Success;
}

usb_status_t USB_HostHidGetReportDescriptor(usb_host_class_handle classHandle,
                                            uint8_t *buffer,
                                            uint16_t buffer_len,
                                            transfer_callback_t callbackFn,
                                            void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_STANDARD | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_REQUEST_STANDARD_GET_DESCRIPTOR, 0, USB_DESCRIPTOR_TYPE_HID_REPORT, buffer_len, buffer, callbackFn,
        callbackParam);
}

usb_status_t USB_HostHidGetIdle(usb_host_class_handle classHandle,
                                uint8_t reportId,
                                uint8_t *idleRate,
                                transfer_callback_t callbackFn,
                                void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_HOST_HID_GET_IDLE, reportId, 0, 1, idleRate, callbackFn, callbackParam);
}

usb_status_t USB_HostHidSetIdle(usb_host_class_handle classHandle,
                                uint8_t reportId,
                                uint8_t idleRate,
                                transfer_callback_t callbackFn,
                                void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_OUT | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_HOST_HID_SET_IDLE, reportId, idleRate, 0, NULL, callbackFn, callbackParam);
}

usb_status_t USB_HostHidGetProtocol(usb_host_class_handle classHandle,
                                    uint8_t *protocol,
                                    transfer_callback_t callbackFn,
                                    void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_HOST_HID_GET_PROTOCOL, 0, 0, 1, protocol, callbackFn, callbackParam);
}

usb_status_t USB_HostHidSetProtocol(usb_host_class_handle classHandle,
                                    uint8_t protocol,
                                    transfer_callback_t callbackFn,
                                    void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_OUT | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_HOST_HID_SET_PROTOCOL, protocol, 0, 0, NULL, callbackFn, callbackParam);
}

usb_status_t USB_HostHidGetReport(usb_host_class_handle classHandle,
                                  uint8_t reportId,
                                  uint8_t reportType,
                                  uint8_t *buffer,
                                  uint32_t bufferLength,
                                  transfer_callback_t callbackFn,
                                  void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_HOST_HID_GET_REPORT, reportId, reportType, (uint16_t)bufferLength, buffer, callbackFn, callbackParam);
}

usb_status_t USB_HostHidSetReport(usb_host_class_handle classHandle,
                                  uint8_t reportId,
                                  uint8_t reportType,
                                  uint8_t *buffer,
                                  uint32_t bufferLength,
                                  transfer_callback_t callbackFn,
                                  void *callbackParam)
{
    return USB_HostHidControl(
        classHandle, USB_REQUEST_TYPE_DIR_OUT | USB_REQUEST_TYPE_TYPE_CLASS | USB_REQUEST_TYPE_RECIPIENT_INTERFACE,
        USB_HOST_HID_SET_REPORT, reportId, reportType, (uint16_t)bufferLength, buffer, callbackFn, callbackParam);
}

#endif /* USB_HOST_CONFIG_HID */
