| /* |
| * Copyright (c) 2021 Gerson Fernando Budke <nandojve@gmail.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT atmel_sam_usbc |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usb_dc_sam_usbc, CONFIG_USB_DRIVER_LOG_LEVEL); |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/usb/usb_device.h> |
| #include <soc.h> |
| #include <string.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/barrier.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/irq.h> |
| |
| #define EP_UDINT_MASK 0x000FF000 |
| |
| #define NUM_OF_EP_MAX DT_INST_PROP(0, num_bidir_endpoints) |
| #define USBC_RAM_ADDR DT_REG_ADDR(DT_NODELABEL(sram1)) |
| #define USBC_RAM_SIZE DT_REG_SIZE(DT_NODELABEL(sram1)) |
| |
| /** |
| * @brief USB Driver Control Endpoint Finite State Machine states |
| * |
| * FSM states to keep tracking of control endpoint hidden states. |
| */ |
| enum usb_dc_epctrl_state { |
| /* Wait a SETUP packet */ |
| USB_EPCTRL_SETUP, |
| /* Wait a OUT data packet */ |
| USB_EPCTRL_DATA_OUT, |
| /* Wait a IN data packet */ |
| USB_EPCTRL_DATA_IN, |
| /* Wait a IN ZLP packet */ |
| USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP, |
| /* Wait a OUT ZLP packet */ |
| USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP, |
| /* STALL enabled on IN & OUT packet */ |
| USB_EPCTRL_STALL_REQ, |
| }; |
| |
| struct sam_usbc_udesc_sizes { |
| uint32_t byte_count:15; |
| uint32_t reserved:1; |
| uint32_t multi_packet_size:15; |
| uint32_t auto_zlp:1; |
| }; |
| |
| struct sam_usbc_udesc_bk_ctrl_stat { |
| uint32_t stallrq:1; |
| uint32_t reserved1:15; |
| uint32_t crcerri:1; |
| uint32_t overfi:1; |
| uint32_t underfi:1; |
| uint32_t reserved2:13; |
| }; |
| |
| struct sam_usbc_udesc_ep_ctrl_stat { |
| uint32_t pipe_dev_addr:7; |
| uint32_t reserved1:1; |
| uint32_t pipe_num:4; |
| uint32_t pipe_error_cnt_max:4; |
| uint32_t pipe_error_status:8; |
| uint32_t reserved2:8; |
| }; |
| |
| struct sam_usbc_desc_table { |
| uint8_t *ep_pipe_addr; |
| union { |
| uint32_t sizes; |
| struct sam_usbc_udesc_sizes udesc_sizes; |
| }; |
| union { |
| uint32_t bk_ctrl_stat; |
| struct sam_usbc_udesc_bk_ctrl_stat udesc_bk_ctrl_stat; |
| }; |
| union { |
| uint32_t ep_ctrl_stat; |
| struct sam_usbc_udesc_ep_ctrl_stat udesc_ep_ctrl_stat; |
| }; |
| }; |
| |
| struct usb_device_ep_data { |
| usb_dc_ep_callback cb_in; |
| usb_dc_ep_callback cb_out; |
| uint16_t mps; |
| bool mps_x2; |
| bool is_configured; |
| uint32_t out_at; |
| }; |
| |
| struct usb_device_data { |
| usb_dc_status_callback status_cb; |
| struct usb_device_ep_data ep_data[NUM_OF_EP_MAX]; |
| }; |
| |
| static struct sam_usbc_desc_table dev_desc[(NUM_OF_EP_MAX + 1) * 2]; |
| static struct usb_device_data dev_data; |
| static volatile Usbc *regs = (Usbc *) DT_INST_REG_ADDR(0); |
| PINCTRL_DT_INST_DEFINE(0); |
| static const struct pinctrl_dev_config *pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0); |
| static enum usb_dc_epctrl_state epctrl_fsm; |
| static const char *const usb_dc_epctrl_state_string[] = { |
| "STP", |
| "DOUT", |
| "DIN", |
| "IN_ZLP", |
| "OUT_ZLP", |
| "STALL", |
| }; |
| |
| #if defined(CONFIG_USB_DRIVER_LOG_LEVEL_DBG) |
| static uint32_t dev_ep_sta_dbg[2][NUM_OF_EP_MAX]; |
| |
| static void usb_dc_sam_usbc_isr_sta_dbg(uint32_t ep_idx, uint32_t sr) |
| { |
| if (regs->UESTA[ep_idx] != dev_ep_sta_dbg[0][ep_idx]) { |
| dev_ep_sta_dbg[0][ep_idx] = regs->UESTA[ep_idx]; |
| dev_ep_sta_dbg[1][ep_idx] = 0; |
| |
| LOG_INF("ISR[%d] CON=%08x INT=%08x INTE=%08x " |
| "ECON=%08x ESTA=%08x%s", ep_idx, |
| regs->UDCON, regs->UDINT, regs->UDINTE, |
| regs->UECON[ep_idx], regs->UESTA[ep_idx], |
| ((sr & USBC_UESTA0_RXSTPI) ? " STP" : "")); |
| } else if (dev_ep_sta_dbg[0][ep_idx] != dev_ep_sta_dbg[1][ep_idx]) { |
| dev_ep_sta_dbg[1][ep_idx] = dev_ep_sta_dbg[0][ep_idx]; |
| |
| LOG_INF("ISR[%d] CON=%08x INT=%08x INTE=%08x " |
| "ECON=%08x ESTA=%08x LOOP", ep_idx, |
| regs->UDCON, regs->UDINT, regs->UDINTE, |
| regs->UECON[ep_idx], regs->UESTA[ep_idx]); |
| } |
| } |
| |
| static void usb_dc_sam_usbc_clean_sta_dbg(void) |
| { |
| for (int i = 0; i < NUM_OF_EP_MAX; i++) { |
| dev_ep_sta_dbg[0][i] = 0; |
| dev_ep_sta_dbg[1][i] = 0; |
| } |
| } |
| #else |
| #define usb_dc_sam_usbc_isr_sta_dbg(ep_idx, sr) |
| #define usb_dc_sam_usbc_clean_sta_dbg() |
| #endif |
| |
| static ALWAYS_INLINE bool usb_dc_sam_usbc_is_frozen_clk(void) |
| { |
| return USBC->USBCON & USBC_USBCON_FRZCLK; |
| } |
| |
| static ALWAYS_INLINE void usb_dc_sam_usbc_freeze_clk(void) |
| { |
| USBC->USBCON |= USBC_USBCON_FRZCLK; |
| } |
| |
| static ALWAYS_INLINE void usb_dc_sam_usbc_unfreeze_clk(void) |
| { |
| USBC->USBCON &= ~USBC_USBCON_FRZCLK; |
| |
| while (USBC->USBCON & USBC_USBCON_FRZCLK) { |
| ; |
| }; |
| } |
| |
| static uint8_t usb_dc_sam_usbc_ep_curr_bank(uint8_t ep_idx) |
| { |
| uint8_t idx = ep_idx * 2; |
| |
| if ((ep_idx > 0) && |
| (regs->UESTA[ep_idx] & USBC_UESTA0_CURRBK(1)) > 0) { |
| idx++; |
| } |
| |
| return idx; |
| } |
| |
| static bool usb_dc_is_attached(void) |
| { |
| return (regs->UDCON & USBC_UDCON_DETACH) == 0; |
| } |
| |
| static bool usb_dc_ep_is_enabled(uint8_t ep_idx) |
| { |
| int reg = regs->UERST; |
| |
| return (reg & BIT(USBC_UERST_EPEN0_Pos + ep_idx)); |
| } |
| |
| static int usb_dc_sam_usbc_ep_alloc_buf(int ep_idx) |
| { |
| struct sam_usbc_desc_table *ep_desc_bk; |
| bool ep_enabled[NUM_OF_EP_MAX]; |
| int desc_mem_alloc; |
| int mps; |
| |
| if (ep_idx >= NUM_OF_EP_MAX) { |
| return -EINVAL; |
| } |
| |
| desc_mem_alloc = 0; |
| |
| mps = dev_data.ep_data[ep_idx].mps_x2 |
| ? dev_data.ep_data[ep_idx].mps * 2 |
| : dev_data.ep_data[ep_idx].mps; |
| |
| /* Check if there are memory to all endpoints */ |
| for (int i = 0; i < NUM_OF_EP_MAX; i++) { |
| if (!dev_data.ep_data[i].is_configured || i == ep_idx) { |
| continue; |
| } |
| |
| desc_mem_alloc += dev_data.ep_data[i].mps_x2 |
| ? dev_data.ep_data[i].mps * 2 |
| : dev_data.ep_data[i].mps; |
| } |
| |
| if ((desc_mem_alloc + mps) > USBC_RAM_SIZE) { |
| memset(&dev_data.ep_data[ep_idx], 0, |
| sizeof(struct usb_device_ep_data)); |
| return -ENOMEM; |
| } |
| |
| for (int i = NUM_OF_EP_MAX - 1; i >= ep_idx; i--) { |
| ep_enabled[i] = usb_dc_ep_is_enabled(i); |
| if (ep_enabled[i]) { |
| usb_dc_ep_disable(i); |
| } |
| } |
| |
| desc_mem_alloc = 0U; |
| for (int i = 0; i < ep_idx; i++) { |
| if (!dev_data.ep_data[i].is_configured) { |
| continue; |
| } |
| |
| desc_mem_alloc += dev_data.ep_data[i].mps_x2 |
| ? dev_data.ep_data[i].mps * 2 |
| : dev_data.ep_data[i].mps; |
| } |
| |
| ep_desc_bk = ((struct sam_usbc_desc_table *) &dev_desc) |
| + (ep_idx * 2); |
| for (int i = ep_idx; i < NUM_OF_EP_MAX; i++) { |
| if (!dev_data.ep_data[i].is_configured && (i != ep_idx)) { |
| ep_desc_bk += 2; |
| continue; |
| } |
| |
| /* Alloc bank 0 */ |
| ep_desc_bk->ep_pipe_addr = ((uint8_t *) USBC_RAM_ADDR) |
| + desc_mem_alloc; |
| ep_desc_bk->sizes = 0; |
| ep_desc_bk->bk_ctrl_stat = 0; |
| ep_desc_bk->ep_ctrl_stat = 0; |
| ep_desc_bk++; |
| |
| /** |
| * Alloc bank 1 |
| * |
| * if dual bank, |
| * then ep_pipe_addr[1] = ep_pipe_addr[0] address + mps size |
| * else ep_pipe_addr[1] = ep_pipe_addr[0] address |
| */ |
| ep_desc_bk->ep_pipe_addr = ((uint8_t *) USBC_RAM_ADDR) |
| + desc_mem_alloc |
| + (dev_data.ep_data[i].mps_x2 |
| ? dev_data.ep_data[i].mps |
| : 0); |
| ep_desc_bk->sizes = 0; |
| ep_desc_bk->bk_ctrl_stat = 0; |
| ep_desc_bk->ep_ctrl_stat = 0; |
| ep_desc_bk++; |
| |
| desc_mem_alloc += dev_data.ep_data[i].mps_x2 |
| ? dev_data.ep_data[i].mps * 2 |
| : dev_data.ep_data[i].mps; |
| } |
| |
| ep_enabled[ep_idx] = false; |
| for (int i = ep_idx; i < NUM_OF_EP_MAX; i++) { |
| if (ep_enabled[i]) { |
| usb_dc_ep_enable(i); |
| } |
| } |
| return 0; |
| } |
| |
| static void usb_dc_ep_enable_interrupts(uint8_t ep_idx) |
| { |
| if (ep_idx == 0U) { |
| /* Control endpoint: enable SETUP */ |
| regs->UECONSET[ep_idx] = USBC_UECON0SET_RXSTPES; |
| } else if (regs->UECFG[ep_idx] & USBC_UECFG0_EPDIR_IN) { |
| /* TX - IN direction: acknowledge FIFO empty interrupt */ |
| regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_TXINIC; |
| regs->UECONSET[ep_idx] = USBC_UECON0SET_TXINES; |
| } else { |
| /* RX - OUT direction */ |
| regs->UECONSET[ep_idx] = USBC_UECON0SET_RXOUTES; |
| } |
| } |
| |
| static void usb_dc_ep_isr_sta(uint8_t ep_idx) |
| { |
| uint32_t sr = regs->UESTA[ep_idx]; |
| |
| usb_dc_sam_usbc_isr_sta_dbg(ep_idx, sr); |
| |
| if (sr & USBC_UESTA0_RAMACERI) { |
| regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_RAMACERIC; |
| LOG_ERR("ISR: EP%d RAM Access Error", ep_idx); |
| } |
| } |
| |
| static void usb_dc_ctrl_init(void) |
| { |
| LOG_INF("STP - INIT"); |
| |
| /* In case of abort of IN Data Phase: |
| * No need to abort IN transfer (rise TXINI), |
| * because it is automatically done by hardware when a Setup packet is |
| * received. But the interrupt must be disabled to don't generate |
| * interrupt TXINI after SETUP reception. |
| */ |
| regs->UECONCLR[0] = USBC_UECON0CLR_TXINEC; |
| |
| /* In case of OUT ZLP event is no processed before Setup event occurs */ |
| regs->UESTACLR[0] = USBC_UESTA0CLR_RXOUTIC; |
| regs->UECONCLR[0] = USBC_UECON0CLR_RXOUTEC |
| | USBC_UECON0CLR_NAKOUTEC |
| | USBC_UECON0CLR_NAKINEC; |
| |
| epctrl_fsm = USB_EPCTRL_SETUP; |
| } |
| |
| static void usb_dc_ctrl_stall_data(uint32_t flags) |
| { |
| LOG_INF("STP - STALL"); |
| |
| epctrl_fsm = USB_EPCTRL_STALL_REQ; |
| |
| regs->UECONSET[0] = USBC_UECON0SET_STALLRQS; |
| regs->UESTACLR[0] = flags; |
| } |
| |
| static void usb_dc_ctrl_send_zlp_in(void) |
| { |
| uint32_t key; |
| |
| LOG_INF("STP - ZLP IN"); |
| |
| epctrl_fsm = USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP; |
| |
| /* Validate and send empty IN packet on control endpoint */ |
| dev_desc[0].sizes = 0; |
| |
| key = irq_lock(); |
| |
| /* Send ZLP on IN endpoint */ |
| regs->UESTACLR[0] = USBC_UESTA0CLR_TXINIC; |
| regs->UECONSET[0] = USBC_UECON0SET_TXINES; |
| |
| /* To detect a protocol error, enable nak interrupt on data OUT phase */ |
| regs->UESTACLR[0] = USBC_UESTA0CLR_NAKOUTIC; |
| regs->UECONSET[0] = USBC_UECON0SET_NAKOUTES; |
| irq_unlock(key); |
| } |
| |
| static void usb_dc_ctrl_send_zlp_out(void) |
| { |
| uint32_t key; |
| |
| LOG_INF("STP - ZLP OUT"); |
| |
| epctrl_fsm = USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP; |
| |
| /* To detect a protocol error, enable nak interrupt on data IN phase */ |
| key = irq_lock(); |
| regs->UESTACLR[0] = USBC_UESTA0CLR_NAKINIC; |
| regs->UECONSET[0] = USBC_UECON0SET_NAKINES; |
| irq_unlock(key); |
| } |
| |
| static void usb_dc_ep0_isr(void) |
| { |
| uint32_t sr = regs->UESTA[0]; |
| uint32_t dev_ctrl = regs->UDCON; |
| |
| usb_dc_ep_isr_sta(0); |
| |
| regs->UECONCLR[0] = USBC_UECON0CLR_NAKINEC; |
| regs->UECONCLR[0] = USBC_UECON0CLR_NAKOUTEC; |
| |
| if (sr & USBC_UESTA0_RXSTPI) { |
| /* May be a hidden DATA or ZLP phase or protocol abort */ |
| if (epctrl_fsm != USB_EPCTRL_SETUP) { |
| /* Reinitializes control endpoint management */ |
| usb_dc_ctrl_init(); |
| } |
| |
| /* SETUP data received */ |
| dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_SETUP); |
| return; |
| } |
| |
| if (sr & USBC_UESTA0_RXOUTI) { |
| LOG_DBG("RXOUT= fsm: %s", |
| usb_dc_epctrl_state_string[epctrl_fsm]); |
| |
| if (epctrl_fsm != USB_EPCTRL_DATA_OUT) { |
| if ((epctrl_fsm == USB_EPCTRL_DATA_IN) |
| || (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP)) { |
| /* End of SETUP request: |
| * - Data IN Phase aborted, |
| * - or last Data IN Phase hidden by ZLP OUT |
| * sending quickly, |
| * - or ZLP OUT received normally. |
| * |
| * Nothing to do |
| */ |
| } else { |
| /* Protocol error during SETUP request */ |
| usb_dc_ctrl_stall_data(0); |
| } |
| |
| usb_dc_ctrl_init(); |
| return; |
| } |
| |
| /* OUT (to device) data received */ |
| dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_DATA_OUT); |
| return; |
| } |
| |
| if ((sr & USBC_UESTA0_TXINI) && |
| (regs->UECON[0] & USBC_UECON0_TXINE)) { |
| LOG_DBG("TXINI= fsm: %s", |
| usb_dc_epctrl_state_string[epctrl_fsm]); |
| |
| regs->UECONCLR[0] = USBC_UECON0CLR_TXINEC; |
| |
| if (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP) { |
| if (!(dev_ctrl & USBC_UDCON_ADDEN) |
| && (dev_ctrl & USBC_UDCON_UADD_Msk) != 0U) { |
| /* Commit the pending address update. This |
| * must be done after the ack to the host |
| * completes else the ack will get dropped. |
| */ |
| regs->UDCON |= USBC_UDCON_ADDEN; |
| } |
| |
| /* ZLP on IN is sent */ |
| usb_dc_ctrl_init(); |
| return; |
| } |
| |
| /* IN (to host) transmit complete */ |
| dev_data.ep_data[0].cb_in(USB_EP_DIR_IN, USB_DC_EP_DATA_IN); |
| return; |
| } |
| |
| if (sr & USBC_UESTA0_NAKOUTI) { |
| LOG_DBG("NAKOUT= fsm: %s", |
| usb_dc_epctrl_state_string[epctrl_fsm]); |
| |
| regs->UESTACLR[0] = USBC_UESTA0CLR_NAKOUTIC; |
| |
| if (regs->UESTA[0] & USBC_UESTA0_TXINI) { |
| /** overflow ignored if IN data is received */ |
| return; |
| } |
| |
| if (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_IN_ZLP) { |
| /* A IN handshake is waiting by device, but host want |
| * extra OUT data then stall extra OUT data |
| */ |
| regs->UECONSET[0] = USBC_UECON0SET_STALLRQS; |
| } |
| return; |
| } |
| |
| if (sr & USBC_UESTA0_NAKINI) { |
| LOG_DBG("NAKIN= fsm: %s", |
| usb_dc_epctrl_state_string[epctrl_fsm]); |
| |
| regs->UESTACLR[0] = USBC_UESTA0CLR_NAKINIC; |
| |
| if (regs->UESTA[0] & USBC_UESTA0_RXOUTI) { |
| /** underflow ignored if OUT data is received */ |
| return; |
| } |
| |
| if (epctrl_fsm == USB_EPCTRL_DATA_OUT) { |
| /* Host want to stop OUT transaction then stop to |
| * wait OUT data phase and wait IN ZLP handshake. |
| */ |
| usb_dc_ctrl_send_zlp_in(); |
| } else if (epctrl_fsm == USB_EPCTRL_HANDSHAKE_WAIT_OUT_ZLP) { |
| /* A OUT handshake is waiting by device, but host want |
| * extra IN data then stall extra IN data. |
| */ |
| regs->UECONSET[0] = USBC_UECON0SET_STALLRQS; |
| } else { |
| /** Nothing to do */ |
| } |
| return; |
| } |
| } |
| |
| static void usb_dc_ep_isr(uint8_t ep_idx) |
| { |
| uint32_t sr = regs->UESTA[ep_idx]; |
| |
| usb_dc_ep_isr_sta(ep_idx); |
| |
| if (sr & USBC_UESTA0_RXOUTI) { |
| uint8_t ep = ep_idx | USB_EP_DIR_OUT; |
| |
| regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_RXOUTIC; |
| |
| /* OUT (to device) data received */ |
| dev_data.ep_data[ep_idx].cb_out(ep, USB_DC_EP_DATA_OUT); |
| } |
| if (sr & USBC_UESTA0_TXINI) { |
| uint8_t ep = ep_idx | USB_EP_DIR_IN; |
| |
| regs->UESTACLR[ep_idx] = USBC_UESTA0CLR_TXINIC; |
| |
| /* IN (to host) transmit complete */ |
| dev_data.ep_data[ep_idx].cb_in(ep, USB_DC_EP_DATA_IN); |
| } |
| } |
| |
| static void usb_dc_sam_usbc_isr(void) |
| { |
| uint32_t sr = regs->UDINT; |
| |
| if (IS_ENABLED(CONFIG_USB_DEVICE_SOF)) { |
| /* SOF interrupt */ |
| if (sr & USBC_UDINT_SOF) { |
| /* Acknowledge the interrupt */ |
| regs->UDINTCLR = USBC_UDINTCLR_SOFC; |
| |
| dev_data.status_cb(USB_DC_SOF, NULL); |
| |
| goto usb_dc_sam_usbc_isr_barrier; |
| } |
| } |
| |
| /* EP0 endpoint interrupt */ |
| if (sr & USBC_UDINT_EP0INT) { |
| usb_dc_ep0_isr(); |
| |
| goto usb_dc_sam_usbc_isr_barrier; |
| } |
| |
| /* Other endpoints interrupt */ |
| if (sr & EP_UDINT_MASK) { |
| for (int ep_idx = 1; ep_idx < NUM_OF_EP_MAX; ep_idx++) { |
| if (sr & (USBC_UDINT_EP0INT << ep_idx)) { |
| usb_dc_ep_isr(ep_idx); |
| } |
| } |
| |
| goto usb_dc_sam_usbc_isr_barrier; |
| } |
| |
| /* End of resume interrupt */ |
| if (sr & USBC_UDINT_EORSM) { |
| LOG_DBG("ISR: End Of Resume"); |
| |
| regs->UDINTCLR = USBC_UDINTCLR_EORSMC; |
| |
| dev_data.status_cb(USB_DC_RESUME, NULL); |
| |
| goto usb_dc_sam_usbc_isr_barrier; |
| } |
| |
| /* End of reset interrupt */ |
| if (sr & USBC_UDINT_EORST) { |
| LOG_DBG("ISR: End Of Reset"); |
| |
| regs->UDINTCLR = USBC_UDINTCLR_EORSTC; |
| |
| if (usb_dc_ep_is_enabled(0)) { |
| /* The device clears some of the configuration of EP0 |
| * when it receives the EORST. Re-enable interrupts. |
| */ |
| usb_dc_ep_enable_interrupts(0); |
| usb_dc_ctrl_init(); |
| } |
| |
| dev_data.status_cb(USB_DC_RESET, NULL); |
| |
| usb_dc_sam_usbc_clean_sta_dbg(); |
| |
| goto usb_dc_sam_usbc_isr_barrier; |
| } |
| |
| /* Suspend interrupt */ |
| if (sr & USBC_UDINT_SUSP && regs->UDINTE & USBC_UDINTE_SUSPE) { |
| LOG_DBG("ISR: Suspend"); |
| |
| regs->UDINTCLR = USBC_UDINTCLR_SUSPC; |
| |
| usb_dc_sam_usbc_unfreeze_clk(); |
| |
| /** |
| * Sync Generic Clock |
| * Check USB clock ready after suspend and |
| * eventually sleep USB clock |
| */ |
| while ((regs->USBSTA & USBC_USBSTA_CLKUSABLE) == 0) { |
| ; |
| }; |
| |
| regs->UDINTECLR = USBC_UDINTECLR_SUSPEC; |
| regs->UDINTCLR = USBC_UDINTCLR_WAKEUPC; |
| regs->UDINTESET = USBC_UDINTESET_WAKEUPES; |
| |
| usb_dc_sam_usbc_freeze_clk(); |
| |
| dev_data.status_cb(USB_DC_SUSPEND, NULL); |
| |
| goto usb_dc_sam_usbc_isr_barrier; |
| } |
| |
| /* Wakeup interrupt */ |
| if (sr & USBC_UDINT_WAKEUP && regs->UDINTE & USBC_UDINTE_WAKEUPE) { |
| LOG_DBG("ISR: Wake Up"); |
| |
| regs->UDINTCLR = USBC_UDINTCLR_WAKEUPC; |
| |
| usb_dc_sam_usbc_unfreeze_clk(); |
| |
| /** |
| * Sync Generic Clock |
| * Check USB clock ready after suspend and |
| * eventually sleep USB clock |
| */ |
| while ((regs->USBSTA & USBC_USBSTA_CLKUSABLE) == 0) { |
| ; |
| }; |
| |
| regs->UDINTECLR = USBC_UDINTECLR_WAKEUPEC; |
| regs->UDINTCLR = USBC_UDINTCLR_SUSPC; |
| regs->UDINTESET = USBC_UDINTESET_SUSPES; |
| } |
| |
| usb_dc_sam_usbc_isr_barrier: |
| 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; |
| } |