/*
 * Copyright (c) 2020 Actions Corporation.
 * Author: Jinang Lv <lvjinang@actions-semi.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Actions USB OTG controller driver
 *
 * Actions USB OTG controller driver. The driver implements
 * the low level control routines to deal directly with the hardware.
 */

#include <kernel.h>
#include <sys/sys_io.h>
#include <board.h>
#include <init.h>
#include <drivers/dma.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/byteorder.h>
#include <devicetree.h>

#include <drivers/usb/usb_dc.h>
#include <drivers/usb/usb_hc.h>
#include <drivers/usb/usb_phy.h>
#include <soc_clock.h>
#include <soc_reset.h>
#include <soc_pmu.h>

#include "usb_otg_aotg.h"

#include <logging/log.h>
#define	LOG_LEVEL CONFIG_SYS_LOG_USB_OTG_DRIVER_LEVEL
LOG_MODULE_REGISTER(usb_otg_aotg);

#define	DMA_ID_USB0			4
#define DMA_IRQ_TC			(0) /* DMA completion flag */
#define DMA_IRQ_HF			(1) /* DMA half-full flag */

#define USB_AOTG_DMA_BURST8_LEN		32
#define USB_AOTG_DMA_MAX_SIZE		(128 * 1024)
#define USB_AOTG_DMA_MAX_LEN		(64 * 1024)

#ifdef CONFIG_USB_AOTG_UDC_DMA
#define USB_EP_OUT_DMA_CAP(ep_idx)	(ep_idx == USB_AOTG_OUT_EP_2)
#define USB_EP_IN_DMA_CAP(ep_idx)	(ep_idx == USB_AOTG_IN_EP_1)
#else
#define USB_EP_OUT_DMA_CAP(ep)		(false)
#define USB_EP_IN_DMA_CAP(ep)		(false)
#endif

/*
 * USB device controller endpoint private structure.
 */
struct aotg_dc_ep_ctrl_prv {
	uint8_t ep_ena;	/* Endpoint Enabled */
	uint8_t ep_type;
	uint16_t mps;	/* Endpoint Max packet size */
	usb_dc_ep_callback cb;	/* Endpoint callback function */
	/*
	 * "data_len" has different meanings in different cases.
	 *
	 * for ep0-in: set in setup phase.
	 * for ep0-out: set in setup or data phase.
	 * for epX-in legacy mode: not used.
	 * for epX-in new mode: set by upper layer, update in epin interrupt.
	 * for epX-in DMA: set by upper layer to help if need to set busy.
	 * for epX-out: set in epout interrupt.
	 * for epX-out async: set by upper layer, update in epout interrupt
	 * for epX-out DMA: set by upper layer.
	 */
	uint32_t data_len;
	uint32_t actual;	/* actual length: only for epX-out async */
	/*
	 * "data" has different meanings in different cases.
	 *
	 * for ep0-in: not used.
	 * for ep0-out: not used.
	 * for epX-in legacy mode: not used.
	 * for epX-in new mode: set by upper layer, update in epin interrupt.
	 * for epX-out: not used.
	 * for epX-out async: set by upper layer, update in epout interrupt.
	 */
	uint8_t *data;
	uint8_t multi;	/* multi-fifo */
};

/*
 * USB device controller private structure.
 */
struct usb_aotg_dc_prv {
	usb_dc_status_callback status_cb;
	struct aotg_dc_ep_ctrl_prv in_ep_ctrl[USB_AOTG_IN_EP_NUM+1];
	struct aotg_dc_ep_ctrl_prv out_ep_ctrl[USB_AOTG_OUT_EP_NUM+1];
	uint8_t attached;
	uint8_t phase;	/* Control transfer stage */
	uint8_t speed;
	union {
		uint8_t raw_setup[sizeof(struct usb_setup_packet)];
		struct usb_setup_packet setup;
	};
};

/*
 * USB host controller endpoint private structure.
 */
struct aotg_hc_ep_ctrl_prv {
	uint8_t ep_ena;	/* Endpoint Enabled */
	uint8_t ep_type;
	uint16_t mps;	/* Endpoint Max packet size */

	struct usb_request *urb;

	uint8_t ep_addr;	/* Endpoint Address */
	uint8_t err_count;
};

/*
 * USB host controller private structure.
 */
struct usb_aotg_hc_prv {
	uint32_t port;

	struct aotg_hc_ep_ctrl_prv in_ep_ctrl[USB_AOTG_IN_EP_NUM];
	struct aotg_hc_ep_ctrl_prv out_ep_ctrl[USB_AOTG_OUT_EP_NUM];
	uint8_t attached;
	uint8_t phase;	/* Control transfer stage */
	uint8_t speed;
};

struct usb_aotg_otg_prv {
	union {
#ifdef CONFIG_USB_AOTG_DC_ENABLED
		struct usb_aotg_dc_prv dc;
#endif
#ifdef CONFIG_USB_AOTG_HC_ENABLED
		struct usb_aotg_hc_prv hc;
#endif
	};
};

static struct usb_aotg_otg_prv usb_aotg;

static uint8_t usb_aotg_mode;

#ifdef CONFIG_USB_AOTG_DC_ENABLED
#define usb_aotg_dc	(usb_aotg.dc)
#endif
#ifdef CONFIG_USB_AOTG_HC_ENABLED
#define usb_aotg_hc	(usb_aotg.hc)
#endif

#ifdef CONFIG_USB_AOTG_UDC_DMA
struct usb_aotg_dma_prv {
	const struct device *dma_dev;
	int epin_dma;
	int epout_dma_single;
	int epout_dma_burst8;
	struct dma_config epin_dma_config;
	struct dma_config epout_dma_config_single;
	struct dma_config epout_dma_config_burst8;
	struct dma_block_config epin_dma_block;
	struct dma_block_config epout_dma_block_single;
	struct dma_block_config epout_dma_block_burst8;
};

static struct usb_aotg_dma_prv usb_aotg_dma;
#endif

/*
 * Dump aotg registers
 */
static inline void usb_aotg_reg_dump(void)
{
	LOG_DBG("USB AOTG registers:");

	LOG_DBG("IDVBUSCTRL: 0x%x", usb_read8(IDVBUSCTRL));
	LOG_DBG("DPDMCTRL: 0x%x", usb_read8(DPDMCTRL));
	LOG_DBG("LINESTATUS: 0x%x", usb_read8(LINESTATUS));
	LOG_DBG("USBIEN: 0x%x", usb_read8(USBIEN));
	LOG_DBG("OTGIEN: 0x%x", usb_read8(OTGIEN));
	LOG_DBG("USBEIEN: 0x%x", usb_read8(USBEIEN));
	LOG_DBG("USBIRQ: 0x%x", usb_read8(USBIRQ));
	LOG_DBG("OTGIRQ: 0x%x", usb_read8(OTGIRQ));
	LOG_DBG("USBEIRQ: 0x%x", usb_read8(USBEIRQ));
	LOG_DBG("OTGSTATE: 0x%x", usb_read8(OTGSTATE));
}

/*
 * Dump aotg endpoint registers
 */
static inline void usb_aotg_ep_reg_dump(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	ARG_UNUSED(ep_idx);

	LOG_DBG("USB AOTG EP 0x%x registers:", ep);

	if (USB_EP_DIR_IS_OUT(ep)) {
		LOG_DBG("HCINCTRL: 0x%x", usb_read8(HCINxCTRL(ep_idx)));
		LOG_DBG("STADDR: 0x%x", usb_read32(EPxOUT_STADDR(ep_idx)));
		LOG_DBG("OUT_CTRL: 0x%x", usb_read8(OUTxCTRL(ep_idx)));
		LOG_DBG("OUT_MAXPKT: 0x%x", usb_read16(OUTxMAXPKT(ep_idx)));
		LOG_DBG("OUT_IEN: 0x%x", usb_read8(OUTIEN));
		LOG_DBG("OUT_CS: 0x%x", usb_read8(OUTxCS(ep_idx)));
		LOG_DBG("OUT_SHTPKT: 0x%x", usb_read8(OUT_SHTPKT));
		LOG_DBG("OUT_BC: 0x%x", usb_read16(OUTxBC(ep_idx)));
		LOG_DBG("OUT_IRQ: 0x%x", usb_read8(OUTIRQ));
	} else {
		LOG_DBG("HCOUTCTRL: 0x%x", usb_read8(HCOUTxCTRL(ep_idx)));
		LOG_DBG("STADDR: 0x%x", usb_read32(EPxIN_STADDR(ep_idx)));
		LOG_DBG("IN_CTRL: 0x%x", usb_read8(INxCTRL(ep_idx)));
		LOG_DBG("IN_MAXPKT: 0x%x", usb_read16(INxMAXPKT(ep_idx)));
		LOG_DBG("IN_IEN: 0x%x", usb_read8(INIEN));
		LOG_DBG("IN_CS: 0x%x", usb_read8(INxCS(ep_idx)));
		LOG_DBG("IN_IRQ: 0x%x", usb_read8(INIRQ));
	}
}

/*
 * Check if the address of endpoint is valid
 *
 * NOTE: we don't check if the address of control endpoint is non-zero!
 */
static inline bool usb_aotg_ep_addr_valid(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (USB_EP_DIR_IS_OUT(ep) && (ep_idx < USB_AOTG_OUT_EP_NUM)) {
		return true;
	} else if (USB_EP_DIR_IS_IN(ep) && (ep_idx < USB_AOTG_IN_EP_NUM)) {
		return true;
	}

	return false;
}

/*
 * Enable USB controller clock
 */
static inline void usb_aotg_clock_enable(void)
{
	acts_clock_peripheral_enable(CLOCK_ID_USB);
}

/*
 * Disable USB controller clock
 */
static inline void usb_aotg_clock_disable(void)
{
	acts_clock_peripheral_disable(CLOCK_ID_USB);
}

/*
 * Reset USB controller
 */
static inline int usb_aotg_reset(void)
{
	acts_reset_peripheral(RESET_ID_USB2);
	acts_reset_peripheral(RESET_ID_USB);

	usb_aotg_reset_specific();

	/* Wait for USB controller until reset done */
	while (usb_read8(LINESTATUS) & BIT(OTGRESET)) {
		;
	}

	return 0;
}

/*
 * Enable controller
 */
static inline void usb_aotg_enable(void)
{
	usb_aotg_power_on();

	usb_aotg_clock_enable();

	usb_aotg_dpdm_init();

	usb_aotg_reset();
}

/*
 * Disable controller
 */
static inline void usb_aotg_disable(void)
{
	usb_aotg_disable_specific();

	usb_aotg_clock_disable();

	usb_aotg_power_off();
}

/*
 * Reset specific endpoint
 */
static inline int usb_aotg_ep_reset(uint8_t aotg_ep, uint8_t type)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);

	if (!ep_idx) {
		LOG_DBG("Ep reset all endpoints");
	}

	/* Select specific endpoint */
	if (USB_EP_DIR_IS_OUT(aotg_ep)) {
		usb_write8(EPRST, ep_idx);
	} else {
		usb_write8(EPRST, ep_idx | BIT(EPRST_IO_IN));
	}

	if (type == USB_AOTG_EP_FIFO_RESET) {
		usb_set_bit8(EPRST, EPRST_FIFORST);
	} else if (type == USB_AOTG_EP_TOGGLE_RESET) {
		usb_set_bit8(EPRST, EPRST_TOGRST);
	} else if (type == USB_AOTG_EP_RESET) {
		usb_set_bit8(EPRST, EPRST_FIFORST);
		usb_set_bit8(EPRST, EPRST_TOGRST);
	} else {
		LOG_ERR("Ep reset type: %d", type);
		return -EINVAL;
	}

	return 0;
}

static inline int usb_aotg_exit(void)
{
	usb_aotg_enable();

	usb_aotg_disable();

	return 0;
}

/*
 * Write data to EPx FIFO
 *
 * @return  Actual length
 */
static inline int ep_write_fifo(uint8_t ep, const uint8_t *const data,
				uint32_t data_len)
{
	uint32_t i;
	uint32_t len;
	uint8_t left;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	const uint8_t *buf = (const uint8_t *)data;

	uint8_t count = 0;

	if (USB_EP_IN_DMA_CAP(ep_idx)) {
		/* FIXME: wait 100us at most */
		while (usb_read8(INxCS(ep_idx)) & BIT(EPCS_BUSY)) {
			if (++count > 100) {
				break;
			}
			k_busy_wait(1);
		}
	}

	/* Check Busy */
	if (usb_read8(INxCS(ep_idx)) & BIT(EPCS_BUSY)) {
		LOG_DBG("EP 0x%x still busy", ep);
		return -EAGAIN;
	}

	if (USB_EP_IN_DMA_CAP(ep_idx)) {
		usb_write8(INIRQ, BIT(ep_idx));
		usb_set_bit8(INIEN, ep_idx);
	}

	/* 32-bit alignment */
	if (((long)buf & 0x3) == 0) {
		len = data_len >> 2;
		left = data_len & 0x3;
		for (i = 0; i < len; i++, buf += 4) {
			usb_write32(FIFOxDAT(ep_idx), *(uint32_t *)buf);
		}
	/* 16-bit alignment */
	} else if (((long)buf & 0x1) == 0) {
		len = data_len >> 1;
		left = data_len & 0x1;
		for (i = 0; i < len; i++, buf += 2) {
			usb_write16(FIFOxDAT(ep_idx), *(uint16_t *)buf);
		}
	} else {
		len = data_len;
		left = 0;
		for (i = 0; i < len; i++, buf++) {
			usb_write8(FIFOxDAT(ep_idx), *buf);
		}
	}

	/* Handle left byte(s) */
	for (i = 0; i < left; i++, buf++) {
		usb_write8(FIFOxDAT(ep_idx), *buf);
	}

	/* Set Byte Counter */
	usb_write16(INxBC(ep_idx), data_len);

	/* Set busy */
	usb_set_bit8(INxCS(ep_idx), EPCS_BUSY);

	return data_len;
}

/*
 * Read data from EP FIFO
 *
 * @return  Actual length
 */
static inline int ep_read_fifo(uint8_t ep, uint8_t *data, uint32_t data_len)
{
	uint32_t i;
	uint32_t len;
	uint8_t left;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	uint32_t addr = FIFOxDAT(ep_idx);
	uint8_t *buf = (uint8_t *)data;

	/* Check Busy */
	if (usb_read8(OUTxCS(ep_idx)) & BIT(EPCS_BUSY)) {
		LOG_DBG("EP 0x%x still busy", ep);
		return -EBUSY;
	}

	/* 32-bit alignment */
	if (((long)buf & 0x3) == 0) {
		len = data_len >> 2;
		left = data_len & 0x3;
		for (i = 0; i < len; i++, buf += 4) {
			*(uint32_t *)buf = usb_read32(addr);
		}
	/* 16-bit alignment */
	} else if (((long)buf & 0x1) == 0) {
		len = data_len >> 1;
		left = data_len & 0x1;
		for (i = 0; i < len; i++, buf += 2) {
			*(uint16_t *)buf = usb_read16(addr);
		}
	} else {
		len = data_len;
		left = 0;
		for (i = 0; i < len; i++, buf++) {
			*buf = usb_read8(addr);
		}
	}

	/* Handle left byte(s) */
	for (i = 0; i < left; i++, buf++) {
		*buf = usb_read8(addr);
	}

	return data_len;
}

