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

#include "usb_device_config.h"
#include "usb.h"
#include "usb_device.h"

#include "usb_device_class.h"
#include "usb_device_hid.h"
#include "usb_device_audio.h"
#include "usb_audio_config.h"
#include "usb_device_ch9.h"
#include "usb_device_descriptor.h"

#include "composite.h"

#include "fsl_device_registers.h"
#include "clock_config.h"
#include "board.h"
#include "fsl_debug_console.h"

#include <stdio.h>
#include <stdlib.h>
#if (defined(FSL_FEATURE_SOC_SYSMPU_COUNT) && (FSL_FEATURE_SOC_SYSMPU_COUNT > 0U))
#include "fsl_sysmpu.h"
#endif /* FSL_FEATURE_SOC_SYSMPU_COUNT */

#if ((defined FSL_FEATURE_SOC_USBPHY_COUNT) && (FSL_FEATURE_SOC_USBPHY_COUNT > 0U))
#include "usb_phy.h"
#endif

#include "fsl_common.h"
#include "pin_mux.h"
#include "fsl_wm8904.h"

#include "fsl_codec_adapter.h"
#include "fsl_codec_common.h"

#include "fsl_sai.h"
#include "audio.h"
#include "usb_feedback.h"
#include "audio_mixer.h"
#include "audio_unified.h"
#include "audio_common.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define BOARD_I2S_DEMO_I2C_BASEADDR     (I2C1)
#define DEMO_I2S_MASTER_CLOCK_FREQUENCY (24576000)

#define DEMO_I2S_TX                     (I2S0)
#define DEMO_I2S_RX                     (I2S0)
#define DEMO_DMA                        (DMA0)
#define DEMO_I2S_TX_CHANNEL             (0)
#define DEMO_I2S_RX_CHANNEL             (2)


typedef enum _class_config_index {

    kCLASS_CONFIG_INDEX_Hid,              /**< HID interface index */

#if USB_CHAT_SPEAKER_ENABLE
    kCLASS_CONFIG_INDEX_AudioChatSpeaker, /**< Audio chat speaker interface index */
#endif
#if USB_GAME_SPEAKER_ENABLE
    kCLASS_CONFIG_INDEX_AudioGameSpeaker, /**< Audio game speaker interface index */
#endif
#if USB_RECORDER_ENABLE
    kCLASS_CONFIG_INDEX_AudioRecorder,    /**< Audio recorder interface index */
#endif
#if USB_HCI_VCOM_ENABLE
    kCLASS_CONFIG_INDEX_VcomHci,          /**< VCOM HCI interface index */
#endif
#if SERIAL_PORT_TYPE_USBCDC
    kCLASS_CONFIG_INDEX_VcomDebug,        /**< VCOM debug interface index */
#endif
} class_config_index_t;

#define COMPOSITE_CLASS_CONFIG_COUNT \
    (USB_DEVICE_CONFIG_HID + USB_DEVICE_CONFIG_AUDIO + USB_DEVICE_CONFIG_CDC_ACM)
/*******************************************************************************
 * Prototypes
 ******************************************************************************/
void BOARD_InitHardware(void);
void USB_DeviceClockInit(void);
void USB_DeviceIsrEnable(void);
#if USB_DEVICE_CONFIG_USE_TASK
void USB_DeviceTaskFn(void *deviceHandle);
#endif

#if defined(AUDIO_DATA_SOURCE_DMIC) && (AUDIO_DATA_SOURCE_DMIC > 0U)
extern void Board_DMIC_DMA_Init(void);
#endif

usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param);
extern void Init_Board_Audio(void);

extern char *SW_GetName(void);
extern void USB_AudioCodecTask(void);
extern void USB_DeviceAudioSpeakerStatusReset(void);
extern void USB_DeviceAudioRecorderStatusReset(void);
extern usb_status_t USB_DeviceAudioRecorderSetInterface(usb_device_handle handle,
                                                        uint8_t interface,
                                                        uint8_t alternateSetting);
