| /* |
| * Copyright (c) 2016 Intel Corporation. |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * @brief USB DesignWare device controller driver |
| * |
| * USB DesignWare device controller driver. The driver implements the low |
| * level control routines to deal directly with the hardware. |
| */ |
| |
| #define DT_DRV_COMPAT snps_dwc2 |
| |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/usb/usb_device.h> |
| |
| #include <usb_dwc2_hw.h> |
| #include "usb_dc_dw_stm32.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usb_dc_dw, CONFIG_USB_DRIVER_LOG_LEVEL); |
| |
| /* FIXME: The actual number of endpoints should be obtained from GHWCFG4. */ |
| enum usb_dw_in_ep_idx { |
| USB_DW_IN_EP_0 = 0, |
| USB_DW_IN_EP_1, |
| USB_DW_IN_EP_2, |
| USB_DW_IN_EP_3, |
| USB_DW_IN_EP_4, |
| USB_DW_IN_EP_5, |
| USB_DW_IN_EP_NUM |
| }; |
| |
| /* FIXME: The actual number of endpoints should be obtained from GHWCFG2. */ |
| enum usb_dw_out_ep_idx { |
| USB_DW_OUT_EP_0 = 0, |
| USB_DW_OUT_EP_1, |
| USB_DW_OUT_EP_2, |
| USB_DW_OUT_EP_3, |
| USB_DW_OUT_EP_NUM |
| }; |
| |
| #define USB_DW_CORE_RST_TIMEOUT_US 10000 |
| |
| /* FIXME: The actual MPS depends on endpoint type and bus speed. */ |
| #define DW_USB_MAX_PACKET_SIZE 64 |
| |
| /* Number of SETUP back-to-back packets */ |
| #define USB_DW_SUP_CNT 1 |
| |
| /* Get Data FIFO access register */ |
| #define USB_DW_EP_FIFO(base, idx) \ |
| (*(uint32_t *)(POINTER_TO_UINT(base) + 0x1000 * (idx + 1))) |
| |
| struct usb_dw_config { |
| struct usb_dwc2_reg *const base; |
| struct pinctrl_dev_config *const pcfg; |
| void (*irq_enable_func)(const struct device *dev); |
| int (*clk_enable_func)(void); |
| int (*pwr_on_func)(struct usb_dwc2_reg *const base); |
| }; |
| |
| /* |
| * USB endpoint private structure. |
| */ |
| struct usb_ep_ctrl_prv { |
| uint8_t ep_ena; |
| uint8_t fifo_num; |
| uint32_t fifo_size; |
| uint16_t mps; /* Max ep pkt size */ |
| usb_dc_ep_callback cb;/* Endpoint callback function */ |
| uint32_t data_len; |
| }; |
| |
| static void usb_dw_isr_handler(const void *unused); |
| |
| /* |
| * USB controller private structure. |
| */ |
| struct usb_dw_ctrl_prv { |
| usb_dc_status_callback status_cb; |
| struct usb_ep_ctrl_prv in_ep_ctrl[USB_DW_IN_EP_NUM]; |
| struct usb_ep_ctrl_prv out_ep_ctrl[USB_DW_OUT_EP_NUM]; |
| int n_tx_fifos; |
| uint8_t attached; |
| }; |
| |
| #if defined(CONFIG_PINCTRL) |
| #include <zephyr/drivers/pinctrl.h> |
| |
| static int usb_dw_init_pinctrl(const struct usb_dw_config *const config) |
| { |
| const struct pinctrl_dev_config *const pcfg = config->pcfg; |
| int ret = 0; |
| |
| if (pcfg == NULL) { |
| LOG_INF("Skip pinctrl configuration"); |
| return 0; |
| } |
| |
| ret = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret) { |
| LOG_ERR("Failed to apply default pinctrl state (%d)", ret); |
| } |
| |
| return ret; |
| } |
| #else |
| static int usb_dw_init_pinctrl(const struct usb_dw_config *const config) |
| { |
| ARG_UNUSED(config); |
| |
| return 0; |
| } |
| #endif |
| |
| #define USB_DW_GET_COMPAT_QUIRK_NONE(n) NULL |
| |
| #define USB_DW_GET_COMPAT_CLK_QUIRK_0(n) \ |
| COND_CODE_1(DT_INST_NODE_HAS_COMPAT(n, st_stm32f4_fsotg), \ |
| (clk_enable_st_stm32f4_fsotg_##n), \ |
| USB_DW_GET_COMPAT_QUIRK_NONE(n)) |
| |
| #define USB_DW_GET_COMPAT_PWR_QUIRK_0(n) \ |
| COND_CODE_1(DT_INST_NODE_HAS_COMPAT(n, st_stm32f4_fsotg), \ |
| (pwr_on_st_stm32f4_fsotg), \ |
| USB_DW_GET_COMPAT_QUIRK_NONE(n)) |
| |
| #define USB_DW_PINCTRL_DT_INST_DEFINE(n) \ |
| COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ |
| (PINCTRL_DT_INST_DEFINE(n)), ()) |
| |
| #define USB_DW_PINCTRL_DT_INST_DEV_CONFIG_GET(n) \ |
| COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ |
| ((void *)PINCTRL_DT_INST_DEV_CONFIG_GET(n)), (NULL)) |
| |
| #define USB_DW_IRQ_FLAGS_TYPE0(n) 0 |
| #define USB_DW_IRQ_FLAGS_TYPE1(n) DT_INST_IRQ(n, type) |
| #define DW_IRQ_FLAGS(n) \ |
| _CONCAT(USB_DW_IRQ_FLAGS_TYPE, DT_INST_IRQ_HAS_CELL(n, type))(n) |
| |
| #define USB_DW_DEVICE_DEFINE(n) \ |
| USB_DW_PINCTRL_DT_INST_DEFINE(n); \ |
| USB_DW_QUIRK_ST_STM32F4_FSOTG_DEFINE(n); \ |
| \ |
| static void usb_dw_irq_enable_func_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| DT_INST_IRQ(n, priority), \ |
| usb_dw_isr_handler, \ |
| 0, \ |
| DW_IRQ_FLAGS(n)); \ |
| \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } \ |
| \ |
| static const struct usb_dw_config usb_dw_cfg_##n = { \ |
| .base = (struct usb_dwc2_reg *)DT_INST_REG_ADDR(n), \ |
| .pcfg = USB_DW_PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .irq_enable_func = usb_dw_irq_enable_func_##n, \ |
| .clk_enable_func = USB_DW_GET_COMPAT_CLK_QUIRK_0(n), \ |
| .pwr_on_func = USB_DW_GET_COMPAT_PWR_QUIRK_0(n), \ |
| }; \ |
| \ |
| static struct usb_dw_ctrl_prv usb_dw_ctrl_##n; |
| |
| USB_DW_DEVICE_DEFINE(0) |
| |
| #define usb_dw_ctrl usb_dw_ctrl_0 |
| #define usb_dw_cfg usb_dw_cfg_0 |
| |
| static void usb_dw_reg_dump(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t i; |
| |
| LOG_DBG("USB registers: GOTGCTL : 0x%x GOTGINT : 0x%x GAHBCFG : " |
| "0x%x", base->gotgctl, base->gotgint, base->gahbcfg); |
| LOG_DBG(" GUSBCFG : 0x%x GINTSTS : 0x%x GINTMSK : 0x%x", |
| base->gusbcfg, base->gintsts, base->gintmsk); |
| LOG_DBG(" DCFG : 0x%x DCTL : 0x%x DSTS : 0x%x", |
| base->dcfg, base->dctl, base->dsts); |
| LOG_DBG(" DIEPMSK : 0x%x DOEPMSK : 0x%x DAINT : 0x%x", |
| base->diepmsk, base->doepmsk, base->daint); |
| LOG_DBG(" DAINTMSK: 0x%x GHWCFG1 : 0x%x GHWCFG2 : 0x%x", |
| base->daintmsk, base->ghwcfg1, base->ghwcfg2); |
| LOG_DBG(" GHWCFG3 : 0x%x GHWCFG4 : 0x%x", |
| base->ghwcfg3, base->ghwcfg4); |
| |
| for (i = 0U; i < USB_DW_OUT_EP_NUM; i++) { |
| LOG_DBG("\n EP %d registers: DIEPCTL : 0x%x DIEPINT : " |
| "0x%x", i, base->in_ep[i].diepctl, |
| base->in_ep[i].diepint); |
| LOG_DBG(" DIEPTSIZ: 0x%x DIEPDMA : 0x%x DOEPCTL : " |
| "0x%x", base->in_ep[i].dieptsiz, |
| base->in_ep[i].diepdma, |
| base->out_ep[i].doepctl); |
| LOG_DBG(" DOEPINT : 0x%x DOEPTSIZ: 0x%x DOEPDMA : " |
| "0x%x", base->out_ep[i].doepint, |
| base->out_ep[i].doeptsiz, |
| base->out_ep[i].doepdma); |
| } |
| } |
| |
| static uint8_t usb_dw_ep_is_valid(uint8_t ep) |
| { |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| /* Check if ep enabled */ |
| if ((USB_EP_DIR_IS_OUT(ep)) && ep_idx < USB_DW_OUT_EP_NUM) { |
| return 1; |
| } else if ((USB_EP_DIR_IS_IN(ep)) && ep_idx < USB_DW_IN_EP_NUM) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static uint8_t usb_dw_ep_is_enabled(uint8_t ep) |
| { |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| /* Check if ep enabled */ |
| if ((USB_EP_DIR_IS_OUT(ep)) && |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena) { |
| return 1; |
| } else if ((USB_EP_DIR_IS_IN(ep)) && |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static inline void usb_dw_udelay(uint32_t us) |
| { |
| k_busy_wait(us); |
| } |
| |
| static int usb_dw_reset(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint32_t cnt = 0U; |
| |
| /* Wait for AHB master idle state. */ |
| while (!(base->grstctl & USB_DWC2_GRSTCTL_AHBIDLE)) { |
| usb_dw_udelay(1); |
| |
| if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { |
| LOG_ERR("USB reset HANG! AHB Idle GRSTCTL=0x%08x", |
| base->grstctl); |
| return -EIO; |
| } |
| } |
| |
| /* Core Soft Reset */ |
| cnt = 0U; |
| base->grstctl |= USB_DWC2_GRSTCTL_CSFTRST; |
| |
| do { |
| if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { |
| LOG_DBG("USB reset HANG! Soft Reset GRSTCTL=0x%08x", |
| base->grstctl); |
| return -EIO; |
| } |
| usb_dw_udelay(1); |
| } while (base->grstctl & USB_DWC2_GRSTCTL_CSFTRST); |
| |
| /* Wait for 3 PHY Clocks */ |
| usb_dw_udelay(100); |
| |
| return 0; |
| } |
| |
| static int usb_dw_num_dev_eps(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| |
| return (base->ghwcfg2 >> 10) & 0xf; |
| } |
| |
| static void usb_dw_flush_tx_fifo(int ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| int fnum = usb_dw_ctrl.in_ep_ctrl[ep].fifo_num; |
| |
| base->grstctl = (fnum << 6) | (1<<5); |
| while (base->grstctl & (1<<5)) { |
| } |
| } |
| |
| static int usb_dw_tx_fifo_avail(int ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| |
| return base->in_ep[ep].dtxfsts & USB_DWC2_DTXFSTS_INEPTXFSPCAVAIL_MASK; |
| } |
| |
| /* Choose a FIFO number for an IN endpoint */ |
| static int usb_dw_set_fifo(uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| int ep_idx = USB_EP_GET_IDX(ep); |
| volatile uint32_t *reg = &base->in_ep[ep_idx].diepctl; |
| uint32_t val; |
| int fifo = 0; |
| int ded_fifo = !!(base->ghwcfg4 & USB_DWC2_GHWCFG4_DEDFIFOMODE); |
| |
| if (!ded_fifo) { |
| /* No support for shared-FIFO mode yet, existing |
| * Zephyr hardware doesn't use it |
| */ |
| return -ENOTSUP; |
| } |
| |
| /* In dedicated-FIFO mode, all IN endpoints must have a unique |
| * FIFO number associated with them in the TXFNUM field of |
| * DIEPCTLx, with EP0 always being assigned to FIFO zero (the |
| * reset default, so we don't touch it). |
| * |
| * FIXME: would be better (c.f. the dwc2 driver in Linux) to |
| * choose a FIFO based on the hardware depth: we want the |
| * smallest one that fits our configured maximum packet size |
| * for the endpoint. This just picks the next available one. |
| */ |
| if (ep_idx != 0) { |
| fifo = ++usb_dw_ctrl.n_tx_fifos; |
| if (fifo >= usb_dw_num_dev_eps()) { |
| return -EINVAL; |
| } |
| |
| reg = &base->in_ep[ep_idx].diepctl; |
| val = *reg & ~USB_DWC2_DEPCTL_TXFNUM_MASK; |
| val |= fifo << USB_DWC2_DEPCTL_TXFNUM_POS; |
| *reg = val; |
| } |
| |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].fifo_num = fifo; |
| |
| usb_dw_flush_tx_fifo(ep_idx); |
| |
| val = usb_dw_tx_fifo_avail(ep_idx); |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].fifo_size = val; |
| |
| return 0; |
| } |
| |
| static int usb_dw_ep_set(uint8_t ep, |
| uint32_t ep_mps, enum usb_dc_ep_transfer_type ep_type) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| volatile uint32_t *p_depctl; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| LOG_DBG("%s ep %x, mps %d, type %d", __func__, ep, ep_mps, ep_type); |
| |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| p_depctl = &base->out_ep[ep_idx].doepctl; |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].mps = ep_mps; |
| } else { |
| p_depctl = &base->in_ep[ep_idx].diepctl; |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].mps = ep_mps; |
| } |
| |
| if (!ep_idx) { |
| /* Set max packet size for EP0 */ |
| *p_depctl &= ~USB_DWC2_DEPCTL0_MPS_MASK; |
| |
| switch (ep_mps) { |
| case 8: |
| *p_depctl |= USB_DWC2_DEPCTL0_MPS_8 << |
| USB_DWC2_DEPCTL_MPS_POS; |
| break; |
| case 16: |
| *p_depctl |= USB_DWC2_DEPCTL0_MPS_16 << |
| USB_DWC2_DEPCTL_MPS_POS; |
| break; |
| case 32: |
| *p_depctl |= USB_DWC2_DEPCTL0_MPS_32 << |
| USB_DWC2_DEPCTL_MPS_POS; |
| break; |
| case 64: |
| *p_depctl |= USB_DWC2_DEPCTL0_MPS_64 << |
| USB_DWC2_DEPCTL_MPS_POS; |
| break; |
| default: |
| return -EINVAL; |
| } |
| /* No need to set EP0 type */ |
| } else { |
| /* Set max packet size for EP */ |
| if (ep_mps > (USB_DWC2_DEPCTL_MPS_MASK >> |
| USB_DWC2_DEPCTL_MPS_POS)) { |
| return -EINVAL; |
| } |
| |
| *p_depctl &= ~USB_DWC2_DEPCTL_MPS_MASK; |
| *p_depctl |= ep_mps << USB_DWC2_DEPCTL_MPS_POS; |
| |
| /* Set endpoint type */ |
| *p_depctl &= ~USB_DWC2_DEPCTL_EPTYPE_MASK; |
| |
| switch (ep_type) { |
| case USB_DC_EP_CONTROL: |
| *p_depctl |= USB_DWC2_DEPCTL_EPTYPE_CONTROL << |
| USB_DWC2_DEPCTL_EPTYPE_POS; |
| break; |
| case USB_DC_EP_BULK: |
| *p_depctl |= USB_DWC2_DEPCTL_EPTYPE_BULK << |
| USB_DWC2_DEPCTL_EPTYPE_POS; |
| break; |
| case USB_DC_EP_INTERRUPT: |
| *p_depctl |= USB_DWC2_DEPCTL_EPTYPE_INTERRUPT << |
| USB_DWC2_DEPCTL_EPTYPE_POS; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* sets the Endpoint Data PID to DATA0 */ |
| *p_depctl |= USB_DWC2_DEPCTL_SETD0PID; |
| } |
| |
| if (USB_EP_DIR_IS_IN(ep)) { |
| int ret = usb_dw_set_fifo(ep); |
| |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void usb_dw_prep_rx(const uint8_t ep, uint8_t setup) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| enum usb_dw_out_ep_idx ep_idx = USB_EP_GET_IDX(ep); |
| uint32_t ep_mps = usb_dw_ctrl.out_ep_ctrl[ep_idx].mps; |
| |
| /* Set max RX size to EP mps so we get an interrupt |
| * each time a packet is received |
| */ |
| |
| base->out_ep[ep_idx].doeptsiz = |
| (USB_DW_SUP_CNT << USB_DWC2_DOEPTSIZ_SUP_CNT_POS) | |
| (1 << USB_DWC2_DEPTSIZ_PKT_CNT_POS) | ep_mps; |
| |
| /* Clear NAK and enable ep */ |
| if (!setup) { |
| base->out_ep[ep_idx].doepctl |= USB_DWC2_DEPCTL_CNAK; |
| } |
| |
| base->out_ep[ep_idx].doepctl |= USB_DWC2_DEPCTL_EPENA; |
| |
| LOG_DBG("USB OUT EP%d armed", ep_idx); |
| } |
| |
| static int usb_dw_tx(uint8_t ep, const uint8_t *const data, |
| uint32_t data_len) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| enum usb_dw_in_ep_idx ep_idx = USB_EP_GET_IDX(ep); |
| uint32_t max_xfer_size, max_pkt_cnt, pkt_cnt, avail_space; |
| uint32_t ep_mps = usb_dw_ctrl.in_ep_ctrl[ep_idx].mps; |
| unsigned int key; |
| uint32_t i; |
| |
| /* Wait for FIFO space available */ |
| do { |
| avail_space = usb_dw_tx_fifo_avail(ep_idx); |
| if (avail_space == usb_dw_ctrl.in_ep_ctrl[ep_idx].fifo_size) { |
| break; |
| } |
| /* Make sure we don't hog the CPU */ |
| k_yield(); |
| } while (1); |
| |
| key = irq_lock(); |
| |
| avail_space *= 4U; |
| if (!avail_space) { |
| LOG_ERR("USB IN EP%d no space available, DTXFSTS %x", ep_idx, |
| base->in_ep[ep_idx].dtxfsts); |
| irq_unlock(key); |
| return -EAGAIN; |
| } |
| |
| /* For now tx-fifo sizes are not configured (cf usb_dw_set_fifo). Here |
| * we force available fifo size to be a multiple of ep mps in order to |
| * prevent splitting data incorrectly. |
| */ |
| avail_space -= avail_space % ep_mps; |
| if (data_len > avail_space) { |
| data_len = avail_space; |
| } |
| |
| if (data_len != 0U) { |
| /* Get max packet size and packet count for ep */ |
| if (ep_idx == USB_DW_IN_EP_0) { |
| max_pkt_cnt = |
| USB_DWC2_DIEPTSIZ0_PKT_CNT_MASK >> |
| USB_DWC2_DEPTSIZ_PKT_CNT_POS; |
| max_xfer_size = |
| USB_DWC2_DEPTSIZ0_XFER_SIZE_MASK >> |
| USB_DWC2_DEPTSIZ_XFER_SIZE_POS; |
| } else { |
| max_pkt_cnt = |
| USB_DWC2_DIEPTSIZn_PKT_CNT_MASK >> |
| USB_DWC2_DEPTSIZ_PKT_CNT_POS; |
| max_xfer_size = |
| USB_DWC2_DEPTSIZn_XFER_SIZE_MASK >> |
| USB_DWC2_DEPTSIZ_XFER_SIZE_POS; |
| } |
| |
| /* Check if transfer len is too big */ |
| if (data_len > max_xfer_size) { |
| LOG_WRN("USB IN EP%d len too big (%d->%d)", ep_idx, |
| data_len, max_xfer_size); |
| data_len = max_xfer_size; |
| } |
| |
| /* |
| * Program the transfer size and packet count as follows: |
| * |
| * transfer size = N * ep_maxpacket + short_packet |
| * pktcnt = N + (short_packet exist ? 1 : 0) |
| */ |
| |
| pkt_cnt = DIV_ROUND_UP(data_len, ep_mps); |
| if (pkt_cnt > max_pkt_cnt) { |
| LOG_WRN("USB IN EP%d pkt count too big (%d->%d)", |
| ep_idx, pkt_cnt, pkt_cnt); |
| pkt_cnt = max_pkt_cnt; |
| data_len = pkt_cnt * ep_mps; |
| } |
| } else { |
| /* Zero length packet */ |
| pkt_cnt = 1U; |
| } |
| |
| /* Set number of packets and transfer size */ |
| base->in_ep[ep_idx].dieptsiz = |
| (pkt_cnt << USB_DWC2_DEPTSIZ_PKT_CNT_POS) | data_len; |
| |
| /* Clear NAK and enable ep */ |
| base->in_ep[ep_idx].diepctl |= (USB_DWC2_DEPCTL_EPENA | |
| USB_DWC2_DEPCTL_CNAK); |
| |
| /* |
| * Write data to FIFO, make sure that we are protected against |
| * other USB register accesses. According to "DesignWare Cores |
| * USB 1.1/2.0 Device Subsystem-AHB/VCI Databook": "During FIFO |
| * access, the application must not access the UDC/Subsystem |
| * registers or vendor registers (for ULPI mode). After starting |
| * to access a FIFO, the application must complete the transaction |
| * before accessing the register." |
| */ |
| for (i = 0U; i < data_len; i += 4U) { |
| uint32_t val = data[i]; |
| |
| if (i + 1 < data_len) { |
| val |= ((uint32_t)data[i+1]) << 8; |
| } |
| if (i + 2 < data_len) { |
| val |= ((uint32_t)data[i+2]) << 16; |
| } |
| if (i + 3 < data_len) { |
| val |= ((uint32_t)data[i+3]) << 24; |
| } |
| |
| USB_DW_EP_FIFO(base, ep_idx) = val; |
| } |
| |
| irq_unlock(key); |
| |
| LOG_DBG("USB IN EP%d write %u bytes", ep_idx, data_len); |
| |
| return data_len; |
| } |
| |
| static int usb_dw_init(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep; |
| int ret; |
| |
| ret = usb_dw_reset(); |
| if (ret) { |
| return ret; |
| } |
| |
| /* |
| * Force device mode as we do no support other roles or role changes. |
| * Wait 25ms for the change to take effect. |
| */ |
| base->gusbcfg |= USB_DWC2_GUSBCFG_FORCEDEVMODE; |
| k_msleep(25); |
| |
| #ifdef CONFIG_USB_DW_USB_2_0 |
| /* set the PHY interface to be 16-bit UTMI */ |
| base->gusbcfg = (base->gusbcfg & ~USB_DWC2_GUSBCFG_PHYIF_16_BIT) | |
| USB_DWC2_GUSBCFG_PHYIF_16_BIT; |
| |
| /* Set USB2.0 High Speed */ |
| base->dcfg |= USB_DWC2_DCFG_DEVSPD_USBHS20; |
| #else |
| /* Set device speed to Full Speed */ |
| base->dcfg |= USB_DWC2_DCFG_DEVSPD_USBFS1148; |
| #endif |
| |
| /* Set NAK for all OUT EPs */ |
| for (ep = 0U; ep < USB_DW_OUT_EP_NUM; ep++) { |
| base->out_ep[ep].doepctl = USB_DWC2_DEPCTL_SNAK; |
| } |
| |
| /* Enable global interrupts */ |
| base->gintmsk = USB_DWC2_GINTSTS_OEPINT | |
| USB_DWC2_GINTSTS_IEPINT | |
| USB_DWC2_GINTSTS_ENUMDONE | |
| USB_DWC2_GINTSTS_USBRST | |
| USB_DWC2_GINTSTS_WKUPINT | |
| USB_DWC2_GINTSTS_USBSUSP; |
| |
| /* Enable global interrupt */ |
| base->gahbcfg |= USB_DWC2_GAHBCFG_GLBINTRMASK; |
| |
| /* Call vendor-specific function to enable peripheral */ |
| if (usb_dw_cfg.pwr_on_func != NULL) { |
| ret = usb_dw_cfg.pwr_on_func(base); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| /* Disable soft disconnect */ |
| base->dctl &= ~USB_DWC2_DCTL_SFTDISCON; |
| |
| usb_dw_reg_dump(); |
| |
| return 0; |
| } |
| |
| static void usb_dw_handle_reset(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| |
| LOG_DBG("USB RESET event"); |
| |
| /* Inform upper layers */ |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_RESET, NULL); |
| } |
| |
| /* Clear device address during reset. */ |
| base->dcfg &= ~USB_DWC2_DCFG_DEVADDR_MASK; |
| |
| /* enable global EP interrupts */ |
| base->doepmsk = 0U; |
| base->gintmsk |= USB_DWC2_GINTSTS_RXFLVL; |
| base->diepmsk |= USB_DWC2_DIEPINT_XFERCOMPL; |
| } |
| |
| static void usb_dw_handle_enum_done(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint32_t speed; |
| |
| speed = (base->dsts & ~USB_DWC2_DSTS_ENUMSPD_MASK) >> |
| USB_DWC2_DSTS_ENUMSPD_POS; |
| |
| LOG_DBG("USB ENUM DONE event, %s speed detected", |
| speed == USB_DWC2_DSTS_ENUMSPD_LS6 ? "Low" : "Full"); |
| |
| /* Inform upper layers */ |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_CONNECTED, NULL); |
| } |
| } |
| |
| /* USB ISR handler */ |
| static inline void usb_dw_int_rx_flvl_handler(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint32_t grxstsp = base->grxstsp; |
| uint32_t status, xfer_size; |
| uint8_t ep_idx; |
| usb_dc_ep_callback ep_cb; |
| |
| /* Packet in RX FIFO */ |
| |
| ep_idx = grxstsp & USB_DWC2_GRXSTSR_EPNUM_MASK; |
| status = (grxstsp & USB_DWC2_GRXSTSR_PKTSTS_MASK) >> |
| USB_DWC2_GRXSTSR_PKTSTS_POS; |
| xfer_size = (grxstsp & USB_DWC2_GRXSTSR_BCNT_MASK) >> |
| USB_DWC2_GRXSTSR_BCNT_POS; |
| |
| LOG_DBG("USB OUT EP%u: RX_FLVL status %u, size %u", |
| ep_idx, status, xfer_size); |
| |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len = xfer_size; |
| ep_cb = usb_dw_ctrl.out_ep_ctrl[ep_idx].cb; |
| |
| switch (status) { |
| case USB_DWC2_GRXSTSR_PKTSTS_SETUP: |
| /* Call the registered callback if any */ |
| if (ep_cb) { |
| ep_cb(USB_EP_GET_ADDR(ep_idx, USB_EP_DIR_OUT), |
| USB_DC_EP_SETUP); |
| } |
| |
| break; |
| case USB_DWC2_GRXSTSR_PKTSTS_OUT_DATA: |
| if (ep_cb) { |
| ep_cb(USB_EP_GET_ADDR(ep_idx, USB_EP_DIR_OUT), |
| USB_DC_EP_DATA_OUT); |
| } |
| |
| break; |
| case USB_DWC2_GRXSTSR_PKTSTS_OUT_DATA_DONE: |
| case USB_DWC2_GRXSTSR_PKTSTS_SETUP_DONE: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static inline void usb_dw_int_iep_handler(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint32_t ep_int_status; |
| uint8_t ep_idx; |
| usb_dc_ep_callback ep_cb; |
| |
| for (ep_idx = 0U; ep_idx < USB_DW_IN_EP_NUM; ep_idx++) { |
| if (base->daint & USB_DWC2_DAINT_INEPINT(ep_idx)) { |
| /* Read IN EP interrupt status */ |
| ep_int_status = base->in_ep[ep_idx].diepint & |
| base->diepmsk; |
| |
| /* Clear IN EP interrupts */ |
| base->in_ep[ep_idx].diepint = ep_int_status; |
| |
| LOG_DBG("USB IN EP%u interrupt status: 0x%x", |
| ep_idx, ep_int_status); |
| |
| ep_cb = usb_dw_ctrl.in_ep_ctrl[ep_idx].cb; |
| if (ep_cb && |
| (ep_int_status & USB_DWC2_DIEPINT_XFERCOMPL)) { |
| |
| /* Call the registered callback */ |
| ep_cb(USB_EP_GET_ADDR(ep_idx, USB_EP_DIR_IN), |
| USB_DC_EP_DATA_IN); |
| } |
| } |
| } |
| |
| /* Clear interrupt. */ |
| base->gintsts = USB_DWC2_GINTSTS_IEPINT; |
| } |
| |
| static inline void usb_dw_int_oep_handler(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint32_t ep_int_status; |
| uint8_t ep_idx; |
| |
| for (ep_idx = 0U; ep_idx < USB_DW_OUT_EP_NUM; ep_idx++) { |
| if (base->daint & USB_DWC2_DAINT_OUTEPINT(ep_idx)) { |
| /* Read OUT EP interrupt status */ |
| ep_int_status = base->out_ep[ep_idx].doepint & |
| base->doepmsk; |
| |
| /* Clear OUT EP interrupts */ |
| base->out_ep[ep_idx].doepint = ep_int_status; |
| |
| LOG_DBG("USB OUT EP%u interrupt status: 0x%x\n", |
| ep_idx, ep_int_status); |
| } |
| } |
| |
| /* Clear interrupt. */ |
| base->gintsts = USB_DWC2_GINTSTS_OEPINT; |
| } |
| |
| static void usb_dw_isr_handler(const void *unused) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint32_t int_status; |
| |
| ARG_UNUSED(unused); |
| |
| /* Read interrupt status */ |
| while ((int_status = (base->gintsts & base->gintmsk))) { |
| |
| LOG_DBG("USB GINTSTS 0x%x", int_status); |
| |
| if (int_status & USB_DWC2_GINTSTS_USBRST) { |
| /* Clear interrupt. */ |
| base->gintsts = USB_DWC2_GINTSTS_USBRST; |
| |
| /* Reset detected */ |
| usb_dw_handle_reset(); |
| } |
| |
| if (int_status & USB_DWC2_GINTSTS_ENUMDONE) { |
| /* Clear interrupt. */ |
| base->gintsts = USB_DWC2_GINTSTS_ENUMDONE; |
| |
| /* Enumeration done detected */ |
| usb_dw_handle_enum_done(); |
| } |
| |
| if (int_status & USB_DWC2_GINTSTS_USBSUSP) { |
| /* Clear interrupt. */ |
| base->gintsts = USB_DWC2_GINTSTS_USBSUSP; |
| |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_SUSPEND, NULL); |
| } |
| } |
| |
| if (int_status & USB_DWC2_GINTSTS_WKUPINT) { |
| /* Clear interrupt. */ |
| base->gintsts = USB_DWC2_GINTSTS_WKUPINT; |
| |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_RESUME, NULL); |
| } |
| } |
| |
| if (int_status & USB_DWC2_GINTSTS_RXFLVL) { |
| /* Packet in RX FIFO */ |
| usb_dw_int_rx_flvl_handler(); |
| } |
| |
| if (int_status & USB_DWC2_GINTSTS_IEPINT) { |
| /* IN EP interrupt */ |
| usb_dw_int_iep_handler(); |
| } |
| |
| if (int_status & USB_DWC2_GINTSTS_OEPINT) { |
| /* No OUT interrupt expected in FIFO mode, |
| * just clear interrupt |
| */ |
| usb_dw_int_oep_handler(); |
| } |
| } |
| } |
| |
| int usb_dc_attach(void) |
| { |
| int ret; |
| |
| if (usb_dw_ctrl.attached) { |
| return 0; |
| } |
| |
| if (usb_dw_cfg.clk_enable_func != NULL) { |
| ret = usb_dw_cfg.clk_enable_func(); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| ret = usb_dw_init_pinctrl(&usb_dw_cfg); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = usb_dw_init(); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Connect and enable USB interrupt */ |
| usb_dw_cfg.irq_enable_func(NULL); |
| |
| usb_dw_ctrl.attached = 1U; |
| |
| return 0; |
| } |
| |
| int usb_dc_detach(void) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| |
| if (!usb_dw_ctrl.attached) { |
| return 0; |
| } |
| |
| irq_disable(DT_INST_IRQN(0)); |
| |
| /* Enable soft disconnect */ |
| base->dctl |= USB_DWC2_DCTL_SFTDISCON; |
| |
| usb_dw_ctrl.attached = 0U; |
| |
| return 0; |
| } |
| |
| int usb_dc_reset(void) |
| { |
| int ret; |
| |
| ret = usb_dw_reset(); |
| |
| /* Clear private data */ |
| (void)memset(&usb_dw_ctrl, 0, sizeof(usb_dw_ctrl)); |
| |
| return ret; |
| } |
| |
| int usb_dc_set_address(const uint8_t addr) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| |
| if (addr > (USB_DWC2_DCFG_DEVADDR_MASK >> USB_DWC2_DCFG_DEVADDR_POS)) { |
| return -EINVAL; |
| } |
| |
| base->dcfg &= ~USB_DWC2_DCFG_DEVADDR_MASK; |
| base->dcfg |= addr << USB_DWC2_DCFG_DEVADDR_POS; |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) |
| { |
| uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr); |
| |
| LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, |
| cfg->ep_type); |
| |
| if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) { |
| LOG_ERR("invalid endpoint configuration"); |
| return -1; |
| } |
| |
| if (cfg->ep_mps > DW_USB_MAX_PACKET_SIZE) { |
| LOG_WRN("unsupported packet size"); |
| return -1; |
| } |
| |
| if (USB_EP_DIR_IS_OUT(cfg->ep_addr) && ep_idx >= USB_DW_OUT_EP_NUM) { |
| LOG_WRN("OUT endpoint address out of range"); |
| return -1; |
| } |
| |
| if (USB_EP_DIR_IS_IN(cfg->ep_addr) && ep_idx >= USB_DW_IN_EP_NUM) { |
| LOG_WRN("IN endpoint address out of range"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg) |
| { |
| uint8_t ep; |
| |
| if (!ep_cfg) { |
| return -EINVAL; |
| } |
| |
| ep = ep_cfg->ep_addr; |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| usb_dw_ep_set(ep, ep_cfg->ep_mps, ep_cfg->ep_type); |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_set_stall(const uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| base->out_ep[ep_idx].doepctl |= USB_DWC2_DEPCTL_STALL; |
| } else { |
| base->in_ep[ep_idx].diepctl |= USB_DWC2_DEPCTL_STALL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_clear_stall(const uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (!ep_idx) { |
| /* Not possible to clear stall for EP0 */ |
| return -EINVAL; |
| } |
| |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| base->out_ep[ep_idx].doepctl &= ~USB_DWC2_DEPCTL_STALL; |
| } else { |
| base->in_ep[ep_idx].diepctl &= ~USB_DWC2_DEPCTL_STALL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_halt(const uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| volatile uint32_t *p_depctl; |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (!ep_idx) { |
| /* Cannot disable EP0, just set stall */ |
| usb_dc_ep_set_stall(ep); |
| } else { |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| p_depctl = &base->out_ep[ep_idx].doepctl; |
| } else { |
| p_depctl = &base->in_ep[ep_idx].diepctl; |
| } |
| |
| /* Set STALL and disable endpoint if enabled */ |
| if (*p_depctl & USB_DWC2_DEPCTL_EPENA) { |
| *p_depctl |= USB_DWC2_DEPCTL_EPDIS | USB_DWC2_DEPCTL_STALL; |
| } else { |
| *p_depctl |= USB_DWC2_DEPCTL_STALL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (!stalled) { |
| return -EINVAL; |
| } |
| |
| *stalled = 0U; |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| if (base->out_ep[ep_idx].doepctl & USB_DWC2_DEPCTL_STALL) { |
| *stalled = 1U; |
| } |
| } else { |
| if (base->in_ep[ep_idx].diepctl & USB_DWC2_DEPCTL_STALL) { |
| *stalled = 1U; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_enable(const uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| /* enable EP interrupts */ |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| base->daintmsk |= USB_DWC2_DAINT_OUTEPINT(ep_idx); |
| } else { |
| base->daintmsk |= USB_DWC2_DAINT_INEPINT(ep_idx); |
| } |
| |
| /* Activate Ep */ |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| base->out_ep[ep_idx].doepctl |= USB_DWC2_DEPCTL_USBACTEP; |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 1U; |
| } else { |
| base->in_ep[ep_idx].diepctl |= USB_DWC2_DEPCTL_USBACTEP; |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 1U; |
| } |
| |
| if (USB_EP_DIR_IS_OUT(ep) && |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].cb != usb_transfer_ep_callback) { |
| /* Start reading now, except for transfer managed eps */ |
| usb_dw_prep_rx(ep, 0); |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_disable(const uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| /* Disable EP interrupts */ |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| base->daintmsk &= ~USB_DWC2_DAINT_OUTEPINT(ep_idx); |
| base->doepmsk &= ~USB_DWC2_DOEPINT_SETUP; |
| } else { |
| base->daintmsk &= ~USB_DWC2_DAINT_INEPINT(ep_idx); |
| base->diepmsk &= ~USB_DWC2_DIEPINT_XFERCOMPL; |
| base->gintmsk &= ~USB_DWC2_GINTSTS_RXFLVL; |
| } |
| |
| /* De-activate, disable and set NAK for Ep */ |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| base->out_ep[ep_idx].doepctl &= |
| ~(USB_DWC2_DEPCTL_USBACTEP | |
| USB_DWC2_DEPCTL_EPENA | |
| USB_DWC2_DEPCTL_SNAK); |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 0U; |
| } else { |
| base->in_ep[ep_idx].diepctl &= |
| ~(USB_DWC2_DEPCTL_USBACTEP | |
| USB_DWC2_DEPCTL_EPENA | |
| USB_DWC2_DEPCTL_SNAK); |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 0U; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_flush(const uint8_t ep) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| uint32_t cnt; |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| /* RX FIFO is global and cannot be flushed per EP */ |
| return -EINVAL; |
| } |
| |
| /* Each endpoint has dedicated Tx FIFO */ |
| base->grstctl |= ep_idx << USB_DWC2_GRSTCTL_TXFNUM_POS; |
| base->grstctl |= USB_DWC2_GRSTCTL_TXFFLSH; |
| |
| cnt = 0U; |
| |
| do { |
| if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { |
| LOG_ERR("USB TX FIFO flush HANG!"); |
| return -EIO; |
| } |
| usb_dw_udelay(1); |
| } while (base->grstctl & USB_DWC2_GRSTCTL_TXFFLSH); |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data, |
| const uint32_t data_len, uint32_t * const ret_bytes) |
| { |
| int ret; |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| /* Check if IN ep */ |
| if (USB_EP_GET_DIR(ep) != USB_EP_DIR_IN) { |
| return -EINVAL; |
| } |
| |
| /* Check if ep enabled */ |
| if (!usb_dw_ep_is_enabled(ep)) { |
| return -EINVAL; |
| } |
| |
| ret = usb_dw_tx(ep, data, data_len); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| if (ret_bytes) { |
| *ret_bytes = ret; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, |
| uint32_t *read_bytes) |
| { |
| struct usb_dwc2_reg *const base = usb_dw_cfg.base; |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| uint32_t i, j, data_len, bytes_to_copy; |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| /* Check if OUT ep */ |
| if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { |
| LOG_ERR("Wrong endpoint direction"); |
| return -EINVAL; |
| } |
| |
| /* Allow to read 0 bytes */ |
| if (!data && max_data_len) { |
| LOG_ERR("Wrong arguments"); |
| return -EINVAL; |
| } |
| |
| /* Check if ep enabled */ |
| if (!usb_dw_ep_is_enabled(ep)) { |
| LOG_ERR("Not enabled endpoint"); |
| return -EINVAL; |
| } |
| |
| data_len = usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len; |
| |
| if (!data && !max_data_len) { |
| /* When both buffer and max data to read are zero return |
| * the available data in buffer |
| */ |
| if (read_bytes) { |
| *read_bytes = data_len; |
| } |
| return 0; |
| } |
| |
| |
| if (data_len > max_data_len) { |
| LOG_ERR("Not enough room to copy all the rcvd data!"); |
| bytes_to_copy = max_data_len; |
| } else { |
| bytes_to_copy = data_len; |
| } |
| |
| LOG_DBG("Read EP%d, req %d, read %d bytes", ep, max_data_len, |
| bytes_to_copy); |
| |
| /* Data in the FIFOs is always stored per 32-bit words */ |
| for (i = 0U; i < (bytes_to_copy & ~0x3); i += 4U) { |
| *(uint32_t *)(data + i) = USB_DW_EP_FIFO(base, ep_idx); |
| } |
| if (bytes_to_copy & 0x3) { |
| /* Not multiple of 4 */ |
| uint32_t last_dw = USB_DW_EP_FIFO(base, ep_idx); |
| |
| for (j = 0U; j < (bytes_to_copy & 0x3); j++) { |
| *(data + i + j) = |
| (sys_cpu_to_le32(last_dw) >> (j * 8U)) & 0xFF; |
| } |
| } |
| |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len -= bytes_to_copy; |
| |
| if (read_bytes) { |
| *read_bytes = bytes_to_copy; |
| } |
| |
| return 0; |
| |
| } |
| |
| int usb_dc_ep_read_continue(uint8_t ep) |
| { |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| /* Check if OUT ep */ |
| if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { |
| LOG_ERR("Wrong endpoint direction"); |
| return -EINVAL; |
| } |
| |
| if (!usb_dw_ctrl.out_ep_ctrl[ep_idx].data_len) { |
| usb_dw_prep_rx(ep_idx, 0); |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, |
| const uint32_t max_data_len, uint32_t * const read_bytes) |
| { |
| if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) { |
| return -EINVAL; |
| } |
| |
| if (!data && !max_data_len) { |
| /* When both buffer and max data to read are zero the above |
| * call would fetch the data len and we simply return. |
| */ |
| return 0; |
| } |
| |
| if (usb_dc_ep_read_continue(ep) != 0) { |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb) |
| { |
| uint8_t ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (USB_EP_DIR_IS_IN(ep)) { |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].cb = cb; |
| } else { |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].cb = cb; |
| } |
| |
| return 0; |
| } |
| |
| void usb_dc_set_status_callback(const usb_dc_status_callback cb) |
| { |
| usb_dw_ctrl.status_cb = cb; |
| } |
| |
| int usb_dc_ep_mps(const uint8_t ep) |
| { |
| enum usb_dw_out_ep_idx ep_idx = USB_EP_GET_IDX(ep); |
| |
| if (!usb_dw_ctrl.attached || !usb_dw_ep_is_valid(ep)) { |
| LOG_ERR("Not attached / Invalid endpoint: EP 0x%x", ep); |
| return -EINVAL; |
| } |
| |
| if (USB_EP_DIR_IS_OUT(ep)) { |
| return usb_dw_ctrl.out_ep_ctrl[ep_idx].mps; |
| } else { |
| return usb_dw_ctrl.in_ep_ctrl[ep_idx].mps; |
| } |
| } |