/*
 * Copyright (c) 2023 ITE Technology Corporation.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT ite_it82xx2_usb

#include <zephyr/kernel.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/drivers/pinctrl.h>
#include <soc.h>
#include <soc_dt.h>
#include <string.h>
#include <zephyr/irq.h>
#include <zephyr/pm/policy.h>
#include <zephyr/drivers/interrupt_controller/wuc_ite_it8xxx2.h>
#include <zephyr/dt-bindings/interrupt-controller/it8xxx2-wuc.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usb_dc_it82xx2, CONFIG_USB_DRIVER_LOG_LEVEL);

#define IT8XXX2_IS_EXTEND_ENDPOINT(n) (USB_EP_GET_IDX(n) >= 4)

/* USB Device Controller Registers Bits & Constants */
#define IT8XXX2_USB_IRQ			DT_INST_IRQ_BY_IDX(0, 0, irq)
#define IT8XXX2_WU90_IRQ		DT_INST_IRQ_BY_IDX(0, 1, irq)

#define FIFO_NUM			3
#define SETUP_DATA_CNT			8
#define DC_ADDR_NULL			0x00
#define DC_ADDR_MASK			0x7F

/* The related definitions of the register EP STATUS:
 * 0x41/0x45/0x49/0x4D
 */
#define EP_STATUS_ERROR			0x0F

/* The related definitions of the register dc_line_status: 0x51 */
#define RX_LINE_STATE_MASK		(RX_LINE_FULL_SPD | RX_LINE_LOW_SPD)
#define RX_LINE_LOW_SPD			0x02
#define RX_LINE_FULL_SPD		0x01
#define RX_LINE_RESET			0x00

/* EPN Extend Control 2 Register Mask Definition */
#define COMPLETED_TRANS			0xF0

/* Bit [1:0] represents the TRANSACTION_TYPE as follows: */
enum it82xx2_transaction_types {
	DC_SETUP_TRANS,
	DC_IN_TRANS,
	DC_OUTDATA_TRANS,
	DC_ALL_TRANS
};

/* The bit definitions of the register EP RX/TX FIFO Control:
 * EP_RX_FIFO_CONTROL: 0X64/0x84/0xA4/0xC4
 * EP_TX_FIFO_CONTROL: 0X74/0x94/0xB4/0xD4
 */
#define FIFO_FORCE_EMPTY		BIT(0)

/* The bit definitions of the register Host/Device Control: 0XE0 */
#define RESET_CORE			BIT(1)

/* ENDPOINT[3..0]_STATUS_REG */
#define DC_STALL_SENT			BIT(5)

/* DC_INTERRUPT_STATUS_REG */
#define DC_TRANS_DONE			BIT(0)
#define DC_RESUME_INT			BIT(1)
#define DC_RESET_EVENT			BIT(2)
#define DC_SOF_RECEIVED			BIT(3)
#define DC_NAK_SENT_INT			BIT(4)

/* DC_CONTROL_REG */
#define DC_GLOBAL_ENABLE		BIT(0)
#define DC_TX_LINE_STATE_DM		BIT(1)
#define DC_DIRECT_CONTROL		BIT(3)
#define DC_FULL_SPEED_LINE_POLARITY	BIT(4)
#define DC_FULL_SPEED_LINE_RATE		BIT(5)
#define DC_CONNECT_TO_HOST		BIT(6) /* internal pull-up */

/* ENDPOINT[3..0]_CONTROL_REG */
#define ENDPOINT_EN			BIT(0)
#define ENDPOINT_RDY			BIT(1)
#define EP_SEND_STALL			BIT(3)

enum it82xx2_ep_status {
	EP_INIT = 0,
	EP_CHECK,
	EP_CONFIG,
	EP_CONFIG_IN,
	EP_CONFIG_OUT,
};

enum it82xx2_trans_type {
	SETUP_TOKEN,
	IN_TOKEN,
	OUT_TOKEN,
};

enum it82xx2_setup_stage {
	INIT_ST,
	SETUP_ST,
	DIN_ST,
	DOUT_ST,
	STATUS_ST,
	STALL_SEND,
};

enum it82xx2_ep_ctrl {
	EP_IN_DIRECTION_SET,
	EP_STALL_SEND,
	EP_STALL_CHECK,
	EP_IOS_ENABLE,
	EP_ENABLE,
	EP_DATA_SEQ_1,
	EP_DATA_SEQ_TOGGLE,
	EP_READY_ENABLE,
};

struct usb_it8xxx2_wuc {
	/* WUC control device structure */
	const struct device *wucs;
	/* WUC pin mask */
	uint8_t mask;
};

struct usb_it82xx2_config {
	struct usb_it82xx2_regs *const base;
	const struct pinctrl_dev_config *pcfg;
	const struct usb_it8xxx2_wuc *wuc_list;
};

static const struct usb_it8xxx2_wuc usb_wuc0[IT8XXX2_DT_INST_WUCCTRL_LEN(0)] =
		IT8XXX2_DT_WUC_ITEMS_LIST(0);

PINCTRL_DT_INST_DEFINE(0);

static const struct usb_it82xx2_config ucfg0 = {
	.base = (struct usb_it82xx2_regs *)DT_INST_REG_ADDR(0),
	.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
	.wuc_list = usb_wuc0
};

struct it82xx2_endpoint_data {
	usb_dc_ep_callback cb_in;
	usb_dc_ep_callback cb_out;
	enum it82xx2_ep_status ep_status;
	enum usb_dc_ep_transfer_type ep_type;
	uint16_t remaining; /* remaining bytes */
	uint16_t mps;
};

struct usb_it82xx2_data {
	const struct device *dev;
	struct it82xx2_endpoint_data ep_data[MAX_NUM_ENDPOINTS];
	enum it82xx2_setup_stage st_state; /* Setup State */

	/* EP0 status */
	enum it82xx2_trans_type last_token;

	/* EP0 status */
	enum it82xx2_trans_type now_token;

	uint8_t attached;
	uint8_t addr;
	bool no_data_ctrl;
	bool suspended;
	usb_dc_status_callback usb_status_cb;

	/* FIFO_1/2/3 ready status */
	bool fifo_ready[3];

	struct k_sem fifo_sem[3];
	struct k_sem suspended_sem;
	struct k_work_delayable check_suspended_work;
};

/* The ep_fifo_res[ep_idx % FIFO_NUM] where the FIFO_NUM is 3 represents the
 * EP mapping because when (ep_idx % FIFO_NUM) is 3, it actually means the EP0.
 */
static const uint8_t ep_fifo_res[3] = {3, 1, 2};

