/*
 * Copyright (c) 2015 - 2016, 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_device_config.h"
#include "usb.h"

#include "usb_device.h"
#include "usb_device_ch9.h"
#include "usb_device_class.h"

#if ((defined(USB_DEVICE_CONFIG_NUM)) && (USB_DEVICE_CONFIG_NUM > 0U))
/* Include the class drivers according to the usb_device_config.h. */
#if ((defined(USB_DEVICE_CONFIG_HID)) && (USB_DEVICE_CONFIG_HID > 0U))
#include "usb_device_hid.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_CDC_ACM)) && (USB_DEVICE_CONFIG_CDC_ACM > 0U))
#include "usb_device_cdc_acm.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_MSC)) && (USB_DEVICE_CONFIG_MSC > 0U))
#include "usb_device_msc.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_AUDIO)) && (USB_DEVICE_CONFIG_AUDIO > 0U))
#include "usb_device_audio.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_PHDC)) && (USB_DEVICE_CONFIG_PHDC > 0U))
#include "usb_device_phdc.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_VIDEO)) && (USB_DEVICE_CONFIG_VIDEO > 0U))
#include "usb_device_video.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_PRINTER)) && (USB_DEVICE_CONFIG_PRINTER > 0U))
#include "usb_device_printer_config.h"
#include "usb_device_printer.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_DFU)) && (USB_DEVICE_CONFIG_DFU > 0U))
#include "usb_device_dfu_config.h"
#include "usb_device_dfu.h"
#endif

#if ((defined(USB_DEVICE_CONFIG_CCID)) && (USB_DEVICE_CONFIG_CCID > 0U))
#include "usb_device_ccid.h"
#endif

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

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static usb_status_t USB_DeviceClassAllocateHandle(uint8_t controllerId, usb_device_common_class_struct_t **handle);
static usb_status_t USB_DeviceClassFreeHandle(uint8_t controllerId);
static usb_status_t USB_DeviceClassGetHandleByControllerId(uint8_t controllerId,
                                                           usb_device_common_class_struct_t **handle);
static usb_status_t USB_DeviceClassGetHandleByDeviceHandle(usb_device_handle deviceHandle,
                                                           usb_device_common_class_struct_t **handle);

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

/* The device class driver list. */
static const usb_device_class_map_t s_UsbDeviceClassInterfaceMap[] = {
#if ((defined(USB_DEVICE_CONFIG_HID)) && (USB_DEVICE_CONFIG_HID > 0U))
    {USB_DeviceHidInit, USB_DeviceHidDeinit, USB_DeviceHidEvent, kUSB_DeviceClassTypeHid},
#endif

#if ((defined(USB_DEVICE_CONFIG_CDC_ACM)) && (USB_DEVICE_CONFIG_CDC_ACM > 0U))
    {USB_DeviceCdcAcmInit, USB_DeviceCdcAcmDeinit, USB_DeviceCdcAcmEvent, kUSB_DeviceClassTypeCdc},
#endif

#if ((defined(USB_DEVICE_CONFIG_MSC)) && (USB_DEVICE_CONFIG_MSC > 0U))
    {USB_DeviceMscInit, USB_DeviceMscDeinit, USB_DeviceMscEvent, kUSB_DeviceClassTypeMsc},
#endif

#if ((defined USB_DEVICE_CONFIG_AUDIO) && (USB_DEVICE_CONFIG_AUDIO > 0U))
    {USB_DeviceAudioInit, USB_DeviceAudioDeinit, USB_DeviceAudioEvent, kUSB_DeviceClassTypeAudio},
#endif

#if ((defined USB_DEVICE_CONFIG_PHDC) && (USB_DEVICE_CONFIG_PHDC > 0U))
    {USB_DevicePhdcInit, USB_DevicePhdcDeinit, USB_DevicePhdcEvent, kUSB_DeviceClassTypePhdc},
#endif

#if ((defined USB_DEVICE_CONFIG_VIDEO) && (USB_DEVICE_CONFIG_VIDEO > 0U))
    {USB_DeviceVideoInit, USB_DeviceVideoDeinit, USB_DeviceVideoEvent, kUSB_DeviceClassTypeVideo},
#endif

#if ((defined USB_DEVICE_CONFIG_PRINTER) && (USB_DEVICE_CONFIG_PRINTER > 0U))
    {usb_device_printer_init, usb_device_printer_deinit, usb_device_printer_event, kUSB_DeviceClassTypePrinter},
#endif

#if ((defined USB_DEVICE_CONFIG_DFU) && (USB_DEVICE_CONFIG_DFU > 0U))
    {usb_device_dfu_init, usb_device_dfu_deinit, usb_device_dfu_event, kUSB_DeviceClassTypeDfu},
#endif

#if ((defined USB_DEVICE_CONFIG_CCID) && (USB_DEVICE_CONFIG_CCID > 0U))
    {USB_DeviceCcidInit, USB_DeviceCcidDeinit, USB_DeviceCcidEvent, kUSB_DeviceClassTypeCcid},
#endif

    {(usb_device_class_init_call_t)NULL, (usb_device_class_deinit_call_t)NULL, (usb_device_class_event_callback_t)NULL,
     (usb_device_class_type_t)0},
};

