text: /*
   * Copyright (c) 2021 Gerson Fernando Budke <nandojve@gmail.com>
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #define DT_DRV_COMPAT atmel_sam_usbc
  
  #include <logging/log.h>
  LOG_MODULE_REGISTER(usb_dc_sam_usbc, CONFIG_USB_DRIVER_LOG_LEVEL);
  
  #include <kernel.h>
  #include <usb/usb_device.h>
  #include <soc.h>
  #include <string.h>
  #include <sys/byteorder.h>
  #include <drivers/pinctrl.h>
  
  #define EP_UDINT_MASK           0x000FF000
  
  #define NUM_OF_EP_MAX           DT_INST_PROP(0, num_bidir_endpoints)
  #define USBC_RAM_ADDR           DT_REG_ADDR(DT_NODELABEL(sram1))
  #define USBC_RAM_SIZE           DT_REG_SIZE(DT_NODELABEL(sram1))
  
  /**
   * @brief USB Driver Control Endpoint Finite State Machine states
   *
   * FSM states to keep tracking of control endpoint hidden states.
   */
  enum usb_dc_epctrl_state {
  	/* Wait a SETUP packet */
  	USB_EPCTRL_SETUP,
  	/* Wait a OUT data packet */
  	USB_EPCTRL_DATA_OUT,
  	/* Wait a IN data packet */
  	USB_EPCTRL_DATA_IN,
  	/* Wait a IN ZLP packet */
  	USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP,
  	/* Wait a OUT ZLP packet */
  	USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP,
  	/* STALL enabled on IN & OUT packet */
  	USB_EPCTRL_STALL_REQ,
  };
  
  struct sam_usbc_udesc_sizes {
  	uint32_t byte_count:15;
  	uint32_t reserved:1;
  	uint32_t multi_packet_size:15;
  	uint32_t auto_zlp:1;
  };
  
  struct sam_usbc_udesc_bk_ctrl_stat {
  	uint32_t stallrq:1;
  	uint32_t reserved1:15;
  	uint32_t crcerri:1;
  	uint32_t overfi:1;
  	uint32_t underfi:1;
  	uint32_t reserved2:13;
  };
  
  struct sam_usbc_udesc_ep_ctrl_stat {
  	uint32_t pipe_dev_addr:7;
  	uint32_t reserved1:1;
  	uint32_t pipe_num:4;
  	uint32_t pipe_error_cnt_max:4;
  	uint32_t pipe_error_status:8;
  	uint32_t reserved2:8;
  };
  
  struct sam_usbc_desc_table {
  	uint8_t *ep_pipe_addr;
  	union {
  		uint32_t sizes;
  		struct sam_usbc_udesc_sizes udesc_sizes;
  	};
  	union {
  		uint32_t bk_ctrl_stat;
  		struct sam_usbc_udesc_bk_ctrl_stat udesc_bk_ctrl_stat;
  	};
  	union {
  		uint32_t ep_ctrl_stat;
  		struct sam_usbc_udesc_ep_ctrl_stat udesc_ep_ctrl_stat;
  	};
  };
  
  struct usb_device_ep_data {
  	usb_dc_ep_callback cb_in;
  	usb_dc_ep_callback cb_out;
  	uint16_t mps;
  	bool mps_x2;
  	bool is_configured;
  	uint32_t out_at;
  };
  
  struct usb_device_data {
  	usb_dc_status_callback status_cb;
  	struct usb_device_ep_data ep_data[NUM_OF_EP_MAX];
  };
  
  static struct sam_usbc_desc_table dev_desc[(NUM_OF_EP_MAX + 1) * 2];
  static struct usb_device_data dev_data;
  static volatile Usbc *regs = (Usbc *) DT_INST_REG_ADDR(0);
  PINCTRL_DT_INST_DEFINE(0);
  static const struct pinctrl_dev_config *pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0);
  static enum usb_dc_epctrl_state epctrl_fsm;
  static const char *const usb_dc_epctrl_state_string[] = {
  	"STP",
  	"DOUT",
  	"DIN",
  	"IN_ZLP",
  	"OUT_ZLP",
  	"STALL",
  };
  
  #if defined(CONFIG_USB_DRIVER_LOG_LEVEL_DBG)
  static uint32_t dev_ep_sta_dbg[2][NUM_OF_EP_MAX];
  
  static void usb_dc_sam_usbc_isr_sta_dbg(uint32_t ep_idx, uint32_t sr)
  {
  	if (regs->UESTA[ep_idx] != dev_ep_sta_dbg[0][ep_idx]) {
  		dev_ep_sta_dbg[0][ep_idx] = regs->UESTA[ep_idx];
  		dev_ep_sta_dbg[1][ep_idx] = 0;
  
  		LOG_INF("ISR[%d] CON=%08x INT=%08x INTE=%08x "
  			"ECON=%08x ESTA=%08x%s", ep_idx,
  			regs->UDCON, regs->UDINT, regs->UDINTE,
  			regs->UECON[ep_idx], regs->UESTA[ep_idx],
  			((sr & USBC_UESTA0_RXSTPI) ? " STP" : ""));
  	} else if (dev_ep_sta_dbg[0][ep_idx] != dev_ep_sta_dbg[1][ep_idx]) {
  		dev_ep_sta_dbg[1][ep_idx] = dev_ep_sta_dbg[0][ep_idx];
  
  		LOG_INF("ISR[%d] CON=%08x INT=%08x INTE=%08x "
  			"ECON=%08x ESTA=%08x LOOP", ep_idx,
  			regs->UDCON, regs->UDINT, regs->UDINTE,
  			regs->UECON[ep_idx], regs->UESTA[ep_idx]);
  	}
  }
  
  static void usb_dc_sam_usbc_clean_sta_dbg(void)
  {
  	for (int i = 0; i < NUM_OF_EP_MAX; i++) {
  		dev_ep_sta_dbg[0][i] = 0;
  		dev_ep_sta_dbg[1][i] = 0;
  	}
  }
  #else
  #define usb_dc_sam_usbc_isr_sta_dbg(ep_idx, sr)
  #define usb_dc_sam_usbc_clean_sta_dbg()
  #endif
  
  static ALWAYS_INLINE bool usb_dc_sam_usbc_is_frozen_clk(void)
  {
  	return USBC->USBCON & USBC_USBCON_FRZCLK;
  }
  
  static ALWAYS_INLINE void usb_dc_sam_usbc_freeze_clk(void)
  {
  	USBC->USBCON |= USBC_USBCON_FRZCLK;
  }
  
  static ALWAYS_INLINE void usb_dc_sam_usbc_unfreeze_clk(void)
  {
  	USBC->USBCON &= ~USBC_USBCON_FRZCLK;
  
  	while (USBC->USBCON & USBC_USBCON_FRZCLK) {
  		;
  	};
  }
  
  static uint8_t usb_dc_sam_usbc_ep_curr_bank(uint8_t ep_idx)
  {
  	uint8_t idx = ep_idx * 2;
  
  	if ((ep_idx > 0) &&
  	    (regs->UESTA[ep_idx] & USBC_UESTA0_CURRBK(1)) > 0) {
  		idx++;
  	}
  
  	return idx;
  }
  
  static bool usb_dc_is_attached(void)
  {
  	return (regs->UDCON & USBC_UDCON_DETACH) == 0;
  }
  
  static bool usb_dc_ep_is_enabled(uint8_t ep_idx)
  {
  	int reg = regs->UERST;
  
  	return (reg & BIT(USBC_UERST_EPEN0_Pos + ep_idx));
  }
  
  static int usb_dc_sam_usbc_ep_alloc_buf(int ep_idx)
  {
  	struct sam_usbc_desc_table *ep_desc_bk;
  	bool ep_enabled[NUM_OF_EP_MAX];
  	int desc_mem_alloc;
  	int mps;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		return -EINVAL;
  	}
  
  	desc_mem_alloc = 0;
  
  	mps = dev_data.ep_data[ep_idx].mps_x2
  	    ? dev_data.ep_data[ep_idx].mps * 2
  	    : dev_data.ep_data[ep_idx].mps;
  
  	/* Check if there are memory to all endpoints */
  	for (int i = 0; i < NUM_OF_EP_MAX; i++) {
  		if (!dev_data.ep_data[i].is_configured || i == ep_idx) {
  			continue;
  		}
  
  		desc_mem_alloc += dev_data.ep_data[i].mps_x2
  				? dev_data.ep_data[i].mps * 2
  				: dev_data.ep_data[i].mps;
  	}
  
  	if ((desc_mem_alloc + mps) > USBC_RAM_SIZE) {
  		memset(&dev_data.ep_data[ep_idx], 0,
  		       sizeof(struct usb_device_ep_data));
  		return -ENOMEM;
  	}
  
  	for (int i = NUM_OF_EP_MAX - 1; i >= ep_idx; i--) {
  		ep_enabled[i] = usb_dc_ep_is_enabled(i);
  		if (ep_enabled[i]) {
  			usb_dc_ep_disable(i);
  		}
  	}
  
  	desc_mem_alloc = 0U;
  	for (int i = 0; i < ep_idx; i++) {
  		if (!dev_data.ep_data[i].is_configured) {
  			continue;
  		}
  
  		desc_mem_alloc += dev_data.ep_data[i].mps_x2
  				? dev_data.ep_data[i].mps * 2
  				: dev_data.ep_data[i].mps;
  	}
  
  	ep_desc_bk = ((struct sam_usbc_desc_table *) &dev_desc)
  		   + (ep_idx * 2);
  	for (int i = ep_idx; i < NUM_OF_EP_MAX; i++) {
  		if (!dev_data.ep_data[i].is_configured && (i != ep_idx)) {
  			ep_desc_bk += 2;
  			continue;
  		}
  
  		/* Alloc bank 0 */
  		ep_desc_bk->ep_pipe_addr = ((uint8_t *) USBC_RAM_ADDR)
  					 + desc_mem_alloc;
  		ep_desc_bk->sizes = 0;
  		ep_desc_bk->bk_ctrl_stat = 0;
  		ep_desc_bk->ep_ctrl_stat = 0;
  		ep_desc_bk++;
  
  		/**
  		 * Alloc bank 1
  		 *
  		 * if dual bank,
  		 * then ep_pipe_addr[1] = ep_pipe_addr[0] address + mps size
  		 * else ep_pipe_addr[1] = ep_pipe_addr[0] address
  		 */
  		ep_desc_bk->ep_pipe_addr = ((uint8_t *) USBC_RAM_ADDR)
  					 + desc_mem_alloc
  					 + (dev_data.ep_data[i].mps_x2
  					 ?  dev_data.ep_data[i].mps
  					 :  0);
  		ep_desc_bk->sizes = 0;
  		ep_desc_bk->bk_ctrl_stat = 0;
  		ep_desc_bk->ep_ctrl_stat = 0;
  		ep_desc_bk++;
  
  		desc_mem_alloc += dev_data.ep_data[i].mps_x2
  				? dev_data.ep_data[i].mps * 2
  				: dev_data.ep_data[i].mps;
  	}
  
  	ep_enabled[ep_idx] = false;
  	for (int i = ep_idx; i < NUM_OF_EP_MAX; i++) {
  		if (ep_enabled[i]) {
  			usb_dc_ep_enable(i);
  		}
  	}
  	return 0;
  }
  
  static void usb_dc_ep_enable_interrupts(uint8_t ep_idx)
  {
  	if (ep_idx == 0U) {
  		/* Control endpoint: enable SETUP */
  		regs->UECONSET[ep_idx] = USBC_UECON0SET_RXSTPES;
  	} else if (regs->UECFG[ep_idx] & USBC_UECFG0_EPDIR_IN) {
  		/* TX - IN direction: acknowledge FIFO empty interrupt */
  		regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_TXINIC;
  		regs->UECONSET[ep_idx] = USBC_UECON0SET_TXINES;
  	} else {
  		/* RX - OUT direction */
  		regs->UECONSET[ep_idx] = USBC_UECON0SET_RXOUTES;
  	}
  }
  
  static void usb_dc_ep_isr_sta(uint8_t ep_idx)
  {
  	uint32_t sr = regs->UESTA[ep_idx];
  
  	usb_dc_sam_usbc_isr_sta_dbg(ep_idx, sr);
  
  	if (sr & USBC_UESTA0_RAMACERI) {
  		regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_RAMACERIC;
  		LOG_ERR("ISR: EP%d RAM Access Error", ep_idx);
  	}
  }
  
  static void usb_dc_ctrl_init(void)
  {
  	LOG_INF("STP - INIT");
  
  	/* In case of abort of IN Data Phase:
  	 * No need to abort IN transfer (rise TXINI),
  	 * because it is automatically done by hardware when a Setup packet is
  	 * received. But the interrupt must be disabled to don't generate
  	 * interrupt TXINI after SETUP reception.
  	 */
  	regs->UECONCLR[0] = USBC_UECON0CLR_TXINEC;
  
  	/* In case of OUT ZLP event is no processed before Setup event occurs */
  	regs->UESTACLR[0] = USBC_UESTA0CLR_RXOUTIC;
  	regs->UECONCLR[0] = USBC_UECON0CLR_RXOUTEC
  			  | USBC_UECON0CLR_NAKOUTEC
  			  | USBC_UECON0CLR_NAKINEC;
  
  	epctrl_fsm = USB_EPCTRL_SETUP;
  }
  
  static void usb_dc_ctrl_stall_data(uint32_t flags)
  {
  	LOG_INF("STP - STALL");
  
  	epctrl_fsm = USB_EPCTRL_STALL_REQ;
  
  	regs->UECONSET[0] = USBC_UECON0SET_STALLRQS;
  	regs->UESTACLR[0] = flags;
  }
  
  static void usb_dc_ctrl_send_zlp_in(void)
  {
  	uint32_t key;
  
  	LOG_INF("STP - ZLP IN");
  
  	epctrl_fsm = USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP;
  
  	/* Validate and send empty IN packet on control endpoint */
  	dev_desc[0].sizes = 0;
  
  	key = irq_lock();
  
  	/* Send ZLP on IN endpoint */
  	regs->UESTACLR[0] = USBC_UESTA0CLR_TXINIC;
  	regs->UECONSET[0] = USBC_UECON0SET_TXINES;
  
  	/* To detect a protocol error, enable nak interrupt on data OUT phase */
  	regs->UESTACLR[0] = USBC_UESTA0CLR_NAKOUTIC;
  	regs->UECONSET[0] = USBC_UECON0SET_NAKOUTES;
  	irq_unlock(key);
  }
  
  static void usb_dc_ctrl_send_zlp_out(void)
  {
  	uint32_t key;
  
  	LOG_INF("STP - ZLP OUT");
  
  	epctrl_fsm = USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP;
  
  	/* To detect a protocol error, enable nak interrupt on data IN phase */
  	key = irq_lock();
  	regs->UESTACLR[0] = USBC_UESTA0CLR_NAKINIC;
  	regs->UECONSET[0] = USBC_UECON0SET_NAKINES;
  	irq_unlock(key);
  }
  
  static void usb_dc_ep0_isr(void)
  {
  	uint32_t sr = regs->UESTA[0];
  	uint32_t dev_ctrl = regs->UDCON;
  
  	usb_dc_ep_isr_sta(0);
  
  	regs->UECONCLR[0] = USBC_UECON0CLR_NAKINEC;
  	regs->UECONCLR[0] = USBC_UECON0CLR_NAKOUTEC;
  
  	if (sr & USBC_UESTA0_RXSTPI) {
  		/* May be a hidden DATA or ZLP phase or protocol abort */
  		if (epctrl_fsm != USB_EPCTRL_SETUP) {
  			/* Reinitializes control endpoint management */
  			usb_dc_ctrl_init();
  		}
  
  		/* SETUP data received */
  		dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_SETUP);
  		return;
  	}
  
  	if (sr & USBC_UESTA0_RXOUTI) {
  		LOG_DBG("RXOUT= fsm: %s",
  			usb_dc_epctrl_state_string[epctrl_fsm]);
  
  		if (epctrl_fsm != USB_EPCTRL_DATA_OUT) {
  			if ((epctrl_fsm == USB_EPCTRL_DATA_IN)
  			||  (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP)) {
  				/* End of SETUP request:
  				 * - Data IN Phase aborted,
  				 * - or last Data IN Phase hidden by ZLP OUT
  				 *   sending quickly,
  				 * - or ZLP OUT received normally.
  				 *
  				 * Nothing to do
  				 */
  			} else {
  				/* Protocol error during SETUP request */
  				usb_dc_ctrl_stall_data(0);
  			}
  
  			usb_dc_ctrl_init();
  			return;
  		}
  
  		/* OUT (to device) data received */
  		dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_DATA_OUT);
  		return;
  	}
  
  	if ((sr & USBC_UESTA0_TXINI) &&
  	    (regs->UECON[0] & USBC_UECON0_TXINE)) {
  		LOG_DBG("TXINI= fsm: %s",
  			usb_dc_epctrl_state_string[epctrl_fsm]);
  
  		regs->UECONCLR[0] = USBC_UECON0CLR_TXINEC;
  
  		if (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP) {
  			if (!(dev_ctrl & USBC_UDCON_ADDEN)
  			&&   (dev_ctrl & USBC_UDCON_UADD_Msk) != 0U) {
  				/* Commit the pending address update.  This
  				 * must be done after the ack to the host
  				 * completes else the ack will get dropped.
  				 */
  				regs->UDCON |= USBC_UDCON_ADDEN;
  			}
  
  			/* ZLP on IN is sent */
  			usb_dc_ctrl_init();
  			return;
  		}
  
  		/* IN (to host) transmit complete */
  		dev_data.ep_data[0].cb_in(USB_EP_DIR_IN, USB_DC_EP_DATA_IN);
  		return;
  	}
  
  	if (sr & USBC_UESTA0_NAKOUTI) {
  		LOG_DBG("NAKOUT= fsm: %s",
  			usb_dc_epctrl_state_string[epctrl_fsm]);
  
  		regs->UESTACLR[0] = USBC_UESTA0CLR_NAKOUTIC;
  
  		if (regs->UESTA[0] & USBC_UESTA0_TXINI) {
  			/** overflow ignored if IN data is received */
  			return;
  		}
  
  		if (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP) {
  			/* A IN handshake is waiting by device, but host want
  			 * extra OUT data then stall extra OUT data
  			 */
  			regs->UECONSET[0] = USBC_UECON0SET_STALLRQS;
  		}
  		return;
  	}
  
  	if (sr & USBC_UESTA0_NAKINI) {
  		LOG_DBG("NAKIN= fsm: %s",
  			usb_dc_epctrl_state_string[epctrl_fsm]);
  
  		regs->UESTACLR[0] = USBC_UESTA0CLR_NAKINIC;
  
  		if (regs->UESTA[0] & USBC_UESTA0_RXOUTI) {
  			/** underflow ignored if OUT data is received */
  			return;
  		}
  
  		if (epctrl_fsm == USB_EPCTRL_DATA_OUT) {
  			/* Host want to stop OUT transaction then stop to
  			 * wait OUT data phase and wait IN ZLP handshake.
  			 */
  			usb_dc_ctrl_send_zlp_in();
  		} else if (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP) {
  			/* A OUT handshake is waiting by device, but host want
  			 * extra IN data then stall extra IN data.
  			 */
  			regs->UECONSET[0] = USBC_UECON0SET_STALLRQS;
  		} else {
  			/** Nothing to do */
  		}
  		return;
  	}
  }
  
  static void usb_dc_ep_isr(uint8_t ep_idx)
  {
  	uint32_t sr = regs->UESTA[ep_idx];
  
  	usb_dc_ep_isr_sta(ep_idx);
  
  	if (sr & USBC_UESTA0_RXOUTI) {
  		uint8_t ep = ep_idx | USB_EP_DIR_OUT;
  
  		regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_RXOUTIC;
  
  		/* OUT (to device) data received */
  		dev_data.ep_data[ep_idx].cb_out(ep, USB_DC_EP_DATA_OUT);
  	}
  	if (sr & USBC_UESTA0_TXINI) {
  		uint8_t ep = ep_idx | USB_EP_DIR_IN;
  
  		regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_TXINIC;
  
  		/* IN (to host) transmit complete */
  		dev_data.ep_data[ep_idx].cb_in(ep, USB_DC_EP_DATA_IN);
  	}
  }
  
  static void usb_dc_sam_usbc_isr(void)
  {
  	uint32_t sr = regs->UDINT;
  
  	if (IS_ENABLED(CONFIG_USB_DEVICE_SOF)) {
  		/* SOF interrupt */
  		if (sr & USBC_UDINT_SOF) {
  			/* Acknowledge the interrupt */
  			regs->UDINTCLR = USBC_UDINTCLR_SOFC;
  
  			dev_data.status_cb(USB_DC_SOF, NULL);
  
  			goto usb_dc_sam_usbc_isr_barrier;
  		}
  	}
  
  	/* EP0 endpoint interrupt */
  	if (sr & USBC_UDINT_EP0INT) {
  		usb_dc_ep0_isr();
  
  		goto usb_dc_sam_usbc_isr_barrier;
  	}
  
  	/* Other endpoints interrupt */
  	if (sr & EP_UDINT_MASK) {
  		for (int ep_idx = 1; ep_idx < NUM_OF_EP_MAX; ep_idx++) {
  			if (sr & (USBC_UDINT_EP0INT << ep_idx)) {
  				usb_dc_ep_isr(ep_idx);
  			}
  		}
  
  		goto usb_dc_sam_usbc_isr_barrier;
  	}
  
  	/* End of resume interrupt */
  	if (sr & USBC_UDINT_EORSM) {
  		LOG_DBG("ISR: End Of Resume");
  
  		regs->UDINTCLR = USBC_UDINTCLR_EORSMC;
  
  		dev_data.status_cb(USB_DC_RESUME, NULL);
  
  		goto usb_dc_sam_usbc_isr_barrier;
  	}
  
  	/* End of reset interrupt */
  	if (sr & USBC_UDINT_EORST) {
  		LOG_DBG("ISR: End Of Reset");
  
  		regs->UDINTCLR = USBC_UDINTCLR_EORSTC;
  
  		if (usb_dc_ep_is_enabled(0)) {
  			/* The device clears some of the configuration of EP0
  			 * when it receives the EORST. Re-enable interrupts.
  			 */
  			usb_dc_ep_enable_interrupts(0);
  			usb_dc_ctrl_init();
  		}
  
  		dev_data.status_cb(USB_DC_RESET, NULL);
  
  		usb_dc_sam_usbc_clean_sta_dbg();
  
  		goto usb_dc_sam_usbc_isr_barrier;
  	}
  
  	/* Suspend interrupt */
  	if (sr & USBC_UDINT_SUSP && regs->UDINTE & USBC_UDINTE_SUSPE) {
  		LOG_DBG("ISR: Suspend");
  
  		regs->UDINTCLR = USBC_UDINTCLR_SUSPC;
  
  		usb_dc_sam_usbc_unfreeze_clk();
  
  		/**
  		 * Sync Generic Clock
  		 * Check USB clock ready after suspend and
  		 * eventually sleep USB clock
  		 */
  		while ((regs->USBSTA & USBC_USBSTA_CLKUSABLE) == 0) {
  			;
  		};
  
  		regs->UDINTECLR = USBC_UDINTECLR_SUSPEC;
  		regs->UDINTCLR = USBC_UDINTCLR_WAKEUPC;
  		regs->UDINTESET = USBC_UDINTESET_WAKEUPES;
  
  		usb_dc_sam_usbc_freeze_clk();
  
  		dev_data.status_cb(USB_DC_SUSPEND, NULL);
  
  		goto usb_dc_sam_usbc_isr_barrier;
  	}
  
  	/* Wakeup interrupt */
  	if (sr & USBC_UDINT_WAKEUP && regs->UDINTE & USBC_UDINTE_WAKEUPE) {
  		LOG_DBG("ISR: Wake Up");
  
  		regs->UDINTCLR = USBC_UDINTCLR_WAKEUPC;
  
  		usb_dc_sam_usbc_unfreeze_clk();
  
  		/**
  		 * Sync Generic Clock
  		 * Check USB clock ready after suspend and
  		 * eventually sleep USB clock
  		 */
  		while ((regs->USBSTA & USBC_USBSTA_CLKUSABLE) == 0) {
  			;
  		};
  
  		regs->UDINTECLR = USBC_UDINTECLR_WAKEUPEC;
  		regs->UDINTCLR = USBC_UDINTCLR_SUSPC;
  		regs->UDINTESET = USBC_UDINTESET_SUSPES;
  	}
  
  usb_dc_sam_usbc_isr_barrier:
  	__DMB();
  }
  
  int usb_dc_attach(void)
  {
  	uint32_t pmcon;
  	uint32_t regval;
  	uint32_t key = irq_lock();
  	int retval;
  
  	/* Enable USBC asynchronous wake-up source */
  	PM->AWEN |= BIT(PM_AWEN_USBC);
  
  	/* Always authorize asynchronous USB interrupts to exit of sleep mode
  	 * For SAM USB wake up device except BACKUP mode
  	 */
  	pmcon = BPM->PMCON | BPM_PMCON_FASTWKUP;
  	BPM->UNLOCK = BPM_UNLOCK_KEY(0xAAu)
  		    | BPM_UNLOCK_ADDR((uint32_t)&BPM->PMCON - (uint32_t)BPM);
  	BPM->PMCON = pmcon;
  
  	/* Start the peripheral clock PBB & DATA */
  	soc_pmc_peripheral_enable(
  		PM_CLOCK_MASK(PM_CLK_GRP_PBB, SYSCLK_USBC_REGS));
  	soc_pmc_peripheral_enable(
  		PM_CLOCK_MASK(PM_CLK_GRP_HSB, SYSCLK_USBC_DATA));
  
  	/* Enable USB Generic clock */
  	SCIF->GCCTRL[GEN_CLK_USBC] = 0;
  	SCIF->GCCTRL[GEN_CLK_USBC] = SCIF_GCCTRL_OSCSEL(SCIF_GC_USES_CLK_HSB)
  				   | SCIF_GCCTRL_CEN;
  
  	/* Sync Generic Clock */
  	while ((regs->USBSTA & USBC_USBSTA_CLKUSABLE) == 0) {
  		;
  	};
  
  	retval = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT);
  	if (retval < 0) {
  		return retval;
  	}
  
  	/* Enable the USB controller in device mode with the clock unfrozen */
  	regs->USBCON = USBC_USBCON_UIMOD | USBC_USBCON_USBE;
  
  	usb_dc_sam_usbc_unfreeze_clk();
  
  	regs->UDESC = USBC_UDESC_UDESCA((int) &dev_desc);
  
  	/* Select the speed with pads detached */
  	regval = USBC_UDCON_DETACH;
  
  	switch (DT_INST_ENUM_IDX(0, maximum_speed)) {
  	case 1:
  		WRITE_BIT(regval, USBC_UDCON_LS_Pos, 0);
  		break;
  	case 0:
  		WRITE_BIT(regval, USBC_UDCON_LS_Pos, 1);
  		break;
  	default:
  		WRITE_BIT(regval, USBC_UDCON_LS_Pos, 0);
  		LOG_WRN("Unsupported maximum speed defined in device tree. "
  			"USB controller will default to its maximum HW "
  			"capability");
  	}
  
  	regs->UDCON = regval;
  
  	/* Enable device interrupts
  	 *  EORSM	End of Resume Interrupt
  	 *  SOF		Start of Frame Interrupt
  	 *  EORST	End of Reset Interrupt
  	 *  SUSP	Suspend Interrupt
  	 *  WAKEUP	Wake-Up Interrupt
  	 */
  	regs->UDINTCLR = USBC_UDINTCLR_EORSMC
  		       | USBC_UDINTCLR_EORSTC
  		       | USBC_UDINTCLR_SOFC
  		       | USBC_UDINTCLR_SUSPC
  		       | USBC_UDINTCLR_WAKEUPC;
  
  	regs->UDINTESET = USBC_UDINTESET_EORSMES
  			| USBC_UDINTESET_EORSTES
  			| USBC_UDINTESET_SUSPES
  			| USBC_UDINTESET_WAKEUPES;
  
  	if (IS_ENABLED(CONFIG_USB_DEVICE_SOF)) {
  		regs->UDINTESET |= USBC_UDINTESET_SOFES;
  	}
  
  	IRQ_CONNECT(DT_INST_IRQN(0),
  		    DT_INST_IRQ(0, priority),
  		    usb_dc_sam_usbc_isr, 0, 0);
  	irq_enable(DT_INST_IRQN(0));
  
  	/* Attach the device */
  	regs->UDCON &= ~USBC_UDCON_DETACH;
  
  	/* Put USB on low power state (wait Susp/Wake int) */
  	usb_dc_sam_usbc_freeze_clk();
  
  	/* Force Susp 2 Wake transition */
  	regs->UDINTSET = USBC_UDINTSET_SUSPS;
  
  	irq_unlock(key);
  
  	LOG_DBG("USB DC attach");
  	return 0;
  }
  
  int usb_dc_detach(void)
  {
  	uint32_t key = irq_lock();
  
  	regs->UDCON |= USBC_UDCON_DETACH;
  
  	/* Disable the USB controller and freeze the clock */
  	regs->USBCON = USBC_USBCON_UIMOD | USBC_USBCON_FRZCLK;
  
  	/* Disable USB Generic clock */
  	SCIF->GCCTRL[GEN_CLK_USBC] = 0;
  
  	/* Disable USBC asynchronous wake-up source */
  	PM->AWEN &= ~(BIT(PM_AWEN_USBC));
  
  	/* Disable the peripheral clock HSB & PBB */
  	soc_pmc_peripheral_enable(
  		PM_CLOCK_MASK(PM_CLK_GRP_HSB, SYSCLK_USBC_DATA));
  	soc_pmc_peripheral_enable(
  		PM_CLOCK_MASK(PM_CLK_GRP_PBB, SYSCLK_USBC_REGS));
  
  	irq_disable(DT_INST_IRQN(0));
  	irq_unlock(key);
  
  	LOG_DBG("USB DC detach");
  	return 0;
  }
  
  int usb_dc_reset(void)
  {
  	uint32_t key = irq_lock();
  
  	/* Reset the controller */
  	regs->USBCON = USBC_USBCON_UIMOD | USBC_USBCON_FRZCLK;
  
  	/* Clear private data */
  	(void)memset(&dev_data, 0, sizeof(dev_data));
  	(void)memset(&dev_desc, 0, sizeof(dev_desc));
  
  	irq_unlock(key);
  
  	LOG_DBG("USB DC reset");
  	return 0;
  }
  
  int usb_dc_set_address(uint8_t addr)
  {
  	/*
  	 * Set the address but keep it disabled for now. It should be enabled
  	 * only after the ack to the host completes.
  	 */
  	regs->UDCON &= ~USBC_UDCON_ADDEN;
  	regs->UDCON |= USBC_UDCON_UADD(addr);
  
  	LOG_DBG("USB DC set address 0x%02x", addr);
  	return 0;
  }
  
  void usb_dc_set_status_callback(const usb_dc_status_callback cb)
  {
  	regs->UDINTECLR = USBC_UDINTECLR_MASK;
  	regs->UDINTCLR = USBC_UDINTCLR_MASK;
  
  	usb_dc_detach();
  	usb_dc_reset();
  
  	dev_data.status_cb = cb;
  
  	LOG_DBG("USB DC set callback");
  }
  
  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);
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("endpoint index/address out of range");
  		return -EINVAL;
  	}
  
  	if (ep_idx == 0U) {
  		if (cfg->ep_type != USB_DC_EP_CONTROL) {
  			LOG_ERR("pre-selected as control endpoint");
  			return -EINVAL;
  		}
  	} else if (ep_idx & BIT(0)) {
  		if (USB_EP_DIR_IS_OUT(cfg->ep_addr)) {
  			LOG_INF("pre-selected as IN endpoint");
  			return -EINVAL;
  		}
  	} else {
  		if (USB_EP_DIR_IS_IN(cfg->ep_addr)) {
  			LOG_INF("pre-selected as OUT endpoint");
  			return -EINVAL;
  		}
  	}
  
  	if (cfg->ep_mps < 1 || cfg->ep_mps > 1024 ||
  	    (cfg->ep_type == USB_DC_EP_CONTROL && cfg->ep_mps > 64)) {
  		LOG_ERR("invalid endpoint size");
  		return -EINVAL;
  	}
  	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);
  	uint32_t regval = 0U;
  	int log2ceil_mps;
  
  	if (usb_dc_ep_check_cap(cfg) != 0) {
  		return -EINVAL;
  	}
  
  	if (!usb_dc_is_attached()) {
  		LOG_ERR("device not attached");
  		return -ENODEV;
  	}
  
  	/* Allow re-configure any endpoint */
  	if (usb_dc_ep_is_enabled(ep_idx)) {
  		usb_dc_ep_disable(ep_idx);
  	}
  
  	LOG_DBG("Configure ep 0x%02x, mps %d, type %d",
  		cfg->ep_addr, cfg->ep_mps, cfg->ep_type);
  
  	switch (cfg->ep_type) {
  	case USB_DC_EP_CONTROL:
  		regval |= USBC_UECFG0_EPTYPE_CONTROL;
  		break;
  	case USB_DC_EP_ISOCHRONOUS:
  		regval |= USBC_UECFG0_EPTYPE_ISOCHRONOUS;
  		break;
  	case USB_DC_EP_BULK:
  		regval |= USBC_UECFG0_EPTYPE_BULK;
  		break;
  	case USB_DC_EP_INTERRUPT:
  		regval |= USBC_UECFG0_EPTYPE_INTERRUPT;
  		break;
  	default:
  		return -EINVAL;
  	}
  
  	if (USB_EP_DIR_IS_OUT(cfg->ep_addr) ||
  	    cfg->ep_type == USB_DC_EP_CONTROL) {
  		regval |= USBC_UECFG0_EPDIR_OUT;
  	} else {
  		regval |= USBC_UECFG0_EPDIR_IN;
  	}
  
  	/*
  	 * Map the endpoint size to the buffer size. Only power of 2 buffer
  	 * sizes between 8 and 1024 are possible, get the next power of 2.
  	 */
  	log2ceil_mps = 32 - __builtin_clz((MAX(cfg->ep_mps, 8) << 1) - 1) - 1;
  	regval |= USBC_UECFG0_EPSIZE(log2ceil_mps - 3);
  	dev_data.ep_data[ep_idx].mps = cfg->ep_mps;
  
  	/* Use double bank buffering for: ISOCHRONOUS, BULK and INTERRUPT */
  	if (cfg->ep_type != USB_DC_EP_CONTROL) {
  		regval |= USBC_UECFG0_EPBK_DOUBLE;
  		dev_data.ep_data[ep_idx].mps_x2 = true;
  	} else {
  		regval |= USBC_UECFG0_EPBK_SINGLE;
  		dev_data.ep_data[ep_idx].mps_x2 = false;
  	}
  
  	/** Enable Global NAK */
  	regs->UDCON |= USBC_UDCON_GNAK;
  	if (usb_dc_sam_usbc_ep_alloc_buf(ep_idx) < 0) {
  		dev_data.ep_data[ep_idx].is_configured = false;
  		regs->UDCON &= ~USBC_UDCON_GNAK;
  		return -ENOMEM;
  	}
  	regs->UDCON &= ~USBC_UDCON_GNAK;
  
  	/* Configure the endpoint */
  	dev_data.ep_data[ep_idx].is_configured = true;
  	regs->UECFG[ep_idx] = regval;
  
  	LOG_DBG("ep 0x%02x configured", cfg->ep_addr);
  	return 0;
  }
  
  int usb_dc_ep_set_stall(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (ep_idx == 0) {
  		if (epctrl_fsm == USB_EPCTRL_SETUP) {
  			usb_dc_ctrl_stall_data(USBC_UESTA0CLR_RXSTPIC);
  		} else if (epctrl_fsm == USB_EPCTRL_DATA_OUT) {
  			usb_dc_ctrl_stall_data(USBC_UESTA0CLR_RXOUTIC);
  		} else {
  			/** Stall without commit any status */
  			usb_dc_ctrl_stall_data(0);
  		}
  	} else {
  		regs->UECONSET[ep_idx] = USBC_UECON0SET_STALLRQS;
  	}
  
  	LOG_WRN("USB DC stall set ep 0x%02x", ep);
  	return 0;
  }
  
  int usb_dc_ep_clear_stall(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  	uint32_t key;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (regs->UECON[ep_idx] & USBC_UECON0_STALLRQ) {
  		key = irq_lock();
  
  		dev_data.ep_data[ep_idx].out_at = 0U;
  
  		regs->UECONCLR[ep_idx] = USBC_UECON0CLR_STALLRQC;
  		if (regs->UESTA[ep_idx] & USBC_UESTA0_STALLEDI) {
  			regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_STALLEDIC;
  			regs->UECONSET[ep_idx] = USBC_UECON0SET_RSTDTS;
  		}
  
  		irq_unlock(key);
  	}
  
  	LOG_DBG("USB DC stall clear ep 0x%02x", ep);
  	return 0;
  }
  
  int usb_dc_ep_is_stalled(uint8_t ep, uint8_t *stalled)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (!stalled) {
  		return -EINVAL;
  	}
  
  	*stalled = ((regs->UECON[ep_idx] & USBC_UECON0_STALLRQ) != 0);
  
  	LOG_DBG("USB DC stall check ep 0x%02x stalled: %d", ep, *stalled);
  	return 0;
  }
  
  int usb_dc_ep_halt(uint8_t ep)
  {
  	return usb_dc_ep_set_stall(ep);
  }
  
  int usb_dc_ep_enable(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  	uint32_t key;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (!dev_data.ep_data[ep_idx].is_configured) {
  		LOG_ERR("endpoint not configured");
  		return -ENODEV;
  	}
  
  	key = irq_lock();
  	dev_data.ep_data[ep_idx].out_at = 0U;
  
  	/* Enable endpoint */
  	regs->UERST |= BIT(USBC_UERST_EPEN0_Pos + ep_idx);
  	/* Enable global endpoint interrupts */
  	regs->UDINTESET = (USBC_UDINTESET_EP0INTES << ep_idx);
  
  	usb_dc_ep_enable_interrupts(ep_idx);
  	irq_unlock(key);
  
  	LOG_DBG("Enable ep 0x%02x", ep);
  	return 0;
  }
  
  int usb_dc_ep_disable(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  	uint32_t key;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	key = irq_lock();
  
  	/* Disable global endpoint interrupt */
  	regs->UDINTECLR = BIT(USBC_UDINTESET_EP0INTES_Pos + ep_idx);
  
  	/* Disable endpoint and reset */
  	regs->UERST &= ~BIT(USBC_UERST_EPEN0_Pos + ep_idx);
  
  	irq_unlock(key);
  
  	LOG_DBG("Disable ep 0x%02x", ep);
  	return 0;
  }
  
  int usb_dc_ep_flush(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  	uint32_t key;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (!usb_dc_ep_is_enabled(ep_idx)) {
  		LOG_ERR("endpoint not enabled");
  		return -ENODEV;
  	}
  
  	key = irq_lock();
  
  	/* Disable the IN interrupt */
  	regs->UECONCLR[ep_idx] = USBC_UECON0CLR_TXINEC;
  
  	/* Reset the endpoint */
  	regs->UERST &= ~(BIT(ep_idx));
  	regs->UERST |= BIT(ep_idx);
  
  	dev_data.ep_data[ep_idx].out_at = 0U;
  
  	/* Reenable interrupts */
  	usb_dc_ep_enable_interrupts(ep_idx);
  
  	irq_unlock(key);
  
  	LOG_DBG("ep 0x%02x flushed", ep);
  	return 0;
  }
  
  int usb_dc_ep_set_callback(uint8_t ep, const usb_dc_ep_callback cb)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (USB_EP_DIR_IS_IN(ep)) {
  		dev_data.ep_data[ep_idx].cb_in = cb;
  	} else {
  		dev_data.ep_data[ep_idx].cb_out = cb;
  	}
  
  	LOG_DBG("set ep 0x%02x %s callback", ep,
  		USB_EP_DIR_IS_IN(ep) ? "IN" : "OUT");
  	return 0;
  }
  
  static int usb_dc_ep_write_stp(uint8_t ep_bank, const uint8_t *data,
  			       uint32_t packet_len)
  {
  	uint32_t key;
  
  	if (epctrl_fsm == USB_EPCTRL_SETUP) {
  		regs->UESTACLR[0] = USBC_UESTA0CLR_RXSTPIC;
  
  		epctrl_fsm = USB_EPCTRL_DATA_IN;
  
  		key = irq_lock();
  		regs->UECONCLR[0] = USBC_UECON0CLR_TXINEC;
  		irq_unlock(key);
  	}
  
  	if (epctrl_fsm == USB_EPCTRL_DATA_IN) {
  		/* All data requested are transferred or a short packet has
  		 * been sent then it is the end of data phase.
  		 *
  		 * Generate an OUT ZLP for handshake phase.
  		 */
  		if (packet_len == 0) {
  			usb_dc_ctrl_send_zlp_out();
  			return 0;
  		}
  
  		/** Critical section
  		 * Only in case of DATA IN phase abort without USB Reset
  		 * signal after.  The IN data don't must be written in
  		 * endpoint 0 DPRAM during a next setup reception in same
  		 * endpoint 0 DPRAM.  Thereby, an OUT ZLP reception must
  		 * check before IN data write and if no OUT ZLP is received
  		 * the data must be written quickly (800us) before an
  		 * eventually ZLP OUT and SETUP reception.
  		 */
  		key = irq_lock();
  
  		if (regs->UESTA[0] & USBC_UESTA0_RXOUTI) {
  
  			/* IN DATA phase aborted by OUT ZLP */
  			irq_unlock(key);
  
  			epctrl_fsm = USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP;
  			return 0;
  		}
  
  		if (data) {
  			memcpy(dev_desc[ep_bank].ep_pipe_addr,
  			       data, packet_len);
  			__DSB();
  		}
  		dev_desc[ep_bank].sizes = packet_len;
  
  		/*
  		 * Control endpoint: clear the interrupt flag to send
  		 * the data, and re-enable the interrupts to trigger
  		 * an interrupt at the end of the transfer.
  		 */
  		regs->UESTACLR[0] = USBC_UESTA0CLR_TXINIC;
  		regs->UECONSET[0] = USBC_UECON0SET_TXINES;
  
  		/* In case of abort of DATA IN phase, no need to enable
  		 * nak OUT interrupt because OUT endpoint is already
  		 * free and ZLP OUT accepted.
  		 */
  		irq_unlock(key);
  	} else if (epctrl_fsm == USB_EPCTRL_DATA_OUT ||
  		   epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP) {
  		/* ZLP on IN is sent, then valid end of setup request
  		 * or
  		 * No data phase requested.
  		 *
  		 * Send IN ZLP to ACK setup request
  		 */
  		usb_dc_ctrl_send_zlp_in();
  	} else {
  		LOG_ERR("Invalid STP state %d on IN phase", epctrl_fsm);
  		return -EPERM;
  	}
  	return 0;
  }
  
  int usb_dc_ep_write(uint8_t ep, const uint8_t *data,
  		    uint32_t data_len, uint32_t *ret_bytes)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  	uint8_t ep_bank;
  	uint32_t packet_len;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (!usb_dc_ep_is_enabled(ep_idx)) {
  		LOG_ERR("endpoint not enabled");
  		return -ENODEV;
  	}
  
  	if (USB_EP_DIR_IS_OUT(ep)) {
  		LOG_ERR("wrong endpoint direction");
  		return -EINVAL;
  	}
  
  	if ((regs->UECON[ep_idx] & USBC_UECON0_STALLRQ) != 0) {
  		LOG_WRN("endpoint is stalled");
  		return -EBUSY;
  	}
  
  	/* Check if there is bank available */
  	if (ep_idx > 0) {
  		if ((regs->UECON[ep_idx] & USBC_UECON0_FIFOCON) == 0) {
  			return -EAGAIN;
  		}
  	}
  
  	ep_bank = usb_dc_sam_usbc_ep_curr_bank(ep_idx);
  
  	packet_len = MIN(data_len, dev_data.ep_data[ep_idx].mps);
  
  	if (ret_bytes) {
  		*ret_bytes = packet_len;
  	}
  
  	if (ep_idx == 0U) {
  		if (usb_dc_ep_write_stp(ep_bank, data, packet_len)) {
  			return -EPERM;
  		}
  	} else {
  		if (data && packet_len > 0) {
  			memcpy(dev_desc[ep_bank].ep_pipe_addr, data, packet_len);
  			__DSB();
  		}
  		dev_desc[ep_bank].sizes = packet_len;
  
  		/*
  		 * Other endpoint types: clear the FIFO control flag to send
  		 * the data.
  		 */
  		regs->UECONCLR[ep_idx] = USBC_UECON0CLR_FIFOCONC;
  	}
  
  	LOG_INF("ep 0x%02x write %d bytes from %d to bank %d%s",
  		ep, packet_len, data_len, ep_bank % 2,
  		packet_len == 0 ? " (ZLP)" : "");
  	return 0;
  }
  
  static int usb_dc_ep_read_ex_stp(uint32_t take, uint32_t wLength)
  {
  	uint32_t key;
  
  	if (epctrl_fsm == USB_EPCTRL_SETUP) {
  		if (regs->UESTA[0] & USBC_UESTA0_CTRLDIR) {
  			/** Do Nothing */
  		} else {
  			regs->UESTACLR[0] = USBC_UESTA0CLR_RXSTPIC;
  
  			epctrl_fsm = USB_EPCTRL_DATA_OUT;
  
  			if (wLength == 0) {
  				/* No data phase requested.
  				 * Send IN ZLP to ACK setup request
  				 *
  				 * This is send at usb_dc_ep_write()
  				 */
  				return 0;
  			}
  
  			regs->UECONSET[0] = USBC_UECON0SET_RXOUTES;
  
  			/* To detect a protocol error, enable nak
  			 * interrupt on data IN phase
  			 */
  			regs->UESTACLR[0] = USBC_UESTA0CLR_NAKINIC;
  			key = irq_lock();
  			regs->UECONSET[0] = USBC_UECON0SET_NAKINES;
  			irq_unlock(key);
  		}
  	} else if (epctrl_fsm == USB_EPCTRL_DATA_OUT) {
  		regs->UESTACLR[0] = USBC_UESTA0CLR_RXOUTIC;
  
  		if (take == 0) {
  			usb_dc_ctrl_send_zlp_in();
  		} else {
  			regs->UESTACLR[0] = USBC_UESTA0CLR_NAKINIC;
  			key = irq_lock();
  			regs->UECONSET[0] = USBC_UECON0SET_NAKINES;
  			irq_unlock(key);
  		}
  	} else {
  		LOG_ERR("Invalid STP state %d on OUT phase", epctrl_fsm);
  		return -EPERM;
  	}
  	return 0;
  }
  
  int usb_dc_ep_read_ex(uint8_t ep, uint8_t *data, uint32_t max_data_len,
  		      uint32_t *read_bytes, bool wait)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  	struct usb_setup_packet *setup;
  	uint8_t ep_bank;
  	uint32_t data_len;
  	uint32_t remaining;
  	uint32_t take;
  	int rc = 0;
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (!usb_dc_ep_is_enabled(ep_idx)) {
  		LOG_ERR("endpoint not enabled");
  		return -ENODEV;
  	}
  
  	if (USB_EP_DIR_IS_IN(ep)) {
  		LOG_ERR("wrong endpoint direction");
  		return -EINVAL;
  	}
  
  	if ((regs->UECON[ep_idx] & USBC_UECON0_STALLRQ) != 0) {
  		LOG_WRN("endpoint is stalled");
  		return -EBUSY;
  	}
  
  	ep_bank = usb_dc_sam_usbc_ep_curr_bank(ep_idx);
  	data_len = dev_desc[ep_bank].udesc_sizes.byte_count;
  
  	if (data == NULL) {
  		dev_data.ep_data[ep_idx].out_at = 0U;
  
  		if (read_bytes) {
  			*read_bytes = data_len;
  		}
  		return 0;
  	}
  
  	remaining = data_len - dev_data.ep_data[ep_idx].out_at;
  	take = MIN(max_data_len, remaining);
  	if (take) {
  		memcpy(data,
  		       (uint8_t *) dev_desc[ep_bank].ep_pipe_addr +
  		       dev_data.ep_data[ep_idx].out_at,
  		       take);
  		__DSB();
  	}
  
  	if (read_bytes) {
  		*read_bytes = take;
  	}
  
  	if (take == remaining || take == 0) {
  		if (!wait) {
  			dev_data.ep_data[ep_idx].out_at = 0U;
  
  			if (ep_idx == 0) {
  				setup = (struct usb_setup_packet *) data;
  				rc = usb_dc_ep_read_ex_stp(take,
  							   setup->wLength);
  			} else {
  				rc = usb_dc_ep_read_continue(ep);
  			}
  		}
  	} else {
  		dev_data.ep_data[ep_idx].out_at += take;
  	}
  
  	LOG_INF("ep 0x%02x read %d bytes from bank %d and %s",
  		ep, take, ep_bank % 2, wait ? "wait" : "NO wait");
  	return rc;
  }
  
  int usb_dc_ep_read_continue(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  
  	if (ep_idx == 0 || ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	if (!usb_dc_ep_is_enabled(ep_idx)) {
  		LOG_ERR("endpoint not enabled");
  		return -ENODEV;
  	}
  
  	if (USB_EP_DIR_IS_IN(ep)) {
  		LOG_ERR("wrong endpoint direction");
  		return -EINVAL;
  	}
  
  	regs->UECONCLR[ep_idx] = USBC_UECON0CLR_FIFOCONC;
  	return 0;
  }
  
  int usb_dc_ep_read(uint8_t ep, uint8_t *data, uint32_t max_data_len,
  		   uint32_t *read_bytes)
  {
  	return usb_dc_ep_read_ex(ep, data, max_data_len, read_bytes, false);
  }
  
  int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len,
  			uint32_t *read_bytes)
  {
  	return usb_dc_ep_read_ex(ep, data, max_data_len, read_bytes, true);
  }
  
  int usb_dc_ep_mps(uint8_t ep)
  {
  	uint8_t ep_idx = USB_EP_GET_IDX(ep);
  
  	if (ep_idx >= NUM_OF_EP_MAX) {
  		LOG_ERR("wrong endpoint index/address");
  		return -EINVAL;
  	}
  
  	return dev_data.ep_data[ep_idx].mps;
  }
  
  int usb_dc_wakeup_request(void)
  {
  	bool is_clk_frozen = usb_dc_sam_usbc_is_frozen_clk();
  
  	if (is_clk_frozen) {
  		usb_dc_sam_usbc_unfreeze_clk();
  	}
  
  	regs->UDCON |= USBC_UDCON_RMWKUP;
  
  	if (is_clk_frozen) {
  		usb_dc_sam_usbc_freeze_clk();
  	}
  	return 0;
  }
  