/*
 * Data stage: write data to EP0 FIFO
 *
 * @return  Actual length
 */
static inline int ep0_write_fifo(const uint8_t *const data, uint32_t data_len)
{
	uint32_t i;
	uint8_t j;
	uint32_t addr = EP0IN_FIFO;

	/* Check Busy */
	if (usb_read8(EP0CS) & BIT(EP0CS_INBUSY)) {
		LOG_DBG("still busy");
		return -EAGAIN;
	}

	/* 32-bit alignment */
	if (((long)data & 0x3) == 0) {
		for (i = 0; i < (data_len & ~0x3); i += 4) {
			usb_write32(addr + i, *(uint32_t *)(data + i));
		}
		for (j = 0; j < (data_len & 0x3); j++) {
			usb_write8(addr + i + j, *(data + i + j));
		}
	/* 16-bit alignment */
	} else if (((long)data & 0x1) == 0) {
		for (i = 0; i < (data_len & ~0x1); i += 2) {
			usb_write16(addr + i, *(uint16_t *)(data + i));
		}
		/* The last byte */
		if (data_len & 0x1) {
			usb_write8(addr + i, *(data + i));
		}
	} else {
		for (i = 0; i < data_len; i++) {
			usb_write8(addr + i, *(data + i));
		}
	}

	/* Set Byte Counter: set busy */
	usb_write8(IN0BC, data_len);

	return data_len;
}

/*
 * Data stage: read data from EP0 FIFO
 *
 * @return  Actual length
 */
static inline int ep0_read_fifo(uint8_t *data, uint32_t data_len)
{
	uint32_t i;
	uint8_t j;
	uint8_t len;
	uint32_t addr = EP0OUT_FIFO;

	/* Check Busy */
	if (usb_read8(EP0CS) & BIT(EP0CS_OUTBUSY)) {
		LOG_DBG("still busy");
		return -EBUSY;
	}

	/* Get Actual length */
	len = usb_read8(OUT0BC);
	if (data_len < len) {
		len = data_len;
	}

	/* 32-bit alignment */
	if (((long)data & 0x3) == 0) {
		for (i = 0; i < (len & ~0x3); i += 4) {
			*(uint32_t *)(data + i) = usb_read32(addr + i);
		}
		for (j = 0; j < (len & 0x3); j++) {
			*(data + i + j) = usb_read8(addr + i + j);
		}
	/* 16-bit alignment */
	} else if (((long)data & 0x1) == 0) {
		for (i = 0; i < (len & ~0x1); i += 2) {
			*(uint16_t *)(data + i) = usb_read16(addr + i);
		}
		/* The last byte */
		if (len & 0x1) {
			*(data + i) = usb_read8(addr + i);
		}
	} else {
		for (i = 0; i < len; i++) {
			*(data + i) = usb_read8(addr + i);
		}
	}

	return len;
}



/*
 * Device related
 */
#ifdef CONFIG_USB_AOTG_DC_ENABLED
/*
 * Check if the MaxPacketSize of endpoint is valid
 */
static inline bool aotg_dc_ep_mps_valid(uint16_t ep_mps,
				enum usb_dc_ep_type ep_type)
{
	enum usb_device_speed speed = usb_aotg_dc.speed;

	switch (ep_type) {
	case USB_DC_EP_CONTROL:
		return ep_mps <= USB_MAX_CTRL_MPS;

	case USB_DC_EP_BULK:
		if (speed == USB_SPEED_HIGH) {
			return ep_mps == USB_MAX_HS_BULK_MPS;
		} else {
			return ep_mps <= USB_MAX_FS_BULK_MPS;
		}

	case USB_DC_EP_INTERRUPT:
		if (speed == USB_SPEED_HIGH) {
			return ep_mps <= USB_MAX_HS_INTR_MPS;
		} else {
			return ep_mps <= USB_MAX_FS_INTR_MPS;
		}

	case USB_DC_EP_ISOCHRONOUS:
		if (speed == USB_SPEED_HIGH) {
			return ep_mps <= USB_MAX_HS_ISOC_MPS;
		} else {
			return ep_mps <= USB_MAX_FS_ISOC_MPS;
		}
	}

	return false;
}

/*
 * Check if the endpoint is enabled
 */
static inline bool aotg_dc_ep_is_enabled(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (USB_EP_DIR_IS_OUT(ep) && usb_aotg_dc.out_ep_ctrl[ep_idx].ep_ena) {
		return true;
	}
	if (USB_EP_DIR_IS_IN(ep) && usb_aotg_dc.in_ep_ctrl[ep_idx].ep_ena) {
		return true;
	}

	return false;
}

/*
 * Using single FIFO for every endpoint (except for ep0) by default
 */
static inline int aotg_dc_ep_alloc_fifo(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (!ep_idx) {
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		if (ep_idx >= USB_AOTG_OUT_EP_NUM) {
			return -EINVAL;
		}

		return aotg_dc_epout_alloc_fifo_specific(ep_idx);
	}

	if (ep_idx >= USB_AOTG_IN_EP_NUM) {
		return -EINVAL;
	}

	return aotg_dc_epin_alloc_fifo_specific(ep_idx);
}

/*
 * Max Packet Size Limit
 */
static inline int aotg_dc_set_max_packet(uint8_t ep, uint16_t ep_mps,
				enum usb_dc_ep_type ep_type)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	/*
	 * NOTE: If the MaxPacketSize is beyond the limit, we won't try to
	 * assign a valid value, because it will fail anyway when the upper
	 * layer try to check capacity of the endpoint!
	 */
	if (!aotg_dc_ep_mps_valid(ep_mps, ep_type)) {
		return -EINVAL;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		usb_write16(OUTxMAXPKT(ep_idx), ep_mps);
		usb_aotg_dc.out_ep_ctrl[ep_idx].mps = ep_mps;
	} else {
		usb_write16(INxMAXPKT(ep_idx), ep_mps);
		usb_aotg_dc.in_ep_ctrl[ep_idx].mps = ep_mps;
	}

	return 0;
}

/*
 * Endpoint type
 */
static inline void aotg_dc_set_ep_type(uint8_t ep, enum usb_dc_ep_type ep_type)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (USB_EP_DIR_IS_OUT(ep)) {
		usb_aotg_dc.out_ep_ctrl[ep_idx].ep_type = ep_type;
	} else {
		usb_aotg_dc.in_ep_ctrl[ep_idx].ep_type = ep_type;
	}
}

/*
 * Initialize AOTG hardware endpoint.
 *
 * Set FIFO address, endpoint type, FIFO type, max packet size
 */
static inline int aotg_dc_ep_set(uint8_t ep, uint16_t ep_mps,
				enum usb_dc_ep_type ep_type)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	int ret;

	LOG_DBG("Ep setup 0x%x, mps %d, type %d", ep, ep_mps, ep_type);

	/* Set FIFO address */
	ret = aotg_dc_ep_alloc_fifo(ep);
	if (ret) {
		return ret;
	}

	/* Set Max Packet */
	ret = aotg_dc_set_max_packet(ep, ep_mps, ep_type);
	if (ret) {
		return ret;
	}

	/* Set EP type */
	aotg_dc_set_ep_type(ep, ep_type);

	/* No need to set for endpoint 0 */
	if (!ep_idx) {
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		ep_ctrl = &usb_aotg_dc.out_ep_ctrl[ep_idx];
		if (ep_ctrl->multi) {
			/* Set endpoint type and FIFO type */
			usb_write8(OUTxCTRL(ep_idx),
				(ep_type << 2) | USB_AOTG_FIFO_DOUBLE);
			/* Set FIFOCTRL */
			usb_write8(FIFOCTRL, ep_idx | BIT(FIFOCTRL_AUTO));
		} else {
			usb_write8(OUTxCTRL(ep_idx),
				(ep_type << 2) | USB_AOTG_FIFO_SINGLE);
			/* Set FIFOCTRL */
			if (USB_EP_OUT_DMA_CAP(ep_idx)) {
				usb_write8(FIFOCTRL, ep_idx | BIT(FIFOCTRL_AUTO));
			} else {
				usb_write8(FIFOCTRL, ep_idx);
			}
		}
	} else {
		ep_ctrl = &usb_aotg_dc.in_ep_ctrl[ep_idx];
		if (ep_ctrl->multi) {
			usb_write8(INxCTRL(ep_idx),
				(ep_type << 2) | USB_AOTG_FIFO_DOUBLE);
		} else {
			usb_write8(INxCTRL(ep_idx),
				(ep_type << 2) | USB_AOTG_FIFO_SINGLE);
			if (USB_EP_IN_DMA_CAP(ep_idx)) {
				usb_write8(FIFOCTRL, ep_idx | BIT(FIFOCTRL_IO_IN) | BIT(FIFOCTRL_AUTO));
			} else {
				usb_write8(FIFOCTRL, ep_idx | BIT(FIFOCTRL_IO_IN));
			}
		}
	}

	return 0;
}

/*
 * Prepare for the next OUT transaction
 *
 * There are two ways: write OUTxBC or set busy for EPxCS.
 * I don't know which one is better really, just keep it both.
 */
static inline void aotg_dc_prep_rx(uint8_t ep_idx)
{
	/* Set busy */
	if (!ep_idx) {
#if 0
		usb_set_bit8(EP0CS, EP0CS_OUTBUSY);
#endif
		usb_write8(OUT0BC, 0x0);
	} else {
#if 0
		usb_write8(OUTxCS(ep_idx), 0);
#endif
		if (!(usb_read8(OUTxCS(ep_idx)) & BIT(EPCS_BUSY))) {
			usb_set_bit8(OUTxCS(ep_idx), EPCS_BUSY);
		}
	}
}

/*
 * Setup stage: read data from Setup FIFO
 */
static inline int ep0_read_setup(uint8_t *data, uint32_t data_len)
{
	uint8_t len = data_len;

	if (data_len > sizeof(struct usb_setup_packet)) {
		len = sizeof(struct usb_setup_packet);
	}

	memcpy(data, usb_aotg_dc.raw_setup, len);

	return len;
}

/*
 * Status stage
 */
static inline void handle_status(void)
{
	usb_set_bit8(EP0CS, EP0CS_NAK);
}

/*
 * Receive data from host: could be Setup, Data or Status
 *
 * @return  transfer length
 */
static inline int aotg_dc_rx(uint8_t ep, uint8_t *data, uint32_t data_len)
{
	int act_len = 0;

	/* Endpoint 0 */
	if (!USB_EP_ADDR2IDX(ep)) {
		if (usb_aotg_dc.phase == USB_AOTG_SETUP) {
			act_len = ep0_read_setup(data, data_len);
		} else if (usb_aotg_dc.phase == USB_AOTG_OUT_DATA) {
			act_len = ep0_read_fifo(data, data_len);
#if 0
		} else if (usb_aotg_dc.phase == USB_AOTG_OUT_STATUS) {
			/*
			 * Case 1: Setup, In Data, Out Status
			 */
			handle_status();
#endif
		}
	} else {
		act_len = ep_read_fifo(ep, data, data_len);
	}

	return act_len;
}

/*
 * Send data to host
 *
 * @return  transfer length
 */
static inline int aotg_dc_tx(uint8_t ep, const uint8_t *const data,
				uint32_t data_len)
{
	uint32_t act_len = 0;

	/* Endpoint 0 */
	if (!USB_EP_ADDR2IDX(ep)) {
		if ((usb_aotg_dc.phase == USB_AOTG_IN_DATA) && !data_len) {
			/*
			 * Handling zero-length packet for In-data phase
			 */
			act_len = ep0_write_fifo(data, data_len);
		} else if (!data_len) {
			/*
			 * Handling in-status stage
			 * Case 2: Setup, In Status
			 * Case 3: Setup, Out Data, In Status
			 */
			if (usb_aotg_dc.phase != USB_AOTG_SET_ADDRESS) {
				handle_status();
			}
			usb_aotg_dc.phase = USB_AOTG_IN_STATUS;
		} else {
			act_len = ep0_write_fifo(data, data_len);
			usb_aotg_dc.phase = USB_AOTG_IN_DATA;
		}
	} else {
		act_len = ep_write_fifo(ep, data, data_len);
	}

	return act_len;
}

static inline void aotg_dc_handle_otg(void)
{
	uint8_t tmp;
	uint8_t state;

	/* Clear OTG IRQ(s) */
	tmp = usb_read8(OTGIEN) & usb_read8(OTGIRQ);
	usb_write8(OTGIRQ, tmp);

	state = usb_read8(OTGSTATE);

	/* If AOTG enters b_peripheral state, enable Core interrupts */
	if (state == OTG_B_PERIPHERAL) {
#ifdef CONFIG_USB_AOTG_OTG_SUPPORT_HS
		usb_set_bit8(USBIEN, USBIEN_HS);
#endif
		usb_set_bit8(USBIEN, USBIEN_RESET);
		usb_set_bit8(USBIEN, USBIEN_SETUP);
	}

	LOG_DBG("State: 0x%x, irq: 0x%x", state, tmp);
}

static inline void aotg_dc_handle_reset(void)
{
	/* Clear USB Reset IRQ */
	usb_write8(USBIRQ, BIT(USBIRQ_RESET));

	usb_aotg_dc.speed = USB_SPEED_UNKNOWN;

	/* Clear USB SOF IRQ */
	usb_write8(USBIRQ, BIT(USBIRQ_SOF));
	/* Enable USB SOF IRQ */
	usb_set_bit8(USBIEN, USBIEN_SOF);

	LOG_DBG("");

#ifdef CONFIG_USB_AOTG_UDC_DMA
	dma_stop(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_single);
	dma_stop(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_burst8);
	dma_stop(usb_aotg_dma.dma_dev, usb_aotg_dma.epin_dma);
#endif

	/* Inform upper layers */
	if (usb_aotg_dc.status_cb) {
		usb_aotg_dc.status_cb(USB_DC_RESET, NULL);
	}
}

#ifdef CONFIG_USB_AOTG_OTG_SUPPORT_HS
static inline void aotg_dc_handle_hs(void)
{
	/* Clear USB high-speed IRQ */
	usb_write8(USBIRQ, BIT(USBIRQ_HS));

	usb_aotg_dc.speed = USB_SPEED_HIGH;

	LOG_DBG("");

	/* Inform upper layers */
	if (usb_aotg_dc.status_cb) {
		usb_aotg_dc.status_cb(USB_DC_HIGHSPEED, NULL);
	}
}
#endif

static inline void aotg_dc_handle_sof(void)
{
	/* Clear USB SOF IRQ */
	usb_write8(USBIRQ, BIT(USBIRQ_SOF));
	/* Disable USB SOF IRQ */
	usb_clear_bit8(USBIEN, USBIEN_SOF);

	if (usb_aotg_dc.speed == USB_SPEED_UNKNOWN) {
		usb_aotg_dc.speed = USB_SPEED_FULL;
	}

	LOG_DBG("");

	/* Inform upper layers */
	if (usb_aotg_dc.status_cb) {
		usb_aotg_dc.status_cb(USB_DC_SOF, NULL);
	}
}

