/*
 * Copyright (c) 2016 - 2023, Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/* This file is undergoing transition towards native Zephyr nrf USB driver. */

/** @cond INTERNAL_HIDDEN */

#include <nrfx.h>

#include "nrf_usbd_common.h"
#include "nrf_usbd_common_errata.h"
#include <string.h>
#include <zephyr/kernel.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nrf_usbd_common, CONFIG_NRF_USBD_COMMON_LOG_LEVEL);

#define NRF_USBD_COMMON_EPIN_CNT      9
#define NRF_USBD_COMMON_EPOUT_CNT     9
#define NRF_USBD_COMMON_EP_NUM(ep)    (ep & 0xF)
#define NRF_USBD_COMMON_EP_IS_IN(ep)  ((ep & 0x80) == 0x80)
#define NRF_USBD_COMMON_EP_IS_OUT(ep) ((ep & 0x80) == 0)
#define NRF_USBD_COMMON_EP_IS_ISO(ep) ((ep & 0xF) >= 8)

#ifndef NRF_USBD_COMMON_ISO_DEBUG
/* Also generate information about ISOCHRONOUS events and transfers.
 * Turn this off if no ISOCHRONOUS transfers are going to be debugged and this
 * option generates a lot of useless messages.
 */
#define NRF_USBD_COMMON_ISO_DEBUG 1
#endif

#ifndef NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG
/* Also generate debug information for failed transfers.
 * It might be useful but may generate a lot of useless debug messages
 * in some library usages (for example when transfer is generated and the
 * result is used to check whatever endpoint was busy.
 */
#define NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG 1
#endif

#ifndef NRF_USBD_COMMON_DMAREQ_PROCESS_DEBUG
/* Generate additional messages that mark the status inside
 * @ref usbd_dmareq_process.
 * It is useful to debug library internals but may generate a lot of
 * useless debug messages.
 */
#define NRF_USBD_COMMON_DMAREQ_PROCESS_DEBUG 1
#endif

#ifndef NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211
/* Anomaly 211 - Device remains in SUSPEND too long when host resumes
 * a bus activity (sending SOF packets) without a RESUME condition.
 */
#define NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211 0
#endif

/**
 * @defgroup nrf_usbd_common_int USB Device driver internal part
 * @internal
 * @ingroup nrf_usbd_common
 *
 * This part contains auxiliary internal macros, variables and functions.
 * @{
 */

/**
 * @brief Assert endpoint number validity.
 *
 * Internal macro to be used during program creation in debug mode.
 * Generates assertion if endpoint number is not valid.
 *
 * @param ep Endpoint number to validity check.
 */
#define NRF_USBD_COMMON_ASSERT_EP_VALID(ep) __ASSERT_NO_MSG(         \
	((NRF_USBD_COMMON_EP_IS_IN(ep) &&                            \
	 (NRF_USBD_COMMON_EP_NUM(ep) < NRF_USBD_COMMON_EPIN_CNT)) || \
	 (NRF_USBD_COMMON_EP_IS_OUT(ep) &&                           \
	 (NRF_USBD_COMMON_EP_NUM(ep) < NRF_USBD_COMMON_EPOUT_CNT))));

/**
 * @brief Lowest position of bit for IN endpoint.
 *
 * The first bit position corresponding to IN endpoint.
 * @sa ep2bit bit2ep
 */
#define NRF_USBD_COMMON_EPIN_BITPOS_0 0

/**
 * @brief Lowest position of bit for OUT endpoint.
 *
 * The first bit position corresponding to OUT endpoint
 * @sa ep2bit bit2ep
 */
#define NRF_USBD_COMMON_EPOUT_BITPOS_0 16

/**
 * @brief Input endpoint bits mask.
 */
#define NRF_USBD_COMMON_EPIN_BIT_MASK (0xFFFFU << NRF_USBD_COMMON_EPIN_BITPOS_0)

/**
 * @brief Output endpoint bits mask.
 */
#define NRF_USBD_COMMON_EPOUT_BIT_MASK (0xFFFFU << NRF_USBD_COMMON_EPOUT_BITPOS_0)

/**
 * @brief Isochronous endpoint bit mask
 */
#define USBD_EPISO_BIT_MASK                                                    \
	((1U << NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT8)) |           \
	 (1U << NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN8)))

/**
 * @brief Auxiliary macro to change EP number into bit position.
 *
 * This macro is used by @ref ep2bit function but also for statically check
 * the bitpos values integrity during compilation.
 *
 * @param[in] ep Endpoint number.
 * @return Endpoint bit position.
 */
#define NRF_USBD_COMMON_EP_BITPOS(ep) ((NRF_USBD_COMMON_EP_IS_IN(ep)      \
	? NRF_USBD_COMMON_EPIN_BITPOS_0 : NRF_USBD_COMMON_EPOUT_BITPOS_0) \
	+ NRF_USBD_COMMON_EP_NUM(ep))

/**
 * @brief Helper macro for creating an endpoint transfer event.
 *
 * @param[in] name     Name of the created transfer event variable.
 * @param[in] endpoint Endpoint number.
 * @param[in] ep_stat  Endpoint state to report.
 *
 * @return Initialized event constant variable.
 */
#define NRF_USBD_COMMON_EP_TRANSFER_EVENT(name, endpont, ep_stat)                                  \
	const nrf_usbd_common_evt_t name = {NRF_USBD_COMMON_EVT_EPTRANSFER,                        \
				      .data = {.eptransfer = {.ep = endpont, .status = ep_stat}}}

/* Check it the bit positions values match defined DATAEPSTATUS bit positions */
BUILD_ASSERT(
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN1) == USBD_EPDATASTATUS_EPIN1_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN2) == USBD_EPDATASTATUS_EPIN2_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN3) == USBD_EPDATASTATUS_EPIN3_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN4) == USBD_EPDATASTATUS_EPIN4_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN5) == USBD_EPDATASTATUS_EPIN5_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN6) == USBD_EPDATASTATUS_EPIN6_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPIN7) == USBD_EPDATASTATUS_EPIN7_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT1) == USBD_EPDATASTATUS_EPOUT1_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT2) == USBD_EPDATASTATUS_EPOUT2_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT3) == USBD_EPDATASTATUS_EPOUT3_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT4) == USBD_EPDATASTATUS_EPOUT4_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT5) == USBD_EPDATASTATUS_EPOUT5_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT6) == USBD_EPDATASTATUS_EPOUT6_Pos) &&
	(NRF_USBD_COMMON_EP_BITPOS(NRF_USBD_COMMON_EPOUT7) == USBD_EPDATASTATUS_EPOUT7_Pos),
	"NRF_USBD_COMMON bit positions do not match hardware"
);

/**
 * @brief Current driver state.
 */
static nrfx_drv_state_t m_drv_state = NRFX_DRV_STATE_UNINITIALIZED;

/**
 * @brief Event handler for the driver.
 *
 * Event handler that would be called on events.
 *
 * @note Currently it cannot be null if any interrupt is activated.
 */
static nrf_usbd_common_event_handler_t m_event_handler;

/**
 * @brief Detected state of the bus.
 *
 * Internal state changed in interrupts handling when
 * RESUME or SUSPEND event is processed.
 *
 * Values:
 * - true  - bus suspended
 * - false - ongoing normal communication on the bus
 *
 * @note This is only the bus state and does not mean that the peripheral is in suspend state.
 */
static volatile bool m_bus_suspend;

/**
 * @brief Direction of last received Setup transfer.
 *
 * This variable is used to redirect internal setup data event
 * into selected endpoint (IN or OUT).
 */