static struct usb_it82xx2_data udata0;

static struct usb_it82xx2_regs *it82xx2_get_usb_regs(void)
{
	const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(usb0));
	const struct usb_it82xx2_config *cfg = dev->config;
	struct usb_it82xx2_regs *const usb_regs = cfg->base;

	return usb_regs;
}

static void it82xx2_enable_sof_int(bool enable)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	usb_regs->dc_interrupt_status = DC_SOF_RECEIVED;
	if (enable) {
		usb_regs->dc_interrupt_mask |= DC_SOF_RECEIVED;
	} else {
		usb_regs->dc_interrupt_mask &= ~DC_SOF_RECEIVED;
	}
}

/* Standby(deep doze) mode enable/disable */
static void it82xx2_enable_standby_state(bool enable)
{
	if (enable) {
		pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
	} else {
		pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES);
	}
}

/* WU90 (USB D+) Enable/Disable */
static void it82xx2_enable_wu90_irq(const struct device *dev, bool enable)
{
	const struct usb_it82xx2_config *cfg = dev->config;

	/* Clear pending interrupt */
	it8xxx2_wuc_clear_status(cfg->wuc_list[0].wucs, cfg->wuc_list[0].mask);

	if (enable) {
		irq_enable(IT8XXX2_WU90_IRQ);
	} else {
		irq_disable(IT8XXX2_WU90_IRQ);
	}
}

static void it82xx2_wu90_isr(const struct device *dev)
{
	it82xx2_enable_wu90_irq(dev, false);
	it82xx2_enable_standby_state(false);
	LOG_DBG("USB D+ (WU90) Triggered");
}

/* WU90 (USB D+) Initializations */
static void it8xxx2_usb_dc_wuc_init(const struct device *dev)
{
	const struct usb_it82xx2_config *cfg = dev->config;

	/* Initializing the WUI */
	it8xxx2_wuc_set_polarity(cfg->wuc_list[0].wucs,
				cfg->wuc_list[0].mask,
				WUC_TYPE_EDGE_FALLING);
	it8xxx2_wuc_clear_status(cfg->wuc_list[0].wucs,
				cfg->wuc_list[0].mask);

	/* Enabling the WUI */
	it8xxx2_wuc_enable(cfg->wuc_list[0].wucs, cfg->wuc_list[0].mask);

	/* Connect WU90 (USB D+) interrupt but make it disabled initially */
	IRQ_CONNECT(IT8XXX2_WU90_IRQ, 0, it82xx2_wu90_isr, 0, 0);
}