static inline void aotg_dc_handle_resume(uint8_t usbeien)
{
	/* clear resume */
	usb_write8(USBEIRQ, BIT(USBEIRQ_RESUME) | usbeien);
	/* disable resume */
	usb_write8(USBEIEN, (~BIT(USBEIEN_RESUME)) & usbeien);
	if (usb_aotg_dc.status_cb) {
		usb_aotg_dc.status_cb(USB_DC_RESUME, NULL);
	}
}

static inline void aotg_dc_handle_suspend(uint8_t usbeien)
{
	usb_write8(USBIRQ, BIT(USBIRQ_SUSPEND));
	/* clear resume */
	usb_write8(USBEIRQ, BIT(USBEIRQ_RESUME) | usbeien);
	/* enable resume */
	usb_write8(USBEIEN, BIT(USBEIEN_RESUME) | usbeien);
	if (usb_aotg_dc.status_cb) {
		usb_aotg_dc.status_cb(USB_DC_SUSPEND, NULL);
	}
}

/*
 * Handle SETUP packet
 */
static inline void aotg_dc_handle_setup(void)
{
	usb_dc_ep_callback ep_cb;
	uint32_t addr = SETUP_FIFO;
	uint8_t i;

	/* Clear SETUP IRQ */
	usb_write8(USBIRQ, BIT(USBIRQ_SETUP));

	/* Fetch setup packet */
	for (i = 0; i < sizeof(struct usb_setup_packet); i++, addr++) {
		*(usb_aotg_dc.raw_setup + i) = usb_read8(addr);
	}

	/* Set setup data length */
	usb_aotg_dc.out_ep_ctrl[USB_AOTG_OUT_EP_0].data_len =
			sizeof(struct usb_setup_packet);

	/* Set in data length */
	if (USB_REQ_DIR_IN(usb_aotg_dc.setup.bmRequestType)) {
		usb_aotg_dc.in_ep_ctrl[USB_AOTG_IN_EP_0].data_len =
				usb_aotg_dc.setup.wLength;
	}

	usb_aotg_dc.phase = USB_AOTG_SETUP;

	ep_cb = usb_aotg_dc.out_ep_ctrl[USB_AOTG_OUT_EP_0].cb;
	/* Call the registered callback if any */
	if (ep_cb) {
		ep_cb(USB_AOTG_OUT_EP_0, USB_DC_EP_SETUP);
	}
}

/*
 * Handle EPxOUT data
 */
static inline void aotg_dc_handle_epout(uint8_t ep_idx)
{
	uint8_t ep = USB_EP_IDX2ADDR(ep_idx, USB_EP_DIR_OUT);
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint16_t len;

	/* Clear EPxOUT IRQ */
	usb_write8(OUTIRQ, BIT(ep_idx));

	ep_ctrl = &usb_aotg_dc.out_ep_ctrl[ep_idx];

	/* Get Actual Length */
	if (!ep_idx) {
		/* Check phase before checking byte counter */
		if (usb_aotg_dc.phase == USB_AOTG_OUT_STATUS) {
			return;
		}
		/*
		 * EP0OUT data irq may come before EPxOUT token irq in
		 * out-status phase, filter it otherwise it will be disturbed.
		 * It may come even in in-data phase, can't explain!
		 *
		 * Keep it (only for record), we will check setup packet!
		 */
		if (usb_aotg_dc.phase == USB_AOTG_IN_DATA) {
			return;
		}
		/* Filter by direction and length of setup packet */
		if (USB_REQ_DIR_IN(usb_aotg_dc.setup.bmRequestType)) {
			return;
		} else if (usb_aotg_dc.setup.wLength == 0) {
			return;
		}

		/* NOTICE: OUT0BC will be set in setup phase and data phase */
		len = usb_read8(OUT0BC);
		if (len == 0) {
			return;
		}
		usb_aotg_dc.phase = USB_AOTG_OUT_DATA;
	} else {
		len = usb_read16(OUTxBC(ep_idx));
		if (len == 0) {
			LOG_DBG("ep 0x%x recv zero-length packet", ep);
		}

		if (ep_ctrl->data) {
			/* Disable interrupt if received all data to support auto-mode */
			if ((ep_ctrl->data_len == len) ||
			    (len < ep_ctrl->mps)) {
				usb_clear_bit8(OUTIEN, ep_idx);
				LOG_DBG("Disable irq");
			}

			/* FIXME: leave it to upper layer, should we clear fifo? */
			if (len > ep_ctrl->data_len) {
				LOG_DBG("babble: %d, %d", len, ep_ctrl->data_len);
				len = ep_ctrl->data_len;
			}

			ep_read_fifo(ep, ep_ctrl->data, len);
			ep_ctrl->actual += len;
			ep_ctrl->data_len -= len;
			ep_ctrl->data += len;

			if (ep_ctrl->data_len == 0) {
				LOG_DBG("0x%x done 0x%x", ep, usb_read8(OUTxCS(ep_idx)));
				ep_ctrl->data = NULL;
				goto done;
			} else if (len < ep_ctrl->mps) {
				LOG_DBG("0x%x short %d", ep, len);
				ep_ctrl->data = NULL;
				goto done;
			} else {
				/* read continue */
				if (!(usb_read8(OUTxCS(ep_idx)) & BIT(EPCS_BUSY))) {
					usb_set_bit8(OUTxCS(ep_idx), EPCS_BUSY);
				}
				return;
			}
		}
	}

	ep_ctrl->data_len = len;
done:
	/* Call the registered callback if any */
	if (ep_ctrl->cb) {
		ep_ctrl->cb(ep, USB_DC_EP_DATA_OUT);
	}
}

/*
 * Handle EPxIN data
 */
static inline void aotg_dc_handle_epin(uint8_t ep_idx)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint32_t len;
	uint8_t ep;

	/* Clear EPxIN IRQ */
	usb_write8(INIRQ, BIT(ep_idx));

	ep_ctrl = &usb_aotg_dc.in_ep_ctrl[ep_idx];
	ep = USB_EP_IDX2ADDR(ep_idx, USB_EP_DIR_IN);

	if (ep_idx == 0) {
		goto done;
	}

	/* NOTICE: should never happen! */
	if (ep_ctrl->data == NULL) {
		LOG_DBG("0x%x", ep_idx);
		goto done;
	}

	if (ep_ctrl->data_len == 0) {
		LOG_DBG("0x%x done", ep);
		ep_ctrl->data = NULL;
		goto done;
	}

	if (ep_ctrl->data_len > ep_ctrl->mps) {
		len = ep_ctrl->mps;
	} else {
		len = ep_ctrl->data_len;
	}

	ep_write_fifo(ep, ep_ctrl->data, len);

	ep_ctrl->data_len -= len;
	ep_ctrl->data += len;

	return;
done:
	/* Call the registered callback if any */
	if (ep_ctrl->cb) {
		ep_ctrl->cb(ep, USB_DC_EP_DATA_IN);
	}
}

/*
 * Handle EPxIN empty
 */
static inline void aotg_dc_handle_epin_empty(void)
{
	uint8_t ien = (usb_read8(INEMPTY_IEN) & INEMPTY_IEN_MASK) >> INEMPTY_IEN_SHIFT;
	uint8_t irq = (usb_read8(INEMPTY_IRQ) & INEMPTY_IRQ_MASK) >> INEMPTY_IRQ_SHIFT;
	uint8_t ep_msk = ien & irq;
	usb_dc_ep_callback ep_cb;
	uint8_t ep_idx, ep;
	uint8_t i;

	/* clear all pendings */
	usb_write8(INEMPTY_IRQ, INEMPTY_IRQ_MASK);
	/* disable all interrupts */
	usb_write8(INEMPTY_IEN, 0);

	for (i = 0; i < USB_AOTG_IN_EP_NUM; i++) {
		if (ep_msk & BIT(i)) {
			ep_idx = INEMPTY_IRQ2ADDR(i) & USB_EP_NUM_MASK;
			ep = USB_EP_IDX2ADDR(ep_idx, USB_EP_DIR_IN);

			LOG_DBG("ep: 0x%x", ep);

			ep_cb = usb_aotg_dc.in_ep_ctrl[ep_idx].cb;
			/* Call the registered callback if any */
			if (ep_cb) {
				ep_cb(ep, USB_DC_EP_DATA_IN);
			}
		}
	}
}

static inline void aotg_dc_handle_out_token_internal(uint8_t ep_idx)
{
	if (ep_idx == USB_AOTG_OUT_EP_0) {
		/*
		 * We may get one or more OUT token(s) even though
		 * we have set ACK bit already, filter the case!
		 */
		if (usb_aotg_dc.phase == USB_AOTG_OUT_STATUS) {
			return;
		}

		/*
		 * Case 1: Setup, In Data, Out Status
		 *
		 * Status stage: no need to check busy
		 */
		if (usb_aotg_dc.phase == USB_AOTG_IN_DATA) {
			handle_status();
			usb_aotg_dc.phase = USB_AOTG_OUT_STATUS;
			return;
		}
	}

	/* Do nothing */
	return;

	/*
	 * NOTE: out token only take cares of out status phase, don't
	 * use it for out data phase, because ep0 may be busy all the
	 * time and can never complete!
	 */
#if 0
	usb_dc_ep_callback ep_cb;

	while (usb_read8(EP0CS) & BIT(EP0CS_OUTBUSY)) {
		;
	}

	usb_aotg_dc.out_ep_ctrl[ep_idx].data_len = usb_read8(OUT0BC);
	usb_aotg_dc.phase = USB_AOTG_OUT_DATA;

	ep_cb = usb_aotg_dc.out_ep_ctrl[ep_idx].cb;
	ep_idx = USB_EP_IDX2ADDR(ep_idx, USB_EP_DIR_OUT);
	/* Call the registered callback if any */
	if (ep_cb) {
		ep_cb(ep_idx, USB_DC_EP_DATA_OUT);
	}
#endif
}

/*
 * Handle EPxOUT Token
 */
static inline void aotg_dc_handle_out_token(void)
{
	uint8_t ep_msk, i;

	/* Get endpoint mask */
	ep_msk = (usb_read8(OUT_TOKIRQ) & usb_read8(OUT_TOKIEN));

	/* NOTE: There may be more than one out token irqs simutaneously */
	for (i = 0; i < EPOUT_TOKEN_NUM; i++) {
		if (ep_msk & BIT(i)) {
			/* Clear IRQ */
			usb_write8(OUT_TOKIRQ, BIT(i));

			aotg_dc_handle_out_token_internal(i);
		}
	}
}

/*
 * Handle EPxIN Token
 */
static inline void aotg_dc_handle_in_token(void)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_msk, i;

	/* Get endpoint mask */
	ep_msk = usb_read8(IN_TOKIRQ) & usb_read8(IN_TOKIEN);

	/* NOTE: There may be more than one in token irqs simutaneously */
	for (i = 0; i < EPIN_TOKEN_NUM; i++) {
		if (ep_msk & BIT(i)) {
			/* Clear IRQ */
			usb_write8(IN_TOKIRQ, BIT(i));

			ep_ctrl = &usb_aotg_dc.in_ep_ctrl[i];
			/* Call the registered callback if any */
			if (ep_ctrl->cb) {
				ep_ctrl->cb(i, USB_DC_EP_DATA_IN);
			}
		}
	}
}

/*
 * Handle EPxOUT Short Packet
 */
static inline void aotg_dc_handle_short_packet(void)
{
	usb_write8(OUT_SHTPKT, usb_read8(OUT_SHTPKT));

	LOG_DBG("aotg short packet");
}

static inline void aotg_dc_isr_dispatch(uint8_t vector)
{
	uint8_t usbeien = usb_read8(USBEIEN) & USBEIEN_MASK;

	/* USB Resume */
	if (vector == UIV_RESUME) {
		aotg_dc_handle_resume(usbeien);
		return;
	}

	/*
	 * Make sure external IRQ has been cleared right!
	 *
	 * TODO: If two or more IRQs are pending simultaneously,
	 * EIRQ will be pending immediately after cleared or not?
	 */
	while (usb_read8(USBEIRQ) & BIT(USBEIRQ_EXTERN)) {
		usb_write8(USBEIRQ, BIT(USBEIRQ_EXTERN) | usbeien);
	}

	switch (vector) {
	/* OTG */
	case UIV_OTGIRQ:
		aotg_dc_handle_otg();
		break;

	/* USB Reset */
	case UIV_USBRST:
		aotg_dc_handle_reset();
		break;

#ifdef CONFIG_USB_AOTG_OTG_SUPPORT_HS
	/* High-speed */
	case UIV_HSPEED:
		aotg_dc_handle_hs();
		break;
#endif

	/* SOF */
	case UIV_SOF:
		aotg_dc_handle_sof();
		break;

	/* USB Suspend */
	case UIV_SUSPEND:
		aotg_dc_handle_suspend(usbeien);
		break;

	/* SETUP */
	case UIV_SUDAV:
		aotg_dc_handle_setup();
		break;

	/* EPxOUT */
	case UIV_EP0OUT:
	case UIV_EP1OUT:
	case UIV_EP2OUT:
	case UIV_EP3OUT:
#if (CONFIG_USB_AOTG_OTG_VERSION == USB_AOTG_VERSION_LEOPARD)
	case UIV_EP4OUT:
	case UIV_EP5OUT:
	case UIV_EP6OUT:
#endif
		aotg_dc_handle_epout(UIV_EPOUT_VEC2ADDR(vector));
		break;

	/* EPxIN */
	case UIV_EP0IN:
	case UIV_EP1IN:
	case UIV_EP2IN:
	case UIV_EP3IN:
#if (CONFIG_USB_AOTG_OTG_VERSION == USB_AOTG_VERSION_LEOPARD)
	case UIV_EP4IN:
	case UIV_EP5IN:
	case UIV_EP6IN:
#endif
		aotg_dc_handle_epin(UIV_EPIN_VEC2ADDR(vector));
		break;

	/* EPxIN empty */
	case UIV_HCOUTEMPTY:
		aotg_dc_handle_epin_empty();
		break;

	/*
	 * Using OUT Token for EP0OUT data transfer for assistance.
	 *
	 * AOTG handles in/out status by software (handle_status()),
	 * which means it depends on USB core totally. But USB core
	 * thinks that controller should take care of out status by
	 * itself (excluding int status phase). That means EP0 Data
	 * IRQ will never be pending until handle_status() called!
	 */
	case UIV_OUTTOKEN:
		aotg_dc_handle_out_token();
		break;

	case UIV_INTOKEN:
		aotg_dc_handle_in_token();
		break;

	/* Short Packet */
	case UIV_EPOUTSHTPKT:
		aotg_dc_handle_short_packet();
		break;

	default:
		break;
	}
}

/* Disable soft disconnect */
static inline void aotg_dc_connect(void)
{
	usb_clear_bit8(USBCS, USBCS_DISCONN);
}

/* Enable soft disconnect */
static inline void aotg_dc_disconnect(void)
{
	usb_set_bit8(USBCS, USBCS_DISCONN);
}

static void usb_dc_hardware_init(void)
{
	usb_write32(AVDDLDO_CTL, 0x05);
	k_msleep(1);
	usb_write32(SPLL_CTL, sys_read32(SPLL_CTL)|0x01);
	k_msleep(1);
	LOG_DBG("SPLL_CTL: 0x%x", sys_read32(SPLL_CTL));

	acts_clock_peripheral_enable(CLOCK_ID_USB);
	acts_clock_peripheral_enable(CLOCK_ID_USB2);
	LOG_DBG("CMU_DEVCLKEN0: 0x%x", sys_read32(CMU_DEVCLKEN0));
}