static nrf_usbd_common_ep_t m_last_setup_dir;

/**
 * @brief Mark endpoint readiness for DMA transfer.
 *
 * Bits in this variable are cleared and set in interrupts.
 * 1 means that endpoint is ready for DMA transfer.
 * 0 means that DMA transfer cannot be performed on selected endpoint.
 */
static uint32_t m_ep_ready;

/**
 * @brief Mark endpoint with prepared data to transfer by DMA.
 *
 * This variable can be set in interrupt context or within critical section.
 * It would be cleared only from USBD interrupt.
 *
 * Mask prepared USBD data for transmission.
 * It is cleared when no more data to transmit left.
 */
static uint32_t m_ep_dma_waiting;

/* Semaphore to guard EasyDMA access.
 * In USBD there is only one DMA channel working in background, and new transfer
 * cannot be started when there is ongoing transfer on any other channel.
 */
static K_SEM_DEFINE(dma_available, 1, 1);

/* Endpoint on which DMA was started. */
static nrf_usbd_common_ep_t dma_ep;

/**
 * @brief Tracks whether total bytes transferred by DMA is even or odd.
 */
static uint8_t m_dma_odd;

/**
 * @brief First time enabling after reset. Used in nRF52 errata 223.
 */
static bool m_first_enable = true;

/**
 * @brief The structure that would hold transfer configuration to every endpoint
 *
 * The structure that holds all the data required by the endpoint to proceed
 * with LIST functionality and generate quick callback directly when data
 * buffer is ready.
 */
typedef struct {
	nrf_usbd_common_transfer_t transfer_state;
	bool more_transactions;
	/** Number of transferred bytes in the current transfer. */
	size_t transfer_cnt;
	/** Configured endpoint size. */
	uint16_t max_packet_size;
	/** NRFX_SUCCESS or error code, never NRFX_ERROR_BUSY - this one is calculated. */
	nrf_usbd_common_ep_status_t status;
} usbd_ep_state_t;

/**
 * @brief The array of transfer configurations for the endpoints.
 *
 * The status of the transfer on each endpoint.
 */
static struct {
	usbd_ep_state_t ep_out[NRF_USBD_COMMON_EPOUT_CNT]; /*!< Status for OUT endpoints. */
	usbd_ep_state_t ep_in[NRF_USBD_COMMON_EPIN_CNT];   /*!< Status for IN endpoints. */
} m_ep_state;

#define NRF_USBD_COMMON_FEEDER_BUFFER_SIZE NRF_USBD_COMMON_EPSIZE

/**
 * @brief Buffer used to send data directly from FLASH.
 *
 * This is internal buffer that would be used to emulate the possibility
 * to transfer data directly from FLASH.
 * We do not have to care about the source of data when calling transfer functions.
 *
 * We do not need more buffers that one, because only one transfer can be pending
 * at once.
 */
static uint32_t m_tx_buffer[NRFX_CEIL_DIV(NRF_USBD_COMMON_FEEDER_BUFFER_SIZE, sizeof(uint32_t))];

/* Early declaration. Documentation above definition. */
static void usbd_dmareq_process(void);
static inline void usbd_int_rise(void);
static void nrf_usbd_common_stop(void);

/* Get EasyDMA end event address for given endpoint */
static volatile uint32_t *usbd_ep_to_endevent(nrf_usbd_common_ep_t ep)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);

	if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) {
		if (ep_in) {
			return &NRF_USBD->EVENTS_ENDEPIN[ep_num];
		} else {
			return &NRF_USBD->EVENTS_ENDEPOUT[ep_num];
		}
	}

	return ep_in ? &NRF_USBD->EVENTS_ENDISOIN : &NRF_USBD->EVENTS_ENDISOOUT;
}

/* Return number of bytes last transferred by EasyDMA on given endpoint */
static uint32_t usbd_ep_amount_get(nrf_usbd_common_ep_t ep)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);

	if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) {
		if (ep_in) {
			return NRF_USBD->EPIN[ep_num].AMOUNT;
		} else {
			return NRF_USBD->EPOUT[ep_num].AMOUNT;
		}
	}

	return ep_in ? NRF_USBD->ISOIN.AMOUNT : NRF_USBD->ISOOUT.AMOUNT;
}

/* Start EasyDMA on given endpoint */
static void usbd_ep_dma_start(nrf_usbd_common_ep_t ep, uint32_t addr, size_t len)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);

	if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) {
		if (ep_in) {
			NRF_USBD->EPIN[ep_num].PTR = addr;
			NRF_USBD->EPIN[ep_num].MAXCNT = len;
			NRF_USBD->TASKS_STARTEPIN[ep_num] = 1;
		} else {
			NRF_USBD->EPOUT[ep_num].PTR = addr;
			NRF_USBD->EPOUT[ep_num].MAXCNT = len;
			NRF_USBD->TASKS_STARTEPOUT[ep_num] = 1;
		}
	} else if (ep_in) {
		NRF_USBD->ISOIN.PTR = addr;
		NRF_USBD->ISOIN.MAXCNT = len;
		NRF_USBD->TASKS_STARTISOIN = 1;
	} else {
		NRF_USBD->ISOOUT.PTR = addr;
		NRF_USBD->ISOOUT.MAXCNT = len;
		NRF_USBD->TASKS_STARTISOOUT = 1;
	}
}

static bool nrf_usbd_common_consumer(nrf_usbd_common_ep_transfer_t *p_next,
				     nrf_usbd_common_transfer_t *p_transfer,
				     size_t ep_size, size_t data_size)
{
	__ASSERT_NO_MSG(ep_size >= data_size);
	__ASSERT_NO_MSG((p_transfer->p_data.rx == NULL) || nrfx_is_in_ram(p_transfer->p_data.rx));

	size_t size = p_transfer->size;

	if (size < data_size) {
		LOG_DBG("consumer: buffer too small: r: %u, l: %u", data_size, size);
		/* Buffer size to small */
		p_next->size = 0;
		p_next->p_data = p_transfer->p_data;
	} else {
		p_next->size = data_size;
		p_next->p_data = p_transfer->p_data;
		size -= data_size;
		p_transfer->size = size;
		p_transfer->p_data.addr += data_size;
	}
	return (ep_size == data_size) && (size != 0);
}

static bool nrf_usbd_common_feeder(nrf_usbd_common_ep_transfer_t *p_next,
				   nrf_usbd_common_transfer_t *p_transfer,
				   size_t ep_size)
{
	size_t tx_size = p_transfer->size;

	if (tx_size > ep_size) {
		tx_size = ep_size;
	}

	if (!nrfx_is_in_ram(p_transfer->p_data.tx)) {
		__ASSERT_NO_MSG(tx_size <= NRF_USBD_COMMON_FEEDER_BUFFER_SIZE);
		memcpy(m_tx_buffer, (p_transfer->p_data.tx), tx_size);
		p_next->p_data.tx = m_tx_buffer;
	} else {
		p_next->p_data = p_transfer->p_data;
	}

	p_next->size = tx_size;

	p_transfer->size -= tx_size;
	p_transfer->p_data.addr += tx_size;

	if (p_transfer->flags & NRF_USBD_COMMON_TRANSFER_ZLP_FLAG) {
		return (tx_size != 0);
	} else {
		return (p_transfer->size != 0);
	}
}

/**
 * @brief Change Driver endpoint number to HAL endpoint number.
 *
 * @param ep Driver endpoint identifier.
 *
 * @return Endpoint identifier in HAL.
 *
 * @sa nrf_usbd_common_ep_from_hal
 */