USB_GLOBAL static usb_device_common_class_struct_t s_UsbDeviceCommonClassStruct[USB_DEVICE_CONFIG_NUM];

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

/*!
 * @brief Allocate a device common class handle.
 *
 * This function allocates a a device common class handle.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 * @param handle          It is out parameter, is used to return pointer of the device common class handle to the
 * caller.
 *
 * @retval kStatus_USB_Success              Get a device handle successfully.
 * @retval kStatus_USB_Busy                 Cannot allocate a common class handle.
 * @retval kStatus_USB_Error                The common class has been initialized.
 */
static usb_status_t USB_DeviceClassAllocateHandle(uint8_t controllerId, usb_device_common_class_struct_t **handle)
{
    int32_t count;
    USB_OSA_SR_ALLOC();

    USB_OSA_ENTER_CRITICAL();
    /* Check the controller is initialized or not. */
    for (count = 0U; count < USB_DEVICE_CONFIG_NUM; count++)
    {
        if ((NULL != s_UsbDeviceCommonClassStruct[count].handle) &&
            (controllerId == s_UsbDeviceCommonClassStruct[count].controllerId))
        {
            USB_OSA_EXIT_CRITICAL();
            return kStatus_USB_Error;
        }
    }
    /* Get a free common class handle. */
    for (count = 0U; count < USB_DEVICE_CONFIG_NUM; count++)
    {
        if (NULL == s_UsbDeviceCommonClassStruct[count].handle)
        {
            s_UsbDeviceCommonClassStruct[count].controllerId = controllerId;
            *handle = &s_UsbDeviceCommonClassStruct[count];
            USB_OSA_EXIT_CRITICAL();
            return kStatus_USB_Success;
        }
    }

    USB_OSA_EXIT_CRITICAL();
    return kStatus_USB_Busy;
}

/*!
 * @brief Free a device common class handle.
 *
 * This function frees a device common class handle.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 *
 * @retval kStatus_USB_Success              Free device hanlde successfully.
 * @retval kStatus_USB_InvalidParameter     The common class can not be found.
 */
static usb_status_t USB_DeviceClassFreeHandle(uint8_t controllerId)
{
    int32_t count = 0U;
    USB_OSA_SR_ALLOC();

    USB_OSA_ENTER_CRITICAL();
    for (; count < USB_DEVICE_CONFIG_NUM; count++)
    {
        if ((NULL != s_UsbDeviceCommonClassStruct[count].handle) &&
            (controllerId == s_UsbDeviceCommonClassStruct[count].controllerId))
        {
            s_UsbDeviceCommonClassStruct[count].handle = NULL;
            s_UsbDeviceCommonClassStruct[count].configList = (usb_device_class_config_list_struct_t *)NULL;
            s_UsbDeviceCommonClassStruct[count].controllerId = 0U;
            USB_OSA_EXIT_CRITICAL();
            return kStatus_USB_Success;
        }
    }
    USB_OSA_EXIT_CRITICAL();

    return kStatus_USB_InvalidParameter;
}

