/**
  *********************************************************************************
  *
  * @file    ald_usb.c
  * @brief   USB module driver.
  *
  * @version V1.0
  * @date    25 Dec 2019
  * @author  AE Team
  * @note
  *          Change Logs:
  *          Date            Author          Notes
  *          25 Dec 2019     AE Team         The first version
  *
  * Copyright (C) Shanghai Eastsoft Microelectronics Co. Ltd. All rights reserved.
  *
  * SPDX-License-Identifier: Apache-2.0
  *
  * Licensed under the Apache License, Version 2.0 (the License); you may
  * not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an AS IS BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  **********************************************************************************
  */

#include "ald_conf.h"

/** @addtogroup ES32FXXX_ALD
  * @{
  */

/** @defgroup USB USB
  * @brief USB module driver
  * @{
  */
#ifdef ALD_USB
/**
  * @defgroup USB_Public_Functions USB Public Function
  * @{
  */
/** @defgroup USB_Public_Functions_Group1 Base functions
  * @brief Base functions
  * @{
  */

/**
  * @brief  Gets the number of current frame.
  * @retval Number of the frame.
  */
uint32_t ald_usb_frame_number_get(void)
{
	return USB0->FRAME;
}

/**
  * @brief  Request the session.
  * @param  start: true/false.
  * @retval None
  */
void ald_usb_otg_session_request(bool start)
{
	if (start)
		USB0->DEVCTL |= USB_DEVCTL_SESSION;
	else
		USB0->DEVCTL &= ~(USB_DEVCTL_SESSION);
}

/**
  * @brief  Gets the mode.
  * @retval Mode
  */
uint32_t ald_usb_mode_get(void)
{
	return USB0->DEVCTL & (USB_DEVCTL_DEV | USB_DEVCTL_HOST
			 | USB_DEVCTL_SESSION | USB_DEVCTL_VBUS_M);
}

/**
  * @brief  Enable/Disable the high mode.
  * @param  enable: ENABLE/DISABLE.
  * @retval None
  */
void ald_usb_high_speed_enable(bool enable)
{
	if (enable)
		USB0->POWER |= USB_POWER_HS_EN;
	else
		USB0->POWER &= ~(USB_POWER_HS_EN);
}

/**
  * @brief  Gets the speed of the device.
  * @retval Type of the speed.
  */
uint32_t ald_usb_device_speed_get(void)
{
	if (USB0->POWER & USB_POWER_HS_EN)
		return USB_HIGH_SPEED;

	return USB_FULL_SPEED;
}

/**
  * @brief  Gets the number of the endpoint.
  * @retval Number of the endpoint.
  */
uint32_t ald_usb_num_ep_get( void)
{
	return NUM_USB_EP;
}

/**
  * @brief  Reset USB Control.
  * @retval None
  */
void ald_usb_control_reset(void)
{
	ald_rmu_reset_periperal(RMU_PERH_USB);
}

/**
  * @brief  Output USB clock via PA15, 60MHz/256=234.375KHz.
  * @retval None
  */
void ald_usb_clock_output(void)
{
	SYSCFG_UNLOCK();
	SYSCFG->TESTKEY = 0x5A962814;
	SYSCFG->TESTKEY = 0xE7CB69A5;
	SYSCFG->USBTEST = 0x43;

	return;
}

/**
  * @brief  Starts eye diagram for high-speed host.
  * @param  buf: Buffer for eye diagram.
  * @param  len: Length of the buffer.
  * @retval Status, 0 means success, other values means failure.
  */