static inline uint8_t ep_to_hal(nrf_usbd_common_ep_t ep)
{
	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);
	return (uint8_t)ep;
}

/**
 * @brief Access selected endpoint state structure.
 *
 * Function used to change or just read the state of selected endpoint.
 * It is used for internal transmission state.
 *
 * @param ep Endpoint number.
 */
static inline usbd_ep_state_t *ep_state_access(nrf_usbd_common_ep_t ep)
{
	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);
	return ((NRF_USBD_COMMON_EP_IS_IN(ep) ? m_ep_state.ep_in : m_ep_state.ep_out) +
		NRF_USBD_COMMON_EP_NUM(ep));
}

/**
 * @brief Change endpoint number to bit position.
 *
 * Bit positions are defined the same way as they are placed in DATAEPSTATUS register,
 * but bits for endpoint 0 are included.
 *
 * @param ep Endpoint number.
 *
 * @return Bit position related to the given endpoint number.
 *
 * @sa bit2ep
 */
static inline uint8_t ep2bit(nrf_usbd_common_ep_t ep)
{
	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);
	return NRF_USBD_COMMON_EP_BITPOS(ep);
}

/**
 * @brief Change bit position to endpoint number.
 *
 * @param bitpos Bit position.
 *
 * @return Endpoint number corresponding to given bit position.
 *
 * @sa ep2bit
 */
static inline nrf_usbd_common_ep_t bit2ep(uint8_t bitpos)
{
	BUILD_ASSERT(NRF_USBD_COMMON_EPOUT_BITPOS_0 > NRF_USBD_COMMON_EPIN_BITPOS_0,
		     "OUT endpoint bits should be higher than IN endpoint bits");
	return (nrf_usbd_common_ep_t)((bitpos >= NRF_USBD_COMMON_EPOUT_BITPOS_0)
		? NRF_USBD_COMMON_EPOUT(bitpos - NRF_USBD_COMMON_EPOUT_BITPOS_0)
		: NRF_USBD_COMMON_EPIN(bitpos));
}

/**
 * @brief Mark that EasyDMA is working.
 *
 * Internal function to set the flag informing about EasyDMA transfer pending.
 * This function is called always just after the EasyDMA transfer is started.
 */
static inline void usbd_dma_pending_set(void)
{
	if (nrf_usbd_common_errata_199()) {
		*((volatile uint32_t *)0x40027C1C) = 0x00000082;
	}
}

/**
 * @brief Mark that EasyDMA is free.
 *
 * Internal function to clear the flag informing about EasyDMA transfer pending.
 * This function is called always just after the finished EasyDMA transfer is detected.
 */
static inline void usbd_dma_pending_clear(void)
{
	if (nrf_usbd_common_errata_199()) {
		*((volatile uint32_t *)0x40027C1C) = 0x00000000;
	}
}

/**
 * @brief Abort pending transfer on selected endpoint.
 *
 * @param ep Endpoint number.
 *
 * @note
 * This function locks interrupts that may be costly.
 * It is good idea to test if the endpoint is still busy before calling this function:
 * @code
   (m_ep_dma_waiting & (1U << ep2bit(ep)))
 * @endcode
 * This function would check it again, but it makes it inside critical section.
 */
static inline void usbd_ep_abort(nrf_usbd_common_ep_t ep)
{
	unsigned int irq_lock_key = irq_lock();
	usbd_ep_state_t *p_state = ep_state_access(ep);

	if (NRF_USBD_COMMON_EP_IS_OUT(ep)) {
		/* Host -> Device */
		if ((~m_ep_dma_waiting) & (1U << ep2bit(ep))) {
			/* If the bit in m_ep_dma_waiting in cleared - nothing would be
			 * processed inside transfer processing
			 */
			nrf_usbd_common_transfer_out_drop(ep);
		} else {
			p_state->more_transactions = false;
			m_ep_dma_waiting &= ~(1U << ep2bit(ep));
			m_ep_ready &= ~(1U << ep2bit(ep));
		}
		/* Aborted */
		p_state->status = NRF_USBD_COMMON_EP_ABORTED;
	} else {
		if (!NRF_USBD_COMMON_EP_IS_ISO(ep)) {
			/* Workaround: Disarm the endpoint if there is any data buffered. */
			if (ep != NRF_USBD_COMMON_EPIN0) {
				*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x800)) =
					0x7B6 + (2u * (NRF_USBD_COMMON_EP_NUM(ep) - 1));
				uint8_t temp =
					*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804));
				temp |= (1U << 1);
				*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)) |= temp;
				(void)(*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)));
			} else {
				*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x800)) = 0x7B4;
				uint8_t temp =
					*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804));
				temp |= (1U << 2);
				*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)) |= temp;
				(void)(*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)));
			}
		}
		if ((m_ep_dma_waiting | (~m_ep_ready)) & (1U << ep2bit(ep))) {
			/* Device -> Host */
			m_ep_dma_waiting &= ~(1U << ep2bit(ep));
			m_ep_ready |= 1U << ep2bit(ep);

			p_state->more_transactions = false;
			p_state->status = NRF_USBD_COMMON_EP_ABORTED;
			NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_ABORTED);
			m_event_handler(&evt);
		}
	}

	irq_unlock(irq_lock_key);
}

void nrf_usbd_common_ep_abort(nrf_usbd_common_ep_t ep)
{
	/* Only abort if there is no active DMA */
	k_sem_take(&dma_available, K_FOREVER);
	usbd_ep_abort(ep);
	k_sem_give(&dma_available);

	/* This function was holding DMA semaphore and could potentially prevent
	 * next DMA from executing. Fire IRQ handler to check if any DMA needs
	 * to be started.
	 */
	usbd_int_rise();
}

/**
 * @brief Abort all pending endpoints.
 *
 * Function aborts all pending endpoint transfers.
 */
static void usbd_ep_abort_all(void)
{
	uint32_t ep_waiting = m_ep_dma_waiting | (m_ep_ready & NRF_USBD_COMMON_EPOUT_BIT_MASK);

	while (ep_waiting != 0) {
		uint8_t bitpos = NRF_CTZ(ep_waiting);

		if (!NRF_USBD_COMMON_EP_IS_ISO(bit2ep(bitpos))) {
			usbd_ep_abort(bit2ep(bitpos));
		}
		ep_waiting &= ~(1U << bitpos);
	}

	m_ep_ready = (((1U << NRF_USBD_COMMON_EPIN_CNT) - 1U) << NRF_USBD_COMMON_EPIN_BITPOS_0);
}

/**
 * @brief Force the USBD interrupt into pending state.
 *
 * This function is used to force USBD interrupt to be processed right now.
 * It makes it possible to process all EasyDMA access on one thread priority level.
 */
static inline void usbd_int_rise(void)
{
	NVIC_SetPendingIRQ(USBD_IRQn);
}

/**
 * @name USBD interrupt runtimes.
 *
 * Interrupt runtimes that would be vectorized using @ref m_isr.
 * @{
 */

static void ev_usbreset_handler(void)
{
	m_bus_suspend = false;
	m_last_setup_dir = NRF_USBD_COMMON_EPOUT0;

	const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_RESET};

	m_event_handler(&evt);
}