static int it82xx2_usb_fifo_ctrl(uint8_t ep)
{
	struct usb_it82xx2_regs *const usb_regs = (struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	volatile uint8_t *ep_fifo_ctrl = usb_regs->fifo_regs[EP_EXT_REGS_BX].fifo_ctrl.ep_fifo_ctrl;
	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	uint8_t fifon_ctrl = (ep_fifo_res[ep_idx % FIFO_NUM] - 1) * 2;
	int ret = 0;

	if (ep_idx == 0) {
		LOG_ERR("Invalid endpoint 0x%x", ep);
		return -EINVAL;
	}

	if (USB_EP_DIR_IS_IN(ep) && udata0.ep_data[ep_idx].ep_status == EP_CONFIG_IN) {
		if (ep_idx < 8) {
			ep_fifo_ctrl[fifon_ctrl] = BIT(ep_idx);
			ep_fifo_ctrl[fifon_ctrl + 1] = 0x0;
		} else {
			ep_fifo_ctrl[fifon_ctrl] = 0x0;
			ep_fifo_ctrl[fifon_ctrl + 1] = BIT(ep_idx - 8);
		}
	} else if (USB_EP_DIR_IS_OUT(ep) &&
		   udata0.ep_data[ep_idx].ep_status == EP_CONFIG_OUT) {
		if (ep_idx < 8) {
			ep_fifo_ctrl[fifon_ctrl] |= BIT(ep_idx);
		} else {
			ep_fifo_ctrl[fifon_ctrl + 1] |= BIT(ep_idx - 8);
		}
	} else {
		LOG_ERR("Failed to set fifo control register for ep 0x%x", ep);
		ret = -EINVAL;
	}

	return ret;
}

static volatile void *it82xx2_get_ext_ctrl(int ep_idx, enum it82xx2_ep_ctrl ctrl)
{
	uint8_t idx;
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	union epn0n1_extend_ctrl_reg *epn0n1_ext_ctrl =
		usb_regs->fifo_regs[EP_EXT_REGS_9X].ext_4_15.epn0n1_ext_ctrl;
	struct epn_ext_ctrl_regs *ext_ctrl =
		usb_regs->fifo_regs[EP_EXT_REGS_DX].ext_0_3.epn_ext_ctrl;

	if ((ctrl == EP_IN_DIRECTION_SET) || (ctrl == EP_ENABLE)) {
		idx = ((ep_idx - 4) % 3) + 1;
		return &ext_ctrl[idx].epn_ext_ctrl1;
	}

	idx = (ep_idx - 4) / 2;
	return &epn0n1_ext_ctrl[idx];
}

static int it82xx2_usb_extend_ep_ctrl(uint8_t ep, enum it82xx2_ep_ctrl ctrl, bool enable)
{
	struct usb_it82xx2_regs *const usb_regs = (struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct epn_ext_ctrl_regs *ext_ctrl =
		usb_regs->fifo_regs[EP_EXT_REGS_DX].ext_0_3.epn_ext_ctrl;
	union epn_extend_ctrl1_reg *epn_ext_ctrl1 = NULL;
	union epn0n1_extend_ctrl_reg *epn0n1_ext_ctrl = NULL;
	uint8_t ep_idx = USB_EP_GET_IDX(ep);

	if (!IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
		return -EINVAL;
	}

	if ((ctrl == EP_IN_DIRECTION_SET) || (ctrl == EP_ENABLE)) {
		epn_ext_ctrl1 = (union epn_extend_ctrl1_reg *)it82xx2_get_ext_ctrl(ep_idx, ctrl);
	} else {
		epn0n1_ext_ctrl =
			(union epn0n1_extend_ctrl_reg *)it82xx2_get_ext_ctrl(ep_idx, ctrl);
	}

	switch (ctrl) {
	case EP_STALL_SEND:
		if (ep_idx % 2) {
			epn0n1_ext_ctrl->fields.epn1_send_stall_bit = enable;
		} else {
			epn0n1_ext_ctrl->fields.epn0_send_stall_bit = enable;
		}
		break;
	case EP_STALL_CHECK:
		if (ep_idx % 2) {
			return epn0n1_ext_ctrl->fields.epn1_send_stall_bit;
		} else {
			return epn0n1_ext_ctrl->fields.epn0_send_stall_bit;
		}
		break;
	case EP_IOS_ENABLE:
		if (ep_idx % 2) {
			epn0n1_ext_ctrl->fields.epn1_iso_enable_bit = enable;
		} else {
			epn0n1_ext_ctrl->fields.epn0_iso_enable_bit = enable;
		}
		break;
	case EP_DATA_SEQ_1:
		if (ep_idx % 2) {
			epn0n1_ext_ctrl->fields.epn1_outdata_sequence_bit = enable;
		} else {
			epn0n1_ext_ctrl->fields.epn0_outdata_sequence_bit = enable;
		}
		break;
	case EP_DATA_SEQ_TOGGLE:
		if (!enable) {
			break;
		}
		if (ep_idx % 2) {
			if (epn0n1_ext_ctrl->fields.epn1_outdata_sequence_bit) {
				epn0n1_ext_ctrl->fields.epn1_outdata_sequence_bit = 0;
			} else {
				epn0n1_ext_ctrl->fields.epn1_outdata_sequence_bit = 1;
			}
		} else {
			if (epn0n1_ext_ctrl->fields.epn0_outdata_sequence_bit) {
				epn0n1_ext_ctrl->fields.epn0_outdata_sequence_bit = 0;
			} else {
				epn0n1_ext_ctrl->fields.epn0_outdata_sequence_bit = 1;
			}
		}
		break;
	case EP_IN_DIRECTION_SET:
		if (((ep_idx - 4) / 3 == 0)) {
			epn_ext_ctrl1->fields.epn0_direction_bit = enable;
		} else if (((ep_idx - 4) / 3 == 1)) {
			epn_ext_ctrl1->fields.epn3_direction_bit = enable;
		} else if (((ep_idx - 4) / 3 == 2)) {
			epn_ext_ctrl1->fields.epn6_direction_bit = enable;
		} else if (((ep_idx - 4) / 3 == 3)) {
			epn_ext_ctrl1->fields.epn9_direction_bit = enable;
		} else {
			LOG_ERR("Invalid endpoint 0x%x for control type 0x%x", ep, ctrl);
			return -EINVAL;
		}
		break;
	case EP_ENABLE:
		if (((ep_idx - 4) / 3 == 0)) {
			epn_ext_ctrl1->fields.epn0_enable_bit = enable;
		} else if (((ep_idx - 4) / 3 == 1)) {
			epn_ext_ctrl1->fields.epn3_enable_bit = enable;
		} else if (((ep_idx - 4) / 3 == 2)) {
			epn_ext_ctrl1->fields.epn6_enable_bit = enable;
		} else if (((ep_idx - 4) / 3 == 3)) {
			epn_ext_ctrl1->fields.epn9_enable_bit = enable;
		} else {
			LOG_ERR("Invalid endpoint 0x%x for control type 0x%x", ep, ctrl);
			return -EINVAL;
		}
		break;
	case EP_READY_ENABLE:
		int idx = ((ep_idx - 4) % 3) + 1;

		(enable) ? (ext_ctrl[idx].epn_ext_ctrl2 |= BIT((ep_idx - 4) / 3))
			 : (ext_ctrl[idx].epn_ext_ctrl2 &= ~BIT((ep_idx - 4) / 3));
		break;
	default:
		LOG_ERR("Unknown control type 0x%x", ctrl);
		return -EINVAL;
	}

	return 0;
}

static int it82xx2_usb_ep_ctrl(uint8_t ep, enum it82xx2_ep_ctrl ctrl, bool enable)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	uint8_t ep_idx = USB_EP_GET_IDX(ep);

	if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
		return -EINVAL;
	}

	switch (ctrl) {
	case EP_IN_DIRECTION_SET:
		ep_regs[ep_idx].ep_ctrl.fields.direction_bit = enable;
		break;
	case EP_STALL_SEND:
		ep_regs[ep_idx].ep_ctrl.fields.send_stall_bit = enable;
		break;
	case EP_STALL_CHECK:
		return ep_regs[ep_idx].ep_ctrl.fields.send_stall_bit;
	case EP_IOS_ENABLE:
		ep_regs[ep_idx].ep_ctrl.fields.iso_enable_bit = enable;
		break;
	case EP_ENABLE:
		ep_regs[ep_idx].ep_ctrl.fields.enable_bit = enable;
		break;
	case EP_READY_ENABLE:
		ep_regs[ep_idx].ep_ctrl.fields.ready_bit = enable;
		break;
	case EP_DATA_SEQ_1:
		ep_regs[ep_idx].ep_ctrl.fields.outdata_sequence_bit = enable;
		break;
	case EP_DATA_SEQ_TOGGLE:
		if (!enable) {
			break;
		}
		if (ep_regs[ep_idx].ep_ctrl.fields.outdata_sequence_bit) {
			ep_regs[ep_idx].ep_ctrl.fields.outdata_sequence_bit = 0;
		} else {
			ep_regs[ep_idx].ep_ctrl.fields.outdata_sequence_bit = 1;
		}
		break;
	default:
		LOG_ERR("Unknown control type 0x%x", ctrl);
		return -EINVAL;
	}
	return 0;
}

static int it82xx2_usb_set_ep_ctrl(uint8_t ep, enum it82xx2_ep_ctrl ctrl, bool enable)
{
	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	int ret = 0;

	if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
		ret = it82xx2_usb_extend_ep_ctrl(ep, ctrl, enable);
	} else {
		ret = it82xx2_usb_ep_ctrl(ep, ctrl, enable);
	}
	return ret;
}

static int it82xx2_usb_dc_ip_init(void)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	/* Reset Device Controller */
	usb_regs->host_device_control = RESET_CORE;
	k_msleep(1);
	usb_regs->port0_misc_control &= ~(PULL_DOWN_EN);
	usb_regs->port1_misc_control &= ~(PULL_DOWN_EN);
	/* clear reset bit */
	usb_regs->host_device_control = 0;

	usb_regs->dc_interrupt_status =
		DC_TRANS_DONE | DC_RESET_EVENT | DC_SOF_RECEIVED;

	usb_regs->dc_interrupt_mask = 0x00;
	usb_regs->dc_interrupt_mask =
		DC_TRANS_DONE | DC_RESET_EVENT | DC_SOF_RECEIVED;

	usb_regs->dc_address = DC_ADDR_NULL;

	return 0;
}

