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

#include "usb_host_config.h"
#include "usb_host.h"
#include "usb_host_hci.h"
#include "usb_host_devices.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "middleware.usb.host.fatfs_usb_stack"
#endif

/*!
 * @brief standard control transfer common code.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param buffer         data buffer pointer.
 * @param bufferLen      data length.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostCh9RequestCommon(usb_host_device_instance_t *deviceInstance,
                                      usb_host_transfer_t *transfer,
                                      uint8_t *buffer,
                                      uint32_t bufferLen);

/*!
 * @brief standard get status implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardGetStatus(usb_host_device_instance_t *deviceInstance,
                                       usb_host_transfer_t *transfer,
                                       void *param);

/*!
 * @brief standard set/clear feature implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardSetClearFeature(usb_host_device_instance_t *deviceInstance,
                                             usb_host_transfer_t *transfer,
                                             void *param);

/*!
 * @brief standard set address implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardSetAddress(usb_host_device_instance_t *deviceInstance,
                                        usb_host_transfer_t *transfer,
                                        void *param);

/*!
 * @brief standard set/get descriptor implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardSetGetDescriptor(usb_host_device_instance_t *deviceInstance,
                                              usb_host_transfer_t *transfer,
                                              void *param);

/*!
 * @brief standard get interface implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardGetInterface(usb_host_device_instance_t *deviceInstance,
                                          usb_host_transfer_t *transfer,
                                          void *param);

/*!
 * @brief standard set interface implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardSetInterface(usb_host_device_instance_t *deviceInstance,
                                          usb_host_transfer_t *transfer,
                                          void *param);

/*!
 * @brief standard sync frame implementation.
 *
 * @param deviceInstance device instance handle.
 * @param transfer       transfer.
 * @param param          parameter.
 *
 * @return kStatus_USB_Success or error codes.
 */
usb_status_t USB_HostStandardSyncFrame(usb_host_device_instance_t *deviceInstance,
                                       usb_host_transfer_t *transfer,
                                       void *param);

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

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

/*******************************************************************************
 * Code
 ******************************************************************************/

usb_status_t USB_HostCh9RequestCommon(usb_host_device_instance_t *deviceInstance,
                                      usb_host_transfer_t *transfer,
                                      uint8_t *buffer,
                                      uint32_t bufferLen)
{
    /* initialize transfer */
    transfer->setupPacket->wLength = USB_SHORT_TO_LITTLE_ENDIAN(bufferLen);
    transfer->transferBuffer       = buffer;
    transfer->transferLength       = bufferLen;

    if (USB_HostSendSetup(deviceInstance->hostHandle, deviceInstance->controlPipe, transfer) !=
        kStatus_USB_Success) /* send setup transfer */
    {
#ifdef HOST_ECHO
        usb_echo("failed for USB_HostSendSetup\r\n");
#endif
        USB_HostFreeTransfer(deviceInstance->hostHandle, transfer);
        return kStatus_USB_Error;
    }
    return kStatus_USB_Success;
}

usb_status_t USB_HostStandardGetStatus(usb_host_device_instance_t *deviceInstance,
                                       usb_host_transfer_t *transfer,
                                       void *param)
{
    usb_host_get_status_param_t *statusParam;
    uint8_t length;

    /* initialize transfer */
    statusParam                          = (usb_host_get_status_param_t *)param;
    transfer->setupPacket->bmRequestType = USB_REQUEST_TYPE_DIR_IN | USB_REQUEST_TYPE_TYPE_STANDARD;
    if (statusParam->requestType == kRequestDevice)
    {
        transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_DEVICE;
    }
    else if (statusParam->requestType == kRequestInterface)
    {
        transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
    }
    else
    {
        transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_ENDPOINT;
    }
    transfer->setupPacket->wIndex = USB_SHORT_TO_LITTLE_ENDIAN(statusParam->statusSelector);

    length = 2;
    if (statusParam->statusSelector == USB_REQUEST_STANDARD_GET_STATUS_OTG_STATUS_SELECTOR)
    {
        length = 1;
    }
    return USB_HostCh9RequestCommon(deviceInstance, transfer, statusParam->statusBuffer, length);
}

usb_status_t USB_HostStandardSetClearFeature(usb_host_device_instance_t *deviceInstance,
                                             usb_host_transfer_t *transfer,
                                             void *param)
{
    usb_host_process_feature_param_t *featureParam;

    /* initialize transfer */
    featureParam = (usb_host_process_feature_param_t *)param;
    if (featureParam->requestType == kRequestDevice)
    {
        transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_DEVICE;
    }
    else if (featureParam->requestType == kRequestInterface)
    {
        transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
    }
    else
    {
        transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_ENDPOINT;
    }
    transfer->setupPacket->wValue = USB_SHORT_TO_LITTLE_ENDIAN(featureParam->featureSelector);
    transfer->setupPacket->wIndex = USB_SHORT_TO_LITTLE_ENDIAN(featureParam->interfaceOrEndpoint);

    return USB_HostCh9RequestCommon(deviceInstance, transfer, NULL, 0);
}

usb_status_t USB_HostStandardSetAddress(usb_host_device_instance_t *deviceInstance,
                                        usb_host_transfer_t *transfer,
                                        void *param)
{
    uint8_t address;

    /* initialize transfer */
    address                       = *(uint8_t *)param;
    transfer->setupPacket->wValue = USB_SHORT_TO_LITTLE_ENDIAN(address);

    return USB_HostCh9RequestCommon(deviceInstance, transfer, NULL, 0);
}