static void nrf_usbd_dma_finished(nrf_usbd_common_ep_t ep)
{
	/* DMA finished, track if total bytes transferred is even or odd */
	m_dma_odd ^= usbd_ep_amount_get(ep) & 1;
	usbd_dma_pending_clear();
	k_sem_give(&dma_available);

	usbd_ep_state_t *p_state = ep_state_access(ep);

	if (p_state->status == NRF_USBD_COMMON_EP_ABORTED) {
		/* Clear transfer information just in case */
		m_ep_dma_waiting &= ~(1U << ep2bit(ep));
	} else if (!p_state->more_transactions) {
		m_ep_dma_waiting &= ~(1U << ep2bit(ep));

		if (NRF_USBD_COMMON_EP_IS_OUT(ep) || (ep == NRF_USBD_COMMON_EPIN8)) {
			/* Send event to the user - for an ISO IN or any OUT endpoint,
			 * the whole transfer is finished in this moment
			 */
			NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_OK);
			m_event_handler(&evt);
		}
	} else if (ep == NRF_USBD_COMMON_EPOUT0) {
		nrf_usbd_common_setup_data_clear();
	}
}

static void ev_sof_handler(void)
{
	nrf_usbd_common_evt_t evt = {
		NRF_USBD_COMMON_EVT_SOF,
		.data = {.sof = {.framecnt = (uint16_t)NRF_USBD->FRAMECNTR}}};

	/* Process isochronous endpoints */
	uint32_t iso_ready_mask = (1U << ep2bit(NRF_USBD_COMMON_EPIN8));

	/* SIZE.ISOOUT is 0 only when no packet was received at all */
	if (NRF_USBD->SIZE.ISOOUT) {
		iso_ready_mask |= (1U << ep2bit(NRF_USBD_COMMON_EPOUT8));
	}
	m_ep_ready |= iso_ready_mask;

	m_event_handler(&evt);
}

/**
 * @brief React on data transfer finished.
 *
 * Auxiliary internal function.
 * @param ep     Endpoint number.
 * @param bitpos Bit position for selected endpoint number.
 */
static void usbd_ep_data_handler(nrf_usbd_common_ep_t ep, uint8_t bitpos)
{
	LOG_DBG("USBD event: EndpointData: %x", ep);
	/* Mark endpoint ready for next DMA access */
	m_ep_ready |= (1U << bitpos);

	if (NRF_USBD_COMMON_EP_IS_IN(ep)) {
		/* IN endpoint (Device -> Host) */
		if (0 == (m_ep_dma_waiting & (1U << bitpos))) {
			LOG_DBG("USBD event: EndpointData: In finished");
			/* No more data to be send - transmission finished */
			NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_OK);
			m_event_handler(&evt);
		}
	} else {
		/* OUT endpoint (Host -> Device) */
		if (0 == (m_ep_dma_waiting & (1U << bitpos))) {
			LOG_DBG("USBD event: EndpointData: Out waiting");
			/* No buffer prepared - send event to the application */
			NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep, NRF_USBD_COMMON_EP_WAITING);
			m_event_handler(&evt);
		}
	}
}

static void ev_setup_handler(void)
{
	LOG_DBG("USBD event: Setup (rt:%.2x r:%.2x v:%.4x i:%.4x l:%u )",
		       NRF_USBD->BMREQUESTTYPE, NRF_USBD->BREQUEST,
		       NRF_USBD->WVALUEL | (NRF_USBD->WVALUEH << 8),
		       NRF_USBD->WINDEXL | (NRF_USBD->WINDEXH << 8),
		       NRF_USBD->WLENGTHL | (NRF_USBD->WLENGTHH << 8));
	uint8_t bmRequestType = NRF_USBD->BMREQUESTTYPE;

	m_last_setup_dir =
		((bmRequestType & USBD_BMREQUESTTYPE_DIRECTION_Msk) ==
		 (USBD_BMREQUESTTYPE_DIRECTION_HostToDevice << USBD_BMREQUESTTYPE_DIRECTION_Pos))
			? NRF_USBD_COMMON_EPOUT0
			: NRF_USBD_COMMON_EPIN0;

	m_ep_dma_waiting &= ~((1U << ep2bit(NRF_USBD_COMMON_EPOUT0)) |
			      (1U << ep2bit(NRF_USBD_COMMON_EPIN0)));
	m_ep_ready &= ~(1U << ep2bit(NRF_USBD_COMMON_EPOUT0));
	m_ep_ready |= 1U << ep2bit(NRF_USBD_COMMON_EPIN0);

	const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_SETUP};

	m_event_handler(&evt);
}

static void ev_usbevent_handler(void)
{
	uint32_t event = NRF_USBD->EVENTCAUSE;

	/* Clear handled events */
	NRF_USBD->EVENTCAUSE = event;

	if (event & USBD_EVENTCAUSE_ISOOUTCRC_Msk) {
		LOG_DBG("USBD event: ISOOUTCRC");
		/* Currently no support */
	}
	if (event & USBD_EVENTCAUSE_SUSPEND_Msk) {
		LOG_DBG("USBD event: SUSPEND");
		m_bus_suspend = true;
		const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_SUSPEND};

		m_event_handler(&evt);
	}
	if (event & USBD_EVENTCAUSE_RESUME_Msk) {
		LOG_DBG("USBD event: RESUME");
		m_bus_suspend = false;
		const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_RESUME};

		m_event_handler(&evt);
	}
	if (event & USBD_EVENTCAUSE_USBWUALLOWED_Msk) {
		LOG_DBG("USBD event: WUREQ (%s)", m_bus_suspend ? "In Suspend" : "Active");
		if (m_bus_suspend) {
			__ASSERT_NO_MSG(!nrf_usbd_common_suspend_check());
			m_bus_suspend = false;

			NRF_USBD->DPDMVALUE = USBD_DPDMVALUE_STATE_Resume
				<< USBD_DPDMVALUE_STATE_Pos;
			NRF_USBD->TASKS_DPDMDRIVE = 1;

			const nrf_usbd_common_evt_t evt = {.type = NRF_USBD_COMMON_EVT_WUREQ};

			m_event_handler(&evt);
		}
	}
}

static void ev_epdata_handler(uint32_t dataepstatus)
{
	LOG_DBG("USBD event: EndpointEPStatus: %x", dataepstatus);

	/* All finished endpoint have to be marked as busy */
	while (dataepstatus) {
		uint8_t bitpos = NRF_CTZ(dataepstatus);
		nrf_usbd_common_ep_t ep = bit2ep(bitpos);

		dataepstatus &= ~(1UL << bitpos);

		(void)(usbd_ep_data_handler(ep, bitpos));
	}
}

/**
 * @brief Function to select the endpoint to start.
 *
 * Function that realizes algorithm to schedule right channel for EasyDMA transfer.
 * It gets a variable with flags for the endpoints currently requiring transfer.
 *
 * @param[in] req Bit flags for channels currently requiring transfer.
 *                Bits 0...8 used for IN endpoints.
 *                Bits 16...24 used for OUT endpoints.
 * @note
 * This function would be never called with 0 as a @c req argument.
 * @return The bit number of the endpoint that should be processed now.
 */
static uint8_t usbd_dma_scheduler_algorithm(uint32_t req)
{
	/* Only prioritized scheduling mode is supported. */
	return NRF_CTZ(req);
}

/**
 * @brief Get the size of isochronous endpoint.
 *
 * The size of isochronous endpoint is configurable.
 * This function returns the size of isochronous buffer taking into account
 * current configuration.
 *
 * @param[in] ep Endpoint number.
 *
 * @return The size of endpoint buffer.
 */