static int it82xx2_usb_dc_attach_init(void)
{
	struct gctrl_it8xxx2_regs *const gctrl_regs = GCTRL_IT8XXX2_REGS_BASE;
	/*
	 * Disable USB debug path , prevent CPU enter
	 * JTAG mode and then reset by USB command.
	 */
	gctrl_regs->GCTRL_MCCR &= ~(IT8XXX2_GCTRL_MCCR_USB_EN);
	gctrl_regs->gctrl_pmer2 |= IT8XXX2_GCTRL_PMER2_USB_PAD_EN;

	return it82xx2_usb_dc_ip_init();
}

/* Check the condition that SETUP_TOKEN following OUT_TOKEN and return it */
static bool it82xx2_check_setup_following_out(void)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;

	return ((ep_regs[EP0].ep_transtype_sts & DC_ALL_TRANS) == 0 ||
		(udata0.last_token == IN_TOKEN &&
		ff_regs[EP0].ep_rx_fifo_dcnt_lsb == SETUP_DATA_CNT));
}

static inline void it82xx2_handler_setup(uint8_t fifo_idx, uint8_t ep_ctrl)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;
	uint8_t ep_idx = fifo_idx;

	/* wrong trans */
	if (ep_ctrl & EP_SEND_STALL) {
		ep_regs[fifo_idx].ep_ctrl.fields.send_stall_bit = 0;
		udata0.st_state = STALL_SEND;
		ff_regs[fifo_idx].ep_rx_fifo_ctrl = FIFO_FORCE_EMPTY;
		LOG_DBG("Clear Stall Bit & RX FIFO");
		return;
	}

	if (udata0.st_state == DIN_ST) {
		/* setup -> in(data) -> out(status) */
		udata0.last_token = udata0.now_token;
		udata0.now_token = OUT_TOKEN;
		udata0.st_state = STATUS_ST;
		udata0.ep_data[ep_idx].cb_out(ep_idx | USB_EP_DIR_OUT, USB_DC_EP_DATA_OUT);

	} else if (udata0.st_state == DOUT_ST || udata0.st_state == SETUP_ST) {
		/* setup -> out(data) -> in(status)
		 * or
		 * setup -> in(status)
		 */
		udata0.last_token = udata0.now_token;
		udata0.now_token = IN_TOKEN;
		udata0.st_state = STATUS_ST;
		udata0.ep_data[ep_idx].cb_in(ep_idx | USB_EP_DIR_IN, USB_DC_EP_DATA_IN);
	}

	udata0.last_token = udata0.now_token;
	udata0.now_token = SETUP_TOKEN;
	udata0.st_state = SETUP_ST;

	ep_regs[fifo_idx].ep_ctrl.fields.outdata_sequence_bit = 1;
	udata0.ep_data[ep_idx].cb_out(ep_idx | USB_EP_DIR_OUT, USB_DC_EP_SETUP);

	/* Set ready bit to no-data control in */
	if (udata0.no_data_ctrl) {
		ep_regs[fifo_idx].ep_ctrl.fields.ready_bit = 1;
		udata0.no_data_ctrl = false;
	}
}

static inline void it82xx2_handler_in(uint8_t fifo_idx, uint8_t ep_ctrl)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	volatile struct epn_ext_ctrl_regs *epn_ext_ctrl =
		usb_regs->fifo_regs[EP_EXT_REGS_DX].ext_0_3.epn_ext_ctrl;
	uint8_t ep_idx;

	if (fifo_idx != 0) {
		if (!udata0.fifo_ready[fifo_idx - 1]) {
			return;
		}

		ep_idx = (epn_ext_ctrl[fifo_idx].epn_ext_ctrl2 & COMPLETED_TRANS) >> 4;
		if (ep_idx == 0 || udata0.ep_data[ep_idx].ep_status != EP_CONFIG_IN) {
			return;
		}
		udata0.fifo_ready[fifo_idx - 1] = false;
	} else {
		ep_idx = 0;
		if (ep_ctrl & EP_SEND_STALL) {
			ep_regs[fifo_idx].ep_ctrl.fields.send_stall_bit = 0;
			udata0.st_state = STALL_SEND;
			LOG_DBG("Clear Stall Bit");
			return;
		}

		if (udata0.st_state >= STATUS_ST) {
			return;
		}

		udata0.last_token = udata0.now_token;
		udata0.now_token = IN_TOKEN;

		if (udata0.addr != DC_ADDR_NULL &&
			udata0.addr != usb_regs->dc_address) {
			usb_regs->dc_address = udata0.addr;
			LOG_DBG("Address Is Set Successfully");
		}

		if (udata0.st_state == DOUT_ST) {
			/* setup -> out(data) -> in(status) */
			udata0.st_state = STATUS_ST;

		} else if (udata0.ep_data[ep_idx].remaining == 0 &&
		udata0.st_state == SETUP_ST) {
			/* setup -> in(status) */
			udata0.st_state = STATUS_ST;
		} else {
			/* setup -> in(data) */
			udata0.st_state = DIN_ST;
		}
	}

	it82xx2_usb_set_ep_ctrl(ep_idx, EP_DATA_SEQ_TOGGLE, true);

	if (udata0.ep_data[ep_idx].cb_in) {
		udata0.ep_data[ep_idx].cb_in(ep_idx | USB_EP_DIR_IN, USB_DC_EP_DATA_IN);
	}

	if (fifo_idx != 0) {
		k_sem_give(&udata0.fifo_sem[fifo_idx - 1]);
	} else {
		if (udata0.st_state == DIN_ST && udata0.ep_data[ep_idx].remaining == 0) {
			ep_regs[fifo_idx].ep_ctrl.fields.ready_bit = 1;
		}
	}
}

