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