static inline size_t usbd_ep_iso_capacity(nrf_usbd_common_ep_t ep)
{
	(void)ep;

	if (NRF_USBD->ISOSPLIT == USBD_ISOSPLIT_SPLIT_HalfIN << USBD_ISOSPLIT_SPLIT_Pos) {
		return NRF_USBD_COMMON_ISOSIZE / 2;
	}
	return NRF_USBD_COMMON_ISOSIZE;
}

/**
 * @brief Process all DMA requests.
 *
 * Function that have to be called from USBD interrupt handler.
 * It have to be called when all the interrupts connected with endpoints transfer
 * and DMA transfer are already handled.
 */
static void usbd_dmareq_process(void)
{
	if ((m_ep_dma_waiting & m_ep_ready) &&
	    (k_sem_take(&dma_available, K_NO_WAIT) == 0)) {
		uint32_t req;

		while (0 != (req = m_ep_dma_waiting & m_ep_ready)) {
			uint8_t pos;

			if (NRFX_USBD_CONFIG_DMASCHEDULER_ISO_BOOST &&
			    ((req & USBD_EPISO_BIT_MASK) != 0)) {
				pos = usbd_dma_scheduler_algorithm(req & USBD_EPISO_BIT_MASK);
			} else {
				pos = usbd_dma_scheduler_algorithm(req);
			}
			nrf_usbd_common_ep_t ep = bit2ep(pos);
			usbd_ep_state_t *p_state = ep_state_access(ep);

			nrf_usbd_common_ep_transfer_t transfer;
			bool continue_transfer;

			__ASSERT_NO_MSG(p_state->more_transactions);

			if (NRF_USBD_COMMON_EP_IS_IN(ep)) {
				/* Device -> Host */
				continue_transfer = nrf_usbd_common_feeder(
					&transfer, &p_state->transfer_state,
					p_state->max_packet_size);
			} else {
				/* Host -> Device */
				const size_t rx_size = nrf_usbd_common_epout_size_get(ep);

				continue_transfer = nrf_usbd_common_consumer(
					&transfer, &p_state->transfer_state,
					p_state->max_packet_size, rx_size);

				if (transfer.p_data.rx == NULL) {
					/* Dropping transfer - allow processing */
					__ASSERT_NO_MSG(transfer.size == 0);
				} else if (transfer.size < rx_size) {
					LOG_DBG("Endpoint %x overload (r: %u, e: %u)", ep,
						       rx_size, transfer.size);
					p_state->status = NRF_USBD_COMMON_EP_OVERLOAD;
					m_ep_dma_waiting &= ~(1U << pos);
					NRF_USBD_COMMON_EP_TRANSFER_EVENT(evt, ep,
						NRF_USBD_COMMON_EP_OVERLOAD);
					m_event_handler(&evt);
					/* This endpoint will not be transmitted now, repeat the
					 * loop
					 */
					continue;
				} else {
					/* Nothing to do - only check integrity if assertions are
					 * enabled
					 */
					__ASSERT_NO_MSG(transfer.size == rx_size);
				}
			}

			if (!continue_transfer) {
				p_state->more_transactions = false;
			}

			usbd_dma_pending_set();
			m_ep_ready &= ~(1U << pos);
			if (NRF_USBD_COMMON_ISO_DEBUG || (!NRF_USBD_COMMON_EP_IS_ISO(ep))) {
				LOG_DBG("USB DMA process: Starting transfer on EP: %x, size: %u",
					ep, transfer.size);
			}
			/* Update number of currently transferred bytes */
			p_state->transfer_cnt += transfer.size;
			/* Start transfer to the endpoint buffer */
			dma_ep = ep;
			usbd_ep_dma_start(ep, transfer.p_data.addr, transfer.size);

			/* Transfer started - exit the loop */
			return;
		}
		k_sem_give(&dma_available);
	} else {
		if (NRF_USBD_COMMON_DMAREQ_PROCESS_DEBUG) {
			LOG_DBG("USB DMA process - EasyDMA busy");
		}
	}
}

/**
 * @brief Begin errata 171.
 */
static inline void usbd_errata_171_begin(void)
{
	unsigned int irq_lock_key = irq_lock();

	if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) {
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
		*((volatile uint32_t *)(0x4006EC14)) = 0x000000C0;
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
	} else {
		*((volatile uint32_t *)(0x4006EC14)) = 0x000000C0;
	}

	irq_unlock(irq_lock_key);
}

/**
 * @brief End errata 171.
 */
static inline void usbd_errata_171_end(void)
{
	unsigned int irq_lock_key = irq_lock();

	if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) {
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
		*((volatile uint32_t *)(0x4006EC14)) = 0x00000000;
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
	} else {
		*((volatile uint32_t *)(0x4006EC14)) = 0x00000000;
	}

	irq_unlock(irq_lock_key);
}

/**
 * @brief Begin erratas 187 and 211.
 */
static inline void usbd_errata_187_211_begin(void)
{
	unsigned int irq_lock_key = irq_lock();

	if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) {
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
		*((volatile uint32_t *)(0x4006ED14)) = 0x00000003;
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
	} else {
		*((volatile uint32_t *)(0x4006ED14)) = 0x00000003;
	}

	irq_unlock(irq_lock_key);
}

/**
 * @brief End erratas 187 and 211.
 */
static inline void usbd_errata_187_211_end(void)
{
	unsigned int irq_lock_key = irq_lock();

	if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) {
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
		*((volatile uint32_t *)(0x4006ED14)) = 0x00000000;
		*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
	} else {
		*((volatile uint32_t *)(0x4006ED14)) = 0x00000000;
	}

	irq_unlock(irq_lock_key);
}

/**
 * @brief Enable USBD peripheral.
 */
static void usbd_enable(void)
{
	if (nrf_usbd_common_errata_187()) {
		usbd_errata_187_211_begin();
	}

	if (nrf_usbd_common_errata_171()) {
		usbd_errata_171_begin();
	}

	/* Enable the peripheral */
	NRF_USBD->ENABLE = 1;

	/* Waiting for peripheral to enable, this should take a few us */
	while ((NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_READY_Msk) == 0) {
	}
	NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk;

	if (nrf_usbd_common_errata_171()) {
		usbd_errata_171_end();
	}

	if (nrf_usbd_common_errata_187()) {
		usbd_errata_187_211_end();
	}
}
/** @} */

/**
 * @name Interrupt handlers
 *
 * @{
 */