static inline void it82xx2_handler_out(uint8_t fifo_idx)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	volatile struct epn_ext_ctrl_regs *epn_ext_ctrl =
		usb_regs->fifo_regs[EP_EXT_REGS_DX].ext_0_3.epn_ext_ctrl;
	uint8_t ep_idx;

	if (fifo_idx != 0) {
		if (!udata0.fifo_ready[fifo_idx - 1]) {
			return;
		}

		ep_idx = (epn_ext_ctrl[fifo_idx].epn_ext_ctrl2 & COMPLETED_TRANS) >> 4;
		if (ep_idx == 0 || udata0.ep_data[ep_idx].ep_status != EP_CONFIG_OUT) {
			return;
		}
		udata0.fifo_ready[fifo_idx - 1] = false;
	} else {
		ep_idx = 0;
		/* ep0 wrong enter check */
		if (udata0.st_state >= STATUS_ST) {
			return;
		}

		udata0.last_token = udata0.now_token;
		udata0.now_token = OUT_TOKEN;

		if (udata0.st_state == SETUP_ST) {
			/* setup -> out(data) */
			udata0.st_state = DOUT_ST;
		} else {
			/* setup -> in(data) -> out(status) */
			udata0.st_state = STATUS_ST;
		}
	}

	if (udata0.ep_data[ep_idx].cb_out) {
		udata0.ep_data[ep_idx].cb_out(ep_idx, USB_DC_EP_DATA_OUT);
	}

	if (fifo_idx == 0) {
		/* SETUP_TOKEN follow OUT_TOKEN */
		if (it82xx2_check_setup_following_out()) {
			udata0.last_token = udata0.now_token;
			udata0.now_token = SETUP_TOKEN;
			udata0.st_state = SETUP_ST;
			ep_regs[fifo_idx].ep_ctrl.fields.outdata_sequence_bit = 1;
			udata0.ep_data[ep_idx].cb_out(ep_idx | USB_EP_DIR_OUT, USB_DC_EP_SETUP);

			if (udata0.no_data_ctrl) {
				ep_regs[fifo_idx].ep_ctrl.fields.ready_bit = 1;
				udata0.no_data_ctrl = false;
			}
		}
	}
}

static void it82xx2_usb_dc_trans_done(void)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;

	for (uint8_t fifo_idx = 0; fifo_idx < 4; fifo_idx++) {
		uint8_t ep_ctrl = ep_regs[fifo_idx].ep_ctrl.value;

		/* check ready bit ,will be 0 when trans done */
		if ((ep_ctrl & ENDPOINT_EN) && !(ep_ctrl & ENDPOINT_RDY)) {
			switch (ep_regs[fifo_idx].ep_transtype_sts & DC_ALL_TRANS) {
			case DC_SETUP_TRANS:
				if (fifo_idx == 0) {
					it82xx2_handler_setup(fifo_idx, ep_ctrl);
				}
				break;
			case DC_IN_TRANS:
				it82xx2_handler_in(fifo_idx, ep_ctrl);
				break;
			case DC_OUTDATA_TRANS:
				it82xx2_handler_out(fifo_idx);
				break;
			default:
				break;
			}
		}
	}
}

static void it82xx2_usb_dc_isr(void)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	uint8_t status = usb_regs->dc_interrupt_status &
		usb_regs->dc_interrupt_mask; /* mask non enable int */

	/* reset */
	if (status & DC_RESET_EVENT) {
		if ((usb_regs->dc_line_status & RX_LINE_STATE_MASK) ==
			RX_LINE_RESET) {
			usb_dc_reset();
			usb_regs->dc_interrupt_status = DC_RESET_EVENT;
			return;
		} else {
			usb_regs->dc_interrupt_status = DC_RESET_EVENT;
		}
	}
	/* sof received */
	if (status & DC_SOF_RECEIVED) {
		it82xx2_enable_sof_int(false);
		k_work_reschedule(&udata0.check_suspended_work, K_MSEC(5));
	}
	/* transaction done */
	if (status & DC_TRANS_DONE) {
		/* clear interrupt before new transaction */
		usb_regs->dc_interrupt_status = DC_TRANS_DONE;
		it82xx2_usb_dc_trans_done();
		return;
	}

}

static void suspended_check_handler(struct k_work *item)
{
	struct k_work_delayable *dwork = k_work_delayable_from_work(item);
	struct usb_it82xx2_data *udata =
		CONTAINER_OF(dwork, struct usb_it82xx2_data, check_suspended_work);

	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	if (usb_regs->dc_interrupt_status & DC_SOF_RECEIVED) {
		usb_regs->dc_interrupt_status = DC_SOF_RECEIVED;
		if (udata->suspended) {
			if (udata->usb_status_cb) {
				(*(udata->usb_status_cb))(USB_DC_RESUME, NULL);
			}
			udata->suspended = false;
			k_sem_give(&udata->suspended_sem);
		}
		k_work_reschedule(&udata->check_suspended_work, K_MSEC(5));
		return;
	}

	it82xx2_enable_sof_int(true);

	if (!udata->suspended) {
		if (udata->usb_status_cb) {
			(*(udata->usb_status_cb))(USB_DC_SUSPEND, NULL);
		}
		udata->suspended = true;
		it82xx2_enable_wu90_irq(udata->dev, true);
		it82xx2_enable_standby_state(true);

		k_sem_reset(&udata->suspended_sem);
	}
}

/*
 * USB Device Controller API
 */
int usb_dc_attach(void)
{
	int ret;
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	if (udata0.attached) {
		LOG_DBG("Already Attached");
		return 0;
	}

	LOG_DBG("Attached");
	ret = it82xx2_usb_dc_attach_init();

	if (ret) {
		return ret;
	}

	for (uint8_t idx = 0; idx < MAX_NUM_ENDPOINTS; idx++) {
		udata0.ep_data[idx].ep_status = EP_INIT;
	}

	udata0.attached = 1U;

	/* init fifo ready status */
	udata0.fifo_ready[0] = false;
	udata0.fifo_ready[1] = false;
	udata0.fifo_ready[2] = false;

	k_sem_init(&udata0.fifo_sem[0], 1, 1);
	k_sem_init(&udata0.fifo_sem[1], 1, 1);
	k_sem_init(&udata0.fifo_sem[2], 1, 1);
	k_sem_init(&udata0.suspended_sem, 0, 1);

	k_work_init_delayable(&udata0.check_suspended_work, suspended_check_handler);

	/* Connect USB interrupt */
	IRQ_CONNECT(IT8XXX2_USB_IRQ, 0, it82xx2_usb_dc_isr, 0, 0);

	usb_regs->dc_control =
		DC_GLOBAL_ENABLE | DC_FULL_SPEED_LINE_POLARITY |
		DC_FULL_SPEED_LINE_RATE | DC_CONNECT_TO_HOST;

	/* Enable USB D+ and USB interrupts */
	it82xx2_enable_wu90_irq(udata0.dev, true);
	irq_enable(IT8XXX2_USB_IRQ);

	return 0;
}

int usb_dc_detach(void)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	if (!udata0.attached) {
		LOG_DBG("Already Detached");
		return 0;
	}

	LOG_DBG("Detached");
	irq_disable(IT8XXX2_USB_IRQ);

	/* stop pull-up D+ D-*/
	usb_regs->dc_control &= ~DC_CONNECT_TO_HOST;
	udata0.attached = 0U;

	return 0;
}