/*!
 * @brief Get the device common class handle according to the controller id.
 *
 * This function gets the device common class handle according to the controller id.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 * @param handle          It is out parameter, is used to return pointer of the device common class handle to the
 * caller.
 *
 * @retval kStatus_USB_Success              Free device hanlde successfully.
 * @retval kStatus_USB_InvalidParameter     The common class can not be found.
 */
static usb_status_t USB_DeviceClassGetHandleByControllerId(uint8_t controllerId,
                                                           usb_device_common_class_struct_t **handle)
{
    int32_t count = 0U;
    USB_OSA_SR_ALLOC();

    USB_OSA_ENTER_CRITICAL();
    for (; count < USB_DEVICE_CONFIG_NUM; count++)
    {
        if ((NULL != s_UsbDeviceCommonClassStruct[count].handle) &&
            (controllerId == s_UsbDeviceCommonClassStruct[count].controllerId))
        {
            *handle = &s_UsbDeviceCommonClassStruct[count];
            USB_OSA_EXIT_CRITICAL();
            return kStatus_USB_Success;
        }
    }
    USB_OSA_EXIT_CRITICAL();
    return kStatus_USB_InvalidParameter;
}

/*!
 * @brief Get the device common class handle according to the device handle.
 *
 * This function gets the device common class handle according to the device handle.
 *
 * @param deviceHandle          The device handle, got from the USB_DeviceInit.
 * @param handle                 It is out parameter, is used to return pointer of the device common class handle to the
 * caller.
 *
 * @retval kStatus_USB_Success              Free device hanlde successfully.
 * @retval kStatus_USB_InvalidParameter     The common class can not be found.
 */
static usb_status_t USB_DeviceClassGetHandleByDeviceHandle(usb_device_handle deviceHandle,
                                                           usb_device_common_class_struct_t **handle)
{
    int32_t count = 0U;
    USB_OSA_SR_ALLOC();

    USB_OSA_ENTER_CRITICAL();
    for (; count < USB_DEVICE_CONFIG_NUM; count++)
    {
        if (deviceHandle == s_UsbDeviceCommonClassStruct[count].handle)
        {
            *handle = &s_UsbDeviceCommonClassStruct[count];
            USB_OSA_EXIT_CRITICAL();
            return kStatus_USB_Success;
        }
    }
    USB_OSA_EXIT_CRITICAL();
    return kStatus_USB_InvalidParameter;
}

/*!
 * @brief Get the device handle according to the controller id.
 *
 * This function gets the device handle according to the controller id.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 * @param handle          It is out parameter, is used to return pointer of the device handle to the caller.
 *
 * @retval kStatus_USB_Success              Free device hanlde successfully.
 * @retval kStatus_USB_InvalidParameter     The device handle not be found.
 */
usb_status_t USB_DeviceClassGetDeviceHandle(uint8_t controllerId, usb_device_handle *handle)
{
    int32_t count = 0U;
    USB_OSA_SR_ALLOC();

    USB_OSA_ENTER_CRITICAL();
    for (; count < USB_DEVICE_CONFIG_NUM; count++)
    {
        if ((NULL != s_UsbDeviceCommonClassStruct[count].handle) &&
            (controllerId == s_UsbDeviceCommonClassStruct[count].controllerId))
        {
            *handle = s_UsbDeviceCommonClassStruct[count].handle;
            USB_OSA_EXIT_CRITICAL();
            return kStatus_USB_Success;
        }
    }
    USB_OSA_EXIT_CRITICAL();
    return kStatus_USB_InvalidParameter;
}

/*!
 * @brief Handle the event passed to the class drivers.
 *
 * This function handles the event passed to the class drivers.
 *
 * @param handle          The device handle, got from the USB_DeviceInit.
 * @param event           The event codes. Please refer to the enumeration usb_device_class_event_t.
 * @param param           The param type is determined by the event code.
 *
 * @return A USB error code or kStatus_USB_Success.
 * @retval kStatus_USB_Success              A valid request has been handled.
 * @retval kStatus_USB_InvalidParameter     The device handle not be found.
 * @retval kStatus_USB_InvalidRequest       The request is invalid, and the control pipe will be stalled by the caller.
 */