int usb_dc_attach(void)
{
	if (usb_aotg_dc.attached) {
		LOG_DBG("already");
		return 0;
	}

	usb_dc_hardware_init();

	aotg_dc_fifo_enable();

	usb_aotg_enable();

	usb_write8(IDVBUSCTRL, IDVBUS_DEVICE);
	usb_write8(DPDMCTRL, DPDM_HOST);

	aotg_dc_phy_init();

#ifdef CONFIG_USB_AOTG_DC_FS
	aotg_dc_force_fs();
#endif

	/* Enable OTG(b_peripheral) interrupt */
	usb_set_bit8(OTGIEN, OTGIEN_PERIPHERAL);
	/* Enable external interrupt */
	usb_set_bit8(USBEIEN, USBEIEN_EXTERN);

	irq_enable(USB_AOTG_IRQ);

	aotg_dc_connect();

	usb_aotg_reg_dump();

	/* status_cb is registered */
	usb_aotg_dc.speed = USB_SPEED_UNKNOWN;
	usb_aotg_dc.attached = 1;

	LOG_DBG("");

	return 0;
}

int usb_dc_detach(void)
{
	unsigned int key = irq_lock();
	uint8_t speed;

	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		LOG_DBG("already");
		return 0;
	}

	irq_disable(USB_AOTG_IRQ);

	aotg_dc_disconnect();

#ifdef CONFIG_USB_AOTG_UDC_DMA
	dma_stop(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_single);
	dma_stop(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_burst8);
	dma_stop(usb_aotg_dma.dma_dev, usb_aotg_dma.epin_dma);
#endif

	/* recover speed */
	speed = usb_aotg_dc.speed;
	memset(&usb_aotg_dc, 0, sizeof(usb_aotg_dc));
	usb_aotg_dc.speed = speed;

	usb_aotg_reset();

	usb_aotg_disable();

	aotg_dc_fifo_disable();

	irq_unlock(key);

	LOG_DBG("");

	return 0;
}

int usb_dc_reset(void)
{
	LOG_DBG("");

	/* Clear private data */
	memset(&usb_aotg_dc, 0, sizeof(usb_aotg_dc));

	return 0;
}

int usb_dc_set_address(const uint8_t addr)
{
	if (addr > USB_AOTG_MAX_ADDR) {
		return -EINVAL;
	}

	/* Respond "Set Address" automatically in device mode */
	LOG_DBG("Set Address: %d", usb_read8(FNADDR));

	/* Set Address phase */
	usb_aotg_dc.phase = USB_AOTG_SET_ADDRESS;

	/* Enable suspend */
	usb_write8(USBIRQ, BIT(USBIRQ_SUSPEND));
	usb_set_bit8(USBIEN, USBIEN_SUSPEND);

	return 0;
}

int usb_dc_set_status_callback(const usb_dc_status_callback cb)
{
	usb_aotg_dc.status_cb = cb;

	return 0;
}

int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg)
{
	LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps,
			cfg->ep_type);

	/*
	 * Check if the address of control endpoint is non-zero!
	 */
	if ((cfg->ep_type == USB_DC_EP_CONTROL)
	    && USB_EP_ADDR2IDX(cfg->ep_addr)) {
		LOG_ERR("invalid endpoint configuration");
		return -EINVAL;
	}

	if (!aotg_dc_ep_mps_valid(cfg->ep_mps, cfg->ep_type))  {
		LOG_WRN("unsupported packet size");
		return -EINVAL;
	}

	if (!usb_aotg_ep_addr_valid(cfg->ep_addr)) {
		LOG_WRN("endpoint 0x%x address out of range", cfg->ep_addr);
		return -EINVAL;
	}

	return 0;
}

int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg)
{
	if (!usb_aotg_dc.attached || !usb_aotg_ep_addr_valid(ep_cfg->ep_addr))
		return -EINVAL;

	return aotg_dc_ep_set(ep_cfg->ep_addr, ep_cfg->ep_mps,
				ep_cfg->ep_type);
}

int usb_dc_ep_set_stall(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;

	if (!usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	/* Endpoint 0 */
	if (!ep_idx) {
		usb_set_bit8(EP0CS, EP0CS_STALL);
		irq_unlock(key);
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		usb_set_bit8(OUTxCTRL(ep_idx), EPCTRL_STALL);
		/* clear pending in case data received before stalled */
		usb_write8(OUTIRQ, BIT(ep_idx));
	} else {
		usb_set_bit8(INxCTRL(ep_idx), EPCTRL_STALL);
	}

	/* Reset the data toggle */
	usb_aotg_ep_reset(ep, USB_AOTG_EP_TOGGLE_RESET);

	irq_unlock(key);
	return 0;
}

int usb_dc_ep_clear_stall(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;

	if (!usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	if (!ep_idx) {
		/*
		 * case 1: EP0 stall will be cleared when SETUP token comes
		 * case 2: we can clear EP0CS_STALL bit to clear stall
		 */
		usb_clear_bit8(EP0CS, EP0CS_STALL);
		irq_unlock(key);
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		usb_clear_bit8(OUTxCTRL(ep_idx), EPCTRL_STALL);
	} else {
		usb_clear_bit8(INxCTRL(ep_idx), EPCTRL_STALL);
	}

	irq_unlock(key);
	return 0;
}

int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;

	if (!usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	if (!stalled) {
		return -EINVAL;
	}

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	*stalled = 0;

	/* Endpoint 0 */
	if (!ep_idx && (usb_read8(EP0CS) & BIT(EP0CS_STALL))) {
		*stalled = 1;
		irq_unlock(key);
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		if (usb_read8(OUTxCTRL(ep_idx)) & BIT(EPCTRL_STALL)) {
			*stalled = 1;
		}
	} else {
		if (usb_read8(INxCTRL(ep_idx)) & BIT(EPCTRL_STALL)) {
			*stalled = 1;
		}
	}

	irq_unlock(key);
	return 0;
}

int usb_dc_ep_halt(const uint8_t ep)
{
	/*
	 * Just set stall, by the way no clear halt operation?
	 */
	return usb_dc_ep_set_stall(ep);
}

int usb_dc_ep_enable(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	bool dir_out = USB_EP_DIR_IS_OUT(ep);

	if (!usb_aotg_dc.attached || !usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	if (usb_aotg_ep_reset(ep, USB_AOTG_EP_RESET)) {
		return -EINVAL;
	}

	/* Enable EP interrupts */
	if (dir_out) {
		/* EP0 OUT uses OUT Token IRQ for assistance */
		if (!ep_idx) {
			usb_set_bit8(OUT_TOKIEN, EP0OUT_TOKEN);
		}
		if (!USB_EP_OUT_DMA_CAP(ep_idx)) {
			usb_set_bit8(OUTIEN, ep_idx);
		}
		usb_aotg_dc.out_ep_ctrl[ep_idx].ep_ena = 1;
	} else {
#if 0
		/* Using In Token IRQ for isochronous transfer */
		if (ep_type_isoc(usb_aotg_dc.in_ep_ctrl[ep_idx].ep_type)) {
			usb_set_bit8(IN_TOKIEN, ep_idx);
		} else {
			usb_set_bit8(INIEN, ep_idx);
		}
#else
		if (!USB_EP_IN_DMA_CAP(ep_idx)) {
			usb_set_bit8(INIEN, ep_idx);
		}
#endif
		usb_aotg_dc.in_ep_ctrl[ep_idx].ep_ena = 1;
	}

	/* Endpoint 0 */
	if (!ep_idx) {
		return 0;
	}

	if (dir_out) {
		/* Activate Ep */
		usb_set_bit8(OUTxCTRL(ep_idx), EPCTRL_VALID);
		/* Short Packet */
		/* usb_set_bit8(OUT_SHTPKT, ep_idx); */
	} else {
		usb_set_bit8(INxCTRL(ep_idx), EPCTRL_VALID);
	}

	/* Prepare EP for rx */
	if (dir_out) {
		aotg_dc_prep_rx(ep_idx);
	}

	usb_aotg_ep_reg_dump(ep);

	return 0;
}

int usb_dc_ep_disable(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	bool dir_out = USB_EP_DIR_IS_OUT(ep);

	if (!usb_aotg_dc.attached || !usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	/* Disable EP interrupts */
	if (dir_out) {
		if (!ep_idx) {
			usb_clear_bit8(OUT_TOKIEN, EP0OUT_TOKEN);
		}
		usb_clear_bit8(OUTIEN, ep_idx);
		usb_aotg_dc.out_ep_ctrl[ep_idx].ep_ena = 0;
	} else {
		usb_clear_bit8(INIEN, ep_idx);
		usb_aotg_dc.in_ep_ctrl[ep_idx].ep_ena = 0;
	}

	/* Endpoint 0 */
	if (!ep_idx) {
		return 0;
	}

	/* De-activate Ep */
	if (dir_out) {
		usb_clear_bit8(OUTxCTRL(ep_idx), EPCTRL_VALID);
	} else {
		usb_clear_bit8(INxCTRL(ep_idx), EPCTRL_VALID);
	}

	return 0;
}

int usb_dc_ep_flush(const uint8_t ep)
{
	unsigned int key;
	int ret;

	if (!usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	ret = usb_aotg_ep_reset(ep, USB_AOTG_EP_FIFO_RESET);
	irq_unlock(key);

	return ret;
}

/*
 * legacy mode: write mps at most and depends on upper layer to write again
 *              if data_len >= mps for all out endpoints.
 * new mode: write once (controller will handle if data_len >= mps) for all
 *           non-control endpoints, control endpoints still works in legacy
 *           mode.
 */
int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data,
				const uint32_t data_len, uint32_t * const ret_bytes)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;
	int ret;
	uint32_t len;

	if (!usb_aotg_ep_addr_valid(ep)) {
		LOG_ERR("No valid endpoint");
		return -EINVAL;
	}

	/* Check if IN ep */
	if (!USB_EP_DIR_IS_IN(ep)) {
		LOG_ERR("Wrong endpoint direction");
		return -EINVAL;
	}

	/* Check if ep enabled */
	if (!aotg_dc_ep_is_enabled(ep)) {
		LOG_ERR("Not enabled endpoint");
		return -EINVAL;
	}

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	ep_ctrl = &usb_aotg_dc.in_ep_ctrl[ep_idx];

#ifdef CONFIG_USB_AOTG_UDC_DMA
	/* need 32-bit alignment */
	if (((long)data & 0x3) != 0) {
		goto cpu_write;
	}

	if (USB_EP_IN_DMA_CAP(ep_idx)) {
		if ((data_len < USB_AOTG_DMA_BURST8_LEN) ||
		    (data_len % USB_AOTG_DMA_BURST8_LEN)) {
			goto cpu_write;
		}
		if (data_len >= USB_AOTG_DMA_MAX_SIZE) {
			len = USB_AOTG_DMA_MAX_LEN;
		} else {
			len = data_len;
		}

		ep_ctrl->data_len = len;

		/* Disable EP interrupt */
		usb_clear_bit8(INIEN, ep_idx);

		usb_write8(IN1_DMACTL, BIT(DMACTL_FIFORST));
		usb_write8(IN1_DMACTL, 0);
		usb_write8(IN1_DMALEN1L, (uint8_t)len);
		usb_write8(IN1_DMALEN1M, (uint8_t)(len >> 8));
		usb_write8(IN1_DMALEN1H, (uint8_t)(len >> 16) | (0x1 << 1));
		usb_write8(IN1_DMALEN2H, 0);
		usb_write8(IN1_DMACTL, BIT(DMACTL_START));

		dma_reload(usb_aotg_dma.dma_dev, usb_aotg_dma.epin_dma,
			(uint32_t)data, FIFO1DAT, len);
		dma_start(usb_aotg_dma.dma_dev, usb_aotg_dma.epin_dma);

		if (ret_bytes) {
			*ret_bytes = len;
		}

		irq_unlock(key);
		return 0;
	}
cpu_write:
#endif
	ep_ctrl->data = NULL;

	len = data_len > ep_ctrl->mps ? ep_ctrl->mps : data_len;

	if (ep_idx != 0) {
		goto new_mode;
	}

	ret = aotg_dc_tx(ep, data, len);
	if (ret < 0) {
		irq_unlock(key);
		return ret;
	}

	if (ret_bytes) {
		*ret_bytes = ret;
	}

	if (usb_aotg_dc.phase != USB_AOTG_IN_DATA) {
		irq_unlock(key);
		return 0;
	}

	usb_aotg_dc.in_ep_ctrl[ep_idx].data_len -= ret;
	if ((usb_aotg_dc.in_ep_ctrl[ep_idx].data_len == 0) ||
	    (ret < MAX_PACKET_SIZE0)) {
		/* Case 1: Setup, In Data, Out Status */
		handle_status();
		usb_aotg_dc.phase = USB_AOTG_OUT_STATUS;
	}

	irq_unlock(key);
	return 0;
new_mode:
	LOG_DBG("%d", data_len);

	ep_ctrl->data_len = data_len - len;
	ep_ctrl->data = (uint8_t *)data + len;

	ret = ep_write_fifo(ep, data, len);
	if (ret < 0) {
		irq_unlock(key);
		return ret;
	}

	if (ret_bytes) {
		*ret_bytes = data_len;
	}

	irq_unlock(key);
	return 0;
}

int usb_dc_ep_read(const uint8_t ep, uint8_t *const data,
		const uint32_t max_data_len, uint32_t * const read_bytes)
{
	if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) {
		return -EINVAL;
	}

	if (!data && !max_data_len) {
		/* When both buffer and max data to read are zero the above
		 * call would fetch the data len and we simply return.
		 */
		return 0;
	}

	if (usb_dc_ep_read_continue(ep) != 0) {
		return -EINVAL;
	}

	return 0;
}

int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (!usb_aotg_dc.attached || !usb_aotg_ep_addr_valid(ep)) {
		return -EINVAL;
	}

	if (USB_EP_DIR_IS_IN(ep)) {
		usb_aotg_dc.in_ep_ctrl[ep_idx].cb = cb;
	} else {
		usb_aotg_dc.out_ep_ctrl[ep_idx].cb = cb;
	}

	return 0;
}

/*
 * read() and read_async() co-exist, which means fisrt read_async() should be
 * called ASAP to make sure outirq is after read_async(). It is recommended
 * called in status_cb().
 */
int usb_dc_ep_read_async(uint8_t ep, uint8_t *data, uint32_t max_data_len,
				uint32_t *read_bytes)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;

	if (!usb_aotg_ep_addr_valid(ep)) {
		LOG_ERR("No valid endpoint");
		return -EINVAL;
	}

	/* Check if OUT ep */
	if (!USB_EP_DIR_IS_OUT(ep)) {
		LOG_ERR("Wrong endpoint direction");
		return -EINVAL;
	}

	/* Allow to read 0 bytes */
	if (!data && max_data_len) {
		LOG_ERR("Wrong arguments");
		return -EINVAL;
	}

	/* Check if ep enabled */
	if (!aotg_dc_ep_is_enabled(ep)) {
		LOG_ERR("Not enabled endpoint");
		return -EINVAL;
	}

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	ep_ctrl = &usb_aotg_dc.out_ep_ctrl[ep_idx];

#ifdef CONFIG_USB_AOTG_UDC_DMA

	/* need 32-bit alignment */
	if (((long)data & 0x3) != 0) {
		goto cpu_mode;
	}

	if (!USB_EP_OUT_DMA_CAP(ep_idx)) {
		LOG_DBG("for DMA only");
		goto cpu_mode;
	}

	if (max_data_len >= USB_AOTG_DMA_MAX_SIZE) {
		max_data_len = USB_AOTG_DMA_MAX_LEN;
	}

	ep_ctrl->data_len = max_data_len;
	ep_ctrl->actual = 0;

	usb_write8(OUT2_DMACTL, BIT(DMACTL_FIFORST));
	usb_write8(OUT2_DMACTL, 0);
	usb_write8(OUT2_DMALENL, (uint8_t)max_data_len);
	usb_write8(OUT2_DMALENM, (uint8_t)(max_data_len >> 8));
	usb_write8(OUT2_DMALENH, (uint8_t)(max_data_len >> 16));
	if ((max_data_len >= USB_AOTG_DMA_BURST8_LEN) &&
	    !(max_data_len % USB_AOTG_DMA_BURST8_LEN)) {
		usb_write8(OUT2_DMACTL, BIT(DMACTL_START));

		dma_reload(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_burst8,
			FIFO2DAT, (uint32_t)data, max_data_len);
		dma_start(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_burst8);
	} else {
		usb_write8(OUT2_DMACTL, BIT(DMACTL_MODE) | BIT(DMACTL_START));

		dma_reload(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_single,
			FIFO2DAT, (uint32_t)data, max_data_len);
		dma_start(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_single);
	}

	if (read_bytes) {
		*read_bytes = max_data_len;
	}

	irq_unlock(key);
	return 0;
cpu_mode:
#endif

	/* FIXME: always return max_data_len */
	if (read_bytes) {
		*read_bytes = max_data_len;
	}

	LOG_DBG("%d", max_data_len);

	ep_ctrl = &usb_aotg_dc.out_ep_ctrl[ep_idx];
	ep_ctrl->data_len = max_data_len;
	ep_ctrl->data = data;
	ep_ctrl->actual = 0;

	/* Enable interrupt */
	usb_set_bit8(OUTIEN, ep_idx);

	aotg_dc_prep_rx(ep_idx);

	irq_unlock(key);
	return 0;
}