int usb_dc_reset(void)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;

	LOG_DBG("USB Device Reset");

	ff_regs[EP0].ep_rx_fifo_ctrl = FIFO_FORCE_EMPTY;
	ff_regs[EP0].ep_tx_fifo_ctrl = FIFO_FORCE_EMPTY;

	for (uint8_t idx = 1; idx < 4; idx++) {
		if (udata0.ep_data[idx].ep_status > EP_CHECK) {
			ff_regs[idx].ep_rx_fifo_ctrl = FIFO_FORCE_EMPTY;
			ff_regs[idx].ep_tx_fifo_ctrl = FIFO_FORCE_EMPTY;
		}
	}

	ep_regs[0].ep_ctrl.value = ENDPOINT_EN;
	usb_regs->dc_address = DC_ADDR_NULL;
	udata0.addr = DC_ADDR_NULL;
	usb_regs->dc_interrupt_status = DC_NAK_SENT_INT | DC_SOF_RECEIVED;

	if (udata0.usb_status_cb) {
		(*(udata0.usb_status_cb))(USB_DC_RESET, NULL);
	}

	return 0;
}

int usb_dc_set_address(const uint8_t addr)
{
	LOG_DBG("Set Address(0x%02x) to Data", addr);
	udata0.addr = addr & DC_ADDR_MASK;
	return 0;
}

void usb_dc_set_status_callback(const usb_dc_status_callback cb)
{
	udata0.usb_status_cb = cb;
}

int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg)
{
	uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr);
	bool in = USB_EP_DIR_IS_IN(cfg->ep_addr);

	if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx > EP0) {
		LOG_ERR("Invalid Endpoint Configuration");
		return -EINVAL;
	}

	if (ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_WRN("Invalid Endpoint Number 0x%02x", cfg->ep_addr);
		return -EINVAL;
	}

	if ((ep_idx != 0) && (!in && ep_idx % FIFO_NUM != 2)) {
		LOG_WRN("Invalid Endpoint Number 0x%02x", cfg->ep_addr);
		return -EINVAL;
	}

	if ((ep_idx != 0) && (in && ep_idx % FIFO_NUM == 2)) {
		LOG_WRN("Invalid Endpoint Number 0x%02x", cfg->ep_addr);
		return -EINVAL;
	}

	if (udata0.ep_data[ep_idx].ep_status > EP_INIT) {
		LOG_WRN("EP%d have been used", ep_idx);
		return -EINVAL;
	}

	if (ep_idx > EP0) {
		udata0.ep_data[ep_idx].mps = cfg->ep_mps;
	}

	udata0.ep_data[ep_idx].ep_status = EP_CHECK;
	LOG_DBG("Check cap(%02x)", cfg->ep_addr);

	return 0;
}

int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const cfg)
{
	uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr);
	bool in = USB_EP_DIR_IS_IN(cfg->ep_addr);

	if (!udata0.attached || ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_DBG("Not attached / Invalid Endpoint: 0x%X", cfg->ep_addr);
		return -EINVAL;
	}

	if (!cfg->ep_mps) {
		LOG_DBG("Wrong EP or Descriptor");
		return -EINVAL;
	}

	udata0.ep_data[ep_idx].ep_status = EP_CONFIG;
	udata0.ep_data[ep_idx].mps = cfg->ep_mps;

	LOG_DBG("ep_status: %d, mps: %d",
		udata0.ep_data[ep_idx].ep_status, udata0.ep_data[ep_idx].mps);

	if (!(ep_idx > EP0)) {
		return 0;
	}

	it82xx2_usb_set_ep_ctrl(ep_idx, EP_IN_DIRECTION_SET, in);

	if (in) {
		if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
			it82xx2_usb_extend_ep_ctrl(ep_idx, EP_DATA_SEQ_1, false);
		}
		udata0.ep_data[ep_idx].ep_status = EP_CONFIG_IN;
	} else {
		udata0.ep_data[ep_idx].ep_status = EP_CONFIG_OUT;
		it82xx2_usb_fifo_ctrl(cfg->ep_addr);
	}

	switch (cfg->ep_type) {
	case USB_DC_EP_CONTROL:
		return -EINVAL;
	case USB_DC_EP_ISOCHRONOUS:
		it82xx2_usb_set_ep_ctrl(ep_idx, EP_IOS_ENABLE, true);
		break;
	case USB_DC_EP_BULK:
		__fallthrough;
	case USB_DC_EP_INTERRUPT:
		__fallthrough;
	default:
		it82xx2_usb_set_ep_ctrl(ep_idx, EP_IOS_ENABLE, false);
		break;
	}

	udata0.ep_data[ep_idx].ep_type = cfg->ep_type;

	LOG_DBG("EP%d Configured: 0x%2X(%d)", ep_idx, !!(in), cfg->ep_type);
	return 0;
}

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

	if (!udata0.attached || ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_ERR("(%d)Not attached / Invalid endpoint: EP 0x%x",
			__LINE__, ep);
		return -EINVAL;
	}

	if (cb == NULL) {
		LOG_ERR("(%d): NO callback function", __LINE__);
		return -EINVAL;
	}

	LOG_DBG("EP%d set callback: %d", ep_idx, !!(ep & USB_EP_DIR_IN));

	if (USB_EP_DIR_IS_IN(ep)) {
		udata0.ep_data[ep_idx].cb_in = cb;
	} else {
		udata0.ep_data[ep_idx].cb_out = cb;
	}

	return 0;
}

int usb_dc_ep_enable(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	int ret = 0;

	if (!udata0.attached || ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep_idx);
		return -EINVAL;
	}

	if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
		uint8_t ep_fifo = ep_fifo_res[ep_idx % FIFO_NUM];

		it82xx2_usb_set_ep_ctrl(ep_fifo, EP_ENABLE, true);
	}

	ret = it82xx2_usb_set_ep_ctrl(ep_idx, EP_ENABLE, true);
	if (ret < 0) {
		return ret;
	}

	LOG_DBG("Endpoint 0x%02x is enabled", ep);

	return 0;
}

int usb_dc_ep_disable(uint8_t ep)
{
	uint8_t ep_idx = USB_EP_GET_IDX(ep);

	if (!udata0.attached || ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep_idx);
		return -EINVAL;
	}

	return it82xx2_usb_set_ep_ctrl(ep_idx, EP_ENABLE, false);
}