void nrf_usbd_common_irq_handler(void)
{
	volatile uint32_t *dma_endevent;
	uint32_t epdatastatus = 0;

	/* Clear EPDATA event and only then get and clear EPDATASTATUS to make
	 * sure we don't miss any event.
	 */
	if (NRF_USBD->EVENTS_EPDATA) {
		NRF_USBD->EVENTS_EPDATA = 0;
		epdatastatus = NRF_USBD->EPDATASTATUS;
		NRF_USBD->EPDATASTATUS = epdatastatus;
	}

	/* Use common variable to store EP0DATADONE processing needed flag */
	if (NRF_USBD->EVENTS_EP0DATADONE) {
		NRF_USBD->EVENTS_EP0DATADONE = 0;
		epdatastatus |= BIT(ep2bit(m_last_setup_dir));
	}

	/* Check DMA end event only for last enabled DMA channel. Other channels
	 * cannot be active and there's no harm in rechecking the event multiple
	 * times (it is not a problem to check it even if DMA is not active).
	 *
	 * It is important to check DMA and handle DMA finished event before
	 * handling acknowledged data transfer bits (epdatastatus) to avoid
	 * a race condition between interrupt handler and host IN token.
	 */
	dma_endevent = usbd_ep_to_endevent(dma_ep);
	if (*dma_endevent) {
		*dma_endevent = 0;
		nrf_usbd_dma_finished(dma_ep);
	}

	/* Process acknowledged transfers so we can prepare next DMA (if any) */
	ev_epdata_handler(epdatastatus);

	if (NRF_USBD->EVENTS_USBRESET) {
		NRF_USBD->EVENTS_USBRESET = 0;
		ev_usbreset_handler();
	}

	/* Always check and clear SOF but call handler only if SOF interrupt
	 * is actually enabled.
	 */
	if (NRF_USBD->EVENTS_SOF) {
		NRF_USBD->EVENTS_SOF = 0;
		if (NRF_USBD->INTENSET & USBD_INTEN_SOF_Msk) {
			ev_sof_handler();
		}
	}

	if (NRF_USBD->EVENTS_USBEVENT) {
		NRF_USBD->EVENTS_USBEVENT = 0;
		ev_usbevent_handler();
	}

	/* Handle SETUP only if there is no active DMA on EP0 */
	if (unlikely(NRF_USBD->EVENTS_EP0SETUP) &&
	    (k_sem_count_get(&dma_available) ||
	     (dma_ep != NRF_USBD_COMMON_EPIN0 && dma_ep != NRF_USBD_COMMON_EPOUT0))) {
		NRF_USBD->EVENTS_EP0SETUP = 0;
		ev_setup_handler();
	}

	usbd_dmareq_process();
}

/** @} */
/** @} */

nrfx_err_t nrf_usbd_common_init(nrf_usbd_common_event_handler_t event_handler)
{
	__ASSERT_NO_MSG(event_handler);

	if (m_drv_state != NRFX_DRV_STATE_UNINITIALIZED) {
		return NRFX_ERROR_INVALID_STATE;
	}

	m_event_handler = event_handler;
	m_drv_state = NRFX_DRV_STATE_INITIALIZED;

	uint8_t n;

	for (n = 0; n < NRF_USBD_COMMON_EPIN_CNT; ++n) {
		nrf_usbd_common_ep_t ep = NRF_USBD_COMMON_EPIN(n);

		nrf_usbd_common_ep_max_packet_size_set(ep, NRF_USBD_COMMON_EP_IS_ISO(ep) ?
			(NRF_USBD_COMMON_ISOSIZE / 2) : NRF_USBD_COMMON_EPSIZE);
		usbd_ep_state_t *p_state = ep_state_access(ep);

		p_state->status = NRF_USBD_COMMON_EP_OK;
		p_state->more_transactions = false;
		p_state->transfer_cnt = 0;
	}
	for (n = 0; n < NRF_USBD_COMMON_EPOUT_CNT; ++n) {
		nrf_usbd_common_ep_t ep = NRF_USBD_COMMON_EPOUT(n);

		nrf_usbd_common_ep_max_packet_size_set(ep, NRF_USBD_COMMON_EP_IS_ISO(ep) ?
			(NRF_USBD_COMMON_ISOSIZE / 2) : NRF_USBD_COMMON_EPSIZE);
		usbd_ep_state_t *p_state = ep_state_access(ep);

		p_state->status = NRF_USBD_COMMON_EP_OK;
		p_state->more_transactions = false;
		p_state->transfer_cnt = 0;
	}

	return NRFX_SUCCESS;
}

void nrf_usbd_common_uninit(void)
{
	__ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_INITIALIZED);

	m_event_handler = NULL;
	m_drv_state = NRFX_DRV_STATE_UNINITIALIZED;
}

void nrf_usbd_common_enable(void)
{
	__ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_INITIALIZED);

	/* Prepare for READY event receiving */
	NRF_USBD->EVENTCAUSE = USBD_EVENTCAUSE_READY_Msk;

	usbd_enable();

	if (nrf_usbd_common_errata_223() && m_first_enable) {
		NRF_USBD->ENABLE = 0;

		usbd_enable();

		m_first_enable = false;
	}

#if NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211
	if (nrf_usbd_common_errata_187() || nrf_usbd_common_errata_211())
#else
	if (nrf_usbd_common_errata_187())
#endif
	{
		usbd_errata_187_211_begin();
	}

	if (nrf_usbd_common_errata_166()) {
		*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x800)) = 0x7E3;
		*((volatile uint32_t *)((uint32_t)(NRF_USBD) + 0x804)) = 0x40;
		__ISB();
		__DSB();
	}

	NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN << USBD_ISOSPLIT_SPLIT_Pos;

	if (IS_ENABLED(CONFIG_NRF_USBD_ISO_IN_ZLP)) {
		NRF_USBD->ISOINCONFIG = USBD_ISOINCONFIG_RESPONSE_ZeroData
			<< USBD_ISOINCONFIG_RESPONSE_Pos;
	} else {
		NRF_USBD->ISOINCONFIG = USBD_ISOINCONFIG_RESPONSE_NoResp
			<< USBD_ISOINCONFIG_RESPONSE_Pos;
	}

	m_ep_ready = (((1U << NRF_USBD_COMMON_EPIN_CNT) - 1U) << NRF_USBD_COMMON_EPIN_BITPOS_0);
	m_ep_dma_waiting = 0;
	m_dma_odd = 0;
	__ASSERT_NO_MSG(k_sem_count_get(&dma_available) == 1);
	usbd_dma_pending_clear();
	m_last_setup_dir = NRF_USBD_COMMON_EPOUT0;

	m_drv_state = NRFX_DRV_STATE_POWERED_ON;

#if NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211
	if (nrf_usbd_common_errata_187() && !nrf_usbd_common_errata_211())
#else
	if (nrf_usbd_common_errata_187())
#endif
	{
		usbd_errata_187_211_end();
	}
}

void nrf_usbd_common_disable(void)
{
	__ASSERT_NO_MSG(m_drv_state != NRFX_DRV_STATE_UNINITIALIZED);

	/* Make sure DMA is not active */
	k_sem_take(&dma_available, K_FOREVER);

	/* Stop just in case */
	nrf_usbd_common_stop();

	/* Disable all parts */
	if (m_dma_odd) {
		/* Prevent invalid bus request after next USBD enable by ensuring
		 * that total number of bytes transferred by DMA is even.
		 */
		NRF_USBD->EVENTS_ENDEPIN[0] = 0;
		usbd_ep_dma_start(NRF_USBD_COMMON_EPIN0, (uint32_t)&m_dma_odd, 1);
		while (!NRF_USBD->EVENTS_ENDEPIN[0]) {
		}
		NRF_USBD->EVENTS_ENDEPIN[0] = 0;
		m_dma_odd = 0;
	}
	NRF_USBD->ENABLE = 0;
	usbd_dma_pending_clear();
	k_sem_give(&dma_available);
	m_drv_state = NRFX_DRV_STATE_INITIALIZED;

#if NRF_USBD_COMMON_USE_WORKAROUND_FOR_ANOMALY_211
	if (nrf_usbd_common_errata_211()) {
		usbd_errata_187_211_end();
	}
#endif
}