int usb_dc_ep_read_actual(uint8_t ep, uint32_t *read_bytes)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx;

	if (!USB_EP_DIR_IS_OUT(ep)) {
		return -EINVAL;
	}

	ep_idx = USB_EP_ADDR2IDX(ep) & USB_EP_NUM_MASK;
	ep_ctrl = &usb_aotg_dc.out_ep_ctrl[ep_idx];

	if (read_bytes) {
		*read_bytes = ep_ctrl->actual;
	}

	return 0;
}

#ifdef CONFIG_USB_AOTG_DC_MULTI_FIFO
static uint8_t usb_dc_ep_io_cancelled;

int usb_dc_ep_set_multi_fifo(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (USB_EP_DIR_IS_OUT(ep)) {
		usb_aotg_dc.out_ep_ctrl[ep_idx].multi = 1;
	} else {
		usb_aotg_dc.in_ep_ctrl[ep_idx].multi = 1;
	}

	return 0;
}

/*
 * Specific for epin multi-fito non-auto mode, depends on class driver heavily!
 * Until now, only tested for mass storage.
 *
 * for short packet: just like write() with new mode.
 * for others: write data_len bytes synchronously.
 */
int usb_dc_ep_write_pending(const uint8_t ep, uint8_t *data,
				uint32_t data_len, uint32_t * const ret_bytes)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	int ret;
	uint32_t len;
	uint16_t count;
	uint8_t in_empty;
	uint8_t npak;
	uint32_t left;

	if (!usb_aotg_dc.attached || !usb_aotg_ep_addr_valid(ep)) {
		LOG_ERR("No valid endpoint");
		return -EINVAL;
	}

	/* for non-control endpoints */
	if (ep_idx == 0) {
		LOG_ERR("control endpoint");
		return -EINVAL;
	}

	/* Check if IN ep */
	if (!USB_EP_DIR_IS_IN(ep)) {
		LOG_ERR("Wrong endpoint direction");
		return -EINVAL;
	}

	/* Check if ep enabled */
	if (!aotg_dc_ep_is_enabled(ep)) {
		LOG_ERR("Not enabled endpoint");
		return -EINVAL;
	}

	LOG_DBG("%d", data_len);

	/* Disable interrupt always */
	usb_clear_bit8(INIEN, ep_idx);

	ep_ctrl = &usb_aotg_dc.in_ep_ctrl[ep_idx];
	if (data_len < ep_ctrl->mps) {
		goto write;
	}

	usb_dc_ep_io_cancelled = 0;

	npak = usb_read8(INxCS(ep_idx)) & EPCS_NPAK_MASK;

	left = data_len;
	while (left) {
		if (left >= ep_ctrl->mps) {
			len = ep_ctrl->mps;
		} else {
			len = left;
		}
		ep_write_fifo(ep, data, len);

		left -= len;
		data += len;

		LOG_DBG("CS: 0x%x", usb_read8(INxCS(ep_idx)));

		count = 0;
		while (usb_read8(INxCS(ep_idx)) & BIT(EPCS_BUSY)) {
			if (usb_dc_ep_io_cancelled) {
				goto exit;
			}

			if (++count < 1000) {
				k_busy_wait(1);
				continue;
			}

			k_sleep(K_MSEC(20));

			/* timeout: 1000us + 200ms */
			if (count > 1010) {
				LOG_ERR("timeout: 0x%x", usb_read8(INxCS(ep_idx)));
				goto exit;
			}
		}
	}

	/* wait for fifo empty */
	count = 0;
	while (1) {
		LOG_DBG("last CS: 0x%x", usb_read8(INxCS(ep_idx)));
		if ((usb_read8(INxCS(ep_idx)) & EPCS_NPAK_MASK) == npak) {
			break;
		}

		if (usb_dc_ep_io_cancelled) {
			goto exit;
		}

		if (++count < 1000) {
			k_busy_wait(1);
			continue;
		}

		k_sleep(K_MSEC(20));

		/* timeout: 1000us + 200ms */
		if (count > 1010) {
			LOG_ERR("last timeout 0x%x", usb_read8(INxCS(ep_idx)));
			goto exit;
		}
	}

	/* Call the registered callback if any */
	if (ep_ctrl->cb) {
		ep_ctrl->cb(ep, USB_DC_EP_DATA_IN);
	}

exit:
	if (ret_bytes) {
		*ret_bytes = data_len - left;
	}

	LOG_DBG("done CS: 0x%x", usb_read8(INxCS(ep_idx)));

	return 0;
write:
	in_empty = usb_read8(INEMPTY_IEN) & INEMPTY_IEN_MASK;
	/* clear pending */
	usb_write8(INEMPTY_IRQ, BIT(IRQ_INxEMPTY(ep_idx)) | in_empty);

	ret = ep_write_fifo(ep, data, data_len);
	if (ret < 0) {
		return ret;
	}
	/* enable empty */
	usb_write8(INEMPTY_IEN, BIT(IEN_INxEMPTY(ep_idx)) | in_empty);

	if (ret_bytes) {
		*ret_bytes = data_len;
	}

	return 0;
}

/*
 * Specific for epout multi-fito auto mode, depends on class driver heavily!
 * Until now, only tested for mass storage.
 *
 * for short packet: just like read_async(), but no need to set busy.
 * for others: read max_data_len bytes synchronously.
 */
int usb_dc_ep_read_pending(uint8_t ep, uint8_t *data, uint32_t data_len,
				uint32_t *read_bytes)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	uint32_t len;
	uint16_t count;
	uint32_t left;

	if (!usb_aotg_dc.attached || !usb_aotg_ep_addr_valid(ep)) {
		LOG_ERR("No valid endpoint");
		return -EINVAL;
	}

	/* for non-control endpoints */
	if (ep_idx == 0) {
		LOG_ERR("control endpoint");
		return -EINVAL;
	}

	/* Check if OUT ep */
	if (!USB_EP_DIR_IS_OUT(ep)) {
		LOG_ERR("Wrong endpoint direction");
		return -EINVAL;
	}

	/* Allow to read 0 bytes */
	if (!data && data_len) {
		LOG_ERR("Wrong arguments");
		return -EINVAL;
	}

	/* Check if ep enabled */
	if (!aotg_dc_ep_is_enabled(ep)) {
		LOG_ERR("Not enabled endpoint");
		return -EINVAL;
	}

	LOG_DBG("%d", data_len);

	ep_ctrl = &usb_aotg_dc.out_ep_ctrl[ep_idx];

	if (data_len < ep_ctrl->mps) {
		goto read_async;
	}

	/* Disable interrupt */
	usb_clear_bit8(OUTIEN, ep_idx);

	usb_dc_ep_io_cancelled = 0;

	left = data_len;
	while (left) {
		count = 0;
		while (usb_read8(OUTxCS(ep_idx)) & BIT(EPCS_BUSY)) {
			if (usb_dc_ep_io_cancelled) {
				goto exit;
			}

			if (++count < 1000) {
				k_busy_wait(1);
				continue;
			}

			k_sleep(K_MSEC(20));

			/* timeout: 1000us + 200ms */
			if (count > 1010) {
				LOG_ERR("timeout: 0x%x left: %d",
					usb_read8(OUTxCS(ep_idx)), left);
				goto exit;
			}
		}

		len = usb_read16(OUTxBC(ep_idx));
		LOG_DBG("BC: %d", len);

		ep_read_fifo(ep, data, len);

		left -= len;
		data += len;

		LOG_DBG("CS: 0x%x", usb_read8(OUTxCS(ep_idx)));
	}

exit:
	if (read_bytes) {
		*read_bytes = data_len - left;
	}

	/* Clear EPxIN IRQ */
	usb_write8(OUTIRQ, BIT(ep_idx));

	/* Call the registered callback if any */
	if (ep_ctrl->cb) {
		ep_ctrl->cb(ep, USB_DC_EP_DATA_OUT);
	}

	return 0;
read_async:
	/* FIXME: always return max_data_len */
	if (read_bytes) {
		*read_bytes = data_len;
	}

	ep_ctrl->data_len = data_len;
	ep_ctrl->data = data;

	/* Enable interrupt */
	usb_set_bit8(OUTIEN, ep_idx);

	return 0;
}

int usb_dc_ep_io_cancel(void)
{
	usb_dc_ep_io_cancelled = 1;

	return 0;
}

#endif /* CONFIG_USB_AOTG_DC_MULTI_FIFO */

int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len,
				uint32_t *read_bytes)
{
	uint32_t data_len, bytes_to_copy;
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;

	if (!usb_aotg_ep_addr_valid(ep)) {
		LOG_ERR("No valid endpoint");
		return -EINVAL;
	}

	/* Check if OUT ep */
	if (!USB_EP_DIR_IS_OUT(ep)) {
		LOG_ERR("Wrong endpoint direction");
		return -EINVAL;
	}

	/* Allow to read 0 bytes */
	if (!data && max_data_len) {
		LOG_ERR("Wrong arguments");
		return -EINVAL;
	}

	/* Check if ep enabled */
	if (!aotg_dc_ep_is_enabled(ep)) {
		LOG_ERR("Not enabled endpoint");
		return -EINVAL;
	}

	data_len = usb_aotg_dc.out_ep_ctrl[ep_idx].data_len;

	if (!data && !max_data_len) {
		/* When both buffer and max data to read are zero return
		 * the available data in buffer
		 */
		if (read_bytes) {
			*read_bytes = data_len;
		}

		return 0;
	}

	if (data_len > max_data_len) {
		LOG_ERR("Not enough room (%d) to copy all the rcvd data(%d)!",
			max_data_len, data_len);
		bytes_to_copy = max_data_len;
	} else {
		bytes_to_copy = data_len;
	}

	LOG_DBG("Read EP%d, req %d, read %d bytes",
		ep, max_data_len, bytes_to_copy);

	key = irq_lock();
	if (!usb_aotg_dc.attached) {
		irq_unlock(key);
		return -ENODEV;
	}

	aotg_dc_rx(ep, data, bytes_to_copy);
	irq_unlock(key);

	usb_aotg_dc.out_ep_ctrl[ep_idx].data_len -= bytes_to_copy;

	if (read_bytes) {
		*read_bytes = bytes_to_copy;
	}

	return 0;
}

int usb_dc_ep_read_continue(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);
	unsigned int key;

	if (!usb_aotg_ep_addr_valid(ep)) {
		LOG_ERR("No valid endpoint");
		return -EINVAL;
	}

	/* Check if OUT ep */
	if (!USB_EP_DIR_IS_OUT(ep)) {
		LOG_ERR("Wrong endpoint direction");
		return -EINVAL;
	}

	if (!usb_aotg_dc.out_ep_ctrl[ep_idx].data_len) {
		key = irq_lock();
		if (!usb_aotg_dc.attached) {
			irq_unlock(key);
			return -ENODEV;
		}

		aotg_dc_prep_rx(ep_idx);
		irq_unlock(key);
	}

	return 0;
}

int usb_dc_ep_mps(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(ep);

	if (USB_EP_DIR_IS_OUT(ep)) {
		return usb_aotg_dc.out_ep_ctrl[ep_idx].mps;
	} else {
		return usb_aotg_dc.in_ep_ctrl[ep_idx].mps;
	}
}

#ifdef CONFIG_USB_AOTG_UDC_DMA
static void usb_aotg_epin_dma_callback(const struct device *dev, void *data,
				uint32_t channel, int reason)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep;

	if (reason != DMA_IRQ_TC) {
		return;
	}

	ep_ctrl = &usb_aotg_dc.in_ep_ctrl[USB_AOTG_IN_EP_1];

	/* Set busy */
	if (ep_ctrl->data_len < ep_ctrl->mps) {
		usb_set_bit8(INxCS(USB_AOTG_IN_EP_1), EPCS_BUSY);
	}

	ep = USB_EP_IDX2ADDR(USB_AOTG_IN_EP_1, USB_EP_DIR_IN);
	/* Call the registered callback if any */
	if (ep_ctrl->cb) {
		ep_ctrl->cb(ep, USB_DC_EP_DATA_IN);
	}
}

static void usb_aotg_epout_dma_callback(const struct device *dev, void *data,
				uint32_t channel, int reason)
{
	struct aotg_dc_ep_ctrl_prv *ep_ctrl;

	if (reason != DMA_IRQ_TC) {
		return;
	}

	ep_ctrl = &usb_aotg_dc.out_ep_ctrl[USB_AOTG_OUT_EP_2];
	ep_ctrl->actual = ep_ctrl->data_len;

	/* Call the registered callback if any */
	if (ep_ctrl->cb) {
		ep_ctrl->cb(USB_AOTG_OUT_EP_2, USB_DC_EP_DATA_OUT);
	}
}