extern usb_status_t USB_DeviceAudioSpeakerSetInterface(usb_device_handle handle,
                                                       uint8_t interface,
                                                       uint8_t alternateSetting);

extern usb_status_t USB_DeviceAudioCompositeSetConfigure(class_handle_t handle, uint8_t configure);
extern usb_status_t USB_DeviceHidConsumerControlSetConfigure(class_handle_t handle, uint8_t configure);

extern usb_status_t USB_DeviceAudioCompositeInit(usb_device_composite_struct_t *device_composite);
extern usb_status_t USB_DeviceHidConsumerControlInit(usb_device_composite_struct_t *deviceComposite);

extern void USB_AudioSpeakerResetTask(void);
extern usb_status_t USB_DeviceAudioProcessTerminalRequest(uint32_t audioCommand, uint32_t *length, uint8_t **buffer, uint8_t entityOrEndpoint);
/*******************************************************************************
 * Variables
 ******************************************************************************/
extern usb_device_composite_struct_t g_composite;
extern uint8_t audioPlayDataBuff[AUDIO_SPEAKER_DATA_WHOLE_BUFFER_LENGTH * FS_ISO_OUT_ENDP_PACKET_SIZE(AUDIO_OUT_FORMAT_SIZE)];
extern uint8_t audioRecDataBuff[AUDIO_RECORDER_DATA_WHOLE_BUFFER_LENGTH * FS_ISO_IN_ENDP_PACKET_SIZE];
volatile bool g_ButtonPress = false;
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
static uint8_t audioPlayDMATempBuff[FS_ISO_OUT_ENDP_PACKET_SIZE(AUDIO_OUT_FORMAT_SIZE)];
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
static uint8_t audioRecDMATempBuff[FS_ISO_IN_ENDP_PACKET_SIZE];
codec_handle_t codecHandle;
wm8904_config_t wm8904Config = {
    .i2cConfig    = {.codecI2CInstance = BOARD_CODEC_I2C_INSTANCE, .codecI2CSourceClock = 48000000},
    .recordSource = kWM8904_RecordSourceLineInput,
    .recordChannelLeft  = kWM8904_RecordChannelLeft2,
    .recordChannelRight = kWM8904_RecordChannelRight2,
    .playSource         = kWM8904_PlaySourceDAC,
    .slaveAddress       = WM8904_I2C_ADDRESS,
    .protocol           = kWM8904_ProtocolI2S,
    .format             = {.sampleRate = kWM8904_SampleRate48kHz, .bitWidth = kWM8904_BitWidth24},
    .mclk_HZ            = DEMO_I2S_MASTER_CLOCK_FREQUENCY,
    .master             = false,
};
codec_config_t boardCodecConfig = {.codecDevType = kCODEC_WM8904, .codecDevConfig = &wm8904Config};

UsbFeedback_t g_UsbChatSpeaker_FeedbackSettings;

/* Composite device structure. */
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE)
usb_device_composite_struct_t g_composite;
extern volatile bool g_ButtonPress;
extern usb_device_composite_struct_t *g_UsbDeviceComposite;
extern usb_device_composite_struct_t *g_deviceComposite;
extern uint8_t g_UsbDeviceInterface[USB_COMPOSITE_INTERFACE_COUNT];
extern uint32_t totalCount;
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) static uint8_t s_SetupOutBuffer[8];

extern void WM8904_USB_Audio_Init(void *I2CBase);
void WM8904_Config_Audio_Formats(uint32_t samplingRate);

void gint0_callback(void)
{
    g_ButtonPress = true;
}


char *SW_GetName(void)
{
    return BOARD_SW1_NAME;
}


void BOARD_Codec_Init()
{
    CODEC_Init(&codecHandle, &boardCodecConfig);
    CODEC_SetVolume(&codecHandle, kCODEC_PlayChannelHeadphoneLeft | kCODEC_PlayChannelHeadphoneRight , 0x0020);
    /* ZY NxH3670 Board: set WM8904 GPIO1 as logic 1 to enable 12.288M osc  */
    WM8904_WriteRegister((wm8904_handle_t *)codecHandle.codecDevHandle, WM8904_GPIO_CONTROL_1, 0x0003);
}