usb_status_t USB_DeviceClassEvent(usb_device_handle handle, usb_device_class_event_t event, void *param)
{
    usb_device_common_class_struct_t *classHandle;
    uint8_t mapIndex;
    uint8_t classIndex;
    usb_status_t errorReturn = kStatus_USB_Error;
    usb_status_t error = kStatus_USB_Error;

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

    /* Get the common class handle according to the device handle. */
    errorReturn = USB_DeviceClassGetHandleByDeviceHandle(handle, &classHandle);
    if (kStatus_USB_Success != errorReturn)
    {
        return kStatus_USB_InvalidParameter;
    }

    for (classIndex = 0U; classIndex < classHandle->configList->count; classIndex++)
    {
        for (mapIndex = 0U; mapIndex < (sizeof(s_UsbDeviceClassInterfaceMap) / sizeof(usb_device_class_map_t));
             mapIndex++)
        {
            if (s_UsbDeviceClassInterfaceMap[mapIndex].type ==
                classHandle->configList->config[classIndex].classInfomation->type)
            {
                /* Call class event callback of supported class */
                errorReturn = s_UsbDeviceClassInterfaceMap[mapIndex].classEventCallback(
                    (void *)classHandle->configList->config[classIndex].classHandle, event, param);
                /* Return the error code kStatus_USB_InvalidRequest immediately, when a class returns
                 * kStatus_USB_InvalidRequest. */
                if (kStatus_USB_InvalidRequest == errorReturn)
                {
                    return kStatus_USB_InvalidRequest;
                }
                /* For composite device, it should return kStatus_USB_Success once a valid request has been handled */
                if (kStatus_USB_Success == errorReturn)
                {
                    error = kStatus_USB_Success;
                }
                break;
            }
        }
    }

    return error;
}

/*!
 * @brief Handle the common class callback.
 *
 * This function handles the common class callback.
 *
 * @param handle          The device handle, got from the USB_DeviceInit.
 * @param event           The event codes. Please refer to the enumeration usb_device_event_t.
 * @param param           The param type is determined by the event code.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceClassCallback(usb_device_handle handle, uint32_t event, void *param)
{
    usb_device_common_class_struct_t *classHandle;
    usb_status_t error = kStatus_USB_Error;

    /* Get the common class handle according to the device handle. */
    error = USB_DeviceClassGetHandleByDeviceHandle(handle, &classHandle);
    if (kStatus_USB_Success != error)
    {
        return error;
    }

    if (kUSB_DeviceEventBusReset == event)
    {
        /* Initialize the control pipes */
        USB_DeviceControlPipeInit(handle, classHandle);

        /* Notify the classes the USB bus reset signal detected. */
        USB_DeviceClassEvent(handle, kUSB_DeviceClassEventDeviceReset, classHandle);
    }

    /* Call the application device callback function. */
    error = classHandle->configList->deviceCallback(handle, event, param);
    return error;
}

