diff --git a/drivers/usb/common/usb_dwc2_hw.h b/drivers/usb/common/usb_dwc2_hw.h index e02ae81722474..30304eec545ff 100644 --- a/drivers/usb/common/usb_dwc2_hw.h +++ b/drivers/usb/common/usb_dwc2_hw.h @@ -40,6 +40,18 @@ struct usb_dwc2_out_ep { volatile uint32_t doepdmab; }; +/* HOST channer register block */ +struct usb_dwc2_host_chan { + volatile uint32_t hcchar; + volatile uint32_t hcsplt; + volatile uint32_t hcint; + volatile uint32_t hcintmsk; + volatile uint32_t hctsiz; + volatile uint32_t hcdma; + volatile uint32_t reserved_0x18[1]; + volatile uint32_t hcdmab; +}; + /* DWC2 register map * TODO: This should probably be split into global, host, and device register * blocks @@ -80,8 +92,20 @@ struct usb_dwc2_reg { volatile uint32_t dieptxf[15]; }; volatile uint32_t reserved2[176]; - /* Host mode register 0x0400 .. 0x0670 */ - uint32_t reserved3[256]; + /* Host mode register 0x0400 .. 0x07FF */ + volatile uint32_t hcfg; + volatile uint32_t hfir; + volatile uint32_t hfnum; + volatile uint32_t reserved_0x40c[1]; + volatile uint32_t hptxsts; + volatile uint32_t haint; + volatile uint32_t haintmsk; + volatile uint32_t hflbaddr; + volatile uint32_t reserved_0x420_0x43c[8]; + volatile uint32_t hprt; + volatile uint32_t reserved_0x0444_0x04fc[47]; + struct usb_dwc2_host_chan host_chan_regs[16]; + volatile uint32_t reserved_0x0704_0x07fc[64]; /* Device mode register 0x0800 .. 0x0D00 */ volatile uint32_t dcfg; volatile uint32_t dctl; @@ -191,6 +215,17 @@ USB_DWC2_GET_FIELD_DEFINE(gahbcfg_hbstlen, GAHBCFG_HBSTLEN) #define USB_DWC2_GUSBCFG_FORCEDEVMODE BIT(USB_DWC2_GUSBCFG_FORCEDEVMODE_POS) #define USB_DWC2_GUSBCFG_FORCEHSTMODE_POS 29UL #define USB_DWC2_GUSBCFG_FORCEHSTMODE BIT(USB_DWC2_GUSBCFG_FORCEHSTMODE_POS) +#define USB_DWC2_GUSBCFG_ULPIEVBUSD_POS 20UL +#define USB_DWC2_GUSBCFG_ULPIEVBUSD BIT(USB_DWC2_GUSBCFG_ULPIEVBUSD_POS) +#define USB_DWC2_GUSBCFG_ULPIEVBUSI_POS 21UL +#define USB_DWC2_GUSBCFG_ULPIEVBUSI BIT(USB_DWC2_GUSBCFG_ULPIEVBUSI_POS) +#define USB_DWC2_GUSBCFG_ULPICLK_SUSM_POS 19UL +#define USB_DWC2_GUSBCFG_ULPICLK_SUSM BIT(USB_DWC2_GUSBCFG_ULPICLK_SUSM_POS) +#define USB_DWC2_GUSBCFG_ULPIFSLS_POS 17UL +#define USB_DWC2_GUSBCFG_ULPIFSLS BIT(USB_DWC2_GUSBCFG_ULPIFSLS_POS) +#define USB_DWC2_GUSBCFG_DDR_SEL_POS 7UL +#define USB_DWC2_GUSBCFG_DDR_SINGLE 0UL +#define USB_DWC2_GUSBCFG_DDR_DOUBLE BIT(USB_DWC2_GUSBCFG_DDR_SEL_POS) #define USB_DWC2_GUSBCFG_PHYSEL_POS 6UL #define USB_DWC2_GUSBCFG_PHYSEL_USB11 BIT(USB_DWC2_GUSBCFG_PHYSEL_POS) #define USB_DWC2_GUSBCFG_PHYSEL_USB20 0UL @@ -663,6 +698,168 @@ USB_DWC2_GET_FIELD_DEFINE(dieptxf_inepntxfstaddr, DIEPTXF_INEPNTXFSTADDR) USB_DWC2_SET_FIELD_DEFINE(dieptxf_inepntxfdep, DIEPTXF_INEPNTXFDEP) USB_DWC2_SET_FIELD_DEFINE(dieptxf_inepntxfstaddr, DIEPTXF_INEPNTXFSTADDR) +/* Periodic transmit FIFO size register (host mode) */ +#define USB_DWC2_HPTXFSIZ 0x0100UL +#define USB_DWC2_HPTXFSIZ_PTXFSIZE_POS 16UL +#define USB_DWC2_HPTXFSIZ_PTXFSIZE_MASK (0xFFFFUL << USB_DWC2_HPTXFSIZ_PTXFSIZE_POS) +#define USB_DWC2_HPTXFSIZ_PTXFSTADDR_POS 0UL +#define USB_DWC2_HPTXFSIZ_PTXFSTADDR_MASK (0xFFFFUL << USB_DWC2_HPTXFSIZ_PTXFSTADDR_POS) + + +/* Host Configuration Register */ +#define USB_DWC2_HCFG 0x0400UL +#define USB_DWC2_HCFG_MODECHTIMEN_POS 31UL +#define USB_DWC2_HCFG_MODECHTIMEN BIT(USB_DWC2_HCFG_MODECHTIMEN_POS) +#define USB_DWC2_HCFG_PERSCHEDENA_POS 26UL +#define USB_DWC2_HCFG_PERSCHEDENA BIT(USB_DWC2_HCFG_PERSCHEDENA_POS) +#define USB_DWC2_HCFG_FRLISTEN_POS 24UL +#define USB_DWC2_HCFG_FRLISTEN_MASK (0x3UL << USB_DWC2_HCFG_FRLISTEN_POS) +#define USB_DWC2_HCFG_DESCDMA_POS 23UL +#define USB_DWC2_HCFG_DESCDMA BIT(USB_DWC2_HCFG_DESCDMA_POS) +#define USB_DWC2_HCFG_DIS_TX_IPGAP_DLY_CHECK_POS 16UL +#define USB_DWC2_HCFG_DIS_TX_IPGAP_DLY_CHECK BIT(USB_DWC2_HCFG_DIS_TX_IPGAP_DLY_CHECK_POS) +#define USB_DWC2_HCFG_RESVALID_POS 8UL +#define USB_DWC2_HCFG_RESVALID_MASK (0xFFUL << USB_DWC2_HCFG_RESVALID_POS) +#define USB_DWC2_HCFG_ENA32KHZS_POS 7UL +#define USB_DWC2_HCFG_ENA32KHZS BIT(USB_DWC2_HCFG_ENA32KHZS_POS) +#define USB_DWC2_HCFG_FSLSSUPP_POS 2UL +#define USB_DWC2_HCFG_FSLSSUPP BIT(USB_DWC2_HCFG_FSLSSUPP_POS) +#define USB_DWC2_HCFG_FSLSPCLKSEL_POS 0UL +#define USB_DWC2_HCFG_FSLSPCLKSEL_MASK (0x3UL << USB_DWC2_HCFG_FSLSPCLKSEL_POS) + +USB_DWC2_SET_FIELD_DEFINE(hcfg_frlisten, HCFG_FRLISTEN) +USB_DWC2_SET_FIELD_DEFINE(hcfg_resvalid, HCFG_RESVALID) +USB_DWC2_SET_FIELD_DEFINE(hcfg_fslspclksel, HCFG_FSLSPCLKSEL) +USB_DWC2_GET_FIELD_DEFINE(hcfg_frlisten, HCFG_FRLISTEN) +USB_DWC2_GET_FIELD_DEFINE(hcfg_resvalid, HCFG_RESVALID) +USB_DWC2_GET_FIELD_DEFINE(hcfg_fslspclksel, HCFG_FSLSPCLKSEL) + +/* Host Frame Interval Register */ +#define USB_DWC2_HFIR 0x0404UL + +#define USB_DWC2_HFIR_HFIRRLDCTRL_POS 16UL +#define USB_DWC2_HFIR_HFIRRLDCTRL BIT(USB_DWC2_HFIR_HFIRRLDCTRL_POS) +#define USB_DWC2_HFIR_FRINT_POS 0UL +#define USB_DWC2_HFIR_FRINT_MASK (0xFFFFUL << USB_DWC2_HFIR_FRINT_POS) + +USB_DWC2_SET_FIELD_DEFINE(hfir_frint, HFIR_FRINT) +USB_DWC2_GET_FIELD_DEFINE(hfir_frint, HFIR_FRINT) + +/* Host All Channels Interrupt Register */ +#define USB_DWC2_HAINT 0x0414UL +#define USB_DWC2_HAINT_HAINT_POS 0UL +#define USB_DWC2_HAINT_HAINT_MASK 0xFFFFUL /* Bits [15:0], 1 bit per host channel */ + +USB_DWC2_GET_FIELD_DEFINE(haint_haint, HAINT_HAINT) + +/* Host Port Control and Status Register */ +#define USB_DWC2_HPRT 0x0440UL +#define USB_DWC2_HPRT_PRTSPD_POS 17UL +#define USB_DWC2_HPRT_PRTSPD_MASK (0x3UL << USB_DWC2_HPRT_PRTSPD_POS) +#define USB_DWC2_HPRT_PRTTSTCTL_POS 13UL +#define USB_DWC2_HPRT_PRTTSTCTL_MASK (0xFUL << USB_DWC2_HPRT_PRTTSTCTL_POS) +#define USB_DWC2_HPRT_PRTLNSTS_POS 10UL +#define USB_DWC2_HPRT_PRTLNSTS_MASK (0x3UL << USB_DWC2_HPRT_PRTLNSTS_POS) +#define USB_DWC2_HPRT_PRTENA BIT(2) +#define USB_DWC2_HPRT_PRTENCHNG BIT(3) +#define USB_DWC2_HPRT_PRTOVRCURRACT BIT(4) +#define USB_DWC2_HPRT_PRTOVRCURRCHNG BIT(5) +#define USB_DWC2_HPRT_PRTRES BIT(6) +#define USB_DWC2_HPRT_PRTSUSP BIT(7) +#define USB_DWC2_HPRT_PRTRST BIT(8) +#define USB_DWC2_HPRT_PRTCONNDET BIT(1) +#define USB_DWC2_HPRT_PRTCONNSTS BIT(0) +#define USB_DWC2_HPRT_PRTPWR BIT(12) +#define USB_DWC2_HPRT_PRTSPD_HIGH 0 +#define USB_DWC2_HPRT_PRTSPD_FULL 1 +#define USB_DWC2_HPRT_PRTSPD_LOW 2 + +USB_DWC2_SET_FIELD_DEFINE(hprt_prtspd, HPRT_PRTSPD) +USB_DWC2_SET_FIELD_DEFINE(hprt_prttstctl, HPRT_PRTTSTCTL) +USB_DWC2_SET_FIELD_DEFINE(hprt_prtlnsts, HPRT_PRTLNSTS) +USB_DWC2_GET_FIELD_DEFINE(hprt_prtspd, HPRT_PRTSPD) +USB_DWC2_GET_FIELD_DEFINE(hprt_prttstctl, HPRT_PRTTSTCTL) +USB_DWC2_GET_FIELD_DEFINE(hprt_prtlnsts, HPRT_PRTLNSTS) + +/* Host Channel Characteristics Register (HCCHAR0) */ +#define USB_DWC2_HCCHAR0 0x0500UL + +/* Bitfield Masks */ +#define USB_DWC2_HCCHAR0_CHENA BIT(31) +#define USB_DWC2_HCCHAR0_CHDIS BIT(30) +#define USB_DWC2_HCCHAR0_ODDFRM BIT(29) +#define USB_DWC2_HCCHAR0_DEVADDR_POS 22UL +#define USB_DWC2_HCCHAR0_DEVADDR_MASK (0x7FUL << USB_DWC2_HCCHAR0_DEVADDR_POS) +#define USB_DWC2_HCCHAR0_EC_POS 20UL +#define USB_DWC2_HCCHAR0_EC_MASK (0x3UL << USB_DWC2_HCCHAR0_EC_POS) +#define USB_DWC2_HCCHAR0_EPTYPE_POS 18UL +#define USB_DWC2_HCCHAR0_EPTYPE_MASK (0x3UL << USB_DWC2_HCCHAR0_EPTYPE_POS) +#define USB_DWC2_HCCHAR0_LSPDDEV BIT(17) +#define USB_DWC2_HCCHAR0_EPDIR BIT(15) +#define USB_DWC2_HCCHAR0_EPNUM_POS 11UL +#define USB_DWC2_HCCHAR0_EPNUM_MASK (0xFUL << USB_DWC2_HCCHAR0_EPNUM_POS) +#define USB_DWC2_HCCHAR0_MPS_POS 0UL +#define USB_DWC2_HCCHAR0_MPS_MASK (0x7FFUL << USB_DWC2_HCCHAR0_MPS_POS) + +USB_DWC2_SET_FIELD_DEFINE(hcchar0_devaddr, HCCHAR0_DEVADDR) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_ec, HCCHAR0_EC) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_eptype, HCCHAR0_EPTYPE) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_epnum, HCCHAR0_EPNUM) +USB_DWC2_SET_FIELD_DEFINE(hcchar0_mps, HCCHAR0_MPS) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_devaddr, HCCHAR0_DEVADDR) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_ec, HCCHAR0_EC) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_eptype, HCCHAR0_EPTYPE) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_epnum, HCCHAR0_EPNUM) +USB_DWC2_GET_FIELD_DEFINE(hcchar0_mps, HCCHAR0_MPS) + +/* + * Host Channel Interrupt Mask Registers (HCINTMSK) + * Offset: 0x050C + (0x20 * i), i = 0 .. (OTG_NUM_HOST_CHAN - 1) + */ +#define USB_DWC2_HCINT0 0x0508UL +#define USB_DWC2_HCINTMSK0 0x050CUL +#define USB_DWC2_HCINT_XFERCOMPL BIT(0) +#define USB_DWC2_HCINT_CHHLTD BIT(1) +#define USB_DWC2_HCINT_AHBERR BIT(2) +#define USB_DWC2_HCINT_STALL BIT(3) +#define USB_DWC2_HCINT_NAK BIT(4) +#define USB_DWC2_HCINT_ACK BIT(5) +#define USB_DWC2_HCINT_NYET BIT(6) +#define USB_DWC2_HCINT_XACTERR BIT(7) +#define USB_DWC2_HCINT_BBLERR BIT(8) +#define USB_DWC2_HCINT_FRMOVRUN BIT(9) +#define USB_DWC2_HCINT_DTGERR BIT(10) +#define USB_DWC2_HCINT_BNA BIT(11) +#define USB_DWC2_HCINT_DESC_LST_ROLL BIT(13) + +/* Host Channel Transfer Size Register */ +#define USB_DWC2_HCTSIZ0 0x0510UL +#define USB_DWC2_HCTSIZ_XFERSIZE_POS 0UL +#define USB_DWC2_HCTSIZ_XFERSIZE_MASK (0x7FFFFUL << USB_DWC2_HCTSIZ_XFERSIZE_POS) +#define USB_DWC2_HCTSIZ_PKTCNT_POS 19UL +#define USB_DWC2_HCTSIZ_PKTCNT_MASK (0x3FFUL << USB_DWC2_HCTSIZ_PKTCNT_POS) +#define USB_DWC2_HCTSIZ_PID_POS 29UL +#define USB_DWC2_HCTSIZ_PID_DATA0 0UL +#define USB_DWC2_HCTSIZ_PID_DATA2 1UL +#define USB_DWC2_HCTSIZ_PID_DATA1 2UL +#define USB_DWC2_HCTSIZ_PID_MDATA 3UL +#define USB_DWC2_HCTSIZ_PID_SETUP 3UL +#define USB_DWC2_HCTSIZ_PID_MASK (0x3UL << USB_DWC2_HCTSIZ_PID_POS) +#define USB_DWC2_HCTSIZ_DOPNG BIT(31) + +USB_DWC2_SET_FIELD_DEFINE(hctsiz_xfersize, HCTSIZ_XFERSIZE) +USB_DWC2_SET_FIELD_DEFINE(hctsiz_pktcnt, HCTSIZ_PKTCNT) +USB_DWC2_SET_FIELD_DEFINE(hctsiz_pid, HCTSIZ_PID) +USB_DWC2_GET_FIELD_DEFINE(hctsiz_xfersize, HCTSIZ_XFERSIZE) +USB_DWC2_GET_FIELD_DEFINE(hctsiz_pktcnt, HCTSIZ_PKTCNT) +USB_DWC2_GET_FIELD_DEFINE(hctsiz_pid, HCTSIZ_PID) + +/* Host Channel DMA Address Register */ +#define USB_DWC2_HCDMA0 0x0514UL + +/* Host Channel DMA Buffer Address Register */ +#define USB_DWC2_HCDMAB0 0x051CUL + /* Device configuration registers */ #define USB_DWC2_DCFG 0x0800UL #define USB_DWC2_DCFG_RESVALID_POS 26UL diff --git a/drivers/usb/udc/udc_dwc2.c b/drivers/usb/udc/udc_dwc2.c index c83e6671c29b8..644369e22ed79 100644 --- a/drivers/usb/udc/udc_dwc2.c +++ b/drivers/usb/udc/udc_dwc2.c @@ -44,31 +44,9 @@ enum dwc2_drv_event_type { DWC2_DRV_EVT_HIBERNATION_EXIT_HOST_RESUME, }; -/* Minimum RX FIFO size in 32-bit words considering the largest used OUT packet - * of 512 bytes. The value must be adjusted according to the number of OUT - * endpoints. - */ -#define UDC_DWC2_GRXFSIZ_FS_DEFAULT (15U + 512U/4U) -/* Default Rx FIFO size in 32-bit words calculated to support High-Speed with: - * * 1 control endpoint in Completer/Buffer DMA mode: 13 locations - * * Global OUT NAK: 1 location - * * Space for 3 * 1024 packets: ((1024/4) + 1) * 3 = 774 locations - * Driver adds 2 locations for each OUT endpoint to this value. - */ -#define UDC_DWC2_GRXFSIZ_HS_DEFAULT (13 + 1 + 774) - -/* TX FIFO0 depth in 32-bit words (used by control IN endpoint) - * Try 2 * bMaxPacketSize0 to allow simultaneous operation with a fallback to - * whatever is available when 2 * bMaxPacketSize0 is not possible. - */ -#define UDC_DWC2_FIFO0_DEPTH (2 * 16U) - /* Get Data FIFO access register */ #define UDC_DWC2_EP_FIFO(base, idx) ((mem_addr_t)base + 0x1000 * (idx + 1)) -/* Percentage limit of how much SPRAM can be allocated for RxFIFO */ -#define MAX_RXFIFO_GDFIFO_PERCENTAGE 25 - enum dwc2_suspend_type { DWC2_SUSPEND_NO_POWER_SAVING, DWC2_SUSPEND_HIBERNATION, @@ -127,11 +105,7 @@ struct udc_dwc2_data { uint16_t iso_out_rearm; uint16_t ep_out_disable; uint16_t ep_out_stall; - uint16_t txf_set; uint16_t pending_tx_flush; - uint16_t dfifodepth; - uint16_t rxfifo_depth; - uint16_t max_txfifo_depth[16]; uint16_t sof_num; /* Configuration flags */ unsigned int dynfifosizing : 1; @@ -331,41 +305,6 @@ static void dwc2_flush_tx_fifo(const struct device *dev, const uint8_t fnum) } } -/* Return TX FIFOi depth in 32-bit words (i = f_idx + 1) */ -static uint32_t dwc2_get_txfdep(const struct device *dev, const uint32_t f_idx) -{ - struct usb_dwc2_reg *const base = dwc2_get_base(dev); - uint32_t dieptxf; - - dieptxf = sys_read32((mem_addr_t)&base->dieptxf[f_idx]); - - return usb_dwc2_get_dieptxf_inepntxfdep(dieptxf); -} - -/* Return TX FIFOi address (i = f_idx + 1) */ -static uint32_t dwc2_get_txfaddr(const struct device *dev, const uint32_t f_idx) -{ - struct usb_dwc2_reg *const base = dwc2_get_base(dev); - uint32_t dieptxf; - - dieptxf = sys_read32((mem_addr_t)&base->dieptxf[f_idx]); - - return usb_dwc2_get_dieptxf_inepntxfstaddr(dieptxf); -} - -/* Set TX FIFOi address and depth (i = f_idx + 1) */ -static void dwc2_set_txf(const struct device *dev, const uint32_t f_idx, - const uint32_t dep, const uint32_t addr) -{ - struct usb_dwc2_reg *const base = dwc2_get_base(dev); - uint32_t dieptxf; - - dieptxf = usb_dwc2_set_dieptxf_inepntxfdep(dep) | - usb_dwc2_set_dieptxf_inepntxfstaddr(addr); - - sys_write32(dieptxf, (mem_addr_t)&base->dieptxf[f_idx]); -} - /* Enable/disable endpoint interrupt */ static void dwc2_set_epint(const struct device *dev, struct udc_ep_config *const cfg, const bool enabled) @@ -1353,24 +1292,6 @@ static void request_hibernation(struct udc_dwc2_data *const priv) } } -static void dwc2_unset_unused_fifo(const struct device *dev) -{ - struct udc_dwc2_data *const priv = udc_get_private(dev); - struct udc_ep_config *tmp; - - for (uint8_t i = priv->ineps - 1U; i > 0; i--) { - tmp = udc_get_ep_cfg(dev, i | USB_EP_DIR_IN); - - if (tmp->stat.enabled && (priv->txf_set & BIT(i))) { - return; - } - - if (!tmp->stat.enabled && (priv->txf_set & BIT(i))) { - priv->txf_set &= ~BIT(i); - } - } -} - /* * In dedicated FIFO mode there are i (i = 1 ... ineps - 1) FIFO size registers, * e.g. DIEPTXF1, DIEPTXF2, ... DIEPTXF4. When dynfifosizing is enabled, @@ -1380,16 +1301,11 @@ static int dwc2_set_dedicated_fifo(const struct device *dev, struct udc_ep_config *const cfg, uint32_t *const diepctl) { - struct udc_dwc2_data *const priv = udc_get_private(dev); - uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); + struct usb_dwc2_reg *const base = dwc2_get_base(dev); const uint32_t addnl = USB_MPS_ADDITIONAL_TRANSACTIONS(cfg->mps); + uint32_t dieptxf; uint32_t reqdep; - uint32_t txfaddr; - uint32_t txfdep; - uint32_t tmp; - - /* Keep everything but FIFO number */ - tmp = *diepctl & ~USB_DWC2_DEPCTL_TXFNUM_MASK; + uint8_t txfnum; reqdep = DIV_ROUND_UP(udc_mps_ep_size(cfg), 4U); if (dwc2_in_buffer_dma_mode(dev)) { @@ -1399,63 +1315,19 @@ static int dwc2_set_dedicated_fifo(const struct device *dev, reqdep *= (1 + addnl); } - if (priv->dynfifosizing) { - if (priv->txf_set & ~BIT_MASK(ep_idx)) { - dwc2_unset_unused_fifo(dev); - } - - if ((ep_idx - 1) != 0U) { - txfaddr = dwc2_get_txfdep(dev, ep_idx - 2) + - dwc2_get_txfaddr(dev, ep_idx - 2); - } else { - txfaddr = priv->rxfifo_depth + - MIN(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]); - } - - if (priv->txf_set & BIT(ep_idx)) { - uint32_t curaddr; + /* TxFIFOs are preassigned, just verify it can be used */ + txfnum = usb_dwc2_get_depctl_txfnum(*diepctl); + dieptxf = sys_read32((mem_addr_t)&base->dieptxf[txfnum]); - curaddr = dwc2_get_txfaddr(dev, ep_idx - 1); - txfdep = dwc2_get_txfdep(dev, ep_idx - 1); - if (txfaddr != curaddr || reqdep > txfdep) { - LOG_ERR("FIFO%u cannot be reused, new addr 0x%04x depth %u", - ep_idx, txfaddr, reqdep); - return -ENOMEM; - } - } else { - txfdep = reqdep; - } - - /* Make sure to not set TxFIFO greater than hardware allows */ - if (txfdep > priv->max_txfifo_depth[ep_idx]) { - return -ENOMEM; - } - - /* Do not allocate TxFIFO outside the SPRAM */ - if (txfaddr + txfdep > priv->dfifodepth) { - return -ENOMEM; - } - - /* Set FIFO depth (32-bit words) and address */ - dwc2_set_txf(dev, ep_idx - 1, txfdep, txfaddr); - } else { - txfdep = dwc2_get_txfdep(dev, ep_idx - 1); - txfaddr = dwc2_get_txfaddr(dev, ep_idx - 1); - - if (reqdep > txfdep) { - return -ENOMEM; - } - - LOG_DBG("Reuse FIFO%u addr 0x%08x depth %u", ep_idx, txfaddr, txfdep); + if (reqdep > usb_dwc2_get_dieptxf_inepntxfdep(dieptxf)) { + return -ENOMEM; } - /* Assign FIFO to the IN endpoint */ - *diepctl = tmp | usb_dwc2_set_depctl_txfnum(ep_idx); - priv->txf_set |= BIT(ep_idx); - dwc2_flush_tx_fifo(dev, ep_idx); + dwc2_flush_tx_fifo(dev, txfnum); LOG_INF("Set FIFO%u (ep 0x%02x) addr 0x%04x depth %u size %u", - ep_idx, cfg->addr, txfaddr, txfdep, dwc2_ftx_avail(dev, ep_idx)); + txfnum, cfg->addr, usb_dwc2_get_dieptxf_inepntxfstaddr(dieptxf), + usb_dwc2_get_dieptxf_inepntxfdep(dieptxf), dwc2_ftx_avail(dev, txfnum)); return 0; } @@ -1583,33 +1455,6 @@ static int udc_dwc2_ep_activate(const struct device *dev, return 0; } -static int dwc2_unset_dedicated_fifo(const struct device *dev, - struct udc_ep_config *const cfg, - uint32_t *const diepctl) -{ - struct udc_dwc2_data *const priv = udc_get_private(dev); - uint8_t ep_idx = USB_EP_GET_IDX(cfg->addr); - - /* Clear FIFO number field */ - *diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; - - if (priv->dynfifosizing) { - uint16_t higher_mask = ~BIT_MASK(ep_idx + 1); - - if (priv->txf_set & higher_mask) { - LOG_WRN("Some of the FIFOs higher than %u are set, %x", - ep_idx, priv->txf_set & higher_mask); - return 0; - } - - dwc2_set_txf(dev, ep_idx - 1, 0, 0); - } - - priv->txf_set &= ~BIT(ep_idx); - - return 0; -} - /* Disabled IN endpoint means that device will send NAK (isochronous: ZLP) after * receiving IN token from host even if there is packet available in TxFIFO. * Disabled OUT endpoint means that device will NAK (isochronous: discard data) @@ -1757,11 +1602,6 @@ static int udc_dwc2_ep_deactivate(const struct device *dev, LOG_DBG("Disable ep 0x%02x DxEPCTL%u %x", cfg->addr, ep_idx, dxepctl); dxepctl &= ~USB_DWC2_DEPCTL_USBACTEP; - if (USB_EP_DIR_IS_IN(cfg->addr) && udc_mps_ep_size(cfg) != 0U && - ep_idx != 0U) { - dwc2_unset_dedicated_fifo(dev, cfg, &dxepctl); - } - sys_write32(dxepctl, dxepctl_reg); dwc2_set_epint(dev, cfg, false); @@ -2031,6 +1871,8 @@ static int udc_dwc2_init_controller(const struct device *dev) uint32_t ghwcfg3; uint32_t ghwcfg4; uint32_t val; + uint16_t dfifodepth; + uint8_t txfnum; int ret; bool hs_phy; @@ -2101,8 +1943,8 @@ static int udc_dwc2_init_controller(const struct device *dev) usb_dwc2_get_ghwcfg2_otgarch(ghwcfg2), usb_dwc2_get_ghwcfg2_otgmode(ghwcfg2)); - priv->dfifodepth = usb_dwc2_get_ghwcfg3_dfifodepth(ghwcfg3); - LOG_DBG("DFIFO depth (DFIFODEPTH) %u bytes", priv->dfifodepth * 4); + dfifodepth = usb_dwc2_get_ghwcfg3_dfifodepth(ghwcfg3); + LOG_DBG("DFIFO depth (DFIFODEPTH) %u bytes", dfifodepth * 4); priv->max_pktcnt = GHWCFG3_PKTCOUNT(usb_dwc2_get_ghwcfg3_pktsizewidth(ghwcfg3)); priv->max_xfersize = GHWCFG3_XFERSIZE(usb_dwc2_get_ghwcfg3_xfersizewidth(ghwcfg3)); @@ -2206,62 +2048,88 @@ static int udc_dwc2_init_controller(const struct device *dev) LOG_DBG("Number of OUT endpoints %u", priv->outeps); - /* Read and store all TxFIFO depths because Programmed FIFO Depths must - * not exceed the power-on values. - */ - val = sys_read32((mem_addr_t)&base->gnptxfsiz); - priv->max_txfifo_depth[0] = usb_dwc2_get_gnptxfsiz_nptxfdep(val); - for (uint8_t i = 1; i < priv->ineps; i++) { - priv->max_txfifo_depth[i] = dwc2_get_txfdep(dev, i - 1); - } - - priv->rxfifo_depth = usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg)); - if (priv->dynfifosizing) { - uint32_t gnptxfsiz; - uint32_t default_depth; - uint32_t spram_size; - uint32_t max_rxfifo; - - /* Get available SPRAM size and calculate max allocatable RX fifo size */ - val = sys_read32((mem_addr_t)&base->gdfifocfg); - spram_size = usb_dwc2_get_gdfifocfg_gdfifocfg(val); - max_rxfifo = ((spram_size * MAX_RXFIFO_GDFIFO_PERCENTAGE) / 100); - - /* TODO: For proper runtime FIFO sizing UDC driver would have to - * have prior knowledge of the USB configurations. Only with the - * prior knowledge, the driver will be able to fairly distribute - * available resources. For the time being just use different - * defaults based on maximum configured PHY speed, but this has - * to be revised if e.g. thresholding support would be necessary - * on some target. + uint32_t start_addr; + + /* Rx FIFO always starts at address 0 */ + __ASSERT_EVAL(, + val = usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg)), + val >= config->fifo_sizes[0], + "DT g-rx-fifo-size %d exceeds power-on value %d", + config->fifo_sizes[0], val); + sys_write32(usb_dwc2_set_grxfsiz(config->fifo_sizes[0]), grxfsiz_reg); + + /* TxFIFO 0 starts after Rx FIFO */ + start_addr = config->fifo_sizes[0]; + __ASSERT_EVAL(, val = usb_dwc2_get_gnptxfsiz_nptxfdep( + sys_read32((mem_addr_t)&base->gnptxfsiz)), + val >= config->fifo_sizes[1], + "DT g-np-tx-fifo-size %d exceeds power-on value %d", + config->fifo_sizes[1], val); + val = usb_dwc2_set_gnptxfsiz_nptxfdep(config->fifo_sizes[1]) | + usb_dwc2_set_gnptxfsiz_nptxfstaddr(start_addr); + sys_write32(val, (mem_addr_t)&base->gnptxfsiz); + + /* Afterwards come all TxFIFOs */ + start_addr += config->fifo_sizes[1]; + for (uint8_t i = 0; i < priv->ineps - 1; i++) { + mem_addr_t dieptxf_reg = (mem_addr_t)&base->dieptxf[i]; + uint32_t dieptxf; + + __ASSERT_EVAL(, val = usb_dwc2_get_dieptxf_inepntxfdep( + sys_read32(dieptxf_reg)), + val >= config->fifo_sizes[2 + i], + "DT g-tx-fifo-size %d at idx %d exceeds power-on value %d", + config->fifo_sizes[2 + i], i, val); + dieptxf = usb_dwc2_set_dieptxf_inepntxfdep(config->fifo_sizes[2 + i]) | + usb_dwc2_set_dieptxf_inepntxfstaddr(start_addr); + sys_write32(dieptxf, dieptxf_reg); + + start_addr += config->fifo_sizes[2 + i]; + } + + /* Sanity check that we don't collide with EPInfoBaseAddr */ + __ASSERT(dfifodepth >= start_addr, + "FIFO memory ends at 0x%x but DFIFO Depth is 0x%x", + start_addr, dfifodepth); + } + + /* Assign FIFOs to IN endpoints */ + txfnum = 0; + for (uint8_t i = 0; i < 16; i++) { + /* Endpoint information was populated based on config->ghwcfg1, + * we have to use the same value even though we can access real + * register here (DT should really match HW, so this shouldn't + * be of any practical difference). */ - if (hs_phy) { - default_depth = UDC_DWC2_GRXFSIZ_HS_DEFAULT; - } else { - default_depth = UDC_DWC2_GRXFSIZ_FS_DEFAULT; - } - default_depth += priv->outeps * 2U; + uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); - /* Driver does not dynamically resize RxFIFO so there is no need - * to store reset value. Read the reset value and make sure that - * the programmed value is not greater than what driver sets. - */ - priv->rxfifo_depth = MIN(MIN(priv->rxfifo_depth, default_depth), max_rxfifo); - sys_write32(usb_dwc2_set_grxfsiz(priv->rxfifo_depth), grxfsiz_reg); + if (epdir == USB_DWC2_GHWCFG1_EPDIR_IN || epdir == USB_DWC2_GHWCFG1_EPDIR_BDIR) { + mem_addr_t diepctl_reg = (mem_addr_t)&base->in_ep[i].diepctl; + uint32_t diepctl; - /* Set TxFIFO 0 depth */ - val = MIN(UDC_DWC2_FIFO0_DEPTH, priv->max_txfifo_depth[0]); - gnptxfsiz = usb_dwc2_set_gnptxfsiz_nptxfdep(val) | - usb_dwc2_set_gnptxfsiz_nptxfstaddr(priv->rxfifo_depth); + diepctl = sys_read32(diepctl_reg); + diepctl &= ~USB_DWC2_DEPCTL_TXFNUM_MASK; + diepctl |= usb_dwc2_set_depctl_txfnum(txfnum); + sys_write32(diepctl, diepctl_reg); - sys_write32(gnptxfsiz, (mem_addr_t)&base->gnptxfsiz); + txfnum++; + } } - LOG_DBG("RX FIFO size %u bytes", priv->rxfifo_depth * 4); - for (uint8_t i = 1U; i < priv->ineps; i++) { - LOG_DBG("TX FIFO%u depth %u addr %u", - i, priv->max_txfifo_depth[i], dwc2_get_txfaddr(dev, i)); + LOG_DBG("RX FIFO depth %u", usb_dwc2_get_grxfsiz(sys_read32(grxfsiz_reg))); + if (CONFIG_UDC_DRIVER_LOG_LEVEL >= LOG_LEVEL_DBG) { + val = sys_read32((mem_addr_t)&base->gnptxfsiz); + LOG_DBG("NPTX FIFO depth %u addr %u", + usb_dwc2_get_gnptxfsiz_nptxfdep(val), + usb_dwc2_get_gnptxfsiz_nptxfstaddr(val)); + + for (uint8_t i = 0; i < priv->ineps - 1; i++) { + val = sys_read32((mem_addr_t)&base->dieptxf[i]); + LOG_DBG("TX FIFO%u depth %u addr %u", + i + 1, usb_dwc2_get_dieptxf_inepntxfdep(val), + usb_dwc2_get_dieptxf_inepntxfstaddr(val)); + } } if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, @@ -2418,9 +2286,10 @@ static int dwc2_driver_preinit(const struct device *dev) const struct udc_dwc2_config *config = dev->config; struct udc_dwc2_data *const priv = udc_get_private(dev); struct udc_data *data = dev->data; - uint16_t mps = 1023; + uint16_t mps = 1023, bulk_bytes = 64; uint32_t numdeveps; uint32_t ineps; + bool high_bandwidth; int err; k_mutex_init(&data->mutex); @@ -2441,6 +2310,7 @@ static int dwc2_driver_preinit(const struct device *dev) (void)dwc2_quirk_caps(dev); if (data->caps.hs) { mps = 1024; + bulk_bytes = 512; } /* @@ -2456,6 +2326,15 @@ static int dwc2_driver_preinit(const struct device *dev) LOG_DBG("Number of endpoints (NUMDEVEPS + 1) %u", numdeveps); LOG_DBG("Number of IN endpoints (INEPS + 1) %u", ineps); + /* RxFIFO has to be able to hold at least: + * * 1 control endpoint in Completer/Buffer DMA mode: 13 locations + * * Global OUT NAK: 1 location + * If in addition to above TxFIFO also has a space for at least: + * * Space for 2 * 1024 packets: ((1024/4) + 1) * 2 = 514 locations + * then consider OUT endpoints to be High-Bandwidth capable. + */ + high_bandwidth = data->caps.hs && config->fifo_sizes[0] >= 528; + for (uint32_t i = 0, n = 0; i < numdeveps; i++) { uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); @@ -2471,7 +2350,7 @@ static int dwc2_driver_preinit(const struct device *dev) config->ep_cfg_out[n].caps.bulk = 1; config->ep_cfg_out[n].caps.interrupt = 1; config->ep_cfg_out[n].caps.iso = 1; - config->ep_cfg_out[n].caps.high_bandwidth = data->caps.hs; + config->ep_cfg_out[n].caps.high_bandwidth = high_bandwidth; config->ep_cfg_out[n].caps.mps = mps; } @@ -2494,21 +2373,25 @@ static int dwc2_driver_preinit(const struct device *dev) for (uint32_t i = 0, n = 0; i < numdeveps; i++) { uint32_t epdir = usb_dwc2_get_ghwcfg1_epdir(config->ghwcfg1, i); + uint32_t fifo_bytes; if (epdir != USB_DWC2_GHWCFG1_EPDIR_IN && epdir != USB_DWC2_GHWCFG1_EPDIR_BDIR) { continue; } + fifo_bytes = config->fifo_sizes[1 + n] * 4; + high_bandwidth = data->caps.hs && fifo_bytes >= 2 * mps; + if (i == 0) { config->ep_cfg_in[n].caps.control = 1; - config->ep_cfg_in[n].caps.mps = 64; + config->ep_cfg_in[n].caps.mps = MIN(fifo_bytes, 64); } else { - config->ep_cfg_in[n].caps.bulk = 1; + config->ep_cfg_in[n].caps.bulk = fifo_bytes >= bulk_bytes; config->ep_cfg_in[n].caps.interrupt = 1; config->ep_cfg_in[n].caps.iso = 1; - config->ep_cfg_in[n].caps.high_bandwidth = data->caps.hs; - config->ep_cfg_in[n].caps.mps = mps; + config->ep_cfg_in[n].caps.high_bandwidth = high_bandwidth; + config->ep_cfg_in[n].caps.mps = MIN(fifo_bytes, mps); } config->ep_cfg_in[n].caps.in = 1; @@ -3560,6 +3443,18 @@ static const struct udc_api udc_dwc2_api = { static struct udc_ep_config ep_cfg_out[DT_INST_PROP(n, num_out_eps)]; \ static struct udc_ep_config ep_cfg_in[DT_INST_PROP(n, num_in_eps)]; \ \ + BUILD_ASSERT(DT_INST_PROP_LEN(n, g_tx_fifo_size) == \ + FIELD_GET(USB_DWC2_GHWCFG4_INEPS_MASK, \ + DT_INST_PROP(n, ghwcfg4)), \ + "g-tx-fifo-size length does not match GHWCFG4.INEps"); \ + \ + static const uint16_t fifo_sizes_##n[] = { \ + DT_INST_PROP(n, g_rx_fifo_size), \ + DT_INST_PROP(n, g_np_tx_fifo_size), \ + DT_INST_FOREACH_PROP_ELEM_SEP(n, g_tx_fifo_size, \ + DT_PROP_BY_IDX, (,)), \ + }; \ + \ static const struct udc_dwc2_config udc_dwc2_config_##n = { \ .num_out_eps = DT_INST_PROP(n, num_out_eps), \ .num_in_eps = DT_INST_PROP(n, num_in_eps), \ @@ -3567,6 +3462,7 @@ static const struct udc_api udc_dwc2_api = { .ep_cfg_out = ep_cfg_out, \ .make_thread = udc_dwc2_make_thread_##n, \ .base = (struct usb_dwc2_reg *)UDC_DWC2_DT_INST_REG_ADDR(n), \ + .fifo_sizes = fifo_sizes_##n, \ .pcfg = UDC_DWC2_PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ .irq_enable_func = udc_dwc2_irq_enable_func_##n, \ .irq_disable_func = udc_dwc2_irq_disable_func_##n, \ diff --git a/drivers/usb/udc/udc_dwc2.h b/drivers/usb/udc/udc_dwc2.h index 77be96adaa5f8..865fa59787307 100644 --- a/drivers/usb/udc/udc_dwc2.h +++ b/drivers/usb/udc/udc_dwc2.h @@ -43,6 +43,7 @@ struct udc_dwc2_config { struct udc_ep_config *ep_cfg_in; struct udc_ep_config *ep_cfg_out; struct usb_dwc2_reg *const base; + const uint16_t *const fifo_sizes; /* Pointer to pin control configuration or NULL */ struct pinctrl_dev_config *const pcfg; /* Pointer to vendor quirks or NULL */ diff --git a/drivers/usb/uhc/CMakeLists.txt b/drivers/usb/uhc/CMakeLists.txt index 69f1cea8433b2..8f834f1d6e086 100644 --- a/drivers/usb/uhc/CMakeLists.txt +++ b/drivers/usb/uhc/CMakeLists.txt @@ -3,8 +3,10 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_library() +zephyr_library_include_directories(${ZEPHYR_BASE}/drivers/usb/common/) zephyr_library_sources(uhc_common.c) +zephyr_library_sources_ifdef(CONFIG_UHC_DWC2 uhc_dwc2.c) zephyr_library_sources_ifdef(CONFIG_UHC_MAX3421E uhc_max3421e.c) zephyr_library_sources_ifdef(CONFIG_UHC_VIRTUAL uhc_virtual.c) zephyr_library_sources_ifdef(CONFIG_UHC_NXP_EHCI uhc_mcux_common.c uhc_mcux_ehci.c) diff --git a/drivers/usb/uhc/Kconfig b/drivers/usb/uhc/Kconfig index 052e9437f6ae2..dc89b9460acab 100644 --- a/drivers/usb/uhc/Kconfig +++ b/drivers/usb/uhc/Kconfig @@ -36,6 +36,7 @@ module = UHC_DRIVER module-str = uhc drv source "subsys/logging/Kconfig.template.log_config" +source "drivers/usb/uhc/Kconfig.dwc2" source "drivers/usb/uhc/Kconfig.max3421e" source "drivers/usb/uhc/Kconfig.virtual" source "drivers/usb/uhc/Kconfig.mcux" diff --git a/drivers/usb/uhc/Kconfig.dwc2 b/drivers/usb/uhc/Kconfig.dwc2 new file mode 100644 index 0000000000000..e28044329ea1f --- /dev/null +++ b/drivers/usb/uhc/Kconfig.dwc2 @@ -0,0 +1,81 @@ +# Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. +# SPDX-License-Identifier: Apache-2.0 + +config UHC_DWC2 + bool "UHC DWC2 USB device controller driver" + default n + depends on DT_HAS_SNPS_DWC2_ENABLED + select EVENTS + help + DWC2 USB host controller driver. + +config UHC_DWC2_DMA + bool "UHC DWC2 USB DMA support" + default n + depends on UHC_DWC2 + help + Enable Buffer DMA if DWC2 USB controller supports Internal DMA. + +config UHC_DWC2_STACK_SIZE + int "UHC DWC2 driver internal thread stack size" + depends on UHC_DWC2 + default 512 + help + DWC2 driver internal thread stack size. + +config UHC_DWC2_THREAD_PRIORITY + int "UDC DWC2 driver thread priority" + depends on UHC_DWC2 + default 8 + help + DWC2 driver thread priority. + +menu "Root port configuration" + + config UHC_DWC2_PORT_DEBOUNCE_DELAY_MS + int "Debounce delay in ms" + default 250 + help + On connection of a USB device, the USB 2.0 specification requires + a "debounce interval with a minimum duration of 100ms" to allow the connection to stabilize + (see USB 2.0 chapter 7.1.7.3 for more details). + During the debounce interval, no new connection/disconnection events are registered. + + The default value is set to 250 ms to be safe. + + config UHC_DWC2_PORT_RESET_HOLD_MS + int "Reset hold in ms" + default 30 + help + The reset signaling can be generated on any Hub or Host Controller port by request from + the USB System Software. The USB 2.0 specification requires that "the reset signaling must + be driven for a minimum of 10ms" (see USB 2.0 chapter 7.1.7.5 for more details). + After the reset, the hub port will transition to the Enabled state (refer to Section 11.5). + + The default value is set to 30 ms to be safe. + + config UHC_DWC2_PORT_RESET_RECOVERY_MS + int "Reset recovery delay in ms" + default 30 + help + After a port stops driving the reset signal, the USB 2.0 specification requires that + the "USB System Software guarantees a minimum of 10 ms for reset recovery" before the + attached device is expected to respond to data transfers (see USB 2.0 chapter 7.1.7.3 for + more details). + The device may ignore any data transfers during the recovery interval. + + The default value is set to 30 ms to be safe. + + config UHC_DWC2_PORT_SET_ADDR_DELAY_MS + int "Delay after SetAddress()" + default 10 + help + "After successful completion of the Status stage, the device is allowed a SetAddress() + recovery interval of 2 ms. At the end of this interval, the device must be able to accept + Setup packets addressed to the new address. Also, at the end of the recovery interval, the + device must not respond to tokens sent to the old address (unless, of course, the old and new + address is the same)." See USB 2.0 chapter 9.2.6.3 for more details. + + The default value is set to 10 ms to be safe. + +endmenu #Root Hub configuration diff --git a/drivers/usb/uhc/uhc_dwc2.c b/drivers/usb/uhc/uhc_dwc2.c new file mode 100644 index 0000000000000..8a7df246665ab --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.c @@ -0,0 +1,1765 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT snps_dwc2 + +#include "uhc_common.h" +#include "uhc_dwc2.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + +#define DEBOUNCE_DELAY_MS CONFIG_UHC_DWC2_PORT_DEBOUNCE_DELAY_MS +#define RESET_HOLD_MS CONFIG_UHC_DWC2_PORT_RESET_HOLD_MS +#define RESET_RECOVERY_MS CONFIG_UHC_DWC2_PORT_RESET_RECOVERY_MS +#define SET_ADDR_DELAY_MS CONFIG_UHC_DWC2_PORT_SET_ADDR_DELAY_MS + +#define CTRL_EP_MAX_MPS_LS 8U +#define CTRL_EP_MAX_MPS_HSFS 64U +#define UHC_DWC2_MAX_CHAN 16 + +enum uhc_dwc2_event { + /* Root port event */ + UHC_DWC2_EVENT_PORT, + /* The host port has been enabled (i.e., connected device has been reset. Send SOFs) */ + UHC_DWC2_EVENT_ENABLED, + /* The host port has been disabled (no more SOFs) */ + UHC_DWC2_EVENT_DISABLED, + /* Overcurrent detected. Port is now UHC_PORT_STATE_RECOVERY */ + UHC_DWC2_EVENT_OVERCURRENT, + /* The host port has been cleared of the overcurrent condition */ + UHC_DWC2_EVENT_OVERCURRENT_CLEAR, + /* A device has been connected to the port */ + UHC_DWC2_EVENT_CONNECTION, + /* A device disconnection has been detected */ + UHC_DWC2_EVENT_DISCONNECTION, + /* Port error detected. Port is now UHC_PORT_STATE_RECOVERY */ + UHC_DWC2_EVENT_ERROR, + /* Event on a channel 0. Use +N for channel N */ + UHC_DWC2_EVENT_CHAN0, +}; + +enum uhc_dwc2_chan_event { + /* The channel has completed execution of a transfer. Channel is now halted */ + DWC2_CHAN_EVENT_CPLT, + /* The channel has encountered an error. Channel is now halted */ + DWC2_CHAN_EVENT_ERROR, + /* A halt has been requested on the channel */ + DWC2_CHAN_EVENT_HALT_REQ, +}; + +enum uhc_dwc2_speed { + UHC_DWC2_SPEED_HIGH = 0, + UHC_DWC2_SPEED_FULL = 1, + UHC_DWC2_SPEED_LOW = 2, +}; + +enum uhc_dwc2_xfer_type { + UHC_DWC2_XFER_TYPE_CTRL = 0, + UHC_DWC2_XFER_TYPE_ISOCHRONOUS = 1, + UHC_DWC2_XFER_TYPE_BULK = 2, + UHC_DWC2_XFER_TYPE_INTR = 3, +}; + +enum uhc_port_state { + /* The port is not powered */ + UHC_PORT_STATE_NOT_POWERED, + /* The port is powered but no device is connected */ + UHC_PORT_STATE_DISCONNECTED, + /* A device is connected to the port but has not been reset. */ + /* SOF/keep alive aren't being sent */ + UHC_PORT_STATE_DISABLED, + /* The port is issuing a reset condition */ + UHC_PORT_STATE_RESETTING, + /* The port has been suspended. */ + UHC_PORT_STATE_SUSPENDED, + /* The port is issuing a resume condition */ + UHC_PORT_STATE_RESUMING, + /* The port has been enabled. SOF/keep alive are being sent */ + UHC_PORT_STATE_ENABLED, + /* Port needs to be recovered from a fatal error (error, overcurrent, or disconnection) */ + UHC_PORT_STATE_RECOVERY, +}; + +enum uhc_dwc2_ctrl_stage { + CTRL_STAGE_DATA0 = 0, + CTRL_STAGE_DATA2 = 1, + CTRL_STAGE_DATA1 = 2, + CTRL_STAGE_SETUP = 3, +}; + +struct uhc_dwc2_chan { + /* Pointer to the transfer associated with the buffer */ + struct uhc_transfer *xfer; + /* Interval in frames (FS) or microframes (HS) */ + unsigned int interval; + /* Offset in the periodic scheduler */ + uint32_t offset; + /* Type of endpoint */ + enum uhc_dwc2_xfer_type type; + atomic_t event; + /* The index of the channel */ + uint8_t chan_idx; + /* Maximum Packet Size */ + uint16_t ep_mps; + /* Endpoint address */ + uint8_t ep_addr; + /* Device Address */ + uint8_t dev_addr; + /* Stage index */ + uint8_t cur_stg; + /* New address */ + uint8_t new_addr; + /* Set address request */ + uint8_t is_setting_addr: 1; + /* Data stage is IN */ + uint8_t data_stg_in: 1; + /* Has no data stage */ + uint8_t data_stg_skip: 1; + /* High-speed flag */ + uint8_t is_hs: 1; + /* Support for Low-Speed is via a Full-Speed HUB */ + uint8_t ls_via_fs_hub: 1; + uint8_t chan_cmd_processing: 1; + /* Halt has been requested */ + uint8_t halt_requested: 1; + /* TODO: Lists of pending and done? */ + /* TODO: Add channel error? */ +}; + +struct uhc_dwc2_data { + struct k_sem irq_sem; + struct k_thread thread; + /* Main events the driver thread waits for */ + struct k_event event; + struct uhc_dwc2_chan chan[UHC_DWC2_MAX_CHAN]; + /* Data, that is used in multiple threads */ + enum uhc_port_state port_state; + /* FIFO */ + uint16_t fifo_top; + uint16_t fifo_nptxfsiz; + uint16_t fifo_rxfsiz; + uint16_t fifo_ptxfsiz; + /* Debounce lock */ + uint8_t debouncing: 1; + /* TODO: Port context and callback? */ + /* TODO: FRAME LIST? */ + /* TODO: Pipes/channels LIST? */ + /* TODO: spinlock? */ +}; + +#define UHC_DWC2_CHAN_REG(base, chan_idx) \ + ((struct usb_dwc2_host_chan *)(((mem_addr_t)(base)) + 0x500UL + ((chan_idx) * 0x20UL))) + +K_THREAD_STACK_DEFINE(uhc_dwc2_stack, CONFIG_UHC_DWC2_STACK_SIZE); + +/* + * DWC2 low-level HAL Functions, + * + * Never use device structs or other driver config/data structs, but only the registers, + * directly provided as arguments. + */ + +static void dwc2_hal_flush_rx_fifo(struct usb_dwc2_reg *const dwc2) +{ + sys_write32(USB_DWC2_GRSTCTL_RXFFLSH, (mem_addr_t)&dwc2->grstctl); + + while (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_RXFFLSH) { + continue; + } +} + +static void dwc2_hal_flush_tx_fifo(struct usb_dwc2_reg *const dwc2, const uint8_t fnum) +{ + uint32_t grstctl; + + grstctl = usb_dwc2_set_grstctl_txfnum(fnum) | USB_DWC2_GRSTCTL_TXFFLSH; + sys_write32(grstctl, (mem_addr_t)&dwc2->grstctl); + + while (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_TXFFLSH) { + continue; + } +} + +static inline void dwc2_hal_set_frame_list(struct usb_dwc2_reg *const dwc2, void *const frame_list) +{ + LOG_WRN("Setting frame list not implemented yet"); +} + +static inline void dwc2_hal_periodic_enable(struct usb_dwc2_reg *const dwc2) +{ + LOG_WRN("Enabling periodic scheduling not implemented yet"); +} + +static inline void dwc2_hal_port_init(struct usb_dwc2_reg *const dwc2) +{ + sys_clear_bits((mem_addr_t)&dwc2->haintmsk, 0xFFFFFFFFUL); + sys_set_bits((mem_addr_t)&dwc2->gintmsk, USB_DWC2_GINTSTS_PRTINT | USB_DWC2_GINTSTS_HCHINT); +} + +#define USB_DWC2_HPRT_W1C_MSK \ + (USB_DWC2_HPRT_PRTENA | USB_DWC2_HPRT_PRTCONNDET | USB_DWC2_HPRT_PRTENCHNG | \ + USB_DWC2_HPRT_PRTOVRCURRCHNG) + +static inline void dwc2_hal_toggle_reset(struct usb_dwc2_reg *const dwc2, const bool reset_on) +{ + uint32_t hprt; + + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + if (reset_on) { + hprt |= USB_DWC2_HPRT_PRTRST; + } else { + hprt &= ~USB_DWC2_HPRT_PRTRST; + } + sys_write32(hprt & (~USB_DWC2_HPRT_W1C_MSK), (mem_addr_t)&dwc2->hprt); +} + +static inline void dwc2_hal_toggle_power(struct usb_dwc2_reg *const dwc2, const bool power_on) +{ + uint32_t hprt; + + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + if (power_on) { + hprt |= USB_DWC2_HPRT_PRTPWR; + } else { + hprt &= ~USB_DWC2_HPRT_PRTPWR; + } + sys_write32(hprt & (~USB_DWC2_HPRT_W1C_MSK), (mem_addr_t)&dwc2->hprt); +} + +static int dwc2_hal_core_reset(struct usb_dwc2_reg *const dwc2, const k_timeout_t timeout) +{ + const k_timepoint_t timepoint = sys_timepoint_calc(timeout); + + /* Check AHB master idle state */ + while ((sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_AHBIDLE) == 0) { + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for AHB idle timeout, GRSTCTL 0x%08x", + sys_read32((mem_addr_t)&dwc2->grstctl)); + return -EIO; + } + + k_busy_wait(1); + } + + /* Apply Core Soft Reset */ + sys_write32(USB_DWC2_GRSTCTL_CSFTRST, (mem_addr_t)&dwc2->grstctl); + + /* Wait for reset to complete */ + while ((sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_CSFTRST) != 0 && + (sys_read32((mem_addr_t)&dwc2->grstctl) & USB_DWC2_GRSTCTL_CSFTRSTDONE) == 0) { + if (sys_timepoint_expired(timepoint)) { + LOG_ERR("Wait for CSR done timeout, GRSTCTL 0x%08x", + sys_read32((mem_addr_t)&dwc2->grstctl)); + return -EIO; + } + + k_busy_wait(1); + } + + /* CSFTRSTDONE is W1C so the write must have the bit set to clear it */ + sys_clear_bits((mem_addr_t)&dwc2->grstctl, USB_DWC2_GRSTCTL_CSFTRST); + + LOG_DBG("DWC2 core reset done"); + + return 0; +} + +static inline enum uhc_dwc2_speed dwc2_hal_get_port_speed(struct usb_dwc2_reg *const dwc2) +{ + uint32_t hprt; + + hprt = sys_read32((mem_addr_t)&dwc2->hprt); + return (hprt & USB_DWC2_HPRT_PRTSPD_MASK) >> USB_DWC2_HPRT_PRTSPD_POS; +} + +/* + * DWC2 FIFO Management + * + * Programming Guide 2.1.2 FIFO RAM allocation + * + * RX: + * - Largest-EPsize/4 + 2 (status info). recommended x2 if high bandwidth or multiple ISO are used. + * - 2 for transfer complete and channel halted status + * - 1 for each Control/Bulk out endpoint to Handle NAK/NYET (i.e max is number of host channel) + * + * TX non-periodic (NPTX): + * - At least largest-EPsize/4, recommended x2 + * + * TX periodic (PTX): + * - At least largest-EPsize*MulCount/4 (MulCount up to 3 for high-bandwidth ISO/interrupt) + */ + +enum { + EPSIZE_BULK_LS = 64, + EPSIZE_BULK_FS = 64, + EPSIZE_BULK_HS = 512, + + EPSIZE_ISO_FS_MAX = 1023, + EPSIZE_ISO_HS_MAX = 1024, +}; + +static inline void uhc_dwc2_config_fifo_fixed_dma(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + const enum uhc_dwc2_speed speed = dwc2_hal_get_port_speed(dwc2); + uint32_t nptx_largest; + const uint32_t ptx_largest = 256 / 4; + + LOG_DBG("Configuring FIFO sizes"); + + switch (speed) { + case UHC_DWC2_SPEED_LOW: + nptx_largest = EPSIZE_BULK_LS / 4; + break; + case UHC_DWC2_SPEED_FULL: + nptx_largest = EPSIZE_BULK_FS / 4; + break; + case UHC_DWC2_SPEED_HIGH: + nptx_largest = EPSIZE_BULK_HS / 4; + break; + default: + CODE_UNREACHABLE; + } + + priv->fifo_top = FIELD_GET(USB_DWC2_GHWCFG3_DFIFODEPTH_MASK, config->ghwcfg3) - + (FIELD_GET(USB_DWC2_GHWCFG2_NUMHSTCHNL_MASK, config->ghwcfg2) + 1); + priv->fifo_nptxfsiz = 2 * nptx_largest; + priv->fifo_rxfsiz = 2 * (ptx_largest + 2) + + (FIELD_GET(USB_DWC2_GHWCFG2_NUMHSTCHNL_MASK, config->ghwcfg2) + 1); + priv->fifo_ptxfsiz = priv->fifo_top - (priv->fifo_nptxfsiz + priv->fifo_rxfsiz); + + /* TODO: verify ptxfsiz is overflowed */ + + LOG_DBG("FIFO sizes: top=%u, nptx=%u, rx=%u, ptx=%u", priv->fifo_top * 4, + priv->fifo_nptxfsiz * 4, priv->fifo_rxfsiz * 4, priv->fifo_ptxfsiz * 4); +} + +static inline void dwc2_apply_fifo_config(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + uint16_t fifo_available = priv->fifo_top; + + sys_write32(fifo_available << USB_DWC2_GDFIFOCFG_EPINFOBASEADDR_POS | fifo_available, + (mem_addr_t)&dwc2->gdfifocfg); + + fifo_available -= priv->fifo_rxfsiz; + + sys_write32(priv->fifo_rxfsiz << USB_DWC2_GRXFSIZ_RXFDEP_POS, (mem_addr_t)&dwc2->grxfsiz); + + fifo_available -= priv->fifo_nptxfsiz; + + sys_write32(priv->fifo_nptxfsiz << USB_DWC2_GNPTXFSIZ_NPTXFDEP_POS | fifo_available, + (mem_addr_t)&dwc2->gnptxfsiz); + + fifo_available -= priv->fifo_ptxfsiz; + + sys_write32(priv->fifo_ptxfsiz << USB_DWC2_HPTXFSIZ_PTXFSIZE_POS | fifo_available, + (mem_addr_t)&dwc2->hptxfsiz); + + dwc2_hal_flush_tx_fifo(dwc2, 0x10UL); + dwc2_hal_flush_rx_fifo(dwc2); + + LOG_DBG("FIFO configuration applied nptx=%u, rx=%u, ptx=%u", + priv->fifo_nptxfsiz * 4, priv->fifo_rxfsiz * 4, priv->fifo_ptxfsiz * 4); +} + +/* + * DWC2 Port Management + * + * Operation of the USB port and handling if events related to it, and helpers to query information + * about their speed, occupancy, status... + */ + +/* Host Port Control and Status Register */ +#define USB_DWC2_HPRT_PRTENCHNG BIT(3) +#define USB_DWC2_HPRT_PRTOVRCURRCHNG BIT(5) +#define USB_DWC2_HPRT_PRTCONNDET BIT(1) + +#define CORE_INTRS_EN_MSK (USB_DWC2_GINTSTS_DISCONNINT) + +/* Interrupts that pertain to core events */ +#define CORE_EVENTS_INTRS_MSK (USB_DWC2_GINTSTS_DISCONNINT | USB_DWC2_GINTSTS_HCHINT) + +/* Interrupt that pertain to host port events */ +#define PORT_EVENTS_INTRS_MSK \ + (USB_DWC2_HPRT_PRTCONNDET | USB_DWC2_HPRT_PRTENCHNG | USB_DWC2_HPRT_PRTOVRCURRCHNG) + +static void uhc_dwc2_debounce_enable(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + + /* Disable Connection and disconnection interrupts to prevent spurious events */ + sys_clear_bits((mem_addr_t)&dwc2->gintmsk, + USB_DWC2_GINTSTS_PRTINT | USB_DWC2_GINTSTS_DISCONNINT); + priv->debouncing = 1; +} + +static inline void uhc_dwc2_debounce_disable(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + + priv->debouncing = 0; + /* Clear Connection and disconnection interrupt in case it triggered again */ + sys_set_bits((mem_addr_t)&dwc2->gintsts, USB_DWC2_GINTSTS_DISCONNINT); + /* Clear the PRTCONNDET interrupt by writing 1 to the corresponding bit (W1C logic) */ + sys_set_bits((mem_addr_t)&dwc2->hprt, USB_DWC2_HPRT_PRTCONNDET); + /* Re-enable the HPRT (connection) and disconnection interrupts */ + sys_set_bits((mem_addr_t)&dwc2->gintmsk, + USB_DWC2_GINTSTS_PRTINT | USB_DWC2_GINTSTS_DISCONNINT); +} + +static inline void uhc_dwc2_port_debounce(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + + k_msleep(DEBOUNCE_DELAY_MS); + + /* Check the post-debounce state (i.e., whether it's actually connected/disconnected) */ + if ((sys_read32((mem_addr_t)&dwc2->hprt) & USB_DWC2_HPRT_PRTCONNSTS) != 0) { + priv->port_state = UHC_PORT_STATE_DISABLED; + } else { + priv->port_state = UHC_PORT_STATE_DISCONNECTED; + } +} + +static inline void uhc_dwc2_init_hfir(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const enum uhc_dwc2_speed speed = dwc2_hal_get_port_speed(dwc2); + uint32_t hfir; + + hfir = sys_read32((mem_addr_t)&dwc2->hfir); + + /* Disable dynamic loading */ + hfir &= ~USB_DWC2_HFIR_HFIRRLDCTRL; + + /* Disable dynamic loading */ + hfir &= ~USB_DWC2_HFIR_HFIRRLDCTRL; + /* Set frame interval to be equal to 1ms + * Note: FSLS PHY has an implicit 8 divider applied when in LS mode, + * so the values of FSLSPclkSel and FrInt have to be adjusted accordingly. + */ + hfir &= ~USB_DWC2_HFIR_FRINT_MASK; + switch (speed) { + case UHC_DWC2_SPEED_LOW: + hfir |= (6 * 1000) << USB_DWC2_HFIR_FRINT_POS; + break; + case UHC_DWC2_SPEED_FULL: + hfir |= (48 * 1000) << USB_DWC2_HFIR_FRINT_POS; + break; + case UHC_DWC2_SPEED_HIGH: + hfir |= (60 * 125) << USB_DWC2_HFIR_FRINT_POS; + break; + } + + sys_write32(hfir, (mem_addr_t)&dwc2->hfir); +} + +static int uhc_dwc2_power_on(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + + /* Port can only be powered on if it's currently unpowered */ + if (priv->port_state == UHC_PORT_STATE_NOT_POWERED) { + priv->port_state = UHC_PORT_STATE_DISCONNECTED; + dwc2_hal_port_init(dwc2); + dwc2_hal_toggle_power(dwc2, true); + return 0; + } + + return -EINVAL; +} + +static inline int uhc_dwc2_port_reset(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct usb_dwc2_reg *const dwc2 = config->base; + int ret; + + /* TODO: implement port checks */ + + /* Hint: + * Port can only a reset when it is in the enabled or disabled (in the case of a new + * connection) states. priv->port_state == UHC_PORT_STATE_ENABLED; + * priv->port_state == UHC_PORT_STATE_DISABLED; + */ + + /* Proceed to resetting the bus: + * - Update the port's state variable + * - Hold the bus in the reset state for RESET_HOLD_MS. + * - Return the bus to the idle state for RESET_RECOVERY_MS + * During this reset the port state should be set to RESETTING and do not change. + */ + priv->port_state = UHC_PORT_STATE_RESETTING; + dwc2_hal_toggle_reset(dwc2, true); + + /* Hold the bus in the reset state */ + k_msleep(RESET_HOLD_MS); + + if (priv->port_state != UHC_PORT_STATE_RESETTING) { + /* The port state has unexpectedly changed */ + LOG_ERR("Port state changed during reset"); + ret = -EIO; + goto bailout; + } + + /* Return the bus to the idle state. Port enabled event should occur */ + dwc2_hal_toggle_reset(dwc2, false); + + /* Give the port time to recover */ + k_msleep(RESET_RECOVERY_MS); + + if (priv->port_state != UHC_PORT_STATE_RESETTING) { + /* The port state has unexpectedly changed */ + LOG_ERR("Port state changed during reset"); + ret = -EIO; + goto bailout; + } + + ret = 0; +bailout: + /* TODO: For each chan, reinitialize the channel with EP characteristics */ + /* TODO: Sync CACHE */ + return ret; +} + +static int uhc_dwc2_init(const struct device *const dev); + +/* + * Port recovery is necessary when the port is in an error state and needs to be reset. + */ +static inline int uhc_dwc2_port_recovery(const struct device *const dev) +{ + int ret; + + /* TODO: Implement port checks */ + + /* Port should be in recovery state and no ongoing transfers + * Port flags should be 0 + */ + + ret = uhc_dwc2_quirk_irq_disable_func(dev); + if (ret) { + LOG_ERR("Quirk IRQ disable failed %d", ret); + return ret; + } + + /* Init controller */ + ret = uhc_dwc2_init(dev); + if (ret) { + LOG_ERR("Failed to init controller: %d", ret); + return ret; + } + + ret = uhc_dwc2_quirk_irq_enable_func(dev); + if (ret) { + LOG_ERR("Quirk IRQ enable failed %d", ret); + return ret; + } + + ret = uhc_dwc2_power_on(dev); + if (ret) { + LOG_ERR("Failed to power on root port: %d", ret); + return ret; + } + + return ret; +} + +/* + * Buffer management + * + * Functions handling the operation of buffers: loading-unloading of the data, filling the + * content, allocate and pass them between USB stack transfers and USB hardware. + */ + +static inline bool uhc_dwc2_buffer_is_done(struct uhc_dwc2_chan *const chan) +{ + /* Only control transfers need to be continued */ + if (chan->type != UHC_DWC2_XFER_TYPE_CTRL) { + return true; + } + + return (chan->cur_stg == 2); +} + +static inline void uhc_dwc2_buffer_fill_ctrl(struct uhc_dwc2_chan *const chan, + struct uhc_transfer *const xfer) +{ + const struct usb_setup_packet *const setup_pkt = (void *)xfer->setup_pkt; + + chan->cur_stg = 0; + chan->data_stg_in = usb_reqtype_is_to_host(setup_pkt); + chan->data_stg_skip = (setup_pkt->wLength == 0); + chan->is_setting_addr = 0; + + if (setup_pkt->bRequest == USB_SREQ_SET_ADDRESS) { + chan->is_setting_addr = 1; + chan->new_addr = setup_pkt->wValue & 0x7F; + LOG_DBG("Set address request, new address %d", chan->new_addr); + } + + LOG_DBG("data_stg_in: %d, data_stg_skip: %d", chan->data_stg_in, chan->data_stg_skip); + + /* Save the xfer pointer in the buffer */ + chan->xfer = xfer; + + /* Control stage is always OUT */ + //sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + + /* TODO Sync data from cache to memory. For OUT and CTRL transfers */ +} + +static inline uint16_t calc_packet_count(const uint16_t size, const uint8_t mps) +{ + if (size == 0) { + return 1; /* in Buffer DMA mode Zero Length Packet still counts as 1 packet */ + } else { + return DIV_ROUND_UP(size, mps); + } +} + +static void uhc_dwc2_buffer_continue(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_transfer *const xfer = chan->xfer; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + bool next_dir_is_in; + enum uhc_dwc2_ctrl_stage next_pid; + uint16_t size = 0; + uint8_t *dma_addr = NULL; + uint16_t pkt_cnt; + uint32_t hctsiz; + uint32_t hcchar; + + __ASSERT(xfer != NULL, "No transfer assigned to buffer"); + __ASSERT(chan->cur_stg != 2, "Invalid control stage: %d", chan->cur_stg); + + if (chan->cur_stg == 0) { /* Just finished control stage */ + if (chan->data_stg_skip) { + /* No data stage. Go straight to status stage */ + next_dir_is_in = true; /* With no data stage, status stage must be IN */ + next_pid = CTRL_STAGE_DATA1; /* Status stage always has a PID of DATA1 */ + chan->cur_stg = 2; /* Skip over */ + } else { + /* Go to data stage */ + next_dir_is_in = chan->data_stg_in; + /* Data stage always starts with a PID of DATA1 */ + next_pid = CTRL_STAGE_DATA1; + chan->cur_stg = 1; + + /* NOTE: + * For OUT - number of bytes host sends to device + * For IN - number of bytes host reserves to receive + */ + size = xfer->buf->size; + + /* TODO: Toggle PID? */ + + /* TODO: Check if the buffer is large enough for the next transfer? */ + + /* TODO: Check that the buffer is DMA and CACHE aligned and compatible with + * the DMA (better to do this on enqueue) + */ + + if (xfer->buf != NULL) { + /* Get the tail of the buffer to append data */ + dma_addr = net_buf_tail(xfer->buf); + /* TODO: Ensure the buffer has enough space? */ + net_buf_add(xfer->buf, size); + } + } + } else { + /* cur_stg == 1. Just finished data stage. Go to status stage + * Status stage is always the opposite direction of data stage + */ + next_dir_is_in = !chan->data_stg_in; + next_pid = CTRL_STAGE_DATA1; /* Status stage always has a PID of DATA1 */ + chan->cur_stg = 2; + } + + /* Calculate new packet count */ + pkt_cnt = calc_packet_count(size, chan->ep_mps); + + if (next_dir_is_in) { + sys_set_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } else { + sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } + + hctsiz = (next_pid << USB_DWC2_HCTSIZ_PID_POS) & USB_DWC2_HCTSIZ_PID_MASK; + hctsiz |= (pkt_cnt << USB_DWC2_HCTSIZ_PKTCNT_POS) & USB_DWC2_HCTSIZ_PKTCNT_MASK; + hctsiz |= (size << USB_DWC2_HCTSIZ_XFERSIZE_POS) & USB_DWC2_HCTSIZ_XFERSIZE_MASK; + sys_write32(hctsiz, (mem_addr_t)&chan_regs->hctsiz); + + sys_write32((uint32_t)dma_addr, (mem_addr_t)&chan_regs->hcdma); + + /* TODO: Configure split transaction if needed */ + + /* TODO: sync CACHE */ + hcchar = sys_read32((mem_addr_t)&chan_regs->hcchar); + hcchar |= USB_DWC2_HCCHAR0_CHENA; + hcchar &= ~USB_DWC2_HCCHAR0_CHDIS; + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); +} + +/* + * Interrupt handler (ISR) + * + * Handle the interrupts being dispatched into events, as well as some immediate handling of + * events directly from the IRQ handler. + */ + +static void uhc_dwc2_isr_chan_handler(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + uint32_t chan_event = 0; + uint32_t hcint; + + /* Clear the interrupt bits by writing them back */ + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* Note: + * Do not change order of checks as some events take precedence over others. + * Errors > Channel Halt Request > Transfer completed + */ + if (hcint & (USB_DWC2_HCINT_STALL | USB_DWC2_HCINT_BBLERR | USB_DWC2_HCINT_XACTERR)) { + __ASSERT(hcint & USB_DWC2_HCINT_CHHLTD, + "Channel error without channel halted interrupt"); + + LOG_ERR("Channel %d error: 0x%08x", chan->chan_idx, hcint); + /* TODO: Store the error in hal context */ + chan_event |= BIT(DWC2_CHAN_EVENT_ERROR); + + } else if (hcint & USB_DWC2_HCINT_CHHLTD) { + if (chan->halt_requested) { + chan->halt_requested = 0; + chan_event |= BIT(DWC2_CHAN_EVENT_HALT_REQ); + } else if (uhc_dwc2_buffer_is_done(chan)) { + chan_event |= BIT(DWC2_CHAN_EVENT_CPLT); + } else { + uhc_dwc2_buffer_continue(dev, chan); + } + + } else if (hcint & USB_DWC2_HCINT_XFERCOMPL) { + /* Note: + * The channel isn't halted yet, so we need to halt it manually to stop the + * execution of the next packet. Relevant only for Scatter-Gather DMA and never + * occurs oin Buffer DMA. + */ + sys_set_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_CHDIS); + + /* + * After setting the halt bit, this will generate another channel halted interrupt. + * We treat this interrupt as no event, then cycle back with the channel halted + * interrupt to handle the CPLT event. + */ + } else { + __ASSERT(false, "Unknown channel interrupt, HCINT=%08Xh", hcint); + } + + if (chan_event != 0) { + atomic_or(&chan->event, chan_event); + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_CHAN0 + chan->chan_idx)); + } +} + +static void uhc_dwc2_isr_handler(const struct device *const dev) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t core_intrs; + uint32_t port_intrs = 0; + uint32_t channels = 0; + + /* Read and clear core interrupt status */ + core_intrs = sys_read32((mem_addr_t)&dwc2->gintsts); + sys_write32(core_intrs, (mem_addr_t)&dwc2->gintsts); + + LOG_DBG("GINTSTS=%08Xh, HPRT=%08Xh", core_intrs, port_intrs); + + if (core_intrs & USB_DWC2_GINTSTS_PRTINT) { + port_intrs = sys_read32((mem_addr_t)&dwc2->hprt); + /* Clear the interrupt status by writing 1 to the W1C bits, except the PRTENA bit */ + sys_write32(port_intrs & ~USB_DWC2_HPRT_PRTENA, (mem_addr_t)&dwc2->hprt); + } + + /* Disconnection takes precedense over connection */ + if (core_intrs & USB_DWC2_GINTSTS_DISCONNINT) { + /* Disconnect event */ + uhc_dwc2_debounce_enable(dev); + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_DISCONNECTION)); + /* Port still connected, check port event */ + } else if (port_intrs & USB_DWC2_HPRT_PRTCONNDET && !priv->debouncing) { + uhc_dwc2_debounce_enable(dev); + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_CONNECTION)); + } else { + /* Nothing */ + } + + if (core_intrs & USB_DWC2_GINTSTS_HCHINT) { + /* One or more channels have pending interrupts. Store the mask of those channels */ + channels = sys_read32((mem_addr_t)&dwc2->haint); + for (uint8_t i; channels != 0; channels &= ~BIT(i)) { + i = __builtin_ffs(channels) - 1; + uhc_dwc2_isr_chan_handler(dev, &priv->chan[i]); + } + } + + if (port_intrs & USB_DWC2_HPRT_PRTOVRCURRCHNG) { + /* Check if this is an overcurrent or an overcurrent cleared */ + if (port_intrs & USB_DWC2_HPRT_PRTOVRCURRACT) { + /* TODO: Verify handling logic during overcurrent */ + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_OVERCURRENT)); + } else { + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_OVERCURRENT_CLEAR)); + } + } + + if (port_intrs & USB_DWC2_HPRT_PRTENCHNG) { + if (port_intrs & USB_DWC2_HPRT_PRTENA) { + /* Host port was enabled */ + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_ENABLED)); + } else { + /* Host port has been disabled */ + k_event_set(&priv->event, BIT(UHC_DWC2_EVENT_DISABLED)); + } + } + + (void)uhc_dwc2_quirk_irq_clear(dev); +} + +/* + * Initialization sequence + * + * Configure registers as described by the programmer manual. + */ + +static inline void uhc_dwc2_init_gusbcfg(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t gusbcfg; + + /* Init PHY based on the speed */ + if (FIELD_GET(USB_DWC2_GHWCFG2_HSPHYTYPE_MASK, config->ghwcfg2) != + USB_DWC2_GHWCFG2_HSPHYTYPE_NO_HS) { + gusbcfg = sys_read32((mem_addr_t)&dwc2->gusbcfg); + + /* De-select FS PHY */ + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYSEL_USB11; + + if (FIELD_GET(USB_DWC2_GHWCFG2_HSPHYTYPE_MASK, config->ghwcfg2) == + USB_DWC2_GHWCFG2_HSPHYTYPE_ULPI) { + LOG_WRN("Highspeed ULPI PHY init"); + /* Select ULPI PHY (external) */ + gusbcfg |= USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_ULPI; + /* ULPI is always 8-bit interface */ + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYIF_16_BIT; + /* ULPI select single data rate */ + gusbcfg &= ~USB_DWC2_GUSBCFG_DDR_DOUBLE; + /* Default internal VBUS Indicator and Drive */ + gusbcfg &= ~(USB_DWC2_GUSBCFG_ULPIEVBUSD | USB_DWC2_GUSBCFG_ULPIEVBUSI); + /* Disable FS/LS ULPI and Supend mode */ + gusbcfg &= ~(USB_DWC2_GUSBCFG_ULPIFSLS | USB_DWC2_GUSBCFG_ULPICLK_SUSM); + } else { + LOG_WRN("Highspeed UTMI+ PHY init"); + /* Select UTMI+ PHY (internal) */ + gusbcfg &= ~USB_DWC2_GUSBCFG_ULPI_UTMI_SEL_ULPI; + /* Set 16-bit interface if supported */ + if (FIELD_GET(USB_DWC2_GHWCFG4_PHYDATAWIDTH_MASK, config->ghwcfg4) > 0) { + gusbcfg |= USB_DWC2_GUSBCFG_PHYIF_16_BIT; + } else { + gusbcfg &= ~USB_DWC2_GUSBCFG_PHYIF_16_BIT; + } + } + sys_write32(gusbcfg, (mem_addr_t)&dwc2->gusbcfg); + } else { + sys_set_bits((mem_addr_t)&dwc2->gusbcfg, USB_DWC2_GUSBCFG_PHYSEL_USB11); + } +} + +static inline void uhc_dwc2_init_gahbcfg(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t core_intrs; + uint32_t gahbcfg; + + /* Disable Global Interrupt */ + sys_clear_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); + + /* Enable Host mode */ + sys_set_bits((mem_addr_t)&dwc2->gusbcfg, USB_DWC2_GUSBCFG_FORCEHSTMODE); + /* Wait until core is in host mode */ + while ((sys_read32((mem_addr_t)&dwc2->gintsts) & USB_DWC2_GINTSTS_CURMOD) != 1) { + continue; + } + + /* TODO: Set AHB burst mode for some ECO only for ESP32S2 */ + /* Make config quirk? */ + + /* TODO: Disable HNP and SRP capabilities */ + /* Also move to quirk? */ + + sys_clear_bits((mem_addr_t)&dwc2->gintmsk, 0xFFFFFFFFUL); + + sys_set_bits((mem_addr_t)&dwc2->gintmsk, CORE_INTRS_EN_MSK); + + /* Clear status */ + core_intrs = sys_read32((mem_addr_t)&dwc2->gintsts); + sys_write32(core_intrs, (mem_addr_t)&dwc2->gintsts); + + /* Configure AHB */ + gahbcfg = sys_read32((mem_addr_t)&dwc2->gahbcfg); + gahbcfg |= USB_DWC2_GAHBCFG_NPTXFEMPLVL; + gahbcfg &= ~USB_DWC2_GAHBCFG_HBSTLEN_MASK; + gahbcfg |= (USB_DWC2_GAHBCFG_HBSTLEN_INCR16 << USB_DWC2_GAHBCFG_HBSTLEN_POS); + sys_write32(gahbcfg, (mem_addr_t)&dwc2->gahbcfg); + + if (FIELD_GET(USB_DWC2_GHWCFG2_OTGARCH_MASK, config->ghwcfg2) == + USB_DWC2_GHWCFG2_OTGARCH_INTERNALDMA) { + sys_set_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_DMAEN); + } + + /* Enable Global Interrupt */ + sys_set_bits((mem_addr_t)&dwc2->gahbcfg, USB_DWC2_GAHBCFG_GLBINTRMASK); +} + +static void uhc_dwc2_init_hcfg(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const enum uhc_dwc2_speed speed = dwc2_hal_get_port_speed(dwc2); + uint32_t hcfg; + + hcfg = sys_read32((mem_addr_t)&dwc2->hcfg); + + /* We can select Buffer DMA of Scatter-Gather DMA mode here: Buffer DMA by default */ + hcfg &= ~USB_DWC2_HCFG_DESCDMA; + + /* Disable periodic scheduling, will enable later */ + hcfg &= ~USB_DWC2_HCFG_PERSCHEDENA; + + if (FIELD_GET(USB_DWC2_GHWCFG2_HSPHYTYPE_MASK, config->ghwcfg2) == + USB_DWC2_GHWCFG2_HSPHYTYPE_NO_HS) { + /* Disable HighSpeed support */ + hcfg |= USB_DWC2_HCFG_FSLSSUPP; + } else { + /* Enable HighSpeed support */ + hcfg &= ~USB_DWC2_HCFG_FSLSSUPP; + } + + /* Indicate to the OTG core what speed the PHY clock is at + * Note: FSLS PHY has an implicit 8 divider applied when in LS mode, + * so the values of FSLSPclkSel and FrInt have to be adjusted accordingly. + */ + switch (speed) { + case UHC_DWC2_SPEED_LOW: + hcfg &= ~USB_DWC2_HCFG_FSLSPCLKSEL_MASK; + hcfg |= 2 << USB_DWC2_HCFG_FSLSPCLKSEL_POS; + break; + case UHC_DWC2_SPEED_FULL: + hcfg &= ~USB_DWC2_HCFG_FSLSPCLKSEL_MASK; + hcfg |= 1 << USB_DWC2_HCFG_FSLSPCLKSEL_POS; + break; + case UHC_DWC2_SPEED_HIGH: + /* Leave to default value */ + break; + } + + sys_write32(hcfg, (mem_addr_t)&dwc2->hcfg); +} + +/* + * Submit a new device connected event to the higher logic. + */ +static inline void uhc_dwc2_submit_new_device(const struct device *const dev, + const enum uhc_dwc2_speed speed) +{ + switch (speed) { + case UHC_DWC2_SPEED_LOW: + LOG_INF("New Low-Speed device"); + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_LS, 0); + break; + case UHC_DWC2_SPEED_FULL: + LOG_INF("New Full-Speed device"); + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_FS, 0); + break; + case UHC_DWC2_SPEED_HIGH: + LOG_INF("New High-Speed device"); + uhc_submit_event(dev, UHC_EVT_DEV_CONNECTED_HS, 0); + break; + default: + LOG_ERR("Unsupported speed %d", speed); + } +} + +/* + * Submit a device gone event to the higher logic. + */ +static inline void uhc_dwc2_submit_dev_gone(const struct device *const dev) +{ + LOG_WRN("Dev gone"); + uhc_submit_event(dev, UHC_EVT_DEV_REMOVED, 0); +} + +/* + * Allocate a chan holding the underlying channel object and the DMA buffer for transfer purposes. + */ +static inline void uhc_dwc2_chan_config(const struct device *const dev, + const uint8_t chan_idx, + const uint8_t ep_addr, + const uint8_t dev_addr, + const enum uhc_dwc2_speed dev_speed, + const enum uhc_dwc2_xfer_type type) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct uhc_dwc2_chan *const chan = &priv->chan[0]; + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan_idx); + uint32_t hcchar; + uint32_t hcint; + + /* TODO: Double buffering scheme? */ + + /* Set the default chan's MPS to the worst case MPS for the device's speed */ + chan->ep_mps = + (dev_speed == UHC_DWC2_SPEED_LOW) ? CTRL_EP_MAX_MPS_LS : CTRL_EP_MAX_MPS_HSFS; + chan->type = type; + chan->ep_addr = ep_addr; + chan->chan_idx = chan_idx; + chan->dev_addr = dev_addr; + chan->ls_via_fs_hub = 0; + chan->interval = 0; + + LOG_DBG("Allocating channel %d", chan->chan_idx); + + /* Init underlying channel registers */ + + /* Clear the interrupt bits by writing them back */ + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* Enable channel interrupts in the core */ + sys_set_bits((mem_addr_t)&dwc2->haintmsk, (1 << chan->chan_idx)); + + /* Enable transfer complete and channel halted interrupts */ + sys_set_bits((mem_addr_t)&chan_regs->hcintmsk, + USB_DWC2_HCINT_XFERCOMPL | USB_DWC2_HCINT_CHHLTD); + + hcchar = ((uint32_t)chan->ep_mps << USB_DWC2_HCCHAR0_MPS_POS); + hcchar |= ((uint32_t)USB_EP_GET_IDX(chan->ep_addr) << USB_DWC2_HCCHAR0_EPNUM_POS); + hcchar |= ((uint32_t)chan->type << USB_DWC2_HCCHAR0_EPTYPE_POS); + hcchar |= ((uint32_t)1UL /* TODO: chan->mult */ << USB_DWC2_HCCHAR0_EC_POS); + hcchar |= ((uint32_t)chan->dev_addr << USB_DWC2_HCCHAR0_DEVADDR_POS); + + if (USB_EP_DIR_IS_IN(chan->ep_addr)) { + hcchar |= USB_DWC2_HCCHAR0_EPDIR; + } + + /* TODO: LS device plugged to HUB */ + if (false) { + hcchar |= USB_DWC2_HCCHAR0_LSPDDEV; + } + + if (chan->type == UHC_DWC2_XFER_TYPE_INTR) { + hcchar |= USB_DWC2_HCCHAR0_ODDFRM; + } + + if (chan->type == UHC_DWC2_XFER_TYPE_ISOCHRONOUS) { + LOG_WRN("ISOC channels are note supported yet"); + } + + if (chan->type == UHC_DWC2_XFER_TYPE_INTR) { + LOG_WRN(" INTR channels are note supported yet"); + } + + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); + + /* TODO: sync CACHE */ + + /* TODO: Add the chan to the list of idle chans in the port object */ +} + +/* + * Free the chan and its resources. + */ +static void uhc_dwc2_chan_deinit(const struct device *const dev, struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + + sys_clear_bits((mem_addr_t)&dwc2->haintmsk, (1 << chan->chan_idx)); +} + +static inline void uhc_dwc2_handle_port_events(const struct device *const dev, + uint32_t events) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + enum uhc_dwc2_speed port_speed; + bool port_has_device; + + LOG_DBG("Port events: 0x08%x", events); + + if (events & BIT(UHC_DWC2_EVENT_ENABLED)) { + priv->port_state = UHC_PORT_STATE_ENABLED; + + /* Configuring clock for selected speed */ + uhc_dwc2_init_hfir(dev); + + port_speed = dwc2_hal_get_port_speed(dwc2); + + dwc2_apply_fifo_config(dev); + dwc2_hal_set_frame_list(dwc2, NULL /* priv->frame_list , FRAME_LIST_LEN */); + dwc2_hal_periodic_enable(dwc2); + + uhc_dwc2_chan_config(dev, 0, 0, 0, port_speed, UHC_DWC2_XFER_TYPE_CTRL); + + /* Notify the higher logic about the new device */ + uhc_dwc2_submit_new_device(dev, port_speed); + } + + if (events & BIT(UHC_DWC2_EVENT_DISABLED)) { + /* Could be due to a disable request or reset request, or due to a port error */ + /* Ignore the disable event if it's due to a reset request */ + switch (priv->port_state) { + case UHC_PORT_STATE_RESETTING: + case UHC_PORT_STATE_ENABLED: + break; + default: + LOG_DBG("port state %d", priv->port_state); + /* Disabled due to a port error */ + LOG_ERR("Port disabled due to an error, changing state to recovery"); + priv->port_state = UHC_PORT_STATE_RECOVERY; + events |= BIT(UHC_DWC2_EVENT_ERROR); + /* TODO: Notify the port event from ISR */ + /* TODO: Port disabled by request, not implemented yet */ + } + } + + if (events & BIT(UHC_DWC2_EVENT_CONNECTION)) { + /* Check the status after debounding the connection events */ + uhc_dwc2_port_debounce(dev); + if (priv->port_state == UHC_PORT_STATE_DISCONNECTED) { + LOG_ERR("Port is not connected after debounce"); + /* TODO: Simulate and/or verify */ + LOG_WRN("Port debounce error handling is not implemented yet"); + } else { + uhc_dwc2_debounce_disable(dev); + /* Device present but port not yet enabled: follow with an USB reset */ + uhc_dwc2_port_reset(dev); + } + } + + if ((events & BIT(UHC_DWC2_EVENT_OVERCURRENT)) || + (events & BIT(UHC_DWC2_EVENT_OVERCURRENT_CLEAR))) { + /* If port state powered, we need to power it off to protect it + * change port state to recovery + * generate port event UHC_DWC2_EVENT_OVERCURRENT + */ + LOG_ERR("Overcurrent detected on port, not implemented yet"); + /* TODO: Handle overcurrent event */ + } + + if ((events & BIT(UHC_DWC2_EVENT_DISCONNECTION)) || + (events & BIT(UHC_DWC2_EVENT_ERROR)) || + (events & BIT(UHC_DWC2_EVENT_OVERCURRENT))) { + port_has_device = false; + + switch (priv->port_state) { + case UHC_PORT_STATE_DISABLED: + break; + case UHC_PORT_STATE_NOT_POWERED: + case UHC_PORT_STATE_ENABLED: + port_has_device = true; + break; + default: + LOG_ERR("Unexpected port state %d", priv->port_state); + break; + } + + if (port_has_device) { + uhc_dwc2_chan_deinit(dev, &priv->chan[0]); + uhc_dwc2_submit_dev_gone(dev); + } + } + + /* Failure events that need a port recovery */ + if ((events & BIT(UHC_DWC2_EVENT_ERROR)) || + (events & BIT(UHC_DWC2_EVENT_OVERCURRENT))) { + uhc_dwc2_port_recovery(dev); + } +} + +static inline void uhc_dwc2_handle_chan_events(const struct device *const dev, + struct uhc_dwc2_chan *const chan) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + const uint32_t events = atomic_clear(&chan->event); + + LOG_DBG("Channel events: 0x%08x", events); + + if (events & BIT(DWC2_CHAN_EVENT_CPLT)) { + /* XFER transfer is done, process the transfer and release the chan buffer */ + struct uhc_transfer *const xfer = (struct uhc_transfer *)chan->xfer; + + if (xfer->buf != NULL) { + LOG_HEXDUMP_INF(xfer->buf->data, xfer->buf->len, "data"); + } + + /* TODO: Refactor the address setting logic. */ + /* To configure the channel, we need to get the dev addr from higher logic */ + if (chan->is_setting_addr) { + chan->is_setting_addr = 0; + chan->dev_addr = chan->new_addr; + /* Set the new device address in the channel */ + sys_set_bits((mem_addr_t)&chan_regs->hcchar, + (chan->dev_addr << USB_DWC2_HCCHAR0_DEVADDR_POS)); + k_msleep(SET_ADDR_DELAY_MS); + } + + uhc_xfer_return(dev, xfer, 0); + } + + if (events & BIT(DWC2_CHAN_EVENT_ERROR)) { + LOG_ERR("Channel error handling not implemented yet"); + /* TODO: get channel error, halt the chan */ + } + + if (events & BIT(DWC2_CHAN_EVENT_HALT_REQ)) { + LOG_ERR("Channel halt request handling not implemented yet"); + + /* TODO: Implement halting the ongoing transfer */ + + /* Hint: + * We've halted a transfer, so we need to trigger the chan callback + * Halt request event is triggered when packet is successful completed. + * But just treat all halted transfers as errors + * Notify the task waiting for the chan halt or halt it right away + * _internal_chan_event_notify(chan, true); + */ + } +} + +static void uhc_dwc2_thread(void *const arg1, void *const arg2, void *const arg3) +{ + const struct device *const dev = (const struct device *)arg1; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + uint32_t events; + + while (true) { + events = k_event_wait_safe(&priv->event, UINT32_MAX, false, K_FOREVER); + + uhc_lock_internal(dev, K_FOREVER); + + uhc_dwc2_handle_port_events(dev, events); + + for (uint32_t i = 0; i < UHC_DWC2_MAX_CHAN; i++) { + if (events & BIT(UHC_DWC2_EVENT_CHAN0 + i)) { + uhc_dwc2_handle_chan_events(dev, &priv->chan[i]); + } + } + + uhc_unlock_internal(dev); + } +} + +/* + * UHC DWC2 Driver API + */ + +static int uhc_dwc2_lock(const struct device *const dev) +{ + struct uhc_data *data = dev->data; + + return k_mutex_lock(&data->mutex, K_FOREVER); +} + +static int uhc_dwc2_unlock(const struct device *const dev) +{ + struct uhc_data *data = dev->data; + + return k_mutex_unlock(&data->mutex); +} + +static int uhc_dwc2_sof_enable(const struct device *const dev) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_bus_suspend(const struct device *const dev) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_bus_reset(const struct device *const dev) +{ + /* TODO: move the reset logic here */ + + /* Hint: + * First reset is done by the uhc dwc2 driver, so we don't need to do anything here. + */ + uhc_submit_event(dev, UHC_EVT_RESETED, 0); + + return 0; +} + +static int uhc_dwc2_bus_resume(const struct device *const dev) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_config_hctsiz(const struct device *const dev, struct uhc_dwc2_chan *const chan, + const size_t buf_size) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + uint16_t pkt_cnt; + uint16_t pkt_size; + uint8_t pkt_type = USB_EP_TYPE_CONTROL; + uint8_t pkt_pid; + uint32_t hctsiz; + + pkt_cnt = calc_packet_count(buf_size, chan->ep_mps); + pkt_size = MIN(buf_size, chan->ep_mps); + + switch (pkt_type) { + case USB_EP_TYPE_CONTROL: + pkt_pid = USB_DWC2_HCTSIZ_PID_SETUP; + pkt_cnt = calc_packet_count(sizeof(struct usb_setup_packet), chan->ep_mps); + pkt_size = sizeof(struct usb_setup_packet); + break; + case USB_EP_TYPE_BULK: + case USB_EP_TYPE_INTERRUPT: + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + break; + case USB_EP_TYPE_ISO: + if (dwc2_hal_get_port_speed(dwc2) == UHC_DWC2_SPEED_HIGH) { + /* Full-Speed isochronous transfers are always having a single packet: + * - only 1 pkt needed: DATA0 + */ + if (pkt_cnt != 1) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + } else { + return -EINVAL; + } + } else if (USB_EP_DIR_IS_OUT(chan->ep_addr)) { + /* High-Speed isochronous OUT transfers are "high-bandwidth": + * - if 1 pkt needed: DATA0 + * - if 2 pkt needed: MDATA, DATA1 + * - if 3 pkt needed: MDATA, MDATA, DATA2 + */ + if (pkt_cnt == 1) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + } else { + pkt_pid = USB_DWC2_HCTSIZ_PID_MDATA; + } + } else { + /* High-Speed isochronous IN transfers are "high-bandwidth": + * - if 1 pkt needed: DATA0 + * - if 2 pkt needed: DATA1, DATA0 + * - if 3 pkt needed: DATA2, DATA1, DATA0 + */ + if (pkt_cnt == 1) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA0; + } else if (pkt_cnt == 2) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA1; + } else if (pkt_cnt == 3) { + pkt_pid = USB_DWC2_HCTSIZ_PID_DATA2; + } else { + LOG_WRN("unsupported transfer size %d, aborting", buf_size); + return -EINVAL; + } + } + break; + default: + LOG_WRN("unsupported transfer type %d, aborting", pkt_type); + return -ENOSYS; + } + + LOG_DBG("ep=%02X, mps=%d", chan->ep_addr, chan->ep_mps); + + if (USB_EP_GET_IDX(chan->ep_addr) == 0) { + /* Control stage is always OUT */ + sys_clear_bits((mem_addr_t)&chan_regs->hcchar, USB_DWC2_HCCHAR0_EPDIR); + } + + hctsiz = (pkt_pid << USB_DWC2_HCTSIZ_PID_POS) & USB_DWC2_HCTSIZ_PID_MASK; + hctsiz |= (pkt_cnt << USB_DWC2_HCTSIZ_PKTCNT_POS) & USB_DWC2_HCTSIZ_PKTCNT_MASK; + hctsiz |= (pkt_size << USB_DWC2_HCTSIZ_XFERSIZE_POS) & USB_DWC2_HCTSIZ_XFERSIZE_MASK; + sys_write32(hctsiz, (mem_addr_t)&chan_regs->hctsiz); + + return 0; +} + +static int uhc_dwc2_process_xfer(const struct device *const dev, struct uhc_transfer *const xfer) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct uhc_dwc2_chan *const chan = &priv->chan[USB_EP_GET_IDX(xfer->ep)]; + const struct usb_dwc2_host_chan *const chan_regs = UHC_DWC2_CHAN_REG(dwc2, chan->chan_idx); + const size_t buf_size = USB_EP_DIR_IS_IN(xfer->ep) ? xfer->buf->size : xfer->buf->len; + uint8_t pkt_type; + uint32_t hcint; + uint32_t hcchar; + int ret; + + /* net_buf library default to sizeof(void *) alignment, which is at least 4 bytes */ + if (xfer->ep == 0 && (uintptr_t)xfer->setup_pkt % 4 != 0) { + LOG_ERR("Setup packet address %p is not 4-byte aligned", (void *)xfer->setup_pkt); + return -EINVAL; + } + + /* TODO: Buffer addr that will used as dma addr also should be aligned */ + if (xfer->buf != NULL && (uintptr_t)net_buf_tail(xfer->buf) % 4 != 0) { + LOG_ERR("XFER buffer address %08lXh is not 4-byte aligned", + (uintptr_t)net_buf_tail(xfer->buf)); + return -EINVAL; + } + + if (xfer->interval != 0) { + LOG_ERR("Periodic transfer is not supported"); + return -EINVAL; + } + + uhc_lock_internal(dev, K_FOREVER); + + /* TODO: Use bmAttributes from the descriptors from the host class */ + if (USB_EP_GET_IDX(xfer->ep) == 0x00 || USB_EP_GET_IDX(xfer->ep) == 0x80) { + pkt_type = USB_EP_TYPE_CONTROL; + } else { + pkt_type = USB_EP_TYPE_BULK; + } + + if (pkt_type == USB_EP_TYPE_CONTROL) { + uhc_dwc2_buffer_fill_ctrl(chan, xfer); + LOG_DBG("endpoint=%02Xh, mps=%d, interval=%d, start_frame=%d, stage=%d, no_status=%d", + xfer->ep, xfer->mps, xfer->interval, xfer->start_frame, xfer->stage, + xfer->no_status); + LOG_HEXDUMP_INF(xfer->setup_pkt, 8, "setup"); + } + + ret = uhc_dwc2_config_hctsiz(dev, chan, buf_size); + if (ret != 0) { + goto err; + } + + switch (pkt_type) { + case USB_EP_TYPE_CONTROL: + sys_write32((uint32_t)xfer->setup_pkt, (mem_addr_t)&chan_regs->hcdma); + break; + default: + sys_write32((uint32_t)xfer->buf->data, (mem_addr_t)&chan_regs->hcdma); + break; + } + + /* TODO: Configure split transaction if needed */ + + hcint = sys_read32((mem_addr_t)&chan_regs->hcint); + sys_write32(hcint, (mem_addr_t)&chan_regs->hcint); + + /* TODO: sync CACHE */ + + hcchar = sys_read32((mem_addr_t)&chan_regs->hcchar); + hcchar |= USB_DWC2_HCCHAR0_CHENA; + hcchar &= ~USB_DWC2_HCCHAR0_CHDIS; + sys_write32(hcchar, (mem_addr_t)&chan_regs->hcchar); + + ret = 0; +err: + uhc_unlock_internal(dev); + return ret; +} + +static int uhc_dwc2_enqueue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + uhc_xfer_append(dev, xfer); + return uhc_dwc2_process_xfer(dev, uhc_xfer_get_next(dev)); +} + +static int uhc_dwc2_dequeue(const struct device *const dev, struct uhc_transfer *const xfer) +{ + LOG_ERR("%s not implemented", __func__); + + return -ENOSYS; +} + +static int uhc_dwc2_preinit(const struct device *const dev) +{ + struct uhc_dwc2_data *const priv = uhc_get_private(dev); + struct uhc_data *const data = dev->data; + + /* Initialize the private data structure */ + memset(priv, 0, sizeof(struct uhc_dwc2_data)); + k_mutex_init(&data->mutex); + k_event_init(&priv->event); + + /* TODO: Overwrite the DWC2 register values with the devicetree values? */ + + uhc_dwc2_quirk_caps(dev); + + k_thread_create(&priv->thread, uhc_dwc2_stack, K_THREAD_STACK_SIZEOF(uhc_dwc2_stack), + uhc_dwc2_thread, (void *)dev, NULL, NULL, + K_PRIO_COOP(CONFIG_UHC_DWC2_THREAD_PRIORITY), K_ESSENTIAL, K_NO_WAIT); + k_thread_name_set(&priv->thread, dev->name); + + return 0; +} + +static int uhc_dwc2_init(const struct device *const dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const dwc2 = config->base; + uint32_t reg; + int ret; + + ret = uhc_dwc2_quirk_init(dev); + if (ret) { + LOG_ERR("Quirk init failed %d", ret); + return ret; + } + + /* Read hardware configuration registers */ + + reg = sys_read32((mem_addr_t)&dwc2->gsnpsid); + if (reg != config->gsnpsid) { + LOG_ERR("Unexpected GSNPSID 0x%08x instead of 0x%08x", reg, config->gsnpsid); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg1); + if (reg != config->ghwcfg1) { + LOG_ERR("Unexpected GHWCFG1 0x%08x instead of 0x%08x", reg, config->ghwcfg1); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg2); + if (reg != config->ghwcfg2) { + LOG_ERR("Unexpected GHWCFG2 0x%08x instead of 0x%08x", reg, config->ghwcfg2); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg3); + if (reg != config->ghwcfg3) { + LOG_ERR("Unexpected GHWCFG3 0x%08x instead of 0x%08x", reg, config->ghwcfg3); + return -ENOTSUP; + } + + reg = sys_read32((mem_addr_t)&dwc2->ghwcfg4); + if (reg != config->ghwcfg4) { + LOG_ERR("Unexpected GHWCFG4 0x%08x instead of 0x%08x", reg, config->ghwcfg4); + return -ENOTSUP; + } + + if ((config->ghwcfg4 & USB_DWC2_GHWCFG4_DEDFIFOMODE) == 0) { + LOG_ERR("Only dedicated TX FIFO mode is supported"); + return -ENOTSUP; + } + + ret = uhc_dwc2_quirk_phy_pre_select(dev); + if (ret) { + LOG_ERR("Quirk PHY pre select failed %d", ret); + return ret; + } + + /* Software reset won't finish without PHY clock */ + if (uhc_dwc2_quirk_is_phy_clk_off(dev)) { + LOG_ERR("PHY clock is turned off, cannot reset"); + return -EIO; + } + + /* Reset core after selecting PHY */ + ret = dwc2_hal_core_reset(config->base, K_MSEC(10)); + if (ret) { + LOG_ERR("DWC2 core reset failed after PHY init: %d", ret); + return ret; + } + + ret = uhc_dwc2_quirk_phy_post_select(dev); + if (ret) { + LOG_ERR("Quirk PHY post select failed %d", ret); + return ret; + } + + /* Pre-calculate FIFO settings */ + uhc_dwc2_config_fifo_fixed_dma(dev); + + /* Program the GAHBCFG register */ + uhc_dwc2_init_gahbcfg(dev); + + /* Disable RX FIFO level interrupts for the time of the configuration */ + /* TODO */ + + /* Configure the reference clock */ + /* TODO */ + + /* Program the GUSBCFG register */ + uhc_dwc2_init_gusbcfg(dev); + + /* Disable OTG and mode-mismatch interrupts */ + /* TODO */ + + /* Program the HCFG register */ + uhc_dwc2_init_hcfg(dev); + + return 0; +} + +static int uhc_dwc2_enable(const struct device *const dev) +{ + int ret; + + ret = uhc_dwc2_quirk_pre_enable(dev); + if (ret) { + LOG_ERR("Quirk pre enable failed %d", ret); + return ret; + } + + ret = uhc_dwc2_quirk_irq_enable_func(dev); + if (ret) { + LOG_ERR("Quirk IRQ enable failed %d", ret); + return ret; + } + + ret = uhc_dwc2_power_on(dev); + if (ret) { + LOG_ERR("Failed to power on port: %d", ret); + return ret; + } + + return 0; +} + +static int uhc_dwc2_disable(const struct device *const dev) +{ + int ret; + + LOG_ERR("%s not implemented", __func__); + + ret = uhc_dwc2_quirk_disable(dev); + if (ret) { + LOG_ERR("Quirk disable failed %d", ret); + return ret; + } + + return -ENOSYS; +} + +static int uhc_dwc2_shutdown(const struct device *const dev) +{ + int ret; + + LOG_ERR("%s not implemented", __func__); + + /* TODO: Release memory for channel handles */ + + ret = uhc_dwc2_quirk_shutdown(dev); + if (ret) { + LOG_ERR("Quirk shutdown failed %d", ret); + return ret; + } + + return -ENOSYS; +} + +/* + * Device Definition and Initialization + */ + +static const struct uhc_api uhc_dwc2_api = { + /* Common */ + .lock = uhc_dwc2_lock, + .unlock = uhc_dwc2_unlock, + .init = uhc_dwc2_init, + .enable = uhc_dwc2_enable, + .disable = uhc_dwc2_disable, + .shutdown = uhc_dwc2_shutdown, + /* Bus related */ + .bus_reset = uhc_dwc2_bus_reset, + .sof_enable = uhc_dwc2_sof_enable, + .bus_suspend = uhc_dwc2_bus_suspend, + .bus_resume = uhc_dwc2_bus_resume, + /* EP related */ + .ep_enqueue = uhc_dwc2_enqueue, + .ep_dequeue = uhc_dwc2_dequeue, +}; + +#define UHC_DWC2_DT_INST_REG_ADDR(n) \ + COND_CODE_1(DT_NUM_REGS(DT_DRV_INST(n)), \ + (DT_INST_REG_ADDR(n)), \ + (DT_INST_REG_ADDR_BY_NAME(n, core))) + +static struct uhc_dwc2_data uhc_dwc2_data = { + .irq_sem = Z_SEM_INITIALIZER(uhc_dwc2_data.irq_sem, 0, 1), +}; + +static const struct uhc_dwc2_config uhc_dwc2_config_host = { + .base = (struct usb_dwc2_reg *)UHC_DWC2_DT_INST_REG_ADDR(0), + .quirks = UHC_DWC2_VENDOR_QUIRK_GET(0), + .gsnpsid = DT_INST_PROP(0, gsnpsid), + .ghwcfg1 = DT_INST_PROP(0, ghwcfg1), + .ghwcfg2 = DT_INST_PROP(0, ghwcfg2), + .ghwcfg3 = DT_INST_PROP(0, ghwcfg3), + .ghwcfg4 = DT_INST_PROP(0, ghwcfg4), +}; + +static struct uhc_data uhc_dwc2_priv_data = { + .priv = &uhc_dwc2_data, +}; + +DEVICE_DT_INST_DEFINE(0, uhc_dwc2_preinit, NULL, &uhc_dwc2_priv_data, &uhc_dwc2_config_host, + POST_KERNEL, 99, &uhc_dwc2_api); diff --git a/drivers/usb/uhc/uhc_dwc2.h b/drivers/usb/uhc/uhc_dwc2.h new file mode 100644 index 0000000000000..f4ccb658f4621 --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_UDC_DWC2_H +#define ZEPHYR_DRIVERS_USB_UDC_DWC2_H + +#include +#include +#include +#include + +/* Vendor quirks per driver instance */ +struct uhc_dwc2_vendor_quirks { + /* Called at the beginning of uhc_dwc2_init() */ + int (*init)(const struct device *dev); + /* Called on uhc_dwc2_enable() before the controller is initialized */ + int (*pre_enable)(const struct device *dev); + /* Called on uhc_dwc2_enable() after the controller is initialized */ + int (*post_enable)(const struct device *dev); + /* Called at the end of uhc_dwc2_disable() */ + int (*disable)(const struct device *dev); + /* Called at the end of uhc_dwc2_shutdown() */ + int (*shutdown)(const struct device *dev); + /* Enable interrupts function */ + int (*irq_enable_func)(const struct device *dev); + /* Disable interrupts function */ + int (*irq_disable_func)(const struct device *dev); + /* Called at the end of IRQ handling */ + int (*irq_clear)(const struct device *dev); + /* Called on driver pre-init */ + int (*caps)(const struct device *dev); + /* Called on PHY pre-select */ + int (*phy_pre_select)(const struct device *dev); + /* Called on PHY post-select and core reset */ + int (*phy_post_select)(const struct device *dev); + /* Called while waiting for bits that require PHY to be clocked */ + int (*is_phy_clk_off)(const struct device *dev); + /* PHY get clock */ + int (*get_phy_clk)(const struct device *dev); + /* Called after hibernation entry sequence */ + int (*post_hibernation_entry)(const struct device *dev); + /* Called before hibernation exit sequence */ + int (*pre_hibernation_exit)(const struct device *dev); +}; + +/* Driver configuration per instance */ +struct uhc_dwc2_config { + /* Pointer to base address of DWC_OTG registers */ + struct usb_dwc2_reg *const base; + /* Pointer to pin control configuration or NULL */ + struct pinctrl_dev_config *const pcfg; + /* Pointer to vendor quirks or NULL */ + const struct uhc_dwc2_vendor_quirks *const quirks; + void (*make_thread)(const struct device *dev); + void (*irq_enable_func)(const struct device *dev); + void (*irq_disable_func)(const struct device *dev); + uint32_t gsnpsid; + uint32_t ghwcfg1; + uint32_t ghwcfg2; + uint32_t ghwcfg3; + uint32_t ghwcfg4; +}; + +#include "uhc_dwc2_vendor_quirks.h" + +#define UHC_DWC2_VENDOR_QUIRK_GET(n) \ + COND_CODE_1(DT_NODE_VENDOR_HAS_IDX(DT_DRV_INST(n), 1), \ + (&uhc_dwc2_vendor_quirks_##n), \ + (NULL)) + +#define DWC2_QUIRK_FUNC_DEFINE(fname) \ + static inline int uhc_dwc2_quirk_##fname(const struct device *dev) \ + { \ + __maybe_unused const struct uhc_dwc2_config *const config = dev->config; \ + const struct uhc_dwc2_vendor_quirks *const quirks = \ + COND_CODE_1(IS_EQ(DT_NUM_INST_STATUS_OKAY(snps_dwc2), 1), \ + (UHC_DWC2_VENDOR_QUIRK_GET(0)), \ + (config->quirks)); \ + \ + if (quirks != NULL && quirks->fname != NULL) { \ + return quirks->fname(dev); \ + } \ + \ + return 0; \ + } + +DWC2_QUIRK_FUNC_DEFINE(init) +DWC2_QUIRK_FUNC_DEFINE(pre_enable) +DWC2_QUIRK_FUNC_DEFINE(post_enable) +DWC2_QUIRK_FUNC_DEFINE(disable) +DWC2_QUIRK_FUNC_DEFINE(shutdown) +DWC2_QUIRK_FUNC_DEFINE(irq_enable_func) +DWC2_QUIRK_FUNC_DEFINE(irq_disable_func) +DWC2_QUIRK_FUNC_DEFINE(irq_clear) +DWC2_QUIRK_FUNC_DEFINE(caps) +DWC2_QUIRK_FUNC_DEFINE(phy_pre_select) +DWC2_QUIRK_FUNC_DEFINE(phy_post_select) +DWC2_QUIRK_FUNC_DEFINE(is_phy_clk_off) +DWC2_QUIRK_FUNC_DEFINE(get_phy_clk) +DWC2_QUIRK_FUNC_DEFINE(post_hibernation_entry) +DWC2_QUIRK_FUNC_DEFINE(pre_hibernation_exit) + +#endif /* ZEPHYR_DRIVERS_USB_UDC_DWC2_H */ diff --git a/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h b/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h new file mode 100644 index 0000000000000..861ba5cff726c --- /dev/null +++ b/drivers/usb/uhc/uhc_dwc2_vendor_quirks.h @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H +#define ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H + +#include "uhc_dwc2.h" + +#include +#include +#include + +static void uhc_dwc2_isr_handler(const struct device *dev); + +#if DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct phy_context_t { + usb_phy_target_t target; + usb_phy_controller_t controller; + usb_phy_status_t status; + usb_otg_mode_t otg_mode; + usb_phy_speed_t otg_speed; + usb_phy_ext_io_conf_t *ext_io_pins; + usb_wrap_hal_context_t wrap_hal; +}; + +struct usb_dw_esp32_config { + const struct device *clock_dev; + const clock_control_subsys_t clock_subsys; + int irq_source; + int irq_priority; + int irq_flags; + struct phy_context_t *phy_ctx; +}; + +struct usb_dw_esp32_data { + struct intr_handle_data_t *int_handle; +}; + +static void uhc_dwc2_isr_handler(const struct device *dev); + +static inline int esp32_usb_otg_init(const struct device *dev, + const struct usb_dw_esp32_config *cfg, + struct usb_dw_esp32_data *data) +{ + int ret; + + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + if (!device_is_ready(cfg->clock_dev)) { + return -ENODEV; + } + + ret = clock_control_on(cfg->clock_dev, cfg->clock_subsys); + + if (ret != 0) { + return ret; + } + + /* pinout config to work in USB_OTG_MODE_HOST */ + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_OTG_IDDIG_IN_IDX, false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_VBUSVALID_IN_IDX, + false); + esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_OTG_AVALID_IN_IDX, false); + + if (cfg->phy_ctx->target == USB_PHY_TARGET_INT) { + gpio_set_drive_capability(USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability(USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); + } + + /* allocate interrupt but keep it disabled to avoid + * spurious suspend/resume event at enumeration phase + */ + ret = esp_intr_alloc(cfg->irq_source, + ESP_INTR_FLAG_INTRDISABLED | ESP_PRIO_TO_FLAGS(cfg->irq_priority) | + ESP_INT_FLAGS_CHECK(cfg->irq_flags), + (intr_handler_t)uhc_dwc2_isr_handler, (void *)dev, &data->int_handle); + + if (ret != 0) { + return -ECANCELED; + } + + LOG_DBG("PHY inited"); + return 0; +} + +static inline int esp32_usb_otg_enable_phy(struct phy_context_t *phy_ctx, bool enable) +{ + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + if (enable) { + usb_wrap_ll_enable_bus_clock(true); + usb_wrap_hal_init(&phy_ctx->wrap_hal); + +#if USB_WRAP_LL_EXT_PHY_SUPPORTED + usb_wrap_hal_phy_set_external(&phy_ctx->wrap_hal, + (phy_ctx->target == USB_PHY_TARGET_EXT)); +#endif + if (phy_ctx->target == USB_PHY_TARGET_INT) { + /* Configure pull resistors for host */ + usb_wrap_pull_override_vals_t vals = { + .dp_pu = false, + .dm_pu = false, + .dp_pd = true, + .dm_pd = true, + }; + usb_wrap_hal_phy_enable_pull_override(&phy_ctx->wrap_hal, &vals); + } + LOG_DBG("PHY enabled"); + } else { + usb_wrap_ll_enable_bus_clock(false); + usb_wrap_ll_phy_enable_pad(phy_ctx->wrap_hal.dev, false); + + LOG_DBG("PHY disabled"); + } + return 0; +} + +static inline int esp32_usb_otg_get_phy_clock(struct phy_context_t *phy_ctx) +{ + if (phy_ctx->otg_speed == USB_PHY_SPEED_FULL) { + return MHZ(48); + } + + if (phy_ctx->otg_speed == USB_PHY_SPEED_LOW) { + /* PHY has implicit divider of 8 when in low speed */ + return MHZ(48) / 8; + } + + /* non supported speed */ + return 0; +} + +#define QUIRK_ESP32_USB_OTG_DEFINE(n) \ + \ + static struct phy_context_t phy_ctx_##n = { \ + .target = USB_PHY_TARGET_INT, \ + .controller = USB_PHY_CTRL_OTG, \ + .otg_mode = USB_OTG_MODE_HOST, \ + .otg_speed = USB_PHY_SPEED_UNDEFINED, \ + .ext_io_pins = NULL, \ + .wrap_hal = {}, \ + }; \ + \ + static const struct usb_dw_esp32_config usb_otg_config_##n = { \ + .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, offset), \ + .irq_source = DT_INST_IRQ_BY_IDX(n, 0, irq), \ + .irq_priority = DT_INST_IRQ_BY_IDX(n, 0, priority), \ + .irq_flags = DT_INST_IRQ_BY_IDX(n, 0, flags), \ + .phy_ctx = &phy_ctx_##n, \ + }; \ + \ + static struct usb_dw_esp32_data usb_otg_data_##n; \ + \ + static int esp32_usb_otg_init_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_init(dev, &usb_otg_config_##n, &usb_otg_data_##n); \ + } \ + \ + static int esp32_usb_otg_enable_phy_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_enable_phy(&phy_ctx_##n, true); \ + } \ + \ + static int esp32_usb_otg_disable_phy_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_enable_phy(&phy_ctx_##n, false); \ + } \ + static int esp32_usb_int_enable_func_##n(const struct device *dev) \ + { \ + return esp_intr_enable(usb_otg_data_##n.int_handle); \ + } \ + \ + static int esp32_usb_int_disable_func_##n(const struct device *dev) \ + { \ + return esp_intr_disable(usb_otg_data_##n.int_handle); \ + } \ + \ + static int esp32_usb_get_phy_clock_##n(const struct device *dev) \ + { \ + return esp32_usb_otg_get_phy_clock(&phy_ctx_##n); \ + } \ + \ + struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \ + .init = esp32_usb_otg_init_##n, \ + .pre_enable = esp32_usb_otg_enable_phy_##n, \ + .disable = esp32_usb_otg_disable_phy_##n, \ + .irq_enable_func = esp32_usb_int_enable_func_##n, \ + .irq_disable_func = esp32_usb_int_disable_func_##n, \ + .get_phy_clk = esp32_usb_get_phy_clock_##n, \ + }; + +DT_INST_FOREACH_STATUS_OKAY(QUIRK_ESP32_USB_OTG_DEFINE) + +#endif /*DT_HAS_COMPAT_STATUS_OKAY(espressif_esp32_usb_otg) */ + +#if DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) + +#define USBHS_DT_WRAPPER_REG_ADDR(n) UINT_TO_POINTER(DT_INST_REG_ADDR_BY_NAME(n, wrapper)) + +#include +#include +#include + +#define NRF_DEFAULT_IRQ_PRIORITY 1 + +/* + * On USBHS, we cannot access the DWC2 register until VBUS is detected and + * valid. If the user tries to force usbd_enable() and the corresponding + * uhc_enable() without a "VBUS ready" notification, the event wait will block. + */ +static K_EVENT_DEFINE(usbhs_events); +#define USBHS_VBUS_READY BIT(0) +#define USBHS_VBUS_REMOVED BIT(1) + +static struct onoff_manager *pclk24m_mgr; +static struct onoff_client pclk24m_cli; + +static void vregusb_isr(const void *arg) +{ + if (NRF_VREGUSB->EVENTS_VBUSDETECTED) { + NRF_VREGUSB->EVENTS_VBUSDETECTED = 0; + k_event_post(&usbhs_events, USBHS_VBUS_READY); + } + + if (NRF_VREGUSB->EVENTS_VBUSREMOVED) { + NRF_VREGUSB->EVENTS_VBUSREMOVED = 0; + k_event_set_masked(&usbhs_events, 0, USBHS_VBUS_REMOVED); + } +} + +static inline int usbhs_enable_core(const struct device *dev) +{ + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + int err; + + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_NO_WAIT)) { + LOG_WRN("VBUS is not ready, block uhc_enable()"); + if (!k_event_wait(&usbhs_events, USBHS_VBUS_READY, false, K_FOREVER)) { + return -ETIMEDOUT; + } + } + + /* Request PCLK24M using clock control driver */ + sys_notify_init_spinwait(&pclk24m_cli.notify); + err = onoff_request(pclk24m_mgr, &pclk24m_cli); + if (err < 0) { + LOG_ERR("Failed to start PCLK24M %d", err); + return err; + } + + /* Power up peripheral */ + wrapper->ENABLE = USBHS_ENABLE_CORE_Msk; + + /* Set ID to Host and disable D+ pull-up */ + wrapper->PHY.OVERRIDEVALUES = (1 << 24) | (1 << 23); + wrapper->PHY.INPUTOVERRIDE = (1 << 31) | (1 << 30) | (1 << 24) | (1 << 23); + + /* Release PHY power-on reset */ + wrapper->ENABLE = USBHS_ENABLE_PHY_Msk | USBHS_ENABLE_CORE_Msk; + + /* Wait for PHY clock to start */ + k_busy_wait(45); + + /* Release DWC2 reset */ + wrapper->TASKS_START = 1UL; + + /* Wait for clock to start to avoid hang on too early register read */ + k_busy_wait(1); + + return 0; +} + +static inline int usbhs_disable_core(const struct device *dev) +{ + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + int err; + + LOG_MODULE_DECLARE(uhc_dwc2, CONFIG_UHC_DRIVER_LOG_LEVEL); + + /* Set ID to Device and forcefully disable D+ pull-up */ + wrapper->PHY.OVERRIDEVALUES = (1 << 31); + wrapper->PHY.INPUTOVERRIDE = (1 << 31) | USBHS_PHY_INPUTOVERRIDE_VBUSVALID_Msk; + + wrapper->ENABLE = 0UL; + + /* Release PCLK24M using clock control driver */ + err = onoff_cancel_or_release(pclk24m_mgr, &pclk24m_cli); + if (err < 0) { + LOG_ERR("Failed to stop PCLK24M %d", err); + return err; + } + + return 0; +} + +static inline int usbhs_init_vreg_and_clock_and_core(const struct device *dev) +{ + /* Init VREG */ + + IRQ_CONNECT(VREGUSB_IRQn, NRF_DEFAULT_IRQ_PRIORITY, vregusb_isr, DEVICE_DT_INST_GET(0), 0); + + NRF_VREGUSB->INTEN = VREGUSB_INTEN_VBUSDETECTED_Msk | VREGUSB_INTEN_VBUSREMOVED_Msk; + NRF_VREGUSB->TASKS_START = 1; + + /* TODO: Determine conditions when VBUSDETECTED is not generated */ + if (sys_read32((mem_addr_t)NRF_VREGUSB + 0x400) & BIT(2)) { + k_event_post(&usbhs_events, USBHS_VBUS_READY); + } + + irq_enable(VREGUSB_IRQn); + + /* Init the clock */ + + pclk24m_mgr = z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF24M); + + /* Enable the core */ + + return usbhs_enable_core(dev); + + /* It is now possible to access the configuration registers */ +} + +static inline int usbhs_disable_vreg(const struct device *dev) +{ + NRF_VREGUSB->INTEN = 0; + NRF_VREGUSB->TASKS_STOP = 1; + + return 0; +} + +static inline int usbhs_init_caps(const struct device *dev) +{ + struct uhc_data *data = dev->data; + + data->caps.hs = true; + + return 0; +} + +static inline int usbhs_is_phy_clk_off(const struct device *dev) +{ + return !k_event_test(&usbhs_events, USBHS_VBUS_READY); +} + +static inline int usbhs_post_hibernation_entry(const struct device *dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const base = config->base; + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + + sys_set_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK); + + wrapper->TASKS_STOP = 1; + + return 0; +} + +static inline int usbhs_pre_hibernation_exit(const struct device *dev) +{ + const struct uhc_dwc2_config *const config = dev->config; + struct usb_dwc2_reg *const base = config->base; + NRF_USBHS_Type *wrapper = USBHS_DT_WRAPPER_REG_ADDR(0); + + sys_clear_bits((mem_addr_t)&base->pcgcctl, USB_DWC2_PCGCCTL_GATEHCLK); + + wrapper->TASKS_START = 1; + + return 0; +} + +#define UHC_DWC2_IRQ_FLAGS_TYPE0(n) 0 +#define UHC_DWC2_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type) + +#define UHC_DWC2_IRQ_FLAGS(n) _CONCAT(UHC_DWC2_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n) + +#define QUIRK_NRF_USBHS_DEFINE(n) \ + \ + static int usbhs_irq_enable_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), uhc_dwc2_isr_handler, \ + DEVICE_DT_INST_GET(n), UHC_DWC2_IRQ_FLAGS(n)); \ + \ + irq_enable(DT_INST_IRQN(n)); \ + \ + return 0; \ + } \ + \ + static int usbhs_irq_disable_func_##n(const struct device *dev) \ + { \ + irq_disable(DT_INST_IRQN(n)); \ + \ + return 0; \ + } \ + \ + struct uhc_dwc2_vendor_quirks uhc_dwc2_vendor_quirks_##n = { \ + .init = usbhs_init_vreg_and_clock_and_core, \ + .pre_enable = usbhs_enable_core, \ + .disable = usbhs_disable_core, \ + .shutdown = usbhs_disable_vreg, \ + .caps = usbhs_init_caps, \ + .is_phy_clk_off = usbhs_is_phy_clk_off, \ + .post_hibernation_entry = usbhs_post_hibernation_entry, \ + .pre_hibernation_exit = usbhs_pre_hibernation_exit, \ + .irq_enable_func = usbhs_irq_enable_func_##n, \ + .irq_disable_func = usbhs_irq_disable_func_##n, \ + }; + +DT_INST_FOREACH_STATUS_OKAY(QUIRK_NRF_USBHS_DEFINE) + +/* TODO remove from uhc_dwc2.c */ +#define IRAM_ATTR + +#endif /* DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_usbhs_nrf54l) */ + +/* Add next vendor quirks definition above this line */ + +#endif /* ZEPHYR_DRIVERS_USB_UHC_DWC2_VENDOR_QUIRKS_H */ diff --git a/dts/arm/intel_socfpga_std/socfpga.dtsi b/dts/arm/intel_socfpga_std/socfpga.dtsi index accf63c8acfaa..a6e08bf6c8327 100644 --- a/dts/arm/intel_socfpga_std/socfpga.dtsi +++ b/dts/arm/intel_socfpga_std/socfpga.dtsi @@ -238,6 +238,11 @@ ghwcfg1 = <0x00000000>; ghwcfg2 = <0x208ffc90>; ghwcfg4 = <0xfe0f0020>; + /* 8064 locations with full flexibility */ + g-rx-fifo-size = <608>; + g-np-tx-fifo-size = <32>; + g-tx-fifo-size = <512 512 512 512 512 512 512 512 + 512 512 512 512 512 512 256>; status = "disabled"; }; @@ -251,6 +256,11 @@ ghwcfg1 = <0x00000000>; ghwcfg2 = <0x208ffc90>; ghwcfg4 = <0xfe0f0020>; + /* 8064 locations with full flexibility */ + g-rx-fifo-size = <608>; + g-np-tx-fifo-size = <32>; + g-tx-fifo-size = <512 512 512 512 512 512 512 512 + 512 512 512 512 512 512 256>; status = "okay"; }; diff --git a/dts/bindings/usb/snps,dwc2.yaml b/dts/bindings/usb/snps,dwc2.yaml index d5b0167f26b32..ee345d3a92413 100644 --- a/dts/bindings/usb/snps,dwc2.yaml +++ b/dts/bindings/usb/snps,dwc2.yaml @@ -31,6 +31,12 @@ properties: description: | Number of configured IN endpoints including control endpoint. + gsnpsid: + type: int + description: | + Value of the GSNPSID register, used to identify the version of the core + and avoid mismatch by checking the register at runtime. + ghwcfg1: type: int required: true @@ -45,9 +51,34 @@ properties: Value of the GHWCFG2 register. It is used to determine available endpoint types during driver pre-initialization. + ghwcfg3: + type: int + description: | + Value of the GHWCFG3 register. It is used to determine available endpoint + types during driver pre-initialization. + ghwcfg4: type: int required: true description: | Value of the GHWCFG4 register. It is used to determine available endpoint types during driver pre-initialization. + + g-rx-fifo-size: + type: int + required: true + description: | + Rx Data FIFO Depth. Must not exceed GRXFSIZ.RxFDep power-on value. + + g-np-tx-fifo-size: + type: int + required: true + description: | + IN Endpoint 0 FIFO Depth. Must not exceed GNPTXFSIZ.NPTXFDep power-on value. + + g-tx-fifo-size: + type: array + required: true + description: | + IN Endpoint Transmit fifo depths. Values must not exceed their respective + DIEPTXFn.INEPnTxFDep power-on values. diff --git a/dts/vendor/nordic/nrf54h20.dtsi b/dts/vendor/nordic/nrf54h20.dtsi index a6a2f10c19ab9..09a9ffbfb1669 100644 --- a/dts/vendor/nordic/nrf54h20.dtsi +++ b/dts/vendor/nordic/nrf54h20.dtsi @@ -637,6 +637,16 @@ ghwcfg1 = <0xaa555000>; ghwcfg2 = <0x22abfc72>; ghwcfg4 = <0x1e10aa60>; + /* 3050 locations available, GRXFSIZ is 548, + * GNPTXFSIZ and DIEPTXFn are 512. The default + * values (upper bounds) sum up above the limit. + * Allow High-Bandwidth only on TxFIFO 1 and 2. + * Use maximum possible for RxFIFO and allocate + * everything remaining for TxFIFO 0. + */ + g-rx-fifo-size = <548>; + g-np-tx-fifo-size = <198>; + g-tx-fifo-size = <512 512 256 256 256 256 256>; status = "disabled"; }; diff --git a/dts/vendor/nordic/nrf54lm20a.dtsi b/dts/vendor/nordic/nrf54lm20a.dtsi index 1b467f1171ed4..045368ee64cd9 100644 --- a/dts/vendor/nordic/nrf54lm20a.dtsi +++ b/dts/vendor/nordic/nrf54lm20a.dtsi @@ -229,9 +229,16 @@ interrupts = <90 NRF_DEFAULT_IRQ_PRIORITY>; num-in-eps = <16>; num-out-eps = <16>; - ghwcfg1 = <0x0>; + gsnpsid = <0x4f54500b>; + ghwcfg1 = <0x00000000>; ghwcfg2 = <0x22affc52>; + ghwcfg3 = <0x0be0c0e8>; ghwcfg4 = <0x3e10aa60>; + /* 3040 locations with full flexibility */ + g-rx-fifo-size = <592>; + g-np-tx-fifo-size = <32>; + g-tx-fifo-size = <512 256 256 256 256 256 256 256 + 16 16 16 16 16 16 16>; status = "disabled"; }; diff --git a/dts/vendor/nordic/nrf9280.dtsi b/dts/vendor/nordic/nrf9280.dtsi index df0a57d99a9b0..725865cfeb0a7 100644 --- a/dts/vendor/nordic/nrf9280.dtsi +++ b/dts/vendor/nordic/nrf9280.dtsi @@ -398,6 +398,16 @@ ghwcfg1 = <0xaa555000>; ghwcfg2 = <0x22abfc72>; ghwcfg4 = <0x1e10aa60>; + /* 3050 locations available, GRXFSIZ is 548, + * GNPTXFSIZ and DIEPTXFn are 512. The default + * values (upper bounds) sum up above the limit. + * Allow High-Bandwidth only on TxFIFO 1 and 2. + * Use maximum possible for RxFIFO and allocate + * everything remaining for TxFIFO 0. + */ + g-rx-fifo-size = <548>; + g-np-tx-fifo-size = <198>; + g-tx-fifo-size = <512 512 256 256 256 256 256>; status = "disabled"; }; diff --git a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi index c7d8fbdc7ddb4..3d2ecceefdb8b 100644 --- a/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi +++ b/dts/xtensa/espressif/esp32s3/esp32s3_common.dtsi @@ -419,9 +419,14 @@ clocks = <&clock ESP32_USB_MODULE>; num-out-eps = <6>; num-in-eps = <6>; + gsnpsid = <0x4f54400a>; ghwcfg1 = <0x00000000>; ghwcfg2 = <0x224dd930>; + ghwcfg3 = <0x00c804b5>; ghwcfg4 = <0xd3f0a030>; + g-rx-fifo-size = <64>; + g-np-tx-fifo-size = <16>; + g-tx-fifo-size = <32 32 32 24>; }; timer0: counter@6001f000 { diff --git a/samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay b/samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay new file mode 100644 index 0000000000000..9f66255629ad7 --- /dev/null +++ b/samples/subsys/usb/shell/boards/esp32s3_devkitm_procpu.overlay @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +zephyr_uhc0: &usb_otg { + status = "okay"; +}; diff --git a/samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay b/samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay new file mode 100644 index 0000000000000..eefe9e01a7996 --- /dev/null +++ b/samples/subsys/usb/shell/boards/nrf54lm20dk_nrf54lm20a_cpuapp.overlay @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +zephyr_uhc0: &usbhs { + status = "okay"; +}; diff --git a/samples/subsys/usb/shell/host_prj.conf b/samples/subsys/usb/shell/host_prj.conf new file mode 100644 index 0000000000000..a5c0c6e326c35 --- /dev/null +++ b/samples/subsys/usb/shell/host_prj.conf @@ -0,0 +1,22 @@ +CONFIG_SHELL=y + +CONFIG_USB_HOST_STACK=y +CONFIG_USBH_SHELL=y + +# CONFIG_DT_HAS_ZEPHYR_UHC_VIRTUAL_ENABLED=y +# CONFIG_UHC_VIRTUAL=y + +CONFIG_UHC_DWC2=y +CONFIG_UHC_DWC2_DMA=y + +# CONFIG_ASSERT=y # Enable asserts +# CONFIG_LOG=y # Logging framework +# CONFIG_LOG_MODE_IMMEDIATE=y # Useful in ISRs +# CONFIG_PRINTK=y # Enable printk (low-level logging) +# CONFIG_STACK_SENTINEL=y # Detect stack overflows +# CONFIG_THREAD_NAME=y # Name threads for easier debugging +# CONFIG_IRQ_OFFLOAD=y # Offload handlers in debug code + +CONFIG_LOG=y +CONFIG_USBH_LOG_LEVEL_WRN=y +CONFIG_UHC_DRIVER_LOG_LEVEL_WRN=y