void BOARD_SetCodecMuteUnmute(bool mute)
{
    CODEC_SetMute(&codecHandle, kCODEC_PlayChannelHeadphoneLeft | kCODEC_PlayChannelHeadphoneRight, mute);
}

void I2S_USB_Audio_TxInit(I2S_Type *SAIBase)
{

}

void I2S_USB_Audio_RxInit(I2S_Type *SAIBase)
{

}

void BOARD_USB_Audio_TxRxInit(uint32_t samplingRate)
{
    audio_Init();
    audio_Start();
}


/*Initialize audio interface and codec.*/
void Init_Board_Audio(void)
{
    usb_echo("Init Audio I2S and CODEC\r\n");

    BOARD_USB_Audio_TxRxInit(AUDIO_SAMPLING_RATE);
    BOARD_Codec_Init();
}




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

void USB0_IRQHandler(void)
{
    USB_DeviceKhciIsrFunction(g_composite.deviceHandle);
}
void USB_DeviceClockInit(void)
{
    SystemCoreClockUpdate();
    CLOCK_EnableUsbfs0Clock(kCLOCK_UsbSrcIrc48M, 48000000U);
}
void USB_DeviceIsrEnable(void)
{
    uint8_t irqNumber;

    uint8_t usbDeviceKhciIrq[] = USB_IRQS;
    irqNumber                  = usbDeviceKhciIrq[CONTROLLER_ID - kUSB_ControllerKhci0];

    /* Install isr, set priority, and enable IRQ. */
    NVIC_SetPriority((IRQn_Type)irqNumber, USB_DEVICE_INTERRUPT_PRIORITY);
    EnableIRQ((IRQn_Type)irqNumber);
}
#if USB_DEVICE_CONFIG_USE_TASK
void USB_DeviceTaskFn(void *deviceHandle)
{
    USB_DeviceKhciTaskFunction(deviceHandle);
}
#endif

/*!
 * @brief USB device callback function.
 *
 * This function handles the usb device specific requests.
 *
 * @param handle          The USB device handle.
 * @param event           The USB device event type.
 * @param param           The parameter of the device specific request.
 *
 * @return A USB error code or kStatus_USB_Success.
 */
usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param)
{
    usb_status_t error = kStatus_USB_Error;
    uint16_t *temp16   = (uint16_t *)param;
    uint8_t *temp8     = (uint8_t *)param;
    uint8_t count      = 0U;

    switch (event)
    {
        case kUSB_DeviceEventBusReset:
        {
            /* USB bus reset signal detected */
            /* Initialize the control IN and OUT pipes */

            for (count = 0U; count < USB_DEVICE_INTERFACE_COUNT; count++)
            {
                g_composite.currentInterfaceAlternateSetting[count] = 0U;
            }
            //USB_DeviceControlPipeInit(handle,NULL);
            g_composite.attach               = 0U;
            g_composite.currentConfiguration = 0U;

        }
        break;
        case kUSB_DeviceEventSetConfiguration:
            if (0U == (*temp8))
            {
                g_composite.attach               = 0U;
                g_composite.currentConfiguration = 0U;
            }
            else if (USB_COMPOSITE_CONFIGURE_INDEX == (*temp8))
            {
                g_composite.attach               = 1U;

                 USB_DeviceAudioCompositeSetConfigure((class_handle_t)NULL, *temp8);
                 USB_DeviceHidConsumerControlSetConfigure((class_handle_t)NULL, *temp8);
                 g_composite.currentConfiguration = *temp8;
                error = kStatus_USB_Success;
            }
            else
            {
                error = kStatus_USB_InvalidRequest;
            }
            break;
        case kUSB_DeviceEventSetInterface:
            if (g_composite.attach)
            {
                uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
                uint8_t alternateSetting = (uint8_t)(*temp16 & 0x00FFU);
                
                if (USB_AUDIO_RECORDER_STREAM_INTERFACE_INDEX == interface)
                {
                    error = USB_DeviceAudioRecorderSetInterface(g_composite.audioUnified.audioRecorderHandle,
                                                                interface,
                                                                alternateSetting);
                }
                else if (USB_AUDIO_SPEAKER_STREAM_INTERFACE_INDEX == interface)
                {
                    error = USB_DeviceAudioSpeakerSetInterface(g_composite.audioUnified.audioChatSpeakerHandle,
                                                               interface,
                                                               alternateSetting);
                }
                if (kStatus_USB_Success == error) {
                    /* Update current alternate setting if succesfull */
                    g_composite.currentInterfaceAlternateSetting[interface] = alternateSetting;
                }
                error = kStatus_USB_Success;
            }
            break;
        case kUSB_DeviceEventGetConfiguration:
            if (param)
            {
                *temp8 = g_composite.currentConfiguration;
                error  = kStatus_USB_Success;
            }
            break;
        case kUSB_DeviceEventGetInterface:
            if (param)
            {
                uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
                if (interface < USB_DEVICE_INTERFACE_COUNT)
                {
                    *temp16 = (*temp16 & 0xFF00U) | g_composite.currentInterfaceAlternateSetting[interface];
                    error   = kStatus_USB_Success;
                }
                else
                {
                    error = kStatus_USB_InvalidRequest;
                }
            }
            break;
        case kUSB_DeviceEventGetDeviceDescriptor:
            if (param)
            {
                error = USB_DeviceGetDeviceDescriptor(handle, (usb_device_get_device_descriptor_struct_t *)param);
            }
            break;
        case kUSB_DeviceEventGetConfigurationDescriptor:
            if (param)
            {
                error = USB_DeviceGetConfigurationDescriptor(handle,
                                                             (usb_device_get_configuration_descriptor_struct_t *)param);
            }
            break;
        case kUSB_DeviceEventGetStringDescriptor:
            if (param)
            {
                error = USB_DeviceGetStringDescriptor(handle, (usb_device_get_string_descriptor_struct_t *)param);
            }
            break;
        case kUSB_DeviceEventGetHidReportDescriptor:
            if (param)
            {
                error =
                    USB_DeviceGetHidReportDescriptor(handle, (usb_device_get_hid_report_descriptor_struct_t *)param);
            }
            break;
#if (defined(USB_DEVICE_CONFIG_CV_TEST) && (USB_DEVICE_CONFIG_CV_TEST > 0U))
        case kUSB_DeviceEventGetDeviceQualifierDescriptor:
            if (param)
            {
                /* Get device descriptor request */
                error = USB_DeviceGetDeviceQualifierDescriptor(
                    handle, (usb_device_get_device_qualifier_descriptor_struct_t *)param);
            }
            break;
#endif
        default:
            __NOP();
            break;
    }

    return error;
}

extern usb_device_class_struct_t g_UsbDeviceAudioClassChatSpeaker;
extern usb_device_class_struct_t g_UsbDeviceHidConsumerControlClass;
extern usb_device_class_struct_t g_UsbDeviceAudioClassRecorder;
static usb_device_class_config_struct_t g_CompositeClassConfig[COMPOSITE_CLASS_CONFIG_COUNT] = {{
                                                                         USB_DeviceHidConsumerControlCallback,
                                                                         (class_handle_t)NULL,
                                                                         &g_UsbDeviceHidConsumerControlClass,
                                                                     },
                                                                     {
                                                                         USB_DeviceAudioCompositeCallback,
                                                                         (class_handle_t)&g_composite.audioUnified.audioChatSpeakerHandle,
                                                                         &g_UsbDeviceAudioClassChatSpeaker,
                                                                     },
                                                                     {
                                                                         USB_DeviceAudioCompositeCallback,
                                                                         (class_handle_t)&g_composite.audioUnified.audioRecorderHandle,
                                                                         &g_UsbDeviceAudioClassRecorder,
                                                                     }

};