static int usb_aotg_dma_init(void)
{
	int ret;

	usb_aotg_dma.dma_dev = device_get_binding(CONFIG_DMA_0_NAME);
	if (!usb_aotg_dma.dma_dev) {
		printk("%s no dma_dev\n", __func__);
		return -ENODEV;
	}

	/*
	 * USB epout single DMA
	 */
	usb_aotg_dma.epout_dma_single = dma_request(usb_aotg_dma.dma_dev, 0xff);
	if (usb_aotg_dma.epout_dma_single < 0) {
		printk("%s epout single dma_request\n", __func__);
		return -EINVAL;
	}

	usb_aotg_dma.epout_dma_config_single.channel_direction = PERIPHERAL_TO_MEMORY;
	usb_aotg_dma.epout_dma_config_single.dma_callback = usb_aotg_epout_dma_callback;
	usb_aotg_dma.epout_dma_config_single.source_data_size = 1;
	usb_aotg_dma.epout_dma_config_single.source_burst_length = 1;
	usb_aotg_dma.epout_dma_config_single.dma_slot = DMA_ID_USB0;
	usb_aotg_dma.epout_dma_config_single.complete_callback_en = 1;
	usb_aotg_dma.epout_dma_config_single.head_block = &usb_aotg_dma.epout_dma_block_single;

	ret = dma_config(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_single,
		&usb_aotg_dma.epout_dma_config_single);
	if (ret) {
		printk("%s epout single dma_config %d\n", __func__, ret);
		return -EINVAL;
	}

	/*
	 * USB epout burst8 DMA
	 */
	usb_aotg_dma.epout_dma_burst8 = dma_request(usb_aotg_dma.dma_dev, 0xff);
	if (usb_aotg_dma.epout_dma_burst8 < 0) {
		printk("%s epout burst8 dma_request\n", __func__);
		return -EINVAL;
	}

	usb_aotg_dma.epout_dma_config_burst8.channel_direction = PERIPHERAL_TO_MEMORY;
	usb_aotg_dma.epout_dma_config_burst8.dma_callback = usb_aotg_epout_dma_callback;
	usb_aotg_dma.epout_dma_config_burst8.source_data_size = 4;
	usb_aotg_dma.epout_dma_config_burst8.source_burst_length = 0;
	usb_aotg_dma.epout_dma_config_burst8.dma_slot = DMA_ID_USB0;
	usb_aotg_dma.epout_dma_config_burst8.complete_callback_en = 1;
	usb_aotg_dma.epout_dma_config_burst8.head_block = &usb_aotg_dma.epout_dma_block_burst8;

	ret = dma_config(usb_aotg_dma.dma_dev, usb_aotg_dma.epout_dma_burst8,
		&usb_aotg_dma.epout_dma_config_burst8);
	if (ret) {
		printk("%s epout burst8 dma_config %d\n", __func__, ret);
		return -EINVAL;
	}

	/*
	 * USB epin burst8 DMA
	 */
	usb_aotg_dma.epin_dma = dma_request(usb_aotg_dma.dma_dev, 0xff);
	if (usb_aotg_dma.epin_dma < 0) {
		printk("%s epin dma_request\n", __func__);
		return -EINVAL;
	}

	usb_aotg_dma.epin_dma_config.channel_direction = MEMORY_TO_PERIPHERAL;
	usb_aotg_dma.epin_dma_config.dma_callback = usb_aotg_epin_dma_callback;
	usb_aotg_dma.epin_dma_config.source_data_size = 4;
	usb_aotg_dma.epout_dma_config_burst8.source_burst_length = 0;
	usb_aotg_dma.epin_dma_config.dma_slot = DMA_ID_USB0;
	usb_aotg_dma.epin_dma_config.complete_callback_en = 1;
	usb_aotg_dma.epin_dma_config.head_block = &usb_aotg_dma.epin_dma_block;

	ret = dma_config(usb_aotg_dma.dma_dev, usb_aotg_dma.epin_dma,
		&usb_aotg_dma.epin_dma_config);
	if (ret) {
		printk("%s epin dma_config %d\n", __func__, ret);
		return -EINVAL;
	}

	return 0;
}
#endif

enum usb_device_speed usb_dc_maxspeed(void)
{
#ifdef CONFIG_USB_AOTG_OTG_SUPPORT_HS
#ifdef CONFIG_USB_AOTG_DC_FS
	return USB_SPEED_FULL;
#else
	return USB_SPEED_HIGH;
#endif
#else
	return USB_SPEED_FULL;
#endif
}

enum usb_device_speed usb_dc_speed(void)
{
	return usb_aotg_dc.speed;
}

int usb_dc_do_remote_wakeup(void)
{
	usb_set_bit8(USBCS, USBCS_WAKEUP);

	return 0;
}
#endif /* CONFIG_USB_AOTG_DC_ENABLED */



/*
 * Host related
 */
#ifdef CONFIG_USB_AOTG_HC_ENABLED
static inline void aotg_hc_handle_reset(void);

/*
 * Map endpoint address of peripheral to controller's
 *
 * mapping rules:
 * 1. out_ep_ctrl[idx] used for peripheral's ep-in
 * 2. in_ep_ctrl[idx] used for peripheral's ep-out
 *
 * for examples:
 * 1. 0x81(peri_ep) -- 0x01(aotg_ep) -- dir_out -- out_ep_ctrl
 * 2. 0x01(peri_ep) -- 0x81(aotg_ep) -- dir_in --in_ep_ctrl
 * 3. 0x80(peri_ep) -- 0x00(aotg_ep) -- dir_out -- out_ep_ctrl
 * 4. 0x00(peri_ep) -- 0x80(aotg_ep) -- dir_in --in_ep_ctrl
 */
static inline int ep_map(uint8_t ep)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	uint8_t i;

	/* suppose ep0-in is 0x80 and ep0-out is 0x0 always */
	if (ep == USB_CONTROL_OUT_EP0) {
		usb_aotg_hc.in_ep_ctrl[0].ep_addr = ep;
		return 0;
	} else if (ep == USB_CONTROL_IN_EP0) {
		usb_aotg_hc.out_ep_ctrl[0].ep_addr = ep;
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		for (i = USB_AOTG_IN_EP_NUM - 1; i > USB_AOTG_EP0_IDX; i--) {
			ep_ctrl = &usb_aotg_hc.in_ep_ctrl[i];
			if (ep_ctrl->ep_ena) {
				continue;
			}
			ep_ctrl->ep_addr = ep;
			return 0;
		}
	} else {
		for (i = USB_AOTG_OUT_EP_1; i < USB_AOTG_OUT_EP_NUM; i++) {
			ep_ctrl = &usb_aotg_hc.out_ep_ctrl[i];
			if (ep_ctrl->ep_ena) {
				continue;
			}
			ep_ctrl->ep_addr = ep;
			return 0;
		}
	}

	return -EINVAL;
}

/*
 * Convert endpoint address of peripheral to controller's
 */
static inline uint8_t find_ep(uint8_t ep)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	uint8_t i;

	if (ep == USB_CONTROL_OUT_EP0) {
		return USB_CONTROL_IN_EP0;
	} else if (ep == USB_CONTROL_IN_EP0) {
		return USB_CONTROL_OUT_EP0;
	}

	if (USB_EP_DIR_IS_OUT(ep)) {
		for (i = USB_AOTG_IN_EP_NUM - 1; i > USB_AOTG_EP0_IDX; i--) {
			ep_ctrl = &usb_aotg_hc.in_ep_ctrl[i];
			if (ep_ctrl->ep_addr != ep) {
				continue;
			}
			return USB_EP_IDX2ADDR(i, USB_EP_DIR_IN);
		}
	} else {
		for (i = USB_AOTG_OUT_EP_NUM - 1; i > USB_AOTG_EP0_IDX; i--) {
			ep_ctrl = &usb_aotg_hc.out_ep_ctrl[i];
			if (ep_ctrl->ep_addr != ep) {
				continue;
			}
			return i;
		}
	}

	/* never */
	return 0;
}

/*
 * Check if the MaxPacketSize of endpoint is valid
 */
static inline bool aotg_hc_ep_mps_valid(uint16_t ep_mps, enum usb_ep_type ep_type)
{
	enum usb_device_speed speed = usb_aotg_hc.speed;

	switch (ep_type) {
	case USB_EP_CONTROL:
		/* Arbitrary: speed may be unknown */
		return ep_mps <= USB_MAX_CTRL_MPS;

	case USB_EP_BULK:
		if (speed == USB_SPEED_HIGH) {
			return ep_mps == USB_MAX_HS_BULK_MPS;
		} else if (speed == USB_SPEED_FULL) {
			return ep_mps <= USB_MAX_FS_BULK_MPS;
		}
		break;

	case USB_EP_INTERRUPT:
		if (speed == USB_SPEED_HIGH) {
			return ep_mps <= USB_MAX_HS_INTR_MPS;
		} else if (speed == USB_SPEED_FULL) {
			return ep_mps <= USB_MAX_FS_INTR_MPS;
		}
		break;

	case USB_EP_ISOCHRONOUS:
		if (speed == USB_SPEED_HIGH) {
			return ep_mps <= USB_MAX_HS_ISOC_MPS;
		} else if (speed == USB_SPEED_FULL) {
			return ep_mps <= USB_MAX_FS_ISOC_MPS;
		}
		break;
	}

	return false;
}

static inline bool aotg_hc_ep_is_enabled(uint8_t aotg_ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);

	if (USB_EP_DIR_IS_OUT(aotg_ep) &&
	    usb_aotg_hc.out_ep_ctrl[ep_idx].ep_ena) {
		return true;
	}
	if (USB_EP_DIR_IS_IN(aotg_ep) &&
	    usb_aotg_hc.in_ep_ctrl[ep_idx].ep_ena) {
		return true;
	}

	return false;
}

/*
 * Using single FIFO for every endpoint (except for ep0) by default
 */
static inline int aotg_hc_ep_alloc_fifo(uint8_t aotg_ep)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);

	if (!ep_idx) {
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(aotg_ep)) {
		if (ep_idx >= USB_AOTG_OUT_EP_NUM) {
			return -EINVAL;
		}

		return aotg_hc_epout_alloc_fifo_specific(ep_idx);
	}

	if (ep_idx >= USB_AOTG_IN_EP_NUM) {
		return -EINVAL;
	}

	return aotg_hc_epin_alloc_fifo_specific(ep_idx);
}

/*
 * Max Packet Size Limit
 */
static inline int aotg_hc_set_max_packet(uint8_t aotg_ep, uint16_t ep_mps,
				enum usb_ep_type ep_type)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);

	if (!aotg_hc_ep_mps_valid(ep_mps, ep_type)) {
		return -EINVAL;
	}

	if (USB_EP_DIR_IS_OUT(aotg_ep)) {
		usb_write16(OUTxMAXPKT(ep_idx), ep_mps);
		usb_aotg_hc.out_ep_ctrl[ep_idx].mps = ep_mps;
	} else {
		usb_write16(INxMAXPKT(ep_idx), ep_mps);
		usb_aotg_hc.in_ep_ctrl[ep_idx].mps = ep_mps;
	}

	return 0;
}

/*
 * Endpoint type
 */
static inline void aotg_hc_set_ep_type(uint8_t aotg_ep, enum usb_ep_type ep_type)
{
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);

	if (USB_EP_DIR_IS_OUT(aotg_ep)) {
		usb_aotg_hc.out_ep_ctrl[ep_idx].ep_type = ep_type;
	} else {
		usb_aotg_hc.in_ep_ctrl[ep_idx].ep_type = ep_type;
	}
}

static inline int aotg_hc_ep_set(uint8_t ep, uint16_t ep_mps,
				enum usb_ep_type ep_type)
{
	uint8_t aotg_ep = find_ep(ep);
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);
	int ret;

	LOG_DBG("0x%x(0x%x), mps %d, type %d", aotg_ep, ep, ep_mps,
			ep_type);

	/* Set FIFO address */
	ret = aotg_hc_ep_alloc_fifo(aotg_ep);
	if (ret) {
		return ret;
	}

	/* Set Max Packet */
	ret = aotg_hc_set_max_packet(aotg_ep, ep_mps, ep_type);
	if (ret) {
		return ret;
	}

	/* Set EP type */
	aotg_hc_set_ep_type(aotg_ep, ep_type);

	/* No need to set for endpoint 0 */
	if (!ep_idx) {
		usb_write8(EP0MAXPKT, ep_mps);
		return 0;
	}

	if (USB_EP_DIR_IS_OUT(aotg_ep)) {
		usb_write8(HCINxCTRL(ep_idx), ep);
		/* Set endpoint type and FIFO type */
		usb_write8(OUTxCTRL(ep_idx),
			(ep_type << 2) | USB_AOTG_FIFO_SINGLE);
	} else {
		usb_write8(HCOUTxCTRL(ep_idx), USB_EP_ADDR2IDX(ep));
		usb_write8(INxCTRL(ep_idx),
			(ep_type << 2) | USB_AOTG_FIFO_SINGLE);
	}

	return 0;
}

int usb_hc_ep_configure(const struct usb_hc_ep_cfg_data * const ep_cfg)
{
	if (!usb_aotg_hc.attached || ep_map(ep_cfg->ep_addr)) {
		return -EINVAL;
	}

	return aotg_hc_ep_set(ep_cfg->ep_addr, ep_cfg->ep_mps,
				ep_cfg->ep_type);
}

int usb_hc_ep_enable(const uint8_t ep)
{
	uint8_t aotg_ep = find_ep(ep);
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);
	bool dir_out = USB_EP_DIR_IS_OUT(aotg_ep);

	if (!usb_aotg_hc.attached || !usb_aotg_ep_addr_valid(aotg_ep)) {
		return -EINVAL;
	}

	/* NOTE: re-enable is allowed */

	if (dir_out) {
		usb_write8(OUTIRQ, BIT(IRQ_EPxOUT(ep_idx)));
		usb_set_bit8(OUTIEN, ep_idx);
		usb_set_bit8(HCINEPERRIEN, ep_idx);
		usb_aotg_hc.out_ep_ctrl[ep_idx].ep_ena = 1;
	} else {
		usb_write8(INIRQ, BIT(IRQ_EPxIN(ep_idx)));
		usb_set_bit8(INIEN, ep_idx);
		usb_set_bit8(HCOUTEPERRIEN, ep_idx);
		usb_aotg_hc.in_ep_ctrl[ep_idx].ep_ena = 1;
	}

	/* Endpoint 0 */
	if (!ep_idx) {
		return 0;
	}

	if (usb_aotg_ep_reset(aotg_ep, USB_AOTG_EP_RESET)) {
		return -EINVAL;
	}

	/* Activate endpoint and set FIFOCTRL */
	if (dir_out) {
		usb_set_bit8(OUTxCTRL(ep_idx), EPCTRL_VALID);
		usb_write8(FIFOCTRL, ep_idx);
	} else {
		usb_set_bit8(INxCTRL(ep_idx), EPCTRL_VALID);
		usb_write8(FIFOCTRL, ep_idx | BIT(FIFOCTRL_IO_IN));
	}

	usb_aotg_ep_reg_dump(aotg_ep);

	return 0;
}