int usb_dc_ep_set_stall(const uint8_t ep)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;

	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	struct gctrl_it8xxx2_regs *const gctrl_regs = GCTRL_IT8XXX2_REGS_BASE;

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

	it82xx2_usb_set_ep_ctrl(ep_idx, EP_STALL_SEND, true);

	if (ep_idx == 0) {
		uint32_t idx = 0;

		ep_regs[ep_idx].ep_ctrl.fields.ready_bit = 1;
		/* polling if stall send for 3ms */
		while (idx < 198 &&
			!(ep_regs[ep_idx].ep_status & DC_STALL_SENT)) {
			/* wait 15.15us */
			gctrl_regs->GCTRL_WNCKR = 0;
			idx++;
		}

		if (idx < 198) {
			ep_regs[ep_idx].ep_ctrl.fields.send_stall_bit = 0;
		}

		udata0.no_data_ctrl = false;
		udata0.st_state = STALL_SEND;
	}

	LOG_DBG("EP(%d) ctrl: 0x%02x", ep_idx, ep_regs[ep_idx].ep_ctrl.value);
	LOG_DBG("EP(%d) Set Stall", ep_idx);

	return 0;
}

int usb_dc_ep_clear_stall(const uint8_t ep)
{
	uint8_t ep_idx = USB_EP_GET_IDX(ep);

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

	it82xx2_usb_set_ep_ctrl(ep_idx, EP_STALL_SEND, false);
	LOG_DBG("EP(%d) clear stall", ep_idx);

	return 0;
}

int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *stalled)
{
	uint8_t ep_idx = USB_EP_GET_IDX(ep);

	if ((!stalled) || (ep_idx >= MAX_NUM_ENDPOINTS)) {
		return -EINVAL;
	}

	*stalled = it82xx2_usb_set_ep_ctrl(ep_idx, EP_STALL_CHECK, true);

	return 0;
}

int usb_dc_ep_halt(uint8_t ep)
{
	return usb_dc_ep_set_stall(ep);
}

int usb_dc_ep_flush(uint8_t ep)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;

	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	uint8_t ep_fifo = (ep_idx > 0) ? (ep_fifo_res[ep_idx % FIFO_NUM]) : 0;

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

	if (USB_EP_DIR_IS_IN(ep)) {
		ff_regs[ep_fifo].ep_tx_fifo_ctrl = FIFO_FORCE_EMPTY;
	} else {
		ff_regs[ep_fifo].ep_rx_fifo_ctrl = FIFO_FORCE_EMPTY;
	}

	return 0;
}

int usb_dc_ep_write(uint8_t ep, const uint8_t *buf,
				uint32_t data_len, uint32_t *ret_bytes)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;

	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	uint8_t ep_fifo = (ep_idx > 0) ? (ep_fifo_res[ep_idx % FIFO_NUM]) : 0;

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

	if (ep_idx == EP0) {
		if ((udata0.now_token == SETUP_TOKEN) && (data_len == 0)) {
			return 0;
		}
		/* clear fifo before write*/
		ff_regs[ep_idx].ep_tx_fifo_ctrl = FIFO_FORCE_EMPTY;

		if (udata0.st_state == SETUP_ST) {
			udata0.st_state = DIN_ST;
		}
	} else {
		k_sem_take(&udata0.fifo_sem[ep_fifo - 1], K_FOREVER);
		it82xx2_usb_fifo_ctrl(ep);
	}

	if (data_len > udata0.ep_data[ep_idx].mps) {

		for (uint32_t idx = 0; idx < udata0.ep_data[ep_idx].mps; idx++) {
			ff_regs[ep_fifo].ep_tx_fifo_data = buf[idx];
		}

		*ret_bytes = udata0.ep_data[ep_idx].mps;
		udata0.ep_data[ep_idx].remaining =
			data_len - udata0.ep_data[ep_idx].mps;

		LOG_DBG("data_len: %d, Write Max Packets to TX FIFO(%d)",
			data_len, ep_idx);
	} else {
		for (uint32_t idx = 0; idx < data_len; idx++) {
			ff_regs[ep_fifo].ep_tx_fifo_data = buf[idx];
		}

		*ret_bytes = data_len;
		udata0.ep_data[ep_idx].remaining = 0;
		LOG_DBG("Write %d Packets to TX FIFO(%d)", data_len, ep_idx);
	}

	if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
		it82xx2_usb_extend_ep_ctrl(ep_idx, EP_READY_ENABLE, true);
	}

	ep_regs[ep_fifo].ep_ctrl.fields.ready_bit = 1;

	if (ep_fifo > EP0) {
		udata0.fifo_ready[ep_fifo - 1] = true;
	}

	LOG_DBG("Set EP%d Ready(%d)", ep_idx, __LINE__);

	return 0;
}

/* Read data from an OUT endpoint */
int usb_dc_ep_read(uint8_t ep, uint8_t *buf, uint32_t max_data_len,
			uint32_t *read_bytes)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;

	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	uint8_t ep_fifo = (ep_idx > 0) ? (ep_fifo_res[ep_idx % FIFO_NUM]) : 0;
	uint16_t rx_fifo_len;

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

	if (ep_regs[ep_fifo].ep_status & EP_STATUS_ERROR) {
		LOG_WRN("fifo_%d error status: 0x%02x", ep_fifo, ep_regs[ep_fifo].ep_status);
	}

	if (max_data_len == 0) {

		*read_bytes = 0;

		if (ep_idx > 0) {
			ep_regs[ep_idx].ep_ctrl.fields.ready_bit = 1;
		}

		return 0;
	}

	rx_fifo_len = (uint16_t)ff_regs[ep_fifo].ep_rx_fifo_dcnt_lsb +
		(((uint16_t)ff_regs[ep_fifo].ep_rx_fifo_dcnt_msb) << 8);

	if (ep_idx == 0) {
		/* Prevent wrong read_bytes cause memory error
		 * if EP0 is in OUT status stage
		 */
		if (udata0.st_state == STATUS_ST) {
			*read_bytes = 0;
			return 0;
		} else if (udata0.now_token == SETUP_TOKEN) {
			if (rx_fifo_len == 0) {
				LOG_ERR("Setup length 0, reset to 8");
				rx_fifo_len = 8;
			}
			if (rx_fifo_len != 8) {
				LOG_ERR("Setup length: %d", rx_fifo_len);
				ff_regs[0].ep_rx_fifo_ctrl = FIFO_FORCE_EMPTY;
				return -EIO;
			}
		}
	}

	if (rx_fifo_len > max_data_len) {
		*read_bytes = max_data_len;
		for (uint32_t idx = 0; idx < max_data_len; idx++) {
			buf[idx] = ff_regs[ep_fifo].ep_rx_fifo_data;
		}

		LOG_DBG("Read Max (%d) Packets", max_data_len);
	} else {

		*read_bytes = rx_fifo_len;

		for (uint32_t idx = 0; idx < rx_fifo_len; idx++) {
			buf[idx] = ff_regs[ep_fifo].ep_rx_fifo_data;
		}

		if (ep_fifo == 0 &&
			udata0.now_token == SETUP_TOKEN) {
			LOG_DBG("RX buf: (%x)(%x)(%x)(%x)(%x)(%x)(%x)(%x)",
			buf[0], buf[1], buf[2], buf[3],
			buf[4], buf[5], buf[6], buf[7]);
		}

		if (ep_fifo > EP0) {
			ep_regs[ep_fifo].ep_ctrl.fields.ready_bit = 1;
			if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
				it82xx2_usb_extend_ep_ctrl(ep_idx, EP_READY_ENABLE, true);
			}
			udata0.fifo_ready[ep_fifo - 1] = true;
		} else if (udata0.now_token == SETUP_TOKEN) {
			if (!(buf[0] & USB_EP_DIR_MASK)) {
				/* request type: host-to-device transfer direction */
				ff_regs[0].ep_tx_fifo_ctrl = FIFO_FORCE_EMPTY;
				if (buf[6] != 0 || buf[7] != 0) {
					/* set status IN after data OUT */
					ep_regs[0].ep_ctrl.fields.outdata_sequence_bit = 1;
					ep_regs[0].ep_ctrl.fields.ready_bit = 1;
				} else {
					/* no_data_ctrl status */
					udata0.no_data_ctrl = true;
				}
			}
		}
	}

	return 0;
}

