/* * Copyright (c) 2018 Aurelien Jarno * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT atmel_sam_usbhs #include #include #include #include #include #include #include #define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL #include LOG_MODULE_REGISTER(usb_dc_sam_usbhs); /* * This is defined in the support files for the SAM S7x, but not for * the SAM E7x nor SAM V7x. */ #ifndef USBHS_RAM_ADDR #define USBHS_RAM_ADDR (0xA0100000) #endif /* * The new Atmel DFP headers provide mode-specific interrupt register field * definitions. Map the existing generic definitions to these. */ #ifndef USBHS_DEVEPTISR_CTRL_RXSTPI #define USBHS_DEVEPTISR_CTRL_RXSTPI USBHS_DEVEPTISR_RXSTPI #endif #ifndef USBHS_DEVEPTICR_CTRL_RXSTPIC #define USBHS_DEVEPTICR_CTRL_RXSTPIC USBHS_DEVEPTICR_RXSTPIC #endif #ifndef USBHS_DEVEPTIMR_CTRL_STALLRQ #define USBHS_DEVEPTIMR_CTRL_STALLRQ USBHS_DEVEPTIMR_STALLRQ #endif #ifndef USBHS_DEVEPTIER_CTRL_RXSTPES #define USBHS_DEVEPTIER_CTRL_RXSTPES USBHS_DEVEPTIER_RXSTPES #endif #ifndef USBHS_DEVEPTIER_CTRL_STALLRQS #define USBHS_DEVEPTIER_CTRL_STALLRQS USBHS_DEVEPTIER_STALLRQS #endif #ifndef USBHS_DEVEPTIDR_CTRL_STALLRQC #define USBHS_DEVEPTIDR_CTRL_STALLRQC USBHS_DEVEPTIDR_STALLRQC #endif #define NUM_OF_EP_MAX DT_INST_PROP(0, num_bidir_endpoints) #define USB_MAXIMUM_SPEED DT_INST_ENUM_IDX_OR(0, maximum_speed, 1) BUILD_ASSERT(USB_MAXIMUM_SPEED, "low-speed is not supported"); struct usb_device_ep_data { uint16_t mps; usb_dc_ep_callback cb_in; usb_dc_ep_callback cb_out; uint8_t *fifo; }; struct usb_device_data { bool addr_enabled; usb_dc_status_callback status_cb; struct usb_device_ep_data ep_data[NUM_OF_EP_MAX]; }; static struct usb_device_data dev_data; /* Enable the USB device clock */ static void usb_dc_enable_clock(void) { /* Start the USB PLL */ PMC->CKGR_UCKR |= CKGR_UCKR_UPLLEN; /* Wait for it to be ready */ while (!(PMC->PMC_SR & PMC_SR_LOCKU)) { k_yield(); } /* In low power mode, provide a 48MHZ clock instead of the 480MHz one */ if ((USBHS->USBHS_DEVCTRL & USBHS_DEVCTRL_SPDCONF_Msk) == USBHS_DEVCTRL_SPDCONF_LOW_POWER) { /* Configure the USB_48M clock to be UPLLCK/10 */ PMC->PMC_MCKR &= ~PMC_MCKR_UPLLDIV2; PMC->PMC_USB = PMC_USB_USBDIV(9) | PMC_USB_USBS; /* Enable USB_48M clock */ PMC->PMC_SCER |= PMC_SCER_USBCLK; } } /* Disable the USB device clock */ static void usb_dc_disable_clock(void) { /* Disable USB_48M clock */ PMC->PMC_SCER &= ~PMC_SCER_USBCLK; /* Disable the USB PLL */ PMC->CKGR_UCKR &= ~CKGR_UCKR_UPLLEN; } /* Check if the USB device is attached */ static bool usb_dc_is_attached(void) { return (USBHS->USBHS_DEVCTRL & USBHS_DEVCTRL_DETACH) == 0; } /* Check if an endpoint is configured */ static bool usb_dc_ep_is_configured(uint8_t ep_idx) { return USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_CFGOK; } /* Check if an endpoint is enabled */ static bool usb_dc_ep_is_enabled(uint8_t ep_idx) { return USBHS->USBHS_DEVEPT & BIT(USBHS_DEVEPT_EPEN0_Pos + ep_idx); } /* Reset and endpoint */ static void usb_dc_ep_reset(uint8_t ep_idx) { USBHS->USBHS_DEVEPT |= BIT(USBHS_DEVEPT_EPRST0_Pos + ep_idx); USBHS->USBHS_DEVEPT &= ~BIT(USBHS_DEVEPT_EPRST0_Pos + ep_idx); barrier_dsync_fence_full(); } /* Enable endpoint interrupts, depending of the type and direction */ static void usb_dc_ep_enable_interrupts(uint8_t ep_idx) { if (ep_idx == 0U) { /* Control endpoint: enable SETUP and OUT */ USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_CTRL_RXSTPES; USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_RXOUTES; } else if ((USBHS->USBHS_DEVEPTCFG[ep_idx] & USBHS_DEVEPTCFG_EPDIR_Msk) == USBHS_DEVEPTCFG_EPDIR_IN) { /* IN direction: acknowledge FIFO empty interrupt */ USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_TXINIC; USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_TXINES; } else { /* OUT direction */ USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_RXOUTES; } } /* Reset the endpoint FIFO pointer to the beginning of the endpoint memory */ static void usb_dc_ep_fifo_reset(uint8_t ep_idx) { uint8_t *p; p = (uint8_t *)(USBHS_RAM_ADDR + 0x8000 * ep_idx); dev_data.ep_data[ep_idx].fifo = p; } /* Fetch a byte from the endpoint FIFO */ static uint8_t usb_dc_ep_fifo_get(uint8_t ep_idx) { return *(dev_data.ep_data[ep_idx].fifo++); } /* Put a byte from the endpoint FIFO */ static void usb_dc_ep_fifo_put(uint8_t ep_idx, uint8_t data) { *(dev_data.ep_data[ep_idx].fifo++) = data; } /* Handle interrupts on a control endpoint */ static void usb_dc_ep0_isr(void) { uint32_t sr = USBHS->USBHS_DEVEPTISR[0] & USBHS->USBHS_DEVEPTIMR[0]; uint32_t dev_ctrl = USBHS->USBHS_DEVCTRL; if (sr & USBHS_DEVEPTISR_CTRL_RXSTPI) { /* SETUP data received */ usb_dc_ep_fifo_reset(0); dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_SETUP); } if (sr & USBHS_DEVEPTISR_RXOUTI) { /* OUT (to device) data received */ usb_dc_ep_fifo_reset(0); dev_data.ep_data[0].cb_out(USB_EP_DIR_OUT, USB_DC_EP_DATA_OUT); } if (sr & USBHS_DEVEPTISR_TXINI) { /* Disable the interrupt */ USBHS->USBHS_DEVEPTIDR[0] = USBHS_DEVEPTIDR_TXINEC; /* IN (to host) transmit complete */ usb_dc_ep_fifo_reset(0); dev_data.ep_data[0].cb_in(USB_EP_DIR_IN, USB_DC_EP_DATA_IN); if (!(dev_ctrl & USBHS_DEVCTRL_ADDEN) && (dev_ctrl & USBHS_DEVCTRL_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. */ USBHS->USBHS_DEVCTRL = dev_ctrl | USBHS_DEVCTRL_ADDEN; } } } /* Handle interrupts on a non-control endpoint */ static void usb_dc_ep_isr(uint8_t ep_idx) { uint32_t sr = USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS->USBHS_DEVEPTIMR[ep_idx]; if (sr & USBHS_DEVEPTISR_RXOUTI) { uint8_t ep = ep_idx | USB_EP_DIR_OUT; /* Acknowledge the interrupt */ USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_RXOUTIC; /* OUT (to device) data received */ usb_dc_ep_fifo_reset(ep_idx); dev_data.ep_data[ep_idx].cb_out(ep, USB_DC_EP_DATA_OUT); } if (sr & USBHS_DEVEPTISR_TXINI) { uint8_t ep = ep_idx | USB_EP_DIR_IN; /* Acknowledge the interrupt */ USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_TXINIC; /* IN (to host) transmit complete */ usb_dc_ep_fifo_reset(ep_idx); dev_data.ep_data[ep_idx].cb_in(ep, USB_DC_EP_DATA_IN); } } /* Top level interrupt handler */ static void usb_dc_isr(void) { uint32_t sr = USBHS->USBHS_DEVISR & USBHS->USBHS_DEVIMR; /* End of resume interrupt */ if (sr & USBHS_DEVISR_EORSM) { /* Acknowledge the interrupt */ USBHS->USBHS_DEVICR = USBHS_DEVICR_EORSMC; /* Callback function */ dev_data.status_cb(USB_DC_RESUME, NULL); } /* End of reset interrupt */ if (sr & USBHS_DEVISR_EORST) { /* Acknowledge the interrupt */ USBHS->USBHS_DEVICR = USBHS_DEVICR_EORSTC; if (!usb_dc_ep_is_configured(0) && dev_data.ep_data[0].mps) { /* Restore EP0 configuration to previously set mps */ struct usb_dc_ep_cfg_data cfg = { .ep_addr = 0, .ep_mps = dev_data.ep_data[0].mps, .ep_type = USB_DC_EP_CONTROL, }; usb_dc_ep_configure(&cfg); usb_dc_ep_enable(0); } 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); } /* Free all endpoint memory */ for (int idx = 1; idx < NUM_OF_EP_MAX; idx++) { usb_dc_ep_disable(idx); USBHS->USBHS_DEVEPTCFG[idx] &= ~USBHS_DEVEPTCFG_ALLOC; } /* Callback function */ dev_data.status_cb(USB_DC_RESET, NULL); } /* Suspend interrupt */ if (sr & USBHS_DEVISR_SUSP) { /* Acknowledge the interrupt */ USBHS->USBHS_DEVICR = USBHS_DEVICR_SUSPC; /* Callback function */ dev_data.status_cb(USB_DC_SUSPEND, NULL); } #ifdef CONFIG_USB_DEVICE_SOF /* SOF interrupt */ if (sr & USBHS_DEVISR_SOF) { /* Acknowledge the interrupt */ USBHS->USBHS_DEVICR = USBHS_DEVICR_SOFC; /* Callback function */ dev_data.status_cb(USB_DC_SOF, NULL); } #endif /* EP0 endpoint interrupt */ if (sr & USBHS_DEVISR_PEP_0) { usb_dc_ep0_isr(); } /* Other endpoints interrupt */ for (int ep_idx = 1; ep_idx < NUM_OF_EP_MAX; ep_idx++) { if (sr & BIT(USBHS_DEVISR_PEP_0_Pos + ep_idx)) { usb_dc_ep_isr(ep_idx); } } } /* Attach USB for device connection */ int usb_dc_attach(void) { const struct atmel_sam_pmc_config clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(0); uint32_t regval; /* Enable USBHS clock in PMC */ (void)clock_control_on(SAM_DT_PMC_CONTROLLER, (clock_control_subsys_t)&clock_cfg); /* Enable the USB controller in device mode with the clock frozen */ USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_USBE | USBHS_CTRL_FRZCLK; barrier_dsync_fence_full(); /* Select the speed */ regval = USBHS_DEVCTRL_DETACH; #if (USB_MAXIMUM_SPEED == 2) && IS_ENABLED(CONFIG_USB_DC_HAS_HS_SUPPORT) /* high-speed */ regval |= USBHS_DEVCTRL_SPDCONF_NORMAL; #else /* full-speed */ regval |= USBHS_DEVCTRL_SPDCONF_LOW_POWER; #endif USBHS->USBHS_DEVCTRL = regval; /* Enable the USB clock */ usb_dc_enable_clock(); /* Unfreeze the clock */ USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_USBE; /* Enable device interrupts */ USBHS->USBHS_DEVIER = USBHS_DEVIER_EORSMES; USBHS->USBHS_DEVIER = USBHS_DEVIER_EORSTES; USBHS->USBHS_DEVIER = USBHS_DEVIER_SUSPES; #ifdef CONFIG_USB_DEVICE_SOF USBHS->USBHS_DEVIER = USBHS_DEVIER_SOFES; #endif /* Connect and enable the interrupt */ IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), usb_dc_isr, 0, 0); irq_enable(DT_INST_IRQN(0)); /* Attach the device */ USBHS->USBHS_DEVCTRL &= ~USBHS_DEVCTRL_DETACH; LOG_DBG(""); return 0; } /* Detach the USB device */ int usb_dc_detach(void) { const struct atmel_sam_pmc_config clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(0); /* Detach the device */ USBHS->USBHS_DEVCTRL |= USBHS_DEVCTRL_DETACH; /* Disable the USB clock */ usb_dc_disable_clock(); /* Disable the USB controller and freeze the clock */ USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_FRZCLK; /* Disable USBHS clock in PMC */ (void)clock_control_off(SAM_DT_PMC_CONTROLLER, (clock_control_subsys_t)&clock_cfg); /* Disable interrupt */ irq_disable(DT_INST_IRQN(0)); LOG_DBG(""); return 0; } /* Reset the USB device */ int usb_dc_reset(void) { /* Reset the controller */ USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD | USBHS_CTRL_FRZCLK; /* Clear private data */ (void)memset(&dev_data, 0, sizeof(dev_data)); LOG_DBG(""); return 0; } /* Set USB device address */ 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. */ USBHS->USBHS_DEVCTRL &= ~(USBHS_DEVCTRL_UADD_Msk | USBHS_DEVCTRL_ADDEN); USBHS->USBHS_DEVCTRL |= USBHS_DEVCTRL_UADD(addr); LOG_DBG(""); return 0; } /* Set USB device controller status callback */ void usb_dc_set_status_callback(const usb_dc_status_callback cb) { LOG_DBG(""); dev_data.status_cb = cb; } /* Check endpoint capabilities */ 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 -1; } if (ep_idx == 0U) { if (cfg->ep_type != USB_DC_EP_CONTROL) { LOG_ERR("pre-selected as control endpoint"); return -1; } } else if (ep_idx & BIT(0)) { if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_IN) { LOG_INF("pre-selected as IN endpoint"); return -1; } } else { if (USB_EP_GET_DIR(cfg->ep_addr) != USB_EP_DIR_OUT) { LOG_INF("pre-selected as OUT endpoint"); return -1; } } 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 -1; } return 0; } /* Configure endpoint */ 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); bool ep_configured[NUM_OF_EP_MAX]; bool ep_enabled[NUM_OF_EP_MAX]; 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; } if (usb_dc_ep_is_enabled(ep_idx)) { LOG_WRN("endpoint already configured & enabled 0x%x", ep_idx); return -EBUSY; } LOG_INF("Configure ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps, cfg->ep_type); /* Reset the endpoint */ usb_dc_ep_reset(ep_idx); /* Initialize the endpoint FIFO */ usb_dc_ep_fifo_reset(ep_idx); /* Map the endpoint type */ switch (cfg->ep_type) { case USB_DC_EP_CONTROL: regval |= USBHS_DEVEPTCFG_EPTYPE_CTRL; break; case USB_DC_EP_ISOCHRONOUS: regval |= USBHS_DEVEPTCFG_EPTYPE_ISO; break; case USB_DC_EP_BULK: regval |= USBHS_DEVEPTCFG_EPTYPE_BLK; break; case USB_DC_EP_INTERRUPT: regval |= USBHS_DEVEPTCFG_EPTYPE_INTRPT; break; default: return -EINVAL; } /* Map the endpoint direction */ if (USB_EP_DIR_IS_OUT(cfg->ep_addr) || cfg->ep_type == USB_DC_EP_CONTROL) { regval |= USBHS_DEVEPTCFG_EPDIR_OUT; } else { regval |= USBHS_DEVEPTCFG_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 |= USBHS_DEVEPTCFG_EPSIZE(log2ceil_mps - 3); dev_data.ep_data[ep_idx].mps = cfg->ep_mps; /* Use double bank buffering for isochronous endpoints */ if (cfg->ep_type == USB_DC_EP_ISOCHRONOUS) { regval |= USBHS_DEVEPTCFG_EPBK_2_BANK; } else { regval |= USBHS_DEVEPTCFG_EPBK_1_BANK; } /* Configure the endpoint */ USBHS->USBHS_DEVEPTCFG[ep_idx] = regval; /* * Allocate the memory. This part is a bit tricky as memory can only be * allocated if all above endpoints are disabled and not allocated. Loop * backward through the above endpoints, disable them if they are * enabled, deallocate their memory if needed. Then loop again through * all the above endpoints to allocate and enabled them. */ for (int i = NUM_OF_EP_MAX - 1; i > ep_idx; i--) { ep_configured[i] = usb_dc_ep_is_configured(i); ep_enabled[i] = usb_dc_ep_is_enabled(i); if (ep_enabled[i]) { LOG_INF("Temporary disable ep idx %x", i); usb_dc_ep_disable(i); } if (ep_configured[i]) { USBHS->USBHS_DEVEPTCFG[i] &= ~USBHS_DEVEPTCFG_ALLOC; } } ep_configured[ep_idx] = true; ep_enabled[ep_idx] = false; for (int i = ep_idx; i < NUM_OF_EP_MAX; i++) { if (ep_configured[i]) { USBHS->USBHS_DEVEPTCFG[i] |= USBHS_DEVEPTCFG_ALLOC; } if (ep_enabled[i]) { usb_dc_ep_enable(i); } } /* Check that the endpoint is correctly configured */ if (!usb_dc_ep_is_configured(ep_idx)) { LOG_ERR("endpoint configuration failed"); return -EINVAL; } return 0; } /* Set stall condition for the selected endpoint */ 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; } USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_CTRL_STALLRQS; LOG_DBG("ep 0x%x", ep); return 0; } /* Clear stall condition for the selected endpoint */ int usb_dc_ep_clear_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; } USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_CTRL_STALLRQC; LOG_DBG("ep 0x%x", ep); return 0; } /* Check if the selected endpoint is stalled */ 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 = (USBHS->USBHS_DEVEPTIMR[ep_idx] & USBHS_DEVEPTIMR_CTRL_STALLRQ) != 0; LOG_DBG("ep 0x%x", ep); return 0; } /* Halt the selected endpoint */ int usb_dc_ep_halt(uint8_t ep) { return usb_dc_ep_set_stall(ep); } /* Enable the selected endpoint */ int usb_dc_ep_enable(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 (!usb_dc_ep_is_configured(ep_idx)) { LOG_ERR("endpoint not configured"); return -ENODEV; } /* Enable endpoint */ USBHS->USBHS_DEVEPT |= BIT(USBHS_DEVEPT_EPEN0_Pos + ep_idx); /* Enable endpoint interrupts */ USBHS->USBHS_DEVIER = BIT(USBHS_DEVIER_PEP_0_Pos + ep_idx); /* Enable SETUP, IN or OUT endpoint interrupts */ usb_dc_ep_enable_interrupts(ep_idx); LOG_INF("Enable ep 0x%x", ep); return 0; } /* Disable the selected endpoint */ int usb_dc_ep_disable(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; } /* Disable endpoint interrupt */ USBHS->USBHS_DEVIDR = BIT(USBHS_DEVIDR_PEP_0_Pos + ep_idx); /* Disable endpoint and SETUP, IN or OUT interrupts */ USBHS->USBHS_DEVEPT &= ~BIT(USBHS_DEVEPT_EPEN0_Pos + ep_idx); LOG_INF("Disable ep 0x%x", ep); return 0; } /* Flush the selected endpoint */ int usb_dc_ep_flush(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 (!usb_dc_ep_is_enabled(ep_idx)) { LOG_ERR("endpoint not enabled"); return -ENODEV; } /* Disable the IN interrupt */ USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_TXINEC; /* Kill the last written bank if needed */ if (USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_NBUSYBK_Msk) { USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_KILLBKS; barrier_dsync_fence_full(); while (USBHS->USBHS_DEVEPTIMR[ep_idx] & USBHS_DEVEPTIMR_KILLBK) { k_yield(); } } /* Reset the endpoint */ usb_dc_ep_reset(ep_idx); /* Re-enable interrupts */ usb_dc_ep_enable_interrupts(ep_idx); LOG_DBG("ep 0x%x", ep); return 0; } /* Write data to the specified endpoint */ 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); 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_GET_DIR(ep) != USB_EP_DIR_IN) { LOG_ERR("wrong endpoint direction"); return -EINVAL; } if ((USBHS->USBHS_DEVEPTIMR[ep_idx] & USBHS_DEVEPTIMR_CTRL_STALLRQ) != 0) { LOG_WRN("endpoint is stalled"); return -EBUSY; } /* Write the data to the FIFO */ packet_len = MIN(data_len, dev_data.ep_data[ep_idx].mps); for (int i = 0; i < packet_len; i++) { usb_dc_ep_fifo_put(ep_idx, data[i]); } barrier_dsync_fence_full(); if (ep_idx == 0U) { /* * 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. */ USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_TXINIC; USBHS->USBHS_DEVEPTIER[ep_idx] = USBHS_DEVEPTIER_TXINES; } else { /* * Other endpoint types: clear the FIFO control flag to send * the data. */ USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_FIFOCONC; } if (ret_bytes) { *ret_bytes = packet_len; } LOG_DBG("ep 0x%x write %d bytes from %d", ep, packet_len, data_len); return 0; } /* Read data from the specified endpoint */ int usb_dc_ep_read(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *read_bytes) { uint8_t ep_idx = USB_EP_GET_IDX(ep); int rc; rc = usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes); if (rc) { return rc; } 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 the packet has been read entirely, get the next one */ if (!(USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_RWALL)) { rc = usb_dc_ep_read_continue(ep); } LOG_DBG("ep 0x%x", ep); return rc; } /* Set callback function for the specified endpoint */ 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("ep 0x%x", ep); return 0; } /* Read data from the specified endpoint */ int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len, uint32_t *read_bytes) { uint8_t ep_idx = USB_EP_GET_IDX(ep); uint32_t data_len = (USBHS->USBHS_DEVEPTISR[ep_idx] & USBHS_DEVEPTISR_BYCT_Msk) >> USBHS_DEVEPTISR_BYCT_Pos; 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_GET_DIR(ep) != USB_EP_DIR_OUT) { LOG_ERR("wrong endpoint direction"); return -EINVAL; } if ((USBHS->USBHS_DEVEPTIMR[ep_idx] & USBHS_DEVEPTIMR_CTRL_STALLRQ) != 0) { LOG_WRN("endpoint is stalled"); return -EBUSY; } if (!data && !max_data_len) { /* * When both buffer and max data to read are zero return * the available data in buffer. */ if (read_bytes) { *read_bytes = data_len; } return 0; } if (data_len > max_data_len) { LOG_WRN("Not enough space to copy all the data!"); data_len = max_data_len; } if (data != NULL) { for (int i = 0; i < data_len; i++) { data[i] = usb_dc_ep_fifo_get(ep_idx); } } if (read_bytes) { *read_bytes = data_len; } LOG_DBG("ep 0x%x read %d bytes", ep, data_len); return 0; } /* Continue reading data from the endpoint */ int usb_dc_ep_read_continue(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 (!usb_dc_ep_is_enabled(ep_idx)) { LOG_ERR("endpoint not enabled"); return -ENODEV; } if (USB_EP_GET_DIR(ep) != USB_EP_DIR_OUT) { LOG_ERR("wrong endpoint direction"); return -EINVAL; } if (ep_idx == 0U) { /* * Control endpoint: clear the interrupt flag to send the data. * It is easier to clear both SETUP and OUT flag than checking * the stage of the transfer. */ USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_RXOUTIC; USBHS->USBHS_DEVEPTICR[ep_idx] = USBHS_DEVEPTICR_CTRL_RXSTPIC; } else { /* * Other endpoint types: clear the FIFO control flag to * receive more data. */ USBHS->USBHS_DEVEPTIDR[ep_idx] = USBHS_DEVEPTIDR_FIFOCONC; } LOG_DBG("ep 0x%x continue", ep); return 0; } /* Endpoint max packet size (mps) */ 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; }