static int aotg_hc_ep_disable(const uint8_t aotg_ep)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);
	bool dir_out = USB_EP_DIR_IS_OUT(aotg_ep);
	struct usb_request *urb;

	if (!usb_aotg_hc.attached || !usb_aotg_ep_addr_valid(aotg_ep)) {
		return -EINVAL;
	}

	if (dir_out) {
		ep_ctrl = &usb_aotg_hc.out_ep_ctrl[ep_idx];
	} else {
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[ep_idx];
	}

	/* Already disabled */
	if (ep_ctrl->ep_ena == 0) {
		return 0;
	}

	ep_ctrl->ep_ena = 0;

	/* Disable EP interrupts */
	if (dir_out) {
		usb_clear_bit8(OUTIEN, ep_idx);
		usb_clear_bit8(HCINEPERRIEN, ep_idx);
	} else {
		usb_clear_bit8(INIEN, ep_idx);
		usb_clear_bit8(HCOUTEPERRIEN, ep_idx);
	}

	/* Endpoint 0 */
	if (!ep_idx) {
		goto done;
	}

	/* De-activate Ep */
	if (dir_out) {
		usb_clear_bit8(OUTxCTRL(ep_idx), EPCTRL_VALID);
	} else {
		usb_clear_bit8(INxCTRL(ep_idx), EPCTRL_VALID);
	}

done:
	urb = ep_ctrl->urb;
	if (urb) {
		ep_ctrl->urb = NULL;
		urb->status = -ENODEV;
		urb->complete(urb);
	}

	return 0;
}

int usb_hc_ep_disable(const uint8_t ep)
{
	const uint8_t aotg_ep = find_ep(ep);

	return aotg_hc_ep_disable(aotg_ep);
}

int usb_hc_ep_flush(const uint8_t ep)
{
	uint8_t aotg_ep = find_ep(ep);

	if (!usb_aotg_hc.attached || !usb_aotg_ep_addr_valid(aotg_ep)) {
		return -EINVAL;
	}

	return usb_aotg_ep_reset(aotg_ep, USB_AOTG_EP_FIFO_RESET);
}

int usb_hc_ep_reset(const uint8_t ep)
{
	uint8_t aotg_ep = find_ep(ep);

	if (!usb_aotg_hc.attached || !usb_aotg_ep_addr_valid(aotg_ep)) {
		return -EINVAL;
	}

	return usb_aotg_ep_reset(aotg_ep, USB_AOTG_EP_RESET);
}

int usb_hc_set_address(const uint8_t addr)
{
	if (addr > USB_AOTG_MAX_ADDR) {
		return -EINVAL;
	}

	usb_write8(FNADDR, addr);

	return 0;
}

static inline bool bus_reset_done(void)
{
	/* reset done after sending a SOF packet */
	if ((usb_read8(USBIRQ) & (BIT(USBIRQ_SOF) | BIT(USBIRQ_RESET))) ==
	    (BIT(USBIRQ_SOF) | BIT(USBIRQ_RESET))) {
		usb_write8(USBIRQ, usb_read8(USBIRQ));
		return true;
	} else {
		return false;
	}
}

/* FIXME: support port status only */
int usb_hc_root_control(void *buf, int len, struct usb_setup_packet *setup,
				int timeout)
{
	uint16_t req, value;
	int ret = -EPIPE;

	if (!usb_aotg_hc.attached) {
		return -ENODEV;
	}

	req = (setup->bmRequestType << 8) | setup->bRequest;
	value = sys_le16_to_cpu(setup->wValue);

	switch (req) {
	case GetPortStatus:
		if (bus_reset_done()) {
			aotg_hc_handle_reset();
		}
		*(uint32_t *)buf = sys_cpu_to_le32(usb_aotg_hc.port);
		return 4;

	case SetPortFeature:
		if (value == USB_PORT_FEAT_RESET) {
			usb_aotg_hc.port &= ~(USB_PORT_STAT_ENABLE
					| USB_PORT_STAT_LOW_SPEED
					| USB_PORT_STAT_HIGH_SPEED);

			usb_aotg_hc.port |= USB_PORT_STAT_RESET;
			/* port reset */
			usb_set_bit8(HCPORTCTRL, PORTRST);

			return 0;
		}
		break;

	default:
		break;
	}

	return ret;
}

static inline int aotg_hc_control_urb(struct usb_request *urb)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;

	if (urb->len == 0) {
		/* setup, in status */
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX];
	} else if (urb->ep == USB_CONTROL_OUT_EP0) {
		/* setup, out data, in status */
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX];
	} else {
		/* setup, in data, out status */
		ep_ctrl = &usb_aotg_hc.out_ep_ctrl[USB_AOTG_EP0_IDX];
	}

	if (ep_ctrl->urb != NULL) {
		return -EBUSY;
	}
	ep_ctrl->urb = urb;

	/* SETUP */
	usb_aotg_hc.phase = USB_AOTG_SETUP;
	usb_set_bit8(EP0CS, EP0CS_HCSET);
	ep0_write_fifo((const uint8_t *const)urb->setup_packet,
			sizeof(struct usb_setup_packet));

	return 0;
}

static inline void aotg_hc_tx_in_pkt(uint8_t ep_idx, uint16_t num_of_pkt)
{
	LOG_DBG("ep_idx: %d, num_of_pkt: %d", ep_idx, num_of_pkt);

	/* set the number of IN Packet(s) */
	usb_write8(HCINxCNTL(ep_idx), (uint8_t)num_of_pkt);
	usb_write8(HCINxCNTH(ep_idx), (uint8_t)(num_of_pkt >> 8));

	/* set busy */
	if (!(usb_read8(OUTxCS(ep_idx)) & BIT(EPCS_BUSY))) {
		usb_set_bit8(OUTxCS(ep_idx), EPCS_BUSY);
	}

	/* short packet control */
	/* usb_set_bit8(HCINCTRL, HCINx_SHORT(ep_idx)); */

	/* send IN Token */
	usb_set_bit8(HCINCTRL, HCINx_START(ep_idx));
}

int usb_hc_submit_urb(struct usb_request *urb)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	uint8_t aotg_ep = find_ep(urb->ep);
	unsigned int key;
	uint8_t ep_idx;
	int ret;

	if (!usb_aotg_hc.attached || !usb_aotg_ep_addr_valid(aotg_ep)) {
		return -EINVAL;
	}

	/* disconnect may happen */
	key = irq_lock();
	/* Check if ep enabled */
	if (!aotg_hc_ep_is_enabled(aotg_ep)) {
		irq_unlock(key);
		return -ENODEV;
	}

	ep_idx = USB_EP_ADDR2IDX(aotg_ep);
	if (!ep_idx) {
		ret = aotg_hc_control_urb(urb);
		irq_unlock(key);
		return ret;
	}

	if (USB_EP_DIR_IS_IN(aotg_ep)) {
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[ep_idx];
		if (ep_ctrl->urb != NULL) {
			irq_unlock(key);
			return -EBUSY;
		}

		ep_ctrl->urb = urb;
		urb->actual = urb->len > ep_ctrl->mps ? ep_ctrl->mps : urb->len;
		/* set actual before write, actual may not correct if error */
		if (ep_write_fifo(aotg_ep, urb->buf, urb->actual) < 0) {
			ep_ctrl->urb = NULL;	/* reset if failed */
			urb->actual = 0;
			irq_unlock(key);
			return -EAGAIN;
		}
		urb->buf += urb->actual;
	} else {
		ep_ctrl = &usb_aotg_hc.out_ep_ctrl[ep_idx];
		if (ep_ctrl->urb != NULL) {
			irq_unlock(key);
			return -EBUSY;
		}

		ep_ctrl->urb = urb;
		aotg_hc_tx_in_pkt(ep_idx, DIV_ROUND_UP(urb->len, ep_ctrl->mps));
	}

	irq_unlock(key);
	return 0;
}

int usb_hc_cancel_urb(struct usb_request *urb)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	uint8_t aotg_ep = find_ep(urb->ep);
	uint8_t ep_idx = USB_EP_ADDR2IDX(aotg_ep);
	bool dir_out = USB_EP_DIR_IS_OUT(aotg_ep);

	if (!usb_aotg_hc.attached || !usb_aotg_ep_addr_valid(aotg_ep)) {
		return -EINVAL;
	}

	if (dir_out) {
		ep_ctrl = &usb_aotg_hc.out_ep_ctrl[ep_idx];
	} else {
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[ep_idx];
	}

	/* Already disabled */
	if (ep_ctrl->ep_ena == 0) {
		return 0;
	}

	if (!ep_idx) {
		goto done;
	}

	if (dir_out) {
		usb_clear_bit8(HCINCTRL, HCINx_START(ep_idx));
	}

	usb_aotg_ep_reset(aotg_ep, USB_AOTG_EP_FIFO_RESET);

done:
	ep_ctrl->urb = NULL;
	urb->complete(urb);

	return 0;
}

static inline void aotg_hc_handle_otg(void)
{
	u8_t tmp;
	u8_t state;

	/* Clear OTG IRQ(s) */
	tmp = usb_read8(OTGIEN) & usb_read8(OTGIRQ);
	usb_write8(OTGIRQ, tmp);

	state = usb_read8(OTGSTATE);

	/* If AOTG enters a_host state, enable state change irq(a_host->a_wait_bcon) */
	if (state == OTG_A_HOST) {
		usb_set_bit8(OTGSTATE_IEN, OTGSTATE_A_WAIT_BCON);
	}

	LOG_DBG("State: 0x%x, irq: 0x%x", state, tmp);
}

static inline void aotg_hc_handle_otg_state(void)
{
	u8_t i;

	/* clear interrupt request */
	usb_set_bit8(OTGSTATE_IRQ, OTGSTATE_A_WAIT_BCON);

	if (usb_read8(OTGSTATE) == OTG_A_WAIT_BCON) {
		for (i = 0; i < USB_AOTG_IN_EP_NUM; i++) {
			aotg_hc_ep_disable(USB_EP_IDX2ADDR(i, USB_EP_DIR_IN));
		}
		for (i = 0; i < USB_AOTG_OUT_EP_NUM; i++) {
			aotg_hc_ep_disable(USB_EP_IDX2ADDR(i, USB_EP_DIR_OUT));
		}
	}
}

static inline void aotg_hc_handle_reset(void)
{
	uint8_t usbcs;

	/* Clear USB Reset IRQ */
	usb_write8(USBIRQ, BIT(USBIRQ_RESET));

	usb_aotg_hc.port &= ~USB_PORT_STAT_RESET;
	usb_aotg_hc.port |= USB_PORT_STAT_ENABLE;

	/* reset all ep-in */
	usb_aotg_ep_reset(0x80, USB_AOTG_EP_RESET);
	/* reset all ep-out */
	usb_aotg_ep_reset(0x0, USB_AOTG_EP_RESET);

	usbcs = usb_read8(USBCS);
	if (usbcs & BIT(USBCS_SPEED)) {
		usb_aotg_hc.speed = USB_SPEED_HIGH;
		usb_aotg_hc.port |= USB_PORT_STAT_HIGH_SPEED;
	} else if (usb_read8(USBCS) & BIT(USBCS_LS)) {
		usb_aotg_hc.speed = USB_SPEED_LOW;
		usb_aotg_hc.port |= USB_PORT_STAT_LOW_SPEED;
	} else {
		usb_aotg_hc.speed = USB_SPEED_FULL;
	}

	LOG_DBG("USBCS: 0x%x", usbcs);
}

static inline void aotg_hc_handle_sof(void)
{
	usb_set_bit8(USBIRQ, USBIRQ_SOF);
}

static inline void aotg_hc_handle_ep0out(void)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	struct usb_request *urb;
	uint8_t rx_len, mps;

	/* find urb */
	ep_ctrl = &usb_aotg_hc.out_ep_ctrl[USB_AOTG_EP0_IDX];
	urb = ep_ctrl->urb;
	if (!urb) {
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX];
		urb = ep_ctrl->urb;
		if (!urb) {
			return;
		}
	}
	ep_ctrl->err_count = 0;

	switch (usb_aotg_hc.phase) {
	case USB_AOTG_IN_DATA:
		mps = usb_aotg_hc.out_ep_ctrl[USB_AOTG_EP0_IDX].mps;
		rx_len = urb->len > mps ? mps : urb->len;

		rx_len = ep0_read_fifo(urb->buf, rx_len);
		urb->buf += rx_len;
		urb->actual += rx_len;

		/* FIXME: handle babble */

		/* short packet or complete */
		if ((rx_len < mps) || (urb->actual == urb->len)) {
			usb_aotg_hc.phase = USB_AOTG_OUT_STATUS;

			usb_clear_bit8(EP0CS, EP0CS_HCSET);
			usb_set_bit8(EP0CS, EP0CS_SETTOGGLE);
			usb_write8(IN0BC, 0);
		} else {
			usb_write8(OUT0BC, 0x0);
		}
		break;

	case USB_AOTG_IN_STATUS:
		usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX].urb = NULL;
		urb->status = 0;
		urb->complete(urb);
		break;

	default:
		break;
	}
}

/*
 * Handle EPxOUT data
 */
static inline void aotg_hc_handle_epout(uint8_t ep_idx)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	struct usb_request *urb;
	bool done = false;
	uint16_t rx_len;
	uint8_t aotg_ep;

	usb_write8(OUTIRQ, BIT(IRQ_EPxOUT(ep_idx)));

	if (!ep_idx) {
		return aotg_hc_handle_ep0out();
	}

	ep_ctrl = &usb_aotg_hc.out_ep_ctrl[ep_idx];
	urb = ep_ctrl->urb;
	if (!urb) {
		LOG_DBG("ep_idx: %d no urb", ep_idx);
		return;
	}
	ep_ctrl->err_count = 0;

	LOG_DBG("hcepin ep_idx: %d, urb->actual: %d, urb->len: %d",
		ep_idx, urb->actual, urb->len);

	aotg_ep = USB_EP_IDX2ADDR(ep_idx, USB_EP_DIR_OUT);

	rx_len = usb_read16(OUTxBC(ep_idx));

	ep_read_fifo(aotg_ep, urb->buf + urb->actual, rx_len);
	urb->actual += rx_len;

	if (urb->actual > urb->len) {
		LOG_DBG("0x%x(0x%x) babble", aotg_ep, ep_ctrl->ep_addr);
		urb->status = -EIO;
		done = true;
	} else if (urb->actual == urb->len) {
		urb->status = 0;
		done = true;
	} else if (rx_len < ep_ctrl->mps) {
		LOG_DBG("0x%x(0x%x) short %d", aotg_ep, ep_ctrl->ep_addr,
				rx_len);
		urb->status = 0;
		done = true;
	} else {
		usb_set_bit8(OUTxCS(ep_idx), EPCS_BUSY);
	}

	if (done) {
		ep_ctrl->urb = NULL;
		urb->complete(urb);
	}
}