void nrf_usbd_common_start(bool enable_sof)
{
	__ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_POWERED_ON);
	m_bus_suspend = false;

	uint32_t int_mask = USBD_INTEN_USBRESET_Msk | USBD_INTEN_ENDEPIN0_Msk |
		USBD_INTEN_ENDEPIN1_Msk | USBD_INTEN_ENDEPIN2_Msk |
		USBD_INTEN_ENDEPIN3_Msk | USBD_INTEN_ENDEPIN4_Msk |
		USBD_INTEN_ENDEPIN5_Msk | USBD_INTEN_ENDEPIN6_Msk |
		USBD_INTEN_ENDEPIN7_Msk | USBD_INTEN_EP0DATADONE_Msk |
		USBD_INTEN_ENDISOIN_Msk | USBD_INTEN_ENDEPOUT0_Msk |
		USBD_INTEN_ENDEPOUT1_Msk | USBD_INTEN_ENDEPOUT2_Msk |
		USBD_INTEN_ENDEPOUT3_Msk | USBD_INTEN_ENDEPOUT4_Msk |
		USBD_INTEN_ENDEPOUT5_Msk | USBD_INTEN_ENDEPOUT6_Msk |
		USBD_INTEN_ENDEPOUT7_Msk | USBD_INTEN_ENDISOOUT_Msk |
		USBD_INTEN_USBEVENT_Msk | USBD_INTEN_EP0SETUP_Msk |
		USBD_INTEN_EPDATA_Msk;

	if (enable_sof) {
		int_mask |= USBD_INTEN_SOF_Msk;
	}

	/* Enable all required interrupts */
	NRF_USBD->INTEN = int_mask;

	/* Enable interrupt globally */
	irq_enable(USBD_IRQn);

	/* Enable pullups */
	NRF_USBD->USBPULLUP = 1;
}

static void nrf_usbd_common_stop(void)
{
	__ASSERT_NO_MSG(m_drv_state == NRFX_DRV_STATE_POWERED_ON);

	/* Clear interrupt */
	NVIC_ClearPendingIRQ(USBD_IRQn);

	if (irq_is_enabled(USBD_IRQn)) {
		/* Abort transfers */
		usbd_ep_abort_all();

		/* Disable pullups */
		NRF_USBD->USBPULLUP = 0;

		/* Disable interrupt globally */
		irq_disable(USBD_IRQn);

		/* Disable all interrupts */
		NRF_USBD->INTEN = 0;
	}
}

bool nrf_usbd_common_is_initialized(void)
{
	return (m_drv_state >= NRFX_DRV_STATE_INITIALIZED);
}

bool nrf_usbd_common_is_enabled(void)
{
	return (m_drv_state >= NRFX_DRV_STATE_POWERED_ON);
}

bool nrf_usbd_common_is_started(void)
{
	return (nrf_usbd_common_is_enabled() && irq_is_enabled(USBD_IRQn));
}

bool nrf_usbd_common_suspend(void)
{
	bool suspended = false;
	unsigned int irq_lock_key = irq_lock();

	if (m_bus_suspend) {
		if (!(NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_RESUME_Msk)) {
			NRF_USBD->LOWPOWER = USBD_LOWPOWER_LOWPOWER_LowPower
				<< USBD_LOWPOWER_LOWPOWER_Pos;
			(void)NRF_USBD->LOWPOWER;
			if (NRF_USBD->EVENTCAUSE & USBD_EVENTCAUSE_RESUME_Msk) {
				NRF_USBD->LOWPOWER = USBD_LOWPOWER_LOWPOWER_ForceNormal
					<< USBD_LOWPOWER_LOWPOWER_Pos;
			} else {
				suspended = true;
			}
		}
	}

	irq_unlock(irq_lock_key);

	return suspended;
}

bool nrf_usbd_common_wakeup_req(void)
{
	bool started = false;
	unsigned int irq_lock_key = irq_lock();

	if (m_bus_suspend && nrf_usbd_common_suspend_check()) {
		NRF_USBD->LOWPOWER = USBD_LOWPOWER_LOWPOWER_ForceNormal
			<< USBD_LOWPOWER_LOWPOWER_Pos;
		started = true;

		if (nrf_usbd_common_errata_171()) {
			if (*((volatile uint32_t *)(0x4006EC00)) == 0x00000000) {
				*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
				*((volatile uint32_t *)(0x4006EC14)) = 0x000000C0;
				*((volatile uint32_t *)(0x4006EC00)) = 0x00009375;
			} else {
				*((volatile uint32_t *)(0x4006EC14)) = 0x000000C0;
			}
		}
	}

	irq_unlock(irq_lock_key);

	return started;
}

bool nrf_usbd_common_suspend_check(void)
{
	return NRF_USBD->LOWPOWER !=
		(USBD_LOWPOWER_LOWPOWER_ForceNormal << USBD_LOWPOWER_LOWPOWER_Pos);
}

bool nrf_usbd_common_bus_suspend_check(void)
{
	return m_bus_suspend;
}

void nrf_usbd_common_force_bus_wakeup(void)
{
	m_bus_suspend = false;
}

void nrf_usbd_common_ep_max_packet_size_set(nrf_usbd_common_ep_t ep, uint16_t size)
{
	/* Only the power of 2 size allowed for Control Endpoints */
	__ASSERT_NO_MSG((((size & (size - 1)) == 0) || (NRF_USBD_COMMON_EP_NUM(ep) != 0)));
	/* Only non zero size allowed for Control Endpoints */
	__ASSERT_NO_MSG((size != 0) || (NRF_USBD_COMMON_EP_NUM(ep) != 0));
	/* Packet size cannot be higher than maximum buffer size */
	__ASSERT_NO_MSG((NRF_USBD_COMMON_EP_IS_ISO(ep) && (size <= usbd_ep_iso_capacity(ep))) ||
		    (!NRF_USBD_COMMON_EP_IS_ISO(ep) && (size <= NRF_USBD_COMMON_EPSIZE)));

	usbd_ep_state_t *p_state = ep_state_access(ep);

	p_state->max_packet_size = size;
}

uint16_t nrf_usbd_common_ep_max_packet_size_get(nrf_usbd_common_ep_t ep)
{
	usbd_ep_state_t const *p_state = ep_state_access(ep);

	return p_state->max_packet_size;
}

bool nrf_usbd_common_ep_enable_check(nrf_usbd_common_ep_t ep)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	NRF_USBD_COMMON_ASSERT_EP_VALID(ep);

	return (ep_in ? NRF_USBD->EPINEN : NRF_USBD->EPOUTEN) & BIT(ep_num);
}

void nrf_usbd_common_ep_enable(nrf_usbd_common_ep_t ep)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	if (nrf_usbd_common_ep_enable_check(ep)) {
		return;
	}

	if (ep_in) {
		NRF_USBD->EPINEN |= BIT(ep_num);
	} else {
		NRF_USBD->EPOUTEN |= BIT(ep_num);
	}

	if (ep >= NRF_USBD_COMMON_EPOUT1 && ep <= NRF_USBD_COMMON_EPOUT7) {
		unsigned int irq_lock_key = irq_lock();

		nrf_usbd_common_transfer_out_drop(ep);
		m_ep_dma_waiting &= ~(1U << ep2bit(ep));

		irq_unlock(irq_lock_key);
	}
}

void nrf_usbd_common_ep_disable(nrf_usbd_common_ep_t ep)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	/* Only disable endpoint if there is no active DMA */
	k_sem_take(&dma_available, K_FOREVER);
	usbd_ep_abort(ep);
	if (ep_in) {
		NRF_USBD->EPINEN &= ~BIT(ep_num);
	} else {
		NRF_USBD->EPOUTEN &= ~BIT(ep_num);
	}
	k_sem_give(&dma_available);

	/* This function was holding DMA semaphore and could potentially prevent
	 * next DMA from executing. Fire IRQ handler to check if any DMA needs
	 * to be started.
	 */
	usbd_int_rise();
}