usb_status_t USB_HostStandardSetGetDescriptor(usb_host_device_instance_t *deviceInstance,
                                              usb_host_transfer_t *transfer,
                                              void *param)
{
    usb_host_process_descriptor_param_t *descriptorParam;

    /* initialize transfer */
    descriptorParam               = (usb_host_process_descriptor_param_t *)param;
    transfer->setupPacket->wValue = USB_SHORT_TO_LITTLE_ENDIAN(
        (uint16_t)((uint16_t)descriptorParam->descriptorType << 8) | descriptorParam->descriptorIndex);
    transfer->setupPacket->wIndex = USB_SHORT_TO_LITTLE_ENDIAN(descriptorParam->languageId);
    return USB_HostCh9RequestCommon(deviceInstance, transfer, descriptorParam->descriptorBuffer,
                                    descriptorParam->descriptorLength);
}

usb_status_t USB_HostStandardGetInterface(usb_host_device_instance_t *deviceInstance,
                                          usb_host_transfer_t *transfer,
                                          void *param)
{
    usb_host_get_interface_param_t *interfaceParam;

    /* initialize transfer */
    interfaceParam = (usb_host_get_interface_param_t *)param;
    transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
    transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
    transfer->setupPacket->wIndex = USB_SHORT_TO_LITTLE_ENDIAN(interfaceParam->interface);

    return USB_HostCh9RequestCommon(deviceInstance, transfer, interfaceParam->alternateInterfaceBuffer, 1);
}

usb_status_t USB_HostStandardSetInterface(usb_host_device_instance_t *deviceInstance,
                                          usb_host_transfer_t *transfer,
                                          void *param)
{
    usb_host_set_interface_param_t *setParam;

    /* initialize transfer */
    setParam = (usb_host_set_interface_param_t *)param;
    transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_INTERFACE;
    transfer->setupPacket->wIndex = USB_SHORT_TO_LITTLE_ENDIAN(setParam->interface);
    transfer->setupPacket->wValue = USB_SHORT_TO_LITTLE_ENDIAN(setParam->alternateSetting);

    return USB_HostCh9RequestCommon(deviceInstance, transfer, NULL, 0);
}

usb_status_t USB_HostStandardSyncFrame(usb_host_device_instance_t *deviceInstance,
                                       usb_host_transfer_t *transfer,
                                       void *param)
{
    usb_host_synch_frame_param_t *frameParam;

    /* initialize transfer */
    frameParam = (usb_host_synch_frame_param_t *)param;
    transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
    transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_RECIPIENT_ENDPOINT;
    transfer->setupPacket->wIndex = USB_SHORT_TO_LITTLE_ENDIAN(frameParam->endpoint);

    return USB_HostCh9RequestCommon(deviceInstance, transfer, frameParam->frameNumberBuffer, 2);
}

usb_status_t USB_HostRequestControl(usb_device_handle deviceHandle,
                                    uint8_t usbRequest,
                                    usb_host_transfer_t *transfer,
                                    void *param)
{
    usb_host_device_instance_t *deviceInstance = (usb_host_device_instance_t *)deviceHandle;
    usb_status_t status                        = kStatus_USB_Error;

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

    /* reset transfer fields */
    transfer->setupPacket->bmRequestType = 0x00;
    transfer->setupPacket->bRequest      = usbRequest;
    transfer->setupPacket->wIndex        = 0;
    transfer->setupPacket->wLength       = 0;
    transfer->setupPacket->wValue        = 0;

    switch (usbRequest)
    {
        case USB_REQUEST_STANDARD_GET_STATUS: /* standard get status request */
            status = USB_HostStandardGetStatus(deviceInstance, transfer, param);
            break;

        case USB_REQUEST_STANDARD_CLEAR_FEATURE: /* standard clear status request */
        case USB_REQUEST_STANDARD_SET_FEATURE:   /* standard set feature request */
            status = USB_HostStandardSetClearFeature(deviceInstance, transfer, param);
            break;

        case USB_REQUEST_STANDARD_SET_ADDRESS: /* standard set address request */
            status = USB_HostStandardSetAddress(deviceInstance, transfer, param);
            break;

        case USB_REQUEST_STANDARD_GET_DESCRIPTOR: /* standard get descriptor request */
        case USB_REQUEST_STANDARD_SET_DESCRIPTOR: /* standard set descriptor request */
            if (usbRequest == USB_REQUEST_STANDARD_GET_DESCRIPTOR)
            {
                transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
            }
            status = USB_HostStandardSetGetDescriptor(deviceInstance, transfer, param);
            break;

        case USB_REQUEST_STANDARD_GET_CONFIGURATION: /* standard get configuration descriptor request */
            transfer->setupPacket->bmRequestType |= USB_REQUEST_TYPE_DIR_IN;
            status =
                USB_HostCh9RequestCommon((usb_host_device_instance_t *)deviceHandle, transfer, (uint8_t *)param, 1);
            break;

        case USB_REQUEST_STANDARD_SET_CONFIGURATION: /* standard set configuration request */
            transfer->setupPacket->wValue = USB_SHORT_TO_LITTLE_ENDIAN(*((uint8_t *)param));
            status = USB_HostCh9RequestCommon((usb_host_device_instance_t *)deviceHandle, transfer, NULL, 0);
            break;

        case USB_REQUEST_STANDARD_GET_INTERFACE: /* standard get interface request */
            status = USB_HostStandardGetInterface(deviceInstance, transfer, param);
            break;

        case USB_REQUEST_STANDARD_SET_INTERFACE: /* standard set interface request */
            status = USB_HostStandardSetInterface(deviceInstance, transfer, param);
            break;

        case USB_REQUEST_STANDARD_SYNCH_FRAME: /* standard synch frame request */
            status = USB_HostStandardSyncFrame(deviceInstance, transfer, param);
            break;

        default:
            break;
    }

    return status;
}