static inline void aotg_hc_handle_ep0in(void)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	struct usb_request *urb;
	uint8_t tx_len, mps;

	/* find urb */
	ep_ctrl = &usb_aotg_hc.out_ep_ctrl[USB_AOTG_EP0_IDX];
	urb = ep_ctrl->urb;
	if (!urb) {
		ep_ctrl = &usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX];
		urb = ep_ctrl->urb;
		if (!urb) {
			return;
		}
	}
	ep_ctrl->err_count = 0;

	switch (usb_aotg_hc.phase) {
	case USB_AOTG_SETUP:
		if (urb->len == 0) {
			usb_aotg_hc.phase = USB_AOTG_IN_STATUS;

			usb_set_bit8(EP0CS, EP0CS_SETTOGGLE);
			usb_write8(OUT0BC, 0x0);

			break;
		}
		if (urb->ep == USB_CONTROL_IN_EP0) {
			usb_aotg_hc.phase = USB_AOTG_IN_DATA;

			/* send IN token */
			usb_write8(OUT0BC, 0x0);
		} else {
			usb_aotg_hc.phase = USB_AOTG_OUT_DATA;

			mps = usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX].mps;
			urb->actual = urb->len > mps ? mps : urb->len;

			usb_clear_bit8(EP0CS, EP0CS_HCSET);
			ep0_write_fifo(urb->buf, urb->actual);
			urb->buf += urb->actual;
		}
		break;

	case USB_AOTG_OUT_DATA:
		if (urb->actual == urb->len) {
			usb_aotg_hc.phase = USB_AOTG_IN_STATUS;

			usb_set_bit8(EP0CS, EP0CS_SETTOGGLE);
			usb_write8(OUT0BC, 0x0);
		} else {
			mps = usb_aotg_hc.in_ep_ctrl[USB_AOTG_EP0_IDX].mps;
			tx_len = urb->len > mps ? mps : urb->len;
			if ((urb->len - urb->actual) > mps) {
				tx_len = mps;
			} else {
				tx_len = urb->len - urb->actual;
			}

			usb_clear_bit8(EP0CS, EP0CS_HCSET);
			ep0_write_fifo(urb->buf, tx_len);
			urb->buf += tx_len;
			urb->actual += tx_len;
		}
		break;

	case USB_AOTG_OUT_STATUS:
		usb_aotg_hc.out_ep_ctrl[USB_AOTG_EP0_IDX].urb = NULL;

		urb->status = 0;
		urb->complete(urb);
		break;

	default:
		break;
	}
}

/*
 * Handle EPxIN data
 */
static inline void aotg_hc_handle_epin(uint8_t ep_idx)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	struct usb_request *urb;
	uint16_t tx_len;
	uint8_t aotg_ep;

	usb_write8(INIRQ, BIT(IRQ_EPxIN(ep_idx)));

	if (!ep_idx) {
		return aotg_hc_handle_ep0in();
	}

	ep_ctrl = &usb_aotg_hc.in_ep_ctrl[ep_idx];
	urb = ep_ctrl->urb;
	if (!urb) {
		LOG_DBG("ep_idx: %d no urb", ep_idx);
		return;
	}
	ep_ctrl->err_count = 0;

	LOG_DBG("ep_idx: %d, urb->actual: %d, urb->len: %d",
		ep_idx, urb->actual, urb->len);

	if (urb->actual == urb->len) {
		ep_ctrl->urb = NULL;
		urb->status = 0;
		urb->complete(urb);
		return;
	}

	aotg_ep = USB_EP_IDX2ADDR(ep_idx, USB_EP_DIR_IN);

	tx_len = urb->len - urb->actual;
	tx_len = tx_len > ep_ctrl->mps ? ep_ctrl->mps : tx_len;

	ep_write_fifo(aotg_ep, urb->buf, tx_len);
	urb->buf += tx_len;
	urb->actual += tx_len;
}

static inline void aotg_hc_handle_epout_err(uint8_t ep_idx)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	struct usb_request *urb;
	uint8_t type;

	usb_write8(HCINEPERRIRQ, BIT(HCEPxERRIRQ(ep_idx)));

	ep_ctrl = &usb_aotg_hc.out_ep_ctrl[ep_idx];
	urb = ep_ctrl->urb;
	if (!urb) {
		return;
	}

	type = usb_read8(HCINxERR(ep_idx)) & HCEPERR_TYPE_MASK;
	LOG_DBG("ep_idx: %d, type: 0x%x", ep_idx, type);

	switch (type) {
	default:
		if (++ep_ctrl->err_count < ERR_COUNT_MAX) {
			/* resend or try again */
			usb_set_bit8(HCINxERR(ep_idx), HCEPERR_RESEND);
		} else {
			usb_clear_bit8(HCINCTRL, HCINx_START(ep_idx));
			ep_ctrl->err_count = 0;
			ep_ctrl->urb = NULL;
			urb->status = -EIO;
			urb->complete(urb);
		}
		break;

	/* could it happen? */
	case NO_ERR:
		break;

	/* stall */
	case ERR_STALL:
		usb_clear_bit8(HCINCTRL, HCINx_START(ep_idx));
		ep_ctrl->urb = NULL;
		urb->status = -EPIPE;
		urb->complete(urb);
		break;
	}
}

static inline void aotg_hc_handle_epin_err(uint8_t ep_idx)
{
	struct aotg_hc_ep_ctrl_prv *ep_ctrl;
	struct usb_request *urb;
	uint8_t type;

	usb_write8(HCOUTEPERRIRQ, BIT(HCEPxERRIRQ(ep_idx)));

	ep_ctrl = &usb_aotg_hc.in_ep_ctrl[ep_idx];
	urb = ep_ctrl->urb;
	if (!urb) {
		return;
	}

	type = usb_read8(HCOUTxERR(ep_idx)) & HCEPERR_TYPE_MASK;
	LOG_DBG("ep_idx: %d, type: 0x%x", ep_idx, type);

	switch (usb_read8(HCOUTxERR(ep_idx)) & HCEPERR_TYPE_MASK) {
	default:
		if (++ep_ctrl->err_count < ERR_COUNT_MAX) {
			/* resend or try again */
			usb_set_bit8(HCOUTxERR(ep_idx), HCEPERR_RESEND);
		} else {
			ep_ctrl->err_count = 0;
			ep_ctrl->urb = NULL;
			urb->status = -EIO;
			urb->complete(urb);
		}
		break;

	/* could it happen? */
	case NO_ERR:
		break;

	/* stall */
	case ERR_STALL:
		ep_ctrl->urb = NULL;
		urb->status = -EPIPE;
		urb->complete(urb);
		break;
	}
}

static inline void aotg_hc_isr_dispatch(uint8_t vector)
{
	uint8_t usbeien = usb_read8(USBEIEN) & USBEIEN_MASK;

	/*
	 * Make sure external IRQ has been cleared right!
	 *
	 * TODO: If two or more IRQs are pending simultaneously,
	 * EIRQ will be pending immediately after cleared or not?
	 */
	while (usb_read8(USBEIRQ) & BIT(USBEIRQ_EXTERN)) {
		usb_write8(USBEIRQ, BIT(USBEIRQ_EXTERN) | usbeien);
	}

	switch (vector) {
	/* OTG */
	case UIV_OTGIRQ:
		aotg_hc_handle_otg();
		break;

	case UIV_OTGSTATE:
		aotg_hc_handle_otg_state();
		break;

	/* USB Reset */
	case UIV_USBRST:
		aotg_hc_handle_reset();
		break;

	case UIV_SOF:
		aotg_hc_handle_sof();
		break;

	/* HCINx */
	case UIV_EP0OUT:
	case UIV_EP1OUT:
	case UIV_EP2OUT:
	case UIV_EP3OUT:
#if (CONFIG_USB_AOTG_OTG_VERSION == USB_AOTG_VERSION_LEOPARD)
	case UIV_EP4OUT:
	case UIV_EP5OUT:
	case UIV_EP6OUT:
#endif
		aotg_hc_handle_epout(UIV_EPOUT_VEC2ADDR(vector));
		break;

	/* HCOUTx */
	case UIV_EP0IN:
	case UIV_EP1IN:
	case UIV_EP2IN:
	case UIV_EP3IN:
#if (CONFIG_USB_AOTG_OTG_VERSION == USB_AOTG_VERSION_LEOPARD)
	case UIV_EP4IN:
	case UIV_EP5IN:
	case UIV_EP6IN:
#endif
		aotg_hc_handle_epin(UIV_EPIN_VEC2ADDR(vector));
		break;

	/* HCINxERR */
	case UIV_HCIN0ERR:
	case UIV_HCIN1ERR:
	case UIV_HCIN2ERR:
	case UIV_HCIN3ERR:
#if (CONFIG_USB_AOTG_OTG_VERSION == USB_AOTG_VERSION_LEOPARD)
	case UIV_HCIN4ERR:
	case UIV_HCIN5ERR:
	case UIV_HCIN6ERR:
#endif
		aotg_hc_handle_epout_err(UIV_HCINERR_VEC2ADDR(vector));
		break;

	/* HCOUTxERR */
	case UIV_HCOUT0ERR:
	case UIV_HCOUT1ERR:
	case UIV_HCOUT2ERR:
	case UIV_HCOUT3ERR:
#if (CONFIG_USB_AOTG_OTG_VERSION == USB_AOTG_VERSION_LEOPARD)
	case UIV_HCOUT4ERR:
	case UIV_HCOUT5ERR:
	case UIV_HCOUT6ERR:
#endif
		aotg_hc_handle_epin_err(UIV_HCOUTERR_VEC2ADDR(vector));
		break;

	default:
		break;
	}
}

int usb_hc_reset(void)
{
	int ret;

	ret = usb_aotg_reset();

	/* Clear private data */
	memset(&usb_aotg_hc, 0, sizeof(usb_aotg_hc));

	return ret;
}

int usb_hc_enable(void)
{
	if (usb_aotg_hc.attached) {
		LOG_DBG("already");
		return 0;
	}

	aotg_hc_fifo_enable();

	/* Enable OTG(a_host) interrupt */
	usb_set_bit8(OTGIEN, OTGIEN_LOCSOF);

	/* Enable external interrupt */
	usb_set_bit8(USBEIEN, USBEIEN_EXTERN);

	irq_enable(USB_AOTG_IRQ);

	memset(&usb_aotg_hc, 0, sizeof(usb_aotg_hc));
	usb_aotg_hc.port = USB_PORT_STAT_CONNECTION;
	usb_aotg_hc.attached = 1;

	usb_aotg_reg_dump();

	LOG_DBG("");

	return 0;
}

int usb_hc_disable(void)
{
	if (!usb_aotg_hc.attached) {
		LOG_DBG("already");
		return 0;
	}

	irq_disable(USB_AOTG_IRQ);

	memset(&usb_aotg_hc, 0, sizeof(usb_aotg_hc));

	usb_aotg_disable();

	aotg_hc_fifo_disable();

	LOG_DBG("");

	return 0;
}

/* usb ram alloc/free */
int usb_hc_fifo_control(bool enable)
{
	if (enable) {
		return aotg_hc_fifo_enable();
	} else {
		return aotg_hc_fifo_disable();
	}

	return 0;
}
#endif /* CONFIG_USB_AOTG_HC_ENABLED */



uint8_t usb_phy_get_id(void)
{
	return USB_ID_INVALID;
}

uint8_t usb_phy_get_vbus(void)
{
	if(soc_pmu_get_dc5v_status()){
		return USB_VBUS_HIGH;
	}

	return USB_VBUS_LOW;
}

bool usb_phy_hc_attached(void)
{
	/* full-speed or high-speed */
	if ((usb_read8(LINESTATUS) & LINESTATE_MASK) == LINESTATE_DP) {
		return true;
	}

	return false;
}

bool usb_phy_hc_connected(void)
{
#ifdef CONFIG_USB_AOTG_HC_ENABLED
	if ((usb_read8(USBSTATE) == OTG_A_HOST) && bus_reset_done()) {

		return true;
	}
#endif

	return false;
}

bool usb_phy_hc_disconnected(void)
{
	if (usb_read8(USBSTATE) != OTG_A_HOST) {
		return true;
	}

	return false;
}

bool usb_phy_dc_attached(void)
{
	return ((usb_read8(DPDMCTRL) & BIT(PLUGIN)) &&
		!(usb_read8(LINESTATUS) & LINESTATE_MASK));
}

bool usb_phy_dc_detached(void)
{
#ifdef CONFIG_USB_AOTG_DC_ENABLED
	return usb_aotg_dc.attached == 0;
#else
	return true;
#endif
}

bool usb_phy_dc_connected(void)
{
#ifdef CONFIG_USB_AOTG_DC_ENABLED
	return usb_aotg_dc.speed != USB_SPEED_UNKNOWN;
#else
	return false;
#endif
}

bool usb_phy_dc_disconnected(void)
{
	return !(usb_read8(DPDMCTRL) & BIT(PLUGIN));
}

void usb_phy_dc_disconnect(void)
{
#ifdef CONFIG_USB_AOTG_DC_ENABLED
	aotg_dc_disconnect();
#endif
}

int usb_phy_enter_a_idle(void)
{
	usb_aotg_mode = USB_OTG_HOST;

	usb_aotg_clock_enable();
	usb_aotg_dpdm_init();
	acts_reset_peripheral(RESET_ID_USB2);

	usb_write8(IDVBUSCTRL, IDVBUS_HOST);
	usb_write8(DPDMCTRL, DPDM_HOST);

	return 0;
}

int usb_phy_enter_b_idle(void)
{
	usb_aotg_mode = USB_OTG_DEVICE;
	usb_dc_hardware_init();
	usb_aotg_clock_enable();
	usb_aotg_dpdm_init();
	acts_reset_peripheral(RESET_ID_USB2);

	usb_write8(IDVBUSCTRL, IDVBUS_DEVICE);
	usb_write8(DPDMCTRL, DPDM_DEVICE);

	return 0;
}

int usb_phy_enter_a_wait_bcon(void)
{
#ifdef CONFIG_USB_AOTG_HC_ENABLED
	usb_aotg_enable();

	usb_write8(IDVBUSCTRL, IDVBUS_HOST);
	usb_write8(DPDMCTRL, DPDM_HOST);

	aotg_hc_phy_init();

	usb_set_bit8(OTGCTRL, OTGCTRL_BUSREQ);
#endif

	return 0;
}

static void usb_aotg_isr_handler(void)
{
	/* IRQ Vector */
	uint8_t vector = usb_read8(IVECT);

	LOG_DBG("vector: 0x%x", vector);

	if (usb_aotg_mode == USB_OTG_HOST) {
#ifdef CONFIG_USB_AOTG_HC_ENABLED
		aotg_hc_isr_dispatch(vector);
#endif
	} else if (usb_aotg_mode == USB_OTG_DEVICE) {
#ifdef CONFIG_USB_AOTG_DC_ENABLED
		aotg_dc_isr_dispatch(vector);
#endif
	}
}

int usb_phy_reset(void)
{
	usb_aotg_exit();

	return 0;
}

int usb_phy_init(void)
{
	usb_aotg_exit();

	/* Connect and enable USB interrupt */
	IRQ_CONNECT(USB_AOTG_IRQ, CONFIG_USB_AOTG_OTG_IRQ_PRIO,
			usb_aotg_isr_handler, 0, 0);
	irq_disable(USB_AOTG_IRQ);

	return 0;
}

int usb_phy_exit(void)
{
	usb_aotg_exit();

	usb_aotg_dpdm_exit();

	return 0;
}

static int usb_aotg_pre_init(const struct device *dev)
{
	ARG_UNUSED(dev);

#ifdef CONFIG_USB_AOTG_UDC_DMA
	usb_aotg_dma_init();
#endif
	return 0;
}

SYS_INIT(usb_aotg_pre_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);