int ald_usb_eye_diagram_start(uint8_t *buf, uint16_t len)
{
	if (len < 53)
		return -1;

	ald_usb_ep_data_put(0, buf, 53);
	USB0->TEST = 0x08 | 0x90;
	ald_delay_ms(20);
	USB0->CSR0L = USB_CSRL0_TXRDY;

	return 0;
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group2 Device functions
  * @brief Device functions
  * @{
  */

/**
  * @brief  Gets the address.
  * @retval Address.
  */
uint8_t ald_usb_dev_get_addr(void)
{
	return USB0->FADDR;
}

/**
  * @brief  Sets the address.
  * @param  addr: The address which will be set.
  * @retval None
  */
void ald_usb_dev_set_addr(uint8_t addr)
{
	USB0->FADDR = addr;
}

/**
  * @brief  Enable connection.
  * @retval None
  */
void ald_usb_dev_connect(void)
{
	USB0->POWER |= USB_POWER_SOFTCONN;
}

/**
  * @brief  Disable connection.
  * @retval None
  */
void ald_usb_dev_disconnect(void)
{
	USB0->POWER &= ~(USB_POWER_SOFTCONN);
}

/**
  * @brief  Configure the endpoint in device mode.
  * @param  ep_idx: Index of the endpoint
  * @param  p_max: Size of the maximum package.
  * @param  flags: Flags of the endpoint.
  * @retval None
  */
void ald_usb_dev_ep_config(uint32_t ep_idx, uint32_t p_max, uint32_t flags)
{
	uint32_t tmp = 0;

	if (flags & USB_EP_DEV_IN) {
		USB0->CSR[ep_idx - 1].TXxMAXP = p_max;
		if (flags & USB_EP_AUTO_SET)
			tmp |= USB_TXCSRH1_AUTOSET;
		if ((flags & USB_EP_MODE_MASK) == USB_EP_MODE_ISOC)
			tmp |= USB_TXCSRH1_ISO;

		USB0->CSR[ep_idx - 1].TXxCSRH = (uint8_t)tmp;
		USB0->CSR[ep_idx - 1].TXxCSRL = USB_TXCSRL1_CLRDT;
	}
	else {
		USB0->CSR[ep_idx - 1].RXxMAXP = p_max;
		if (flags & USB_EP_AUTO_CLEAR)
			tmp = USB_RXCSRH1_AUTOCL;
		if (flags & USB_EP_DIS_NYET)
			tmp |= USB_RXCSRH1_DISNYET;

		if ((flags & USB_EP_MODE_MASK) == USB_EP_MODE_ISOC)
			tmp |= USB_RXCSRH1_ISO;

		USB0->CSR[ep_idx - 1].RXxCSRH = (uint8_t)tmp;
		USB0->CSR[ep_idx - 1].RXxCSRL = USB_RXCSRL1_CLRDT;
	}
}

/**
  * @brief  Gets the parameters of the endpoint.
  * @param  ep_idx: Index of the endpoint
  * @param  p_max: Size of the maximum package.
  * @param  flags: Flags of the endpoint.
  * @retval None
  */
void ald_usb_dev_ep_get_config(uint32_t ep_idx, uint32_t *p_max, uint32_t *flags)
{
	uint32_t tmp;

	if (*flags & USB_EP_DEV_IN) {
		*flags = USB_EP_DEV_IN;
		*p_max = (uint32_t)USB0->CSR[ep_idx - 1].TXxMAXP;
		tmp    = (uint32_t)USB0->CSR[ep_idx - 1].TXxCSRH;

		if (tmp & USB_TXCSRH1_AUTOSET)
			*flags |= USB_EP_AUTO_SET;
		if (tmp & USB_TXCSRH1_ISO)
			*flags |= USB_EP_MODE_ISOC;
		else
			*flags |= USB_EP_MODE_BULK;
	}
	else {
		*flags = USB_EP_DEV_OUT;
		*p_max = (uint32_t)USB0->CSR[ep_idx - 1].RXxMAXP;
		tmp    = (uint32_t)USB0->CSR[ep_idx - 1].RXxCSRH;

		if (tmp & USB_RXCSRH1_AUTOCL)
			*flags |= USB_EP_AUTO_CLEAR;
		if (tmp & USB_RXCSRH1_ISO)
			*flags |= USB_EP_MODE_ISOC;
		else
			*flags |= USB_EP_MODE_BULK;
	}
}

/**
  * @brief  Acknowledge the data from the host.
  * @param  ep_idx: Index of the endpoint
  * @param  last: true/false
  * @retval None
  */
void ald_usb_dev_ep_data_ack(uint32_t ep_idx, bool last)
{
	if (ep_idx == USB_EP_0)
		USB0->CSR0L = USB_CSRL0_RXRDYC | (last ? USB_CSRL0_DATAEND : 0);
	else
		USB0->CSR[ep_idx - 1].RXxCSRL &= ~(USB_RXCSRL1_RXRDY);
}

/**
  * @brief  Stall the endpoint.
  * @param  ep_idx: Index of the endpoint
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_dev_ep_stall(uint32_t ep_idx, uint32_t flags)
{
	if (ep_idx == USB_EP_0)
		USB0->CSR0L |= (USB_CSRL0_STALL | USB_CSRL0_RXRDYC);
	else if (flags == USB_EP_DEV_IN)
		USB0->CSR[ep_idx - 1].TXxCSRL |= USB_TXCSRL1_STALL;
	else
		USB0->CSR[ep_idx - 1].RXxCSRL |= USB_RXCSRL1_STALL;
}

/**
  * @brief  Cancel the stall status.
  * @param  ep_idx: Index of the endpoint
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_dev_ep_stall_clear(uint32_t ep_idx, uint32_t flags)
{
	if (ep_idx == USB_EP_0)
		USB0->CSR0L &= ~USB_CSRL0_STALLED;
	else if (flags == USB_EP_DEV_IN) {
		USB0->CSR[ep_idx - 1].TXxCSRL &= ~(USB_TXCSRL1_STALL | USB_TXCSRL1_STALLED);
		USB0->CSR[ep_idx - 1].TXxCSRL |= USB_TXCSRL1_CLRDT;
	}
	else {
		USB0->CSR[ep_idx - 1].RXxCSRL &= ~(USB_RXCSRL1_STALL | USB_RXCSRL1_STALLED);
		USB0->CSR[ep_idx - 1].RXxCSRL |= USB_RXCSRL1_CLRDT;
	}
}

/**
  * @brief  Clear the status of the endpoint.
  * @param  ep_idx: Index of the endpoint
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_dev_ep_status_clear(uint32_t ep_idx, uint32_t flags)
{
	if (ep_idx == USB_EP_0) {
		if (flags & USB_DEV_EP0_OUT_PKTRDY)
			USB0->CSR0L |= USB_CSRL0_RXRDYC;
		if (flags & USB_DEV_EP0_SETUP_END)
			USB0->CSR0L |= USB_CSRL0_SETENDC;
		if (flags & USB_DEV_EP0_SENT_STALL)
			USB0->CSR0L &= ~(USB_DEV_EP0_SENT_STALL);
	}
	else {
		USB0->CSR[ep_idx - 1].TXxCSRL &= ~(flags & (USB_DEV_TX_SENT_STALL | USB_DEV_TX_UNDERRUN));
		USB0->CSR[ep_idx - 1].RXxCSRL &= ~((flags & (USB_DEV_RX_SENT_STALL | USB_DEV_RX_DATA_ERROR
						 | USB_DEV_RX_OVERRUN)) >> USB_RX_EPSTATUS_SHIFT);
	}
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group3 Host functions
  * @brief Host functions
  * @{
  */
/**
  * @brief  Gets the device's address.
  * @param  ep_idx: Index of the endpoint
  * @param  flags: Flags.
  * @retval Address
  */
uint32_t ald_usb_host_addr_get(uint32_t ep_idx, uint32_t flags)
{
	if (flags & USB_EP_HOST_OUT)
		return USB0->ADDR[ep_idx].TXxFUNCADDR;
	else
		return USB0->ADDR[ep_idx].RXxFUNCADDR;
}

/**
  * @brief  Sets the device's address.
  * @param  ep_idx: Index of the endpoint.
  * @param  addr: The device's address.
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_host_addr_set(uint32_t ep_idx, uint32_t addr, uint32_t flags)
{
	if (flags & USB_EP_HOST_OUT)
		USB0->ADDR[ep_idx].TXxFUNCADDR = addr;
	else
		USB0->ADDR[ep_idx].RXxFUNCADDR = addr;
}

/**
  * @brief  Configure the endpoint in host mode.
  * @param  ep_idx: Index of the endpoint.
  * @param  p_max: Size of the maximum package.
  * @param  nak_val: Value of the nack.
  * @param  t_ep: Target endpoint.
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_host_ep_config(uint32_t ep_idx, uint32_t p_max, uint32_t nak_val, uint32_t t_ep, uint32_t flags)
{
	uint32_t tmp;

	if (ep_idx == USB_EP_0) {
		USB0->NACK = nak_val;

		if (flags & USB_EP_SPEED_HIGH)
			USB0->TYPE0 = USB_TYPE0_SPEED_HIGH;
		else if (flags & USB_EP_SPEED_FULL)
			USB0->TYPE0 = USB_TYPE0_SPEED_FULL;
		else
			USB0->TYPE0 = USB_TYPE0_SPEED_LOW;
	}
	else {
		tmp = t_ep;

		if (flags & USB_EP_SPEED_HIGH)
			tmp |= USB_TXTYPE1_SPEED_HIGH;
		else if (flags & USB_EP_SPEED_FULL)
			tmp |= USB_TXTYPE1_SPEED_FULL;
		else
			tmp |= USB_TXTYPE1_SPEED_LOW;

		switch (flags & USB_EP_MODE_MASK) {
		case USB_EP_MODE_BULK:
			tmp |= USB_TXTYPE1_PROTO_BULK;
			break;

		case USB_EP_MODE_ISOC:
			tmp |= USB_TXTYPE1_PROTO_ISOC;
			break;

		case USB_EP_MODE_INT:
			tmp |= USB_TXTYPE1_PROTO_INT;
			break;

		case USB_EP_MODE_CTRL:
			tmp |= USB_TXTYPE1_PROTO_CTRL;
			break;
		}

		if (flags & USB_EP_HOST_OUT) {
			USB0->CSR[ep_idx - 1].TXxTYPE     = tmp;
			USB0->CSR[ep_idx - 1].TXxINTERVAL = nak_val;
			USB0->CSR[ep_idx - 1].TXxMAXP     = p_max;

			tmp = 0;
			if (flags & USB_EP_AUTO_SET)
				tmp |= USB_TXCSRH1_AUTOSET;
			USB0->CSR[ep_idx - 1].TXxCSRH = (uint8_t)tmp;
		}
		else {
			USB0->CSR[ep_idx - 1].RXxTYPE     = tmp;
			USB0->CSR[ep_idx - 1].RXxINTERVAL = nak_val;
			USB0->CSR[ep_idx - 1].RXxMAXP     = p_max;

			tmp = 0;
			if (flags & USB_EP_AUTO_CLEAR)
				tmp |= USB_RXCSRH1_AUTOCL;
			if (flags & USB_EP_AUTO_REQUEST)
				tmp |= USB_RXCSRH1_AUTORQ;

			USB0->CSR[ep_idx - 1].RXxCSRH = (uint8_t)tmp;
		}
	}
}

/**
  * @brief  Acknowledge the data in host mode.
  * @param  ep_idx: Index of the endpoint.
  * @retval None
  */
void ald_usb_host_ep_data_ack(uint32_t ep_idx)
{
	if (ep_idx == USB_EP_0)
		USB0->CSR0L &= ~(USB_CSRL0_RXRDY);
	else
		USB0->CSR[ep_idx - 1].RXxCSRL &= ~(USB_RXCSRL1_RXRDY);
}

/**
  * @brief  Toggle the data in host mode.
  * @param  ep_idx: Index of the endpoint.
  * @param  toggle: true/false.
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_host_ep_data_toggle(uint32_t ep_idx, bool toggle, uint32_t flags)
{
	uint32_t tmp = 0;

	if (toggle) {
		if (ep_idx == USB_EP_0)
			tmp = USB_CSRH0_DT;
		else if (flags == USB_EP_HOST_IN)
			tmp = USB_RXCSRH1_DT;
		else
			tmp = USB_TXCSRH1_DT;
	}

	if (ep_idx == USB_EP_0) {
		USB0->CSR0H = (uint8_t)((USB0->CSR0H  & ~(USB_CSRH0_DTWE | USB_CSRH0_DT)) |
						(tmp | USB_CSRH0_DTWE));
	}
	else if (flags == USB_EP_HOST_IN) {
		USB0->CSR[ep_idx - 1].RXxCSRH = (uint8_t)((USB0->CSR[ep_idx - 1].RXxCSRH &
							~(USB_RXCSRH1_DTWE | USB_RXCSRH1_DT)) |
							(tmp | USB_RXCSRH1_DTWE));
	}
	else {
		USB0->CSR[ep_idx - 1].TXxCSRH = (uint8_t)((USB0->CSR[ep_idx - 1].TXxCSRH &
							~(USB_TXCSRH1_DTWE | USB_TXCSRH1_DT)) |
							(tmp | USB_TXCSRH1_DTWE));
	}
}

/**
  * @brief  Clear the status of endpoint in host mode.
  * @param  ep_idx: Index of the endpoint.
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_host_ep_status_clear(uint32_t ep_idx, uint32_t flags)
{
	if (ep_idx == USB_EP_0) {
		USB0->CSR0L &= ~flags;
	}
	else {
		USB0->CSR[ep_idx - 1].TXxCSRL &= ~flags;
		USB0->CSR[ep_idx - 1].RXxCSRL &= ~flags;
	}
}

/**
  * @brief  Gets the HUB's address.
  * @param  ep_idx: Index of the endpoint.
  * @param  flags: Flags.
  * @retval Address
  */
uint32_t ald_usb_host_hub_addr_get(uint32_t ep_idx, uint32_t flags)
{
	if (flags & USB_EP_HOST_OUT)
		return USB0->ADDR[ep_idx].TXxHUBADDR;
	else
		return USB0->ADDR[ep_idx].RXxHUBADDR;
}

/**
  * @brief  Sets the HUB's address.
  * @param  ep_idx: Index of the endpoint.
  * @param  addr: HUB's address which will be set.
  * @param  flags: Flags.
  * @retval Address
  */
void ald_usb_host_hub_addr_set(uint32_t ep_idx, uint32_t addr, uint32_t flags)
{
	if (flags & USB_EP_HOST_OUT)
		USB0->ADDR[ep_idx].TXxHUBADDR = addr;
	else
		USB0->ADDR[ep_idx].RXxHUBADDR = addr;

	if (ep_idx == USB_EP_0) {
		if (flags & USB_EP_SPEED_FULL)
			USB0->TYPE0 |= USB_TYPE0_SPEED_FULL;
		else if (flags & USB_EP_SPEED_HIGH)
			USB0->TYPE0 |= USB_TYPE0_SPEED_HIGH;
		else
			USB0->TYPE0 |= USB_TYPE0_SPEED_LOW;
	}
}

/**
  * @brief  Disable power.
  * @retval None
  */
void ald_usb_host_pwr_disable(void)
{
	return;
}

/**
  * @brief  Enable power.
  * @retval None
  */
void ald_usb_host_pwr_enable(void)
{
	return;
}

/**
  * @brief  Configure power in host mode.
  * @param  flags: Flags
  * @retval None
  */
void ald_usb_host_pwr_config(uint32_t flags)
{
	return;
}

/**
  * @brief  Disable the fault parameters of the power.
  * @retval None
  */
void ald_usb_host_pwr_fault_disable(void)
{
	return;
}

/**
  * @brief  Enable the fault parameters of the power.
  * @retval None
  */
void ald_usb_host_pwr_fault_enable(void)
{
	return;
}

/**
  * @brief  Request data IN(from device to host)
  * @param  ep_idx: Index of the endpoint.
  * @retval None
  */
void ald_usb_host_request_in(uint32_t ep_idx)
{
	if (ep_idx == USB_EP_0)
		USB0->CSR0L = USB_RXCSRL1_REQPKT;
	else
		USB0->CSR[ep_idx - 1].RXxCSRL = USB_RXCSRL1_REQPKT;
}

/**
  * @brief  Clear the status of request IN.
  * @param  ep_idx: Index of the endpoint.
  * @retval None
  */
void ald_usb_host_request_in_clear(uint32_t ep_idx)
{
	if (ep_idx == USB_EP_0)
		USB0->CSR0L &= ~(USB_RXCSRL1_REQPKT);
	else
		USB0->CSR[ep_idx - 1].RXxCSRL &= ~(USB_RXCSRL1_REQPKT);
}

/**
  * @brief  Request data IN at endpoint 0.
  * @retval None
  */
void ald_usb_host_request_status(void)
{
	USB0->CSR0L = USB_CSRL0_REQPKT | USB_CSRL0_STATUS;
}

/**
  * @brief  Reset the USB's bus.
  * @param  start: true/false.
  * @retval None
  */
void ald_usb_host_reset(bool start)
{
	if (start)
		USB0->POWER |= USB_POWER_RESET;
	else
		USB0->POWER &= ~(USB_POWER_RESET);
}

/**
  * @brief  Resume the devices.
  * @param  start: true/false.
  * @retval None
  */
void ald_usb_host_resume(bool start)
{
	if (start)
		USB0->POWER |= USB_POWER_RESUME;
	else
		USB0->POWER &= ~(USB_POWER_RESUME);
}

/**
  * @brief  Suspend the devices.
  * @retval None
  */
void ald_usb_host_suspend(void)
{
	USB0->POWER |= USB_POWER_SUSPEND;
}

/**
  * @brief  Gets the device's speed.
  * @retval Type of the speed.
  */
uint32_t ald_usb_host_speed_get(void)
{
	if (USB0->POWER & USB_POWER_HS_M)
		return USB_HIGH_SPEED;

	if (USB0->DEVCTL & USB_DEVCTL_FSDEV)
		return USB_FULL_SPEED;

	if (USB0->DEVCTL & USB_DEVCTL_LSDEV)
		return USB_LOW_SPEED;

	return USB_UNDEF_SPEED;
}

/**
  * @brief  Sets the endpoint speed.
  * @param  ep_idx: Index of the endpoint.
  * @param  flags: Type of the speed.
  * @retval None
  */
void ald_usb_host_ep_speed_set(uint32_t ep_idx, uint32_t flags)
{
	uint32_t tmp;

	if (flags & USB_EP_SPEED_HIGH)
		tmp = USB_TYPE0_SPEED_HIGH;
	else if (flags & USB_EP_SPEED_FULL)
		tmp = USB_TYPE0_SPEED_FULL;
	else
		tmp = USB_TYPE0_SPEED_LOW;

	if (ep_idx == USB_EP_0)
		USB0->TYPE0 |= tmp;
	else if (flags & USB_EP_HOST_OUT)
		USB0->CSR[ep_idx - 1].TXxTYPE |= tmp;
	else
		USB0->CSR[ep_idx - 1].RXxTYPE |= tmp;
}

/**
  * @brief  Ping the endpoint.
  * @param  ep_idx: Index of the endpoint.
  * @param  enable: ENABLE/DISABLE.
  * @retval None
  */
void ald_usb_host_ep_ping(uint32_t ep_idx, bool enable)
{
	if (enable)
		USB0->CSR0H &= ~(USB_CSRH0_DISPING);
	else
		USB0->CSR0H |= USB_CSRH0_DISPING;
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group4 Endpoint functions
  * @brief Endpoint functions
  * @{
  */
/**
  * @brief  Gets the size of the available data.
  * @param  ep_idx: Index of the endpoint
  * @retval Size in bytes.
  */
uint32_t ald_usb_ep_data_avail(uint32_t ep_idx)
{
	if (ep_idx == USB_EP_0) {
		if ((USB0->CSR0L & USB_CSRL0_RXRDY) == 0)
			return 0;

		return USB0->COUNT0;
	}
	else {
		if ((USB0->CSR[ep_idx - 1].RXxCSRL & USB_CSRL0_RXRDY) == 0)
			return 0;

		return USB0->CSR[ep_idx - 1].RXxCOUNT;
	}
}

/**
  * @brief  Gets the data from FIFO.
  * @param  ep_idx: Index of the endpoint
  * @param  data: Pointer to the buffer.
  * @param  size: Size of the data.
  * @retval Status.
  */
int32_t ald_usb_ep_data_get(uint32_t ep_idx, uint8_t *data, uint32_t *size)
{
	uint32_t i;

	if (ep_idx == USB_EP_0) {
		if ((USB0->CSR0L & USB_CSRL0_RXRDY) == 0) {
			*size = 0;
			return -1;
		}
		i = USB0->COUNT0;
	}
	else {
		if ((USB0->CSR[ep_idx - 1].RXxCSRL & USB_CSRL0_RXRDY) == 0) {
			*size = 0;
			return -1;
		}
		i = USB0->CSR[ep_idx - 1].RXxCOUNT;
	}

	i     = (i < *size) ? i : *size;
	*size = i;

	for (; i > 0; i--)
		*data++ = USB0->FIFO[ep_idx].Byte[0];

	return 0;
}

/**
  * @brief  Puts data to the FIFO.
  * @param  ep_idx: Index of the endpoint
  * @param  data: Pointer to the data.
  * @param  size: Size of the data.
  * @retval Status.
  */
int32_t ald_usb_ep_data_put(uint32_t ep_idx, uint8_t *data, uint32_t size)
{
	if (ep_idx == USB_EP_0) {
		if (USB0->CSR0L & USB_CSRL0_TXRDY)
			return -1;
	}
	else {
		if (USB0->CSR[ep_idx - 1].TXxCSRL & USB_TXCSRL1_TXRDY)
			return -1;
	}

	for (; size > 0; size--)
		USB0->FIFO[ep_idx].Byte[0] = *data++;

	return 0;
}

/**
  * @brief  Send data.
  * @param  ep_idx: Index of the endpoint
  * @param  tx_type: Type.
  * @retval Status.
  */
int32_t ald_usb_ep_data_send(uint32_t ep_idx, uint32_t tx_type)
{
	uint32_t tmp;

	if (ep_idx == USB_EP_0) {
		if (USB0->CSR0L & USB_CSRL0_TXRDY)
			return -1;

		tmp = tx_type & 0xff;
		USB0->CSR0L = tmp;
	}
	else {
		if (USB0->CSR[ep_idx - 1].TXxCSRL & USB_TXCSRL1_TXRDY)
			return -1;

		tmp = (tx_type >> 8) & 0xff;
		USB0->CSR[ep_idx - 1].TXxCSRL = tmp;
	}

	return 0;
}

/**
  * @brief  Clear the status of the toggle.
  * @param  ep_idx: Index of the endpoint
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_ep_data_toggle_clear(uint32_t ep_idx, uint32_t flags)
{
	if (flags & (USB_EP_HOST_OUT | USB_EP_DEV_IN))
		USB0->CSR[ep_idx - 1].TXxCSRL |= USB_TXCSRL1_CLRDT;
	else
		USB0->CSR[ep_idx - 1].RXxCSRL |= USB_RXCSRL1_CLRDT;
}

/**
  * @brief  Sets the size of request data IN
  * @param  ep_idx: Index of the endpoint
  * @param  count: Size of request data IN.
  * @retval None
  */
void ald_usb_ep_req_packet_count(uint32_t ep_idx, uint32_t count)
{
	USB0->EP_RQPKTCOUNT[ep_idx - 1] = count;
}

/**
  * @brief  Gets the status of the endpoint.
  * @param  ep_idx: Index of the endpoint
  * @retval Status.
  */
uint32_t ald_usb_ep_status(uint32_t ep_idx)
{
	uint32_t status;

	if (ep_idx == USB_EP_0) {
		status  = USB0->CSR0L;
		status |= (USB0->CSR0H) << USB_RX_EPSTATUS_SHIFT;
	}
	else {
		status  = USB0->CSR[ep_idx - 1].TXxCSRL;
		status |= USB0->CSR[ep_idx - 1].TXxCSRH << 8;
		status |= USB0->CSR[ep_idx - 1].RXxCSRL << 16;
		status |= USB0->CSR[ep_idx - 1].RXxCSRH << 24;
	}

	return status;
}

/**
  * @brief  Configure the endpoint in DMA mode.
  * @param  ep_idx: Index of the endpoint
  * @param  flag: Flags.
  * @param  en: ENABLE/DISABLE.
  * @retval None
  */
void ald_usb_ep_dma_config(uint32_t ep_idx, uint32_t flag, type_func_t en)
{
	if (ep_idx == USB_EP_0)
		return;

	if (en) {
		switch (flag) {
		case USB_DMA_EP_CFG_TX:
			USB0->CSR[ep_idx - 1].TXxCSRH |= USB_DMA_EP_TX_MSK;
			break;
		case USB_DMA_EP_CFG_RX_DEV:
			USB0->CSR[ep_idx - 1].RXxCSRH |= USB_DMA_EP_RX_DEV_MSK;
			break;
		case USB_DMA_EP_CFG_RX_HOST:
			USB0->CSR[ep_idx - 1].RXxCSRH |= USB_DMA_EP_RX_HOST_MSK;
			break;
		default:
			break;
		}
	}
	else {
		switch (flag) {
		case USB_DMA_EP_CFG_TX:
			USB0->CSR[ep_idx - 1].TXxCSRH &= ~(USB_DMA_EP_TX_MSK);
			break;
		case USB_DMA_EP_CFG_RX_DEV:
			USB0->CSR[ep_idx - 1].RXxCSRH &= ~(USB_DMA_EP_RX_DEV_MSK);
			break;
		case USB_DMA_EP_CFG_RX_HOST:
			USB0->CSR[ep_idx - 1].RXxCSRH &= ~(USB_DMA_EP_RX_HOST_MSK);
			break;
		default:
			break;
		}
	}

	return;
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group5 FIFO functions
  * @brief FIFO functions
  * @{
  */
/**
  * @brief  Gets the address of the FIFO.
  * @param  ep_idx: Index of the endpoint
  * @retval Address
  */
uint32_t ald_usb_fifo_addr_get(uint32_t ep_idx)
{
	return (uint32_t)&USB0->FIFO[ep_idx].Word;
}

/**
  * @brief  Gets the parameters of the FIFO.
  * @param  ep_idx: Index of the endpoint
  * @param  addr: Address.
  * @param  size: Size of FIFO.
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_fifo_config_get(uint32_t ep_idx, uint32_t *addr, uint32_t *size, uint32_t flags)
{
	uint32_t tmp = USB0->INDEX;

	USB0->INDEX = ep_idx;

	if (flags & (USB_EP_HOST_OUT | USB_EP_DEV_IN)) {
		*addr = (USB0->TXFIFOADD << 3);
		*size = (USB0->TXFIFOSIZE & 0xF);
	}
	else {
		*addr = (USB0->RXFIFOADD << 3);
		*size = (USB0->RXFIFOSIZE & 0xF);
	}

	USB0->INDEX = tmp;
	return;
}

/**
  * @brief  Sets the parameters of the FIFO.
  * @param  ep_idx: Index of the endpoint
  * @param  addr: Address.
  * @param  size: Size of FIFO.
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_fifo_config_set(uint32_t ep_idx, uint32_t addr, uint32_t size, uint32_t flags)
{
	uint32_t tmp = USB0->INDEX;

	USB0->INDEX = ep_idx;

	if (flags & (USB_EP_HOST_OUT | USB_EP_DEV_IN)) {
		USB0->TXFIFOADD  = (addr >> 3);
		USB0->TXFIFOSIZE = (size & 0xF);
	}
	else {
		USB0->RXFIFOADD  = (addr >> 3);
		USB0->RXFIFOSIZE = (size & 0xF);
	}

	USB0->INDEX = tmp;
	return;
}

/**
  * @brief  Flush the FIFO
  * @param  ep_idx: Index of the endpoint
  * @param  flags: Flags.
  * @retval None
  */
void ald_usb_fifo_flush(uint32_t ep_idx, uint32_t flags)
{
	if (ep_idx == USB_EP_0) {
		if ((USB0->CSR0L & (USB_CSRL0_RXRDY | USB_CSRL0_TXRDY)) != 0)
			USB0->CSR0H |= USB_CSRH0_FLUSH;
	}
	else {
		if (flags & (USB_EP_HOST_OUT | USB_EP_DEV_IN)) {
			if (USB0->CSR[ep_idx - 1].TXxCSRL & USB_TXCSRL1_TXRDY)
				USB0->CSR[ep_idx - 1].TXxCSRL |= USB_TXCSRL1_FLUSH;
		}
		else {
			if (USB0->CSR[ep_idx - 1].RXxCSRL & USB_RXCSRL1_RXRDY)
				USB0->CSR[ep_idx - 1].RXxCSRL |= USB_RXCSRL1_FLUSH;
		}
	}
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group6 Interrupt functions
  * @brief Interrupt functions
  * @{
  */
/**
  * @brief  Disable interrupt.
  * @param  flags: Type of the interrupt.
  * @retval None
  */
void ald_usb_int_disable(uint32_t flags)
{
	if (flags & USB_INTCTRL_STATUS)
		USB0->USBIE &= ~(flags & USB_INTCTRL_STATUS);
}

/**
  * @brief  Enable interrupt.
  * @param  flags: Type of the interrupt.
  * @retval None
  */
void ald_usb_int_enable(uint32_t flags)
{
	if (flags & USB_INTCTRL_STATUS)
		USB0->USBIE |= flags ;
}

/**
  * @brief  Gets the status of the interrupt.
  * @retval Status.
  */
uint32_t ald_usb_int_status_get(void)
{
	return USB0->USBIS;
}

/**
  * @brief  Disable interrupt of the endpoint.
  * @param  flags: Type of the interrupt.
  * @retval None
  */
void ald_usb_int_disable_ep(uint32_t flags)
{
	USB0->TXIE &= ~(flags & (USB_INTEP_HOST_OUT | USB_INTEP_DEV_IN | USB_INTEP_0));
	USB0->RXIE &= ~((flags & (USB_INTEP_HOST_IN | USB_INTEP_DEV_OUT)) >> USB_INTEP_RX_SHIFT);
}

/**
  * @brief  Enable interrupt of the endpoint.
  * @param  flags: Type of the interrupt.
  * @retval None
  */
void ald_usb_int_enable_ep(uint32_t flags)
{

	USB0->TXIE |= flags & (USB_INTEP_HOST_OUT | USB_INTEP_DEV_IN | USB_INTEP_0);
	USB0->RXIE |= ((flags & (USB_INTEP_HOST_IN | USB_INTEP_DEV_OUT)) >> USB_INTEP_RX_SHIFT);
}

/**
  * @brief  Gets the ststus of the endpoint interrupt.
  * @retval Status.
  */
uint32_t ald_usb_int_status_ep_get(void)
{
	uint32_t status;

	status = USB0->TXIS;
	status |= (USB0->RXIS << USB_INTEP_RX_SHIFT);

	return status;
}

/**
  * @brief  Register USB's interrupt.
  * @retval None
  */
void ald_usb_int_register(void)
{
	ald_mcu_irq_config(USB_INT_IRQn, 2, 2, ENABLE);

}

/**
  * @brief  Unregister USB's interrupt.
  * @retval None
  */
void ald_usb_int_unregister(void)
{
	ald_mcu_irq_config(USB_INT_IRQn, 2, 2, DISABLE);
}

/**
  * @brief  Get USB's interrupt number.
  * @retval None
  */
uint32_t ald_usb_int_num_get(void)
{
	return USB_INT_IRQn;
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group7 DMA functions
  * @brief DMA functions
  * @{
  */
/**
  * @brief  Configure DMA's channel.
  * @param  ch: Channel.
  * @param  addr: Address.
  * @param  count: Size of the data to be moved.
  * @param  ctrl: Parameters of the DMA's controler
  * @retval None
  */
void ald_usb_dma_channel_config(uint8_t ch, uint32_t addr, uint32_t count, uint32_t ctrl)
{
	USB0->DMA_CH[ch].DMA_ADDR  = addr;
	USB0->DMA_CH[ch].DMA_COUNT = count;
	USB0->DMA_CH[ch].DMA_CNTL  = ctrl;
	return;
}

/**
  * @brief  Start multiple receive.
  * @param  ep_idx: Index of the endpoint
  * @retval None
  */
void ald_usb_dma_mult_recv_start(uint32_t ep_idx)
{
	USB0->CSR[ep_idx - 1].RXxCSRH &= ~(USB_RXCSRH1_DMAMOD);
	return;
}


/**
  * @brief  Start DMA's machine.
  * @param  ch: Channel.
  * @retval None
  */
void ald_usb_dma_channel_start(uint8_t ch)
{
	USB0->DMA_CH[ch].DMA_CNTL |= 0x1;
	return;
}

/**
  * @brief  Stop DMA's machine.
  * @param  ch: Channel.
  * @retval None
  */
void ald_usb_dma_channel_stop(uint8_t ch)
{
	USB0->DMA_CH[ch].DMA_CNTL &= (uint32_t)(~0x1);
	return;
}

/**
  * @brief  Gets flags of the interrupt.
  * @retval Flags of the interrupt.
  */
uint32_t ald_usb_dma_get_interrupt_flag(void)
{
	return USB0->DMA_INTR;
}

/**
  * @brief  Gets the status of the error.
  * @param  ch: Channel.
  * @retval Status.
  */
uint32_t ald_usb_dma_get_channel_error(uint8_t ch)
{
	if (USB0->DMA_CH[ch].DMA_CNTL & USB_DMA_CH_ERR_MSK)
		return 1;

	return 0;
}

/**
  * @brief  Clear the status of the error.
  * @param  ch: Channel.
  * @retval None
  */
void ald_usb_dma_clear_channel_error(uint8_t ch)
{
	USB0->DMA_CH[ch].DMA_CNTL &= ~(USB_DMA_CH_ERR_MSK);
}
/**
  * @}
  */

/** @defgroup USB_Public_Functions_Group8 LPM functions
  * @brief LPM functions
  * @{
  */
/**
  * @brief  Transmit a LPM transaction in host mode.
  * @param  addr: Address.
  * @param  ep_idx: Index of the endpoint.
  * @retval None
  */
void ald_usb_host_lpm_send(uint32_t addr, uint32_t ep_idx)
{
	uint32_t tmp;

	USB0->LPM_FADDR = addr;

	tmp = USB0->LPM_ATTR & ~(USB_LPMATTR_ENDPT_M);
	tmp |= ep_idx << USB_LPMATTR_ENDPT_S;

	USB0->LPM_ATTR = tmp;
	USB0->LPM_CNTRL |= USB_LPMCNTRL_LPMXMT;
}

/**
  * @brief  Configure the LPM parameters in host mode.
  * @param  resume_time: Resume time.
  * @param  config: Parameters
  * @retval None
  */
void ald_usb_host_lpm_config(uint32_t resume_time, uint32_t config)
{
	uint32_t tmp;

	tmp  = USB0->LPM_ATTR;
	tmp &= ~(USB_LPMATTR_HIRD_M);
	tmp |= ((((resume_time - 50) / 75) & 0xF) << USB_LPMATTR_HIRD_S);
	tmp |= config;
	USB0->LPM_ATTR = tmp;
}

/**
  * @brief  Gets status of remote wakeup.
  * @retval Status.
  */
uint32_t ald_usb_lpm_remote_wake_is_enable(void)
{
	if (USB0->LPM_ATTR & USB_LPMATTR_RMTWAK)
		return 1;

	return 0;
}

/**
  * @brief  Initiate a RESUME from the L1 state in host mode.
  * @retval None
  */
void ald_usb_host_lpm_resume(void)
{
	USB0->LPM_CNTRL |= USB_LPMCNTRL_LPMRES;
}

/**
  * @brief  Enable remote wakeup in device mode.
  * @retval None
  */
void ald_usb_dev_lpm_remote_wake(void)
{
	USB0->LPM_CNTRL |= USB_LPMCNTRL_LPMRES;
}

/**
  * @brief  Enable remote wakeup in device mode.
  * @retval None
  */
void ald_usb_dev_lpm_config(uint32_t config)
{
	USB0->LPM_CNTRL = config;
}

/**
  * @brief  Enable LPM in device mode.
  * @retval None
  */
void ald_usb_dev_lpm_enable(void)
{
	USB0->LPM_CNTRL |= (USB_LPMCNTRL_LPMXMT | USB_LPMCNTRL_ENABLE);
}

/**
  * @brief  Disable LPM in device mode.
  * @retval None
  */
void ald_usb_dev_lpm_disable(void)
{
	USB0->LPM_CNTRL &= ~(USB_LPMCNTRL_LPMXMT);
}

/**
  * @brief  Gets the link status
  * @retval Status
  */
uint32_t ald_usb_lpm_link_status_get(void)
{
	return (USB0->LPM_ATTR & USB_LPMATTR_LS_M);
}

/**
  * @brief  Gets the index of the endpoint.
  * @retval Index of the endpoint.
  */
uint32_t ald_usb_lpm_ep_get(void)
{
	uint32_t tmp;

	tmp  = USB0->LPM_ATTR;
	tmp &= USB_LPMATTR_ENDPT_M;
	tmp  = tmp >> USB_LPMATTR_ENDPT_S;

	return tmp;
}

/**
  * @brief  Gets the status of the interrupt.
  * @retval Status.
  */
uint32_t ald_usb_lpm_int_status_get(void)
{
	return USB0->LPM_INTR;
}

/**
  * @brief  Disable the LPM interrupt.
  * @retval None
  */
void ald_usb_lpm_int_disable(uint32_t ints)
{
	USB0->LPM_INTREN &= ~ints;
}

/**
  * @brief  Enable the LPM interrupt.
  * @retval None
  */
void ald_usb_lpm_int_enable(uint32_t ints)
{
	USB0->LPM_INTREN |= ints;
}
/**
  * @}
  */

/**
  * @}
  */
#endif
/**
  * @}
  */

/**
  * @}
  */
