| /* usb_dc_dw.c - USB DesignWare device controller driver */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation. |
| * |
| * 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. |
| */ |
| |
| #include <soc.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <misc/byteorder.h> |
| #include <usb/usb_dc.h> |
| #include "usb_dw_registers.h" |
| #include "clk.h" |
| |
| #define SYS_LOG_LEVEL CONFIG_SYS_LOG_USB_DRIVER_LEVEL |
| #include <logging/sys_log.h> |
| |
| /* convert from endpoint address to hardware endpoint index */ |
| #define USB_DW_EP_ADDR2IDX(ep) ((ep) & ~USB_EP_DIR_MASK) |
| /* get direction from endpoint address */ |
| #define USB_DW_EP_ADDR2DIR(ep) ((ep) & USB_EP_DIR_MASK) |
| /* convert from hardware endpoint index and direction to endpoint address */ |
| #define USB_DW_EP_IDX2ADDR(idx, dir) ((idx) | ((dir) & USB_EP_DIR_MASK)) |
| |
| /* Number of SETUP back-to-back packets */ |
| #define USB_DW_SUP_CNT (1) |
| |
| /* |
| * USB endpoint private structure. |
| */ |
| struct usb_ep_ctrl_prv { |
| u8_t ep_ena; |
| u16_t mps; /* Max ep pkt size */ |
| usb_dc_ep_callback cb;/* Endpoint callback function */ |
| u32_t data_len; |
| }; |
| |
| /* |
| * 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]; |
| u8_t attached; |
| }; |
| |
| |
| static struct usb_dw_ctrl_prv usb_dw_ctrl; |
| |
| static inline void _usb_dw_int_unmask(void) |
| { |
| #if defined(CONFIG_SOC_QUARK_SE_C1000) |
| QM_INTERRUPT_ROUTER->usb_0_int_mask &= ~BIT(0); |
| #endif |
| } |
| |
| #if (CONFIG_SYS_LOG_USB_DRIVER_LEVEL >= SYS_LOG_LEVEL_DEBUG) |
| static void usb_dw_reg_dump(void) |
| { |
| u8_t i; |
| |
| SYS_LOG_DBG("USB registers:"); |
| SYS_LOG_DBG(" GOTGCTL : 0x%x", USB_DW->gotgctl); |
| SYS_LOG_DBG(" GOTGINT : 0x%x", USB_DW->gotgint); |
| SYS_LOG_DBG(" GAHBCFG : 0x%x", USB_DW->gahbcfg); |
| SYS_LOG_DBG(" GUSBCFG : 0x%x", USB_DW->gusbcfg); |
| SYS_LOG_DBG(" GINTSTS : 0x%x", USB_DW->gintsts); |
| SYS_LOG_DBG(" GINTMSK : 0x%x", USB_DW->gintmsk); |
| SYS_LOG_DBG(" DCFG : 0x%x", USB_DW->dcfg); |
| SYS_LOG_DBG(" DCTL : 0x%x", USB_DW->dctl); |
| SYS_LOG_DBG(" DSTS : 0x%x", USB_DW->dsts); |
| SYS_LOG_DBG(" DIEPMSK : 0x%x", USB_DW->diepmsk); |
| SYS_LOG_DBG(" DOEPMSK : 0x%x", USB_DW->doepmsk); |
| SYS_LOG_DBG(" DAINT : 0x%x", USB_DW->daint); |
| SYS_LOG_DBG(" DAINTMSK: 0x%x", USB_DW->daintmsk); |
| SYS_LOG_DBG(" GHWCFG1 : 0x%x", USB_DW->ghwcfg1); |
| SYS_LOG_DBG(" GHWCFG2 : 0x%x", USB_DW->ghwcfg2); |
| SYS_LOG_DBG(" GHWCFG3 : 0x%x", USB_DW->ghwcfg3); |
| SYS_LOG_DBG(" GHWCFG4 : 0x%x", USB_DW->ghwcfg4); |
| |
| for (i = 0; i < USB_DW_OUT_EP_NUM; i++) { |
| SYS_LOG_DBG("\n EP %d registers:", i); |
| SYS_LOG_DBG(" DIEPCTL : 0x%x", |
| USB_DW->in_ep_reg[i].diepctl); |
| SYS_LOG_DBG(" DIEPINT : 0x%x", |
| USB_DW->in_ep_reg[i].diepint); |
| SYS_LOG_DBG(" DIEPTSIZ: 0x%x", |
| USB_DW->in_ep_reg[i].dieptsiz); |
| SYS_LOG_DBG(" DIEPDMA : 0x%x", |
| USB_DW->in_ep_reg[i].diepdma); |
| SYS_LOG_DBG(" DOEPCTL : 0x%x", |
| USB_DW->out_ep_reg[i].doepctl); |
| SYS_LOG_DBG(" DOEPINT : 0x%x", |
| USB_DW->out_ep_reg[i].doepint); |
| SYS_LOG_DBG(" DOEPTSIZ: 0x%x", |
| USB_DW->out_ep_reg[i].doeptsiz); |
| SYS_LOG_DBG(" DOEPDMA : 0x%x", |
| USB_DW->out_ep_reg[i].doepdma); |
| } |
| } |
| #else |
| #define usb_dw_reg_dump() |
| #endif |
| |
| static u8_t usb_dw_ep_is_valid(u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| /* Check if ep enabled */ |
| if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) && |
| ep_idx < USB_DW_OUT_EP_NUM) { |
| return 1; |
| } else if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) && |
| ep_idx < USB_DW_IN_EP_NUM) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static u8_t usb_dw_ep_is_enabled(u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| /* Check if ep enabled */ |
| if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) && |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena) { |
| return 1; |
| } else if ((USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) && |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static inline void usb_dw_udelay(u32_t us) |
| { |
| k_busy_wait(us); |
| } |
| |
| static int usb_dw_reset(void) |
| { |
| u32_t cnt = 0; |
| |
| /* Wait for AHB master idle state. */ |
| while (!(USB_DW->grstctl & USB_DW_GRSTCTL_AHB_IDLE)) { |
| usb_dw_udelay(1); |
| if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { |
| SYS_LOG_ERR("USB reset HANG! AHB Idle GRSTCTL=0x%08x", |
| USB_DW->grstctl); |
| return -EIO; |
| } |
| } |
| |
| /* Core Soft Reset */ |
| cnt = 0; |
| USB_DW->grstctl |= USB_DW_GRSTCTL_C_SFT_RST; |
| do { |
| if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { |
| SYS_LOG_DBG("USB reset HANG! Soft Reset GRSTCTL=0x%08x", |
| USB_DW->grstctl); |
| return -EIO; |
| } |
| usb_dw_udelay(1); |
| } while (USB_DW->grstctl & USB_DW_GRSTCTL_C_SFT_RST); |
| |
| /* Wait for 3 PHY Clocks */ |
| usb_dw_udelay(100); |
| |
| return 0; |
| } |
| |
| static int usb_dw_clock_enable(void) |
| { |
| #if defined(CONFIG_SOC_QUARK_SE_C1000) |
| /* 7.2.7 USB Clock Operation */ |
| clk_sys_set_mode(CLK_SYS_CRYSTAL_OSC, CLK_SYS_DIV_1); |
| |
| /* Enable the USB Clock */ |
| QM_SCSS_CCU->ccu_mlayer_ahb_ctl |= QM_CCU_USB_CLK_EN; |
| |
| /* Set up the PLL. */ |
| QM_USB_PLL_CFG0 = QM_USB_PLL_CFG0_DEFAULT | QM_USB_PLL_PDLD; |
| |
| /* Wait for the PLL lock */ |
| while (0 == (QM_USB_PLL_CFG0 & QM_USB_PLL_LOCK)) { |
| } |
| #endif |
| return 0; |
| } |
| |
| static void usb_dw_clock_disable(void) |
| { |
| #if defined(CONFIG_SOC_QUARK_SE_C1000) |
| /* Disable the USB Clock */ |
| QM_SCSS_CCU->ccu_mlayer_ahb_ctl &= ~QM_CCU_USB_CLK_EN; |
| |
| /* Disable the PLL */ |
| QM_USB_PLL_CFG0 &= ~QM_USB_PLL_PDLD; |
| #endif |
| } |
| |
| static int usb_dw_ep_set(u8_t ep, |
| u32_t ep_mps, enum usb_dc_ep_type ep_type) |
| { |
| volatile u32_t *p_depctl; |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| SYS_LOG_DBG("usb_dw_ep_set ep %x, mps %d, type %d", ep, ep_mps, |
| ep_type); |
| |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| p_depctl = &USB_DW->out_ep_reg[ep_idx].doepctl; |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].mps = ep_mps; |
| } else { |
| p_depctl = &USB_DW->in_ep_reg[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_DW_DEPCTL0_MSP_MASK; |
| switch (ep_mps) { |
| case 8: |
| *p_depctl |= USB_DW_DEPCTL0_MSP_8 << |
| USB_DW_DEPCTL_MSP_OFFSET; |
| break; |
| case 16: |
| *p_depctl |= USB_DW_DEPCTL0_MSP_16 << |
| USB_DW_DEPCTL_MSP_OFFSET; |
| break; |
| case 32: |
| *p_depctl |= USB_DW_DEPCTL0_MSP_32 << |
| USB_DW_DEPCTL_MSP_OFFSET; |
| break; |
| case 64: |
| *p_depctl |= USB_DW_DEPCTL0_MSP_64 << |
| USB_DW_DEPCTL_MSP_OFFSET; |
| break; |
| default: |
| return -EINVAL; |
| } |
| /* No need to set EP0 type */ |
| } else { |
| /* Set max packet size for EP */ |
| if (ep_mps > (USB_DW_DEPCTLn_MSP_MASK >> |
| USB_DW_DEPCTL_MSP_OFFSET)) { |
| return -EINVAL; |
| } |
| |
| *p_depctl &= ~USB_DW_DEPCTLn_MSP_MASK; |
| *p_depctl |= ep_mps << USB_DW_DEPCTL_MSP_OFFSET; |
| |
| /* Set endpoint type */ |
| *p_depctl &= ~USB_DW_DEPCTL_EP_TYPE_MASK; |
| switch (ep_type) { |
| case USB_DC_EP_CONTROL: |
| *p_depctl |= USB_DW_DEPCTL_EP_TYPE_CONTROL << |
| USB_DW_DEPCTL_EP_TYPE_OFFSET; |
| break; |
| case USB_DC_EP_BULK: |
| *p_depctl |= USB_DW_DEPCTL_EP_TYPE_BULK << |
| USB_DW_DEPCTL_EP_TYPE_OFFSET; |
| break; |
| case USB_DC_EP_INTERRUPT: |
| *p_depctl |= USB_DW_DEPCTL_EP_TYPE_INTERRUPT << |
| USB_DW_DEPCTL_EP_TYPE_OFFSET; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| /* sets the Endpoint Data PID to DATA0 */ |
| *p_depctl |= USB_DW_DEPCTL_SETDOPID; |
| } |
| |
| return 0; |
| } |
| |
| static void usb_dw_prep_rx(const u8_t ep, u8_t setup) |
| { |
| enum usb_dw_out_ep_idx ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| u32_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 |
| */ |
| |
| USB_DW->out_ep_reg[ep_idx].doeptsiz = |
| (USB_DW_SUP_CNT << USB_DW_DOEPTSIZ_SUP_CNT_OFFSET) | |
| (1 << USB_DW_DEPTSIZ_PKT_CNT_OFFSET) | ep_mps; |
| |
| /* Clear NAK and enable ep */ |
| if (!setup) { |
| USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_CNAK; |
| } |
| USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_EP_ENA; |
| |
| SYS_LOG_DBG("USB OUT EP%d armed", ep_idx); |
| } |
| |
| static int usb_dw_tx(u8_t ep, const u8_t *const data, |
| u32_t data_len) |
| { |
| enum usb_dw_in_ep_idx ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| u32_t max_xfer_size, max_pkt_cnt, pkt_cnt, avail_space; |
| u32_t ep_mps = usb_dw_ctrl.in_ep_ctrl[ep_idx].mps; |
| unsigned int key; |
| u32_t i; |
| |
| /* Check if FIFO space available */ |
| avail_space = USB_DW->in_ep_reg[ep_idx].dtxfsts & |
| USB_DW_DTXFSTS_TXF_SPC_AVAIL_MASK; |
| avail_space *= 4; |
| if (!avail_space) { |
| SYS_LOG_ERR("USB IN EP%d no space available, DTXFSTS %x", |
| ep_idx, USB_DW->in_ep_reg[ep_idx].dtxfsts); |
| return -EAGAIN; |
| } |
| |
| if (data_len > avail_space) { |
| data_len = avail_space; |
| } |
| |
| if (data_len != 0) { |
| /* Get max packet size and packet count for ep */ |
| if (ep_idx == USB_DW_IN_EP_0) { |
| max_pkt_cnt = |
| USB_DW_DIEPTSIZ0_PKT_CNT_MASK >> |
| USB_DW_DEPTSIZ_PKT_CNT_OFFSET; |
| max_xfer_size = |
| USB_DW_DEPTSIZ0_XFER_SIZE_MASK >> |
| USB_DW_DEPTSIZ_XFER_SIZE_OFFSET; |
| } else { |
| max_pkt_cnt = |
| USB_DW_DIEPTSIZn_PKT_CNT_MASK >> |
| USB_DW_DEPTSIZ_PKT_CNT_OFFSET; |
| max_xfer_size = |
| USB_DW_DEPTSIZn_XFER_SIZE_MASK >> |
| USB_DW_DEPTSIZ_XFER_SIZE_OFFSET; |
| } |
| |
| /* Check if transfer len is too big */ |
| if (data_len > max_xfer_size) { |
| SYS_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 = (data_len + ep_mps - 1) / ep_mps; |
| |
| if (pkt_cnt > max_pkt_cnt) { |
| SYS_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 = 1; |
| } |
| |
| /* Set number of packets and transfer size */ |
| USB_DW->in_ep_reg[ep_idx].dieptsiz = |
| (pkt_cnt << USB_DW_DEPTSIZ_PKT_CNT_OFFSET) | data_len; |
| |
| /* Clear NAK and enable ep */ |
| USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_CNAK; |
| USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_EP_ENA; |
| |
| /* |
| * 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." |
| */ |
| key = irq_lock(); |
| for (i = 0; i < data_len; i += 4) { |
| USB_DW_EP_FIFO(ep_idx) = *(u32_t *)(data + i); |
| } |
| irq_unlock(key); |
| |
| SYS_LOG_DBG("USB IN EP%d write %u bytes", ep_idx, data_len); |
| |
| return data_len; |
| } |
| |
| static int usb_dw_init(void) |
| { |
| u8_t ep; |
| int ret; |
| |
| ret = usb_dw_reset(); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Set device speed to FS */ |
| USB_DW->dcfg |= USB_DW_DCFG_DEV_SPD_FS; |
| |
| /* No FIFO setup needed, the default values are used */ |
| |
| /* Set NAK for all OUT EPs */ |
| for (ep = 0; ep < USB_DW_OUT_EP_NUM; ep++) { |
| USB_DW->out_ep_reg[ep].doepctl = USB_DW_DEPCTL_SNAK; |
| } |
| |
| /* Enable global interrupts */ |
| USB_DW->gintmsk = USB_DW_GINTSTS_OEP_INT | |
| USB_DW_GINTSTS_IEP_INT | |
| USB_DW_GINTSTS_ENUM_DONE | |
| USB_DW_GINTSTS_USB_RST | |
| USB_DW_GINTSTS_WK_UP_INT | |
| USB_DW_GINTSTS_USB_SUSP; |
| |
| /* Enable global interrupt */ |
| USB_DW->gahbcfg |= USB_DW_GAHBCFG_GLB_INTR_MASK; |
| |
| /* Disable soft disconnect */ |
| USB_DW->dctl &= ~USB_DW_DCTL_SFT_DISCON; |
| |
| usb_dw_reg_dump(); |
| |
| return 0; |
| } |
| |
| static void usb_dw_handle_reset(void) |
| { |
| SYS_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. */ |
| USB_DW->dcfg &= ~USB_DW_DCFG_DEV_ADDR_MASK; |
| |
| /* enable global EP interrupts */ |
| USB_DW->doepmsk = 0; |
| USB_DW->gintmsk |= USB_DW_GINTSTS_RX_FLVL; |
| USB_DW->diepmsk |= USB_DW_DIEPINT_XFER_COMPL; |
| } |
| |
| static void usb_dw_handle_enum_done(void) |
| { |
| u32_t speed; |
| |
| speed = (USB_DW->dsts & ~USB_DW_DSTS_ENUM_SPD_MASK) >> |
| USB_DW_DSTS_ENUM_SPD_OFFSET; |
| |
| SYS_LOG_DBG("USB ENUM DONE event, %s speed detected", |
| speed == USB_DW_DSTS_ENUM_LS ? "Low" : "Full"); |
| |
| /* Inform upper layers */ |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_CONNECTED, NULL); |
| } |
| } |
| |
| /* USB ISR handler */ |
| static void usb_dw_isr_handler(void) |
| { |
| u32_t int_status, ep_int_status; |
| u8_t ep_idx; |
| usb_dc_ep_callback ep_cb; |
| |
| /* Read interrupt status */ |
| while ((int_status = (USB_DW->gintsts & USB_DW->gintmsk))) { |
| |
| SYS_LOG_DBG("USB GINTSTS 0x%x", int_status); |
| |
| if (int_status & USB_DW_GINTSTS_USB_RST) { |
| /* Clear interrupt. */ |
| USB_DW->gintsts = USB_DW_GINTSTS_USB_RST; |
| |
| /* Reset detected */ |
| usb_dw_handle_reset(); |
| } |
| |
| if (int_status & USB_DW_GINTSTS_ENUM_DONE) { |
| /* Clear interrupt. */ |
| USB_DW->gintsts = USB_DW_GINTSTS_ENUM_DONE; |
| |
| /* Enumeration done detected */ |
| usb_dw_handle_enum_done(); |
| } |
| |
| if (int_status & USB_DW_GINTSTS_USB_SUSP) { |
| /* Clear interrupt. */ |
| USB_DW->gintsts = USB_DW_GINTSTS_USB_SUSP; |
| |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_SUSPEND, NULL); |
| } |
| } |
| |
| if (int_status & USB_DW_GINTSTS_WK_UP_INT) { |
| /* Clear interrupt. */ |
| USB_DW->gintsts = USB_DW_GINTSTS_WK_UP_INT; |
| |
| if (usb_dw_ctrl.status_cb) { |
| usb_dw_ctrl.status_cb(USB_DC_RESUME, NULL); |
| } |
| } |
| |
| if (int_status & USB_DW_GINTSTS_RX_FLVL) { |
| /* Packet in RX FIFO */ |
| u32_t status, xfer_size; |
| u32_t grxstsp = USB_DW->grxstsp; |
| |
| ep_idx = grxstsp & USB_DW_GRXSTSR_EP_NUM_MASK; |
| status = (grxstsp & USB_DW_GRXSTSR_PKT_STS_MASK) >> |
| USB_DW_GRXSTSR_PKT_STS_OFFSET; |
| xfer_size = (grxstsp & USB_DW_GRXSTSR_PKT_CNT_MASK) >> |
| USB_DW_GRXSTSR_PKT_CNT_OFFSET; |
| |
| SYS_LOG_DBG("USB OUT EP%d: RX_FLVL status %d, size %d", |
| 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_DW_GRXSTSR_PKT_STS_SETUP: |
| /* Call the registered callback if any */ |
| if (ep_cb) { |
| ep_cb(USB_DW_EP_IDX2ADDR(ep_idx, |
| USB_EP_DIR_OUT), |
| USB_DC_EP_SETUP); |
| } |
| break; |
| case USB_DW_GRXSTSR_PKT_STS_OUT_DATA: |
| if (ep_cb) { |
| ep_cb(USB_DW_EP_IDX2ADDR(ep_idx, |
| USB_EP_DIR_OUT), |
| USB_DC_EP_DATA_OUT); |
| } |
| break; |
| case USB_DW_GRXSTSR_PKT_STS_OUT_DATA_DONE: |
| case USB_DW_GRXSTSR_PKT_STS_SETUP_DONE: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (int_status & USB_DW_GINTSTS_IEP_INT) { |
| /* IN EP interrupt */ |
| for (ep_idx = 0; ep_idx < USB_DW_IN_EP_NUM; ep_idx++) { |
| if (USB_DW->daint & |
| USB_DW_DAINT_IN_EP_INT(ep_idx)) { |
| /* Read IN EP interrupt status */ |
| ep_int_status = |
| USB_DW->in_ep_reg[ep_idx].diepint & |
| USB_DW->diepmsk; |
| |
| /* Clear IN EP interrupts */ |
| USB_DW->in_ep_reg[ep_idx].diepint = |
| ep_int_status; |
| |
| SYS_LOG_DBG("USB IN EP%d interrupt " |
| "status: 0x%x", ep_idx, |
| ep_int_status); |
| |
| ep_cb = |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].cb; |
| |
| if ((ep_int_status & |
| USB_DW_DIEPINT_XFER_COMPL) && |
| ep_cb) { |
| |
| /* Call the registered |
| * callback |
| */ |
| ep_cb(USB_DW_EP_IDX2ADDR(ep_idx, |
| USB_EP_DIR_IN), |
| USB_DC_EP_DATA_IN); |
| } |
| } |
| } |
| /* Clear interrupt. */ |
| USB_DW->gintsts = USB_DW_GINTSTS_IEP_INT; |
| } |
| |
| if (int_status & USB_DW_GINTSTS_OEP_INT) { |
| /* No OUT interrupt expected in FIFO mode, |
| * just clear interruot |
| */ |
| for (ep_idx = 0; ep_idx < USB_DW_OUT_EP_NUM; ep_idx++) { |
| if (USB_DW->daint & |
| USB_DW_DAINT_OUT_EP_INT(ep_idx)) { |
| /* Read OUT EP interrupt status */ |
| ep_int_status = |
| USB_DW->out_ep_reg[ep_idx].doepint & |
| USB_DW->doepmsk; |
| |
| /* Clear OUT EP interrupts */ |
| USB_DW->out_ep_reg[ep_idx].doepint = |
| ep_int_status; |
| |
| SYS_LOG_DBG("USB OUT EP%d interrupt " |
| "status: 0x%x\n", ep_idx, |
| ep_int_status); |
| } |
| } |
| /* Clear interrupt. */ |
| USB_DW->gintsts = USB_DW_GINTSTS_OEP_INT; |
| } |
| } |
| } |
| |
| int usb_dc_attach(void) |
| { |
| int ret; |
| |
| if (usb_dw_ctrl.attached) { |
| return 0; |
| } |
| |
| ret = usb_dw_clock_enable(); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = usb_dw_init(); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Connect and enable USB interrupt */ |
| IRQ_CONNECT(USB_DW_IRQ, CONFIG_USB_DW_IRQ_PRI, |
| usb_dw_isr_handler, 0, IOAPIC_EDGE | IOAPIC_HIGH); |
| irq_enable(USB_DW_IRQ); |
| |
| _usb_dw_int_unmask(); |
| |
| usb_dw_ctrl.attached = 1; |
| |
| return 0; |
| } |
| |
| int usb_dc_detach(void) |
| { |
| if (!usb_dw_ctrl.attached) { |
| return 0; |
| } |
| |
| usb_dw_clock_disable(); |
| |
| irq_disable(USB_DW_IRQ); |
| |
| /* Enable soft disconnect */ |
| USB_DW->dctl |= USB_DW_DCTL_SFT_DISCON; |
| |
| usb_dw_ctrl.attached = 0; |
| |
| return 0; |
| } |
| |
| int usb_dc_reset(void) |
| { |
| int ret; |
| |
| ret = usb_dw_reset(); |
| |
| /* Clear private data */ |
| memset(&usb_dw_ctrl, 0, sizeof(usb_dw_ctrl)); |
| |
| return ret; |
| } |
| |
| int usb_dc_set_address(const u8_t addr) |
| { |
| if (addr > (USB_DW_DCFG_DEV_ADDR_MASK >> USB_DW_DCFG_DEV_ADDR_OFFSET)) { |
| return -EINVAL; |
| } |
| |
| USB_DW->dcfg &= ~USB_DW_DCFG_DEV_ADDR_MASK; |
| USB_DW->dcfg |= addr << USB_DW_DCFG_DEV_ADDR_OFFSET; |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg) |
| { |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep_cfg->ep_addr)) { |
| return -EINVAL; |
| } |
| |
| usb_dw_ep_set(ep_cfg->ep_addr, ep_cfg->ep_mps, ep_cfg->ep_type); |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_set_stall(const u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_STALL; |
| } else { |
| USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_STALL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_clear_stall(const u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| if (!ep_idx) { |
| /* Not possible to clear stall for EP0 */ |
| return -EINVAL; |
| } |
| |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| USB_DW->out_ep_reg[ep_idx].doepctl &= ~USB_DW_DEPCTL_STALL; |
| } else { |
| USB_DW->in_ep_reg[ep_idx].diepctl &= ~USB_DW_DEPCTL_STALL; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_halt(const u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| volatile u32_t *p_depctl; |
| |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| if (!ep_idx) { |
| /* Cannot disable EP0, just set stall */ |
| usb_dc_ep_set_stall(ep); |
| } else { |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| p_depctl = &USB_DW->out_ep_reg[ep_idx].doepctl; |
| } else { |
| p_depctl = &USB_DW->in_ep_reg[ep_idx].diepctl; |
| } |
| |
| /* Set STALL and disable endpoint if enabled */ |
| if (*p_depctl & USB_DW_DEPCTL_EP_ENA) { |
| *p_depctl |= USB_DW_DEPCTL_EP_DIS | USB_DW_DEPCTL_STALL; |
| } else { |
| *p_depctl |= USB_DW_DEPCTL_STALL; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_is_stalled(const u8_t ep, u8_t *const stalled) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| if (!stalled) { |
| return -EINVAL; |
| } |
| |
| *stalled = 0; |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| if (USB_DW->out_ep_reg[ep_idx].doepctl & USB_DW_DEPCTL_STALL) { |
| *stalled = 1; |
| } |
| } else { |
| if (USB_DW->in_ep_reg[ep_idx].diepctl & USB_DW_DEPCTL_STALL) { |
| *stalled = 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_enable(const u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| /* enable EP interrupts */ |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| USB_DW->daintmsk |= USB_DW_DAINT_OUT_EP_INT(ep_idx); |
| } else { |
| USB_DW->daintmsk |= USB_DW_DAINT_IN_EP_INT(ep_idx); |
| } |
| |
| /* Activate Ep */ |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| USB_DW->out_ep_reg[ep_idx].doepctl |= USB_DW_DEPCTL_USB_ACT_EP; |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 1; |
| } else { |
| USB_DW->in_ep_reg[ep_idx].diepctl |= USB_DW_DEPCTL_USB_ACT_EP; |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 1; |
| } |
| |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| /* Prepare EP for rx */ |
| usb_dw_prep_rx(ep, 0); |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_disable(const u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| /* Disable EP interrupts */ |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| USB_DW->daintmsk &= ~USB_DW_DAINT_OUT_EP_INT(ep_idx); |
| USB_DW->doepmsk &= ~USB_DW_DOEPINT_SET_UP; |
| } else { |
| USB_DW->daintmsk &= ~USB_DW_DAINT_IN_EP_INT(ep_idx); |
| USB_DW->diepmsk &= ~USB_DW_DIEPINT_XFER_COMPL; |
| USB_DW->gintmsk &= ~USB_DW_GINTSTS_RX_FLVL; |
| } |
| |
| /* De-activate, disable and set NAK for Ep */ |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| USB_DW->out_ep_reg[ep_idx].doepctl &= |
| ~(USB_DW_DEPCTL_USB_ACT_EP | |
| USB_DW_DEPCTL_EP_ENA | |
| USB_DW_DEPCTL_SNAK); |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].ep_ena = 0; |
| } else { |
| USB_DW->in_ep_reg[ep_idx].diepctl &= |
| ~(USB_DW_DEPCTL_USB_ACT_EP | |
| USB_DW_DEPCTL_EP_ENA | |
| USB_DW_DEPCTL_SNAK); |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].ep_ena = 0; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_flush(const u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| u32_t cnt; |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_OUT) { |
| /* RX FIFO is global and cannot be flushed per EP */ |
| return -EINVAL; |
| } |
| |
| /* Each endpoint has dedicated Tx FIFO */ |
| USB_DW->grstctl |= ep_idx << USB_DW_GRSTCTL_TX_FNUM_OFFSET; |
| USB_DW->grstctl |= USB_DW_GRSTCTL_TX_FFLSH; |
| |
| cnt = 0; |
| do { |
| if (++cnt > USB_DW_CORE_RST_TIMEOUT_US) { |
| SYS_LOG_ERR("USB TX FIFO flush HANG!"); |
| return -EIO; |
| } |
| usb_dw_udelay(1); |
| } while (USB_DW->grstctl & USB_DW_GRSTCTL_TX_FFLSH); |
| |
| return 0; |
| } |
| |
| int usb_dc_ep_write(const u8_t ep, const u8_t *const data, |
| const u32_t data_len, u32_t * const ret_bytes) |
| { |
| int ret; |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| /* Check if IN ep */ |
| if (USB_DW_EP_ADDR2DIR(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(u8_t ep, u8_t *data, u32_t max_data_len, |
| u32_t *read_bytes) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| u32_t i, j, data_len, bytes_to_copy; |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| SYS_LOG_ERR("No valid endpoint"); |
| return -EINVAL; |
| } |
| |
| /* Check if OUT ep */ |
| if (USB_DW_EP_ADDR2DIR(ep) != USB_EP_DIR_OUT) { |
| SYS_LOG_ERR("Wrong endpoint direction"); |
| return -EINVAL; |
| } |
| |
| /* Allow to read 0 bytes */ |
| if (!data && max_data_len) { |
| SYS_LOG_ERR("Wrong arguments"); |
| return -EINVAL; |
| } |
| |
| /* Check if ep enabled */ |
| if (!usb_dw_ep_is_enabled(ep)) { |
| SYS_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) { |
| SYS_LOG_ERR("Not enough room to copy all the rcvd data!"); |
| bytes_to_copy = max_data_len; |
| } else { |
| bytes_to_copy = data_len; |
| } |
| |
| SYS_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 = 0; i < (bytes_to_copy & ~0x3); i += 4) { |
| *(u32_t *)(data + i) = USB_DW_EP_FIFO(ep_idx); |
| } |
| if (bytes_to_copy & 0x3) { |
| /* Not multiple of 4 */ |
| u32_t last_dw = USB_DW_EP_FIFO(ep_idx); |
| |
| for (j = 0; j < (bytes_to_copy & 0x3); j++) { |
| *(data + i + j) = |
| (sys_cpu_to_le32(last_dw) >> (8 * j)) & 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(u8_t ep) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| SYS_LOG_ERR("No valid endpoint"); |
| return -EINVAL; |
| } |
| |
| /* Check if OUT ep */ |
| if (USB_DW_EP_ADDR2DIR(ep) != USB_EP_DIR_OUT) { |
| SYS_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 u8_t ep, u8_t *const data, |
| const u32_t max_data_len, u32_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 u8_t ep, const usb_dc_ep_callback cb) |
| { |
| u8_t ep_idx = USB_DW_EP_ADDR2IDX(ep); |
| |
| if (!usb_dw_ctrl.attached && !usb_dw_ep_is_valid(ep)) { |
| return -EINVAL; |
| } |
| |
| if (USB_DW_EP_ADDR2DIR(ep) == USB_EP_DIR_IN) { |
| usb_dw_ctrl.in_ep_ctrl[ep_idx].cb = cb; |
| } else { |
| usb_dw_ctrl.out_ep_ctrl[ep_idx].cb = cb; |
| } |
| |
| return 0; |
| } |
| |
| int usb_dc_set_status_callback(const usb_dc_status_callback cb) |
| { |
| usb_dw_ctrl.status_cb = cb; |
| |
| return 0; |
| } |