int usb_dc_ep_read_wait(uint8_t ep, uint8_t *buf, uint32_t max_data_len,
			uint32_t *read_bytes)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;
	struct it82xx2_usb_ep_fifo_regs *ff_regs = usb_regs->fifo_regs;

	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	uint8_t ep_fifo = (ep_idx > 0) ? (ep_fifo_res[ep_idx % FIFO_NUM]) : 0;
	uint16_t rx_fifo_len;

	if (ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_ERR("(%d): Wrong Endpoint Index/Address", __LINE__);
		return -EINVAL;
	}

	if (USB_EP_DIR_IS_IN(ep)) {
		LOG_ERR("Wrong Endpoint Direction");
		return -EINVAL;
	}

	if (ep_regs[ep_fifo].ep_status & EP_STATUS_ERROR) {
		LOG_WRN("fifo_%d error status(%02x)", ep_fifo, ep_regs[ep_fifo].ep_status);
	}

	rx_fifo_len = (uint16_t)ff_regs[ep_fifo].ep_rx_fifo_dcnt_lsb +
		(((uint16_t)ff_regs[ep_fifo].ep_rx_fifo_dcnt_msb) << 8);

	LOG_DBG("ep_read_wait (EP: %d), len: %d", ep_idx, rx_fifo_len);

	*read_bytes = (rx_fifo_len > max_data_len) ?
		max_data_len : rx_fifo_len;

	for (uint32_t idx = 0; idx < *read_bytes; idx++) {
		buf[idx] = ff_regs[ep_fifo].ep_rx_fifo_data;
	}

	LOG_DBG("Read %d packets", *read_bytes);

	return 0;
}

int usb_dc_ep_read_continue(uint8_t ep)
{
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();
	struct it82xx2_usb_ep_regs *ep_regs = usb_regs->usb_ep_regs;

	uint8_t ep_idx = USB_EP_GET_IDX(ep);
	uint8_t ep_fifo = (ep_idx > 0) ? (ep_fifo_res[ep_idx % FIFO_NUM]) : 0;

	if (ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_ERR("(%d): Wrong Endpoint Index/Address", __LINE__);
		return -EINVAL;
	}

	if (USB_EP_DIR_IS_IN(ep)) {
		LOG_ERR("Wrong Endpoint Direction");
		return -EINVAL;
	}

	if (IT8XXX2_IS_EXTEND_ENDPOINT(ep_idx)) {
		it82xx2_usb_extend_ep_ctrl(ep_idx, EP_READY_ENABLE, true);
	}
	ep_regs[ep_fifo].ep_ctrl.fields.ready_bit = 1;
	udata0.fifo_ready[ep_fifo - 1] = true;
	LOG_DBG("EP(%d) Read Continue", ep_idx);
	return 0;
}


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

	if (ep_idx >= MAX_NUM_ENDPOINTS) {
		LOG_ERR("(%d): Wrong Endpoint Index/Address", __LINE__);
		return -EINVAL;
	}
	/* Not configured, return length 0 */
	if (udata0.ep_data[ep_idx].ep_status < EP_CONFIG) {
		LOG_WRN("(%d)EP not set", __LINE__);
		return 0;
	}

	return udata0.ep_data[ep_idx].mps;
}

int usb_dc_wakeup_request(void)
{
	int ret;
	struct usb_it82xx2_regs *const usb_regs =
		(struct usb_it82xx2_regs *)it82xx2_get_usb_regs();

	if (udata0.suspended) {

		usb_regs->dc_control =
			DC_GLOBAL_ENABLE | DC_FULL_SPEED_LINE_POLARITY |
			DC_FULL_SPEED_LINE_RATE | DC_DIRECT_CONTROL |
			DC_TX_LINE_STATE_DM | DC_CONNECT_TO_HOST;

		/* The remote wakeup device must hold the resume signal for */
		/* at least 1 ms but for no more than 15 ms                 */
		k_msleep(2);

		usb_regs->dc_control =
			DC_GLOBAL_ENABLE | DC_FULL_SPEED_LINE_POLARITY |
			DC_FULL_SPEED_LINE_RATE | DC_CONNECT_TO_HOST;

		ret = k_sem_take(&udata0.suspended_sem, K_MSEC(500));
		if (ret < 0) {
			LOG_ERR("failed to wake up host");
		}
	}
	return 0;
}

static int it82xx2_usb_dc_init(const struct device *dev)
{
	const struct usb_it82xx2_config *cfg = dev->config;

	int status = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);

	if (status < 0) {
		LOG_ERR("Failed to configure USB pins");
		return status;
	}

	/* Initializing WU90 (USB D+) */
	it8xxx2_usb_dc_wuc_init(dev);

	udata0.dev = dev;

	return 0;
}

DEVICE_DT_INST_DEFINE(0,
	&it82xx2_usb_dc_init,
	NULL,
	&udata0,
	&ucfg0,
	POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
	NULL);