nrfx_err_t nrf_usbd_common_ep_transfer(nrf_usbd_common_ep_t ep,
				       nrf_usbd_common_transfer_t const *p_transfer)
{
	nrfx_err_t ret;
	const uint8_t ep_bitpos = ep2bit(ep);
	unsigned int irq_lock_key = irq_lock();

	__ASSERT_NO_MSG(p_transfer != NULL);

	/* Setup data transaction can go only in one direction at a time */
	if ((NRF_USBD_COMMON_EP_NUM(ep) == 0) && (ep != m_last_setup_dir)) {
		ret = NRFX_ERROR_INVALID_ADDR;
		if (NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG &&
		    (NRF_USBD_COMMON_ISO_DEBUG || (!NRF_USBD_COMMON_EP_IS_ISO(ep)))) {
			LOG_DBG("Transfer failed: Invalid EPr\n");
		}
	} else if ((m_ep_dma_waiting | ((~m_ep_ready) & NRF_USBD_COMMON_EPIN_BIT_MASK)) &
		   (1U << ep_bitpos)) {
		/* IN (Device -> Host) transfer has to be transmitted out to allow new transmission
		 */
		ret = NRFX_ERROR_BUSY;
		if (NRF_USBD_COMMON_FAILED_TRANSFERS_DEBUG) {
			LOG_DBG("Transfer failed: EP is busy");
		}
	} else {
		usbd_ep_state_t *p_state = ep_state_access(ep);

		__ASSERT_NO_MSG(NRF_USBD_COMMON_EP_IS_IN(ep) ||
				(p_transfer->p_data.rx == NULL) ||
				(nrfx_is_in_ram(p_transfer->p_data.rx)));
		p_state->more_transactions = true;
		p_state->transfer_state = *p_transfer;

		p_state->transfer_cnt = 0;
		p_state->status = NRF_USBD_COMMON_EP_OK;
		m_ep_dma_waiting |= 1U << ep_bitpos;
		ret = NRFX_SUCCESS;
		usbd_int_rise();
	}

	irq_unlock(irq_lock_key);

	return ret;
}

nrf_usbd_common_ep_status_t nrf_usbd_common_ep_status_get(nrf_usbd_common_ep_t ep, size_t *p_size)
{
	nrf_usbd_common_ep_status_t ret;
	usbd_ep_state_t const *p_state = ep_state_access(ep);
	unsigned int irq_lock_key = irq_lock();

	*p_size = p_state->transfer_cnt;
	ret = (!p_state->more_transactions) ? p_state->status : NRF_USBD_COMMON_EP_BUSY;

	irq_unlock(irq_lock_key);

	return ret;
}

size_t nrf_usbd_common_epout_size_get(nrf_usbd_common_ep_t ep)
{
	if (NRF_USBD_COMMON_EP_IS_ISO(ep)) {
		size_t size = NRF_USBD->SIZE.ISOOUT;

		if ((size & USBD_SIZE_ISOOUT_ZERO_Msk) ==
		    (USBD_SIZE_ISOOUT_ZERO_ZeroData << USBD_SIZE_ISOOUT_ZERO_Pos)) {
			size = 0;
		}
		return size;
	}

	return NRF_USBD->SIZE.EPOUT[NRF_USBD_COMMON_EP_NUM(ep)];
}

bool nrf_usbd_common_ep_is_busy(nrf_usbd_common_ep_t ep)
{
	return (0 != ((m_ep_dma_waiting | ((~m_ep_ready) & NRF_USBD_COMMON_EPIN_BIT_MASK)) &
		      (1U << ep2bit(ep))));
}

void nrf_usbd_common_ep_stall(nrf_usbd_common_ep_t ep)
{
	__ASSERT_NO_MSG(!NRF_USBD_COMMON_EP_IS_ISO(ep));

	LOG_DBG("USB: EP %x stalled.", ep);
	NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_Stall << USBD_EPSTALL_STALL_Pos) | ep;
}

void nrf_usbd_common_ep_stall_clear(nrf_usbd_common_ep_t ep)
{
	__ASSERT_NO_MSG(!NRF_USBD_COMMON_EP_IS_ISO(ep));

	if (NRF_USBD_COMMON_EP_IS_OUT(ep) && nrf_usbd_common_ep_stall_check(ep)) {
		nrf_usbd_common_transfer_out_drop(ep);
	}
	NRF_USBD->EPSTALL = (USBD_EPSTALL_STALL_UnStall << USBD_EPSTALL_STALL_Pos) | ep;
}

bool nrf_usbd_common_ep_stall_check(nrf_usbd_common_ep_t ep)
{
	int ep_in = NRF_USBD_COMMON_EP_IS_IN(ep);
	int ep_num = NRF_USBD_COMMON_EP_NUM(ep);

	if (!NRF_USBD_COMMON_EP_IS_ISO(ep_num)) {
		if (ep_in) {
			return NRF_USBD->HALTED.EPIN[ep_num];
		} else {
			return NRF_USBD->HALTED.EPOUT[ep_num];
		}
	}

	return false;
}

void nrf_usbd_common_ep_dtoggle_clear(nrf_usbd_common_ep_t ep)
{
	__ASSERT_NO_MSG(!NRF_USBD_COMMON_EP_IS_ISO(ep));

	NRF_USBD->DTOGGLE = ep | (USBD_DTOGGLE_VALUE_Nop << USBD_DTOGGLE_VALUE_Pos);
	NRF_USBD->DTOGGLE = ep | (USBD_DTOGGLE_VALUE_Data0 << USBD_DTOGGLE_VALUE_Pos);
}

void nrf_usbd_common_setup_get(nrf_usbd_common_setup_t *p_setup)
{
	memset(p_setup, 0, sizeof(nrf_usbd_common_setup_t));
	p_setup->bmRequestType = NRF_USBD->BMREQUESTTYPE;
	p_setup->bRequest = NRF_USBD->BREQUEST;
	p_setup->wValue = NRF_USBD->WVALUEL | (NRF_USBD->WVALUEH << 8);
	p_setup->wIndex = NRF_USBD->WINDEXL | (NRF_USBD->WINDEXH << 8);
	p_setup->wLength = NRF_USBD->WLENGTHL | (NRF_USBD->WLENGTHH << 8);
}

void nrf_usbd_common_setup_data_clear(void)
{
	NRF_USBD->TASKS_EP0RCVOUT = 1;
}

void nrf_usbd_common_setup_clear(void)
{
	LOG_DBG(">> ep0status >>");
	NRF_USBD->TASKS_EP0STATUS = 1;
}

void nrf_usbd_common_setup_stall(void)
{
	LOG_DBG("Setup stalled.");
	NRF_USBD->TASKS_EP0STALL = 1;
}

nrf_usbd_common_ep_t nrf_usbd_common_last_setup_dir_get(void)
{
	return m_last_setup_dir;
}

void nrf_usbd_common_transfer_out_drop(nrf_usbd_common_ep_t ep)
{
	unsigned int irq_lock_key = irq_lock();

	__ASSERT_NO_MSG(NRF_USBD_COMMON_EP_IS_OUT(ep));

	m_ep_ready &= ~(1U << ep2bit(ep));
	if (!NRF_USBD_COMMON_EP_IS_ISO(ep)) {
		NRF_USBD->SIZE.EPOUT[NRF_USBD_COMMON_EP_NUM(ep)] = 0;
	}

	irq_unlock(irq_lock_key);
}

/** @endcond */