/*!
 * @brief Initialize the common class and the supported classes.
 *
 * This function is used to initialize the common class and the supported classes.
 *
 * @param[in] controllerId   The controller id of the USB IP. Please refer to the enumeration #usb_controller_index_t.
 * @param[in] configList     The class configurations. The pointer must point to the goblal variable.
 *                           Please refer to the structure #usb_device_class_config_list_struct_t.
 * @param[out] handle        It is out parameter, is used to return pointer of the device handle to the caller.
 *                           The value of parameter is a pointer points the device handle, and this design is uesd to
 *                           make simple device align with composite device. For composite device, there are many
 *                           kinds of class handle, but there is only one device handle. So the handle points to
 *                           a device instead of a class. And the class handle can be got from the
 *                           #usb_device_class_config_struct_t::classHandle after the the function successfully.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceClassInit(
    uint8_t controllerId,                              /*!< [IN] Controller ID */
    usb_device_class_config_list_struct_t *configList, /*!< [IN] Pointer to class configuration list */
    usb_device_handle *handle                          /*!< [OUT] Pointer to the device handle */
    )
{
    usb_device_common_class_struct_t *classHandle;
    usb_status_t error = kStatus_USB_Error;
    uint8_t mapIndex;
    uint8_t classIndex;

    if ((NULL == handle) || (NULL == configList) || ((usb_device_callback_t)NULL == configList->deviceCallback))
    {
        return kStatus_USB_InvalidParameter;
    }

    /* Allocate a common class driver handle. */
    error = USB_DeviceClassAllocateHandle(controllerId, &classHandle);
    if (kStatus_USB_Success != error)
    {
        return error;
    }
    /* Save the configuration list */
    classHandle->configList = configList;

    /* Initialize the device stack. */
    error = USB_DeviceInit(controllerId, USB_DeviceClassCallback, &classHandle->handle);

    if (kStatus_USB_Success != error)
    {
        USB_DeviceDeinit(classHandle->handle);
        USB_DeviceClassFreeHandle(controllerId);
        return error;
    }

    /* Initialize the all supported classes according to the configuration list. */
    for (classIndex = 0U; classIndex < classHandle->configList->count; classIndex++)
    {
        for (mapIndex = 0U; mapIndex < (sizeof(s_UsbDeviceClassInterfaceMap) / sizeof(usb_device_class_map_t));
             mapIndex++)
        {
            if (classHandle->configList->config[classIndex].classInfomation->type ==
                s_UsbDeviceClassInterfaceMap[mapIndex].type)
            {
                (void)s_UsbDeviceClassInterfaceMap[mapIndex].classInit(
                    controllerId, &classHandle->configList->config[classIndex],
                    &classHandle->configList->config[classIndex].classHandle);
            }
        }
    }

    *handle = classHandle->handle;
    return error;
}

/*!
 * @brief De-initialize the common class and the supported classes.
 *
 * This function is used to de-initialize the common class and the supported classes.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceClassDeinit(uint8_t controllerId /*!< [IN] Controller ID */
                                   )
{
    usb_device_common_class_struct_t *classHandle;
    usb_status_t error = kStatus_USB_Error;
    uint8_t mapIndex;
    uint8_t classIndex;

    /* Get the common class handle according to the controller id. */
    error = USB_DeviceClassGetHandleByControllerId(controllerId, &classHandle);

    if (kStatus_USB_Success != error)
    {
        return error;
    }

    /* De-initialize the all supported classes according to the configuration list. */
    for (classIndex = 0U; classIndex < classHandle->configList->count; classIndex++)
    {
        for (mapIndex = 0U; mapIndex < (sizeof(s_UsbDeviceClassInterfaceMap) / sizeof(usb_device_class_map_t));
             mapIndex++)
        {
            if (classHandle->configList->config[classIndex].classInfomation->type ==
                s_UsbDeviceClassInterfaceMap[mapIndex].type)
            {
                (void)s_UsbDeviceClassInterfaceMap[mapIndex].classDeinit(
                    classHandle->configList->config[classIndex].classHandle);
            }
        }
    }

    /* De-initialize the USB device stack. */
    error = USB_DeviceDeinit(classHandle->handle);
    if (kStatus_USB_Success == error)
    {
        /* Free the common class handle. */
        (void)USB_DeviceClassFreeHandle(controllerId);
    }
    return error;
}

/*!
 * @brief Get the USB bus speed.
 *
 * This function is used to get the USB bus speed.
 *
 * @param controllerId   The controller id of the USB IP. Please refer to the enumeration usb_controller_index_t.
 * @param speed           It is an OUT parameter, return current speed of the controller.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceClassGetSpeed(uint8_t controllerId, /*!< [IN] Controller ID */
                                     uint8_t *speed        /*!< [OUT] Current speed */
                                     )
{
    usb_device_common_class_struct_t *classHandle;
    usb_status_t error = kStatus_USB_Error;

    /* Get the common class handle according to the controller id. */
    error = USB_DeviceClassGetHandleByControllerId(controllerId, &classHandle);

    if (kStatus_USB_Success != error)
    {
        return error;
    }

    /* Get the current speed. */
    error = USB_DeviceGetStatus(classHandle->handle, kUSB_DeviceStatusSpeed, speed);

    return error;
}
#endif /* USB_DEVICE_CONFIG_NUM */