/* USB device class configuration information */
static usb_device_class_config_list_struct_t g_UsbDeviceCompositeConfigList = {
    g_CompositeClassConfig,
    USB_DeviceCallback,
    3,
};

static uint32_t GetFillingChat(void)
{
    return AUDIO_MIXER_GetBufferFilling(kUSB_SPEAKER_INSTANCE_Chat);
}


static void COMPOSITE_FeedbackInit(void)
{
#if USB_CHAT_SPEAKER_ENABLE
    USB_FeedbackInit(
        &g_UsbChatSpeaker_FeedbackSettings,
        AUDIO_MIXER_GetBufferSize(kUSB_SPEAKER_INSTANCE_Chat),
        AUDIO_SERVICE_SYSTEM_SAMPLING_RATE,
        AUDIO_APP_FEEDBACK_UPDATE_MS,
        GetFillingChat
    );
#endif
#if USB_GAME_SPEAKER_ENABLE
    USB_FeedbackInit(
        &g_UsbGameSpeaker_FeedbackSettings,
        AUDIO_MIXER_GetBufferSize(kUSB_SPEAKER_INSTANCE_Game),
        AUDIO_SERVICE_SYSTEM_SAMPLING_RATE,
        AUDIO_APP_FEEDBACK_UPDATE_MS,
        GetFillingGame
    );
#endif
}



/*!
 * @brief Application initialization function.
 *
 * This function initializes the application.
 *
 * @return None.
 */
void APPInit(void)
{
    USB_DeviceClockInit();
#if (defined(FSL_FEATURE_SOC_SYSMPU_COUNT) && (FSL_FEATURE_SOC_SYSMPU_COUNT > 0U))
    SYSMPU_Enable(SYSMPU, 0);
#endif /* FSL_FEATURE_SOC_SYSMPU_COUNT */

    g_composite.speed        = USB_SPEED_FULL;
    g_composite.attach       = 0U;
    g_composite.deviceHandle = NULL;
    
    g_composite.audioUnified.audioChatSpeakerHandle = (class_handle_t)NULL;
    g_composite.audioUnified.audioRecorderHandle = (class_handle_t)NULL;
    g_composite.hidConsumerControlCtxt.hidHandle = (class_handle_t)NULL;
     

    if (kStatus_USB_Success != 
        USB_DeviceClassInit(CONTROLLER_ID, &g_UsbDeviceCompositeConfigList, &g_composite.deviceHandle))
    {
        usb_echo("USB device composite demo init failed\r\n");
        return;
    }
    else
    {
        usb_echo("USB device composite demo\r\n");
        usb_echo("Please Press  switch(%s) to mute/unmute device audio speaker.\r\n", SW_GetName());

        
        g_composite.audioUnified.audioChatSpeakerHandle  = g_UsbDeviceCompositeConfigList.config[kCLASS_CONFIG_INDEX_AudioChatSpeaker].classHandle;
        g_composite.audioUnified.audioRecorderHandle = g_UsbDeviceCompositeConfigList.config[kCLASS_CONFIG_INDEX_AudioRecorder].classHandle;
        USB_DeviceAudioCompositeInit(&g_composite);
        
        g_composite.hidConsumerControlCtxt.hidHandle = g_UsbDeviceCompositeConfigList.config[kCLASS_CONFIG_INDEX_Hid].classHandle;
        USB_DeviceHidConsumerControlInit(&g_composite);

    }

    Init_Board_Audio();

    COMPOSITE_FeedbackInit();
    
    USB_DeviceIsrEnable();

    /*Add one delay here to make the DP pull down long enough to allow host to detect the previous disconnection.*/
    SDK_DelayAtLeastUs(5000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
    USB_DeviceRun(g_composite.deviceHandle);
}

#if defined(__CC_ARM) || (defined(__ARMCC_VERSION)) || defined(__GNUC__)
int main(void)
#else
void main(void)
#endif
{
    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();

    APPInit();

    while (1)
    {
#if USB_DEVICE_CONFIG_USE_TASK
        USB_DeviceTaskFn(g_composite.deviceHandle);
#endif
    }
}
