You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
936 lines
23 KiB
936 lines
23 KiB
/* |
|
* Copyright (c) 2018 Aurelien Jarno <aurelien@aurel32.net> |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT atmel_sam_usbhs |
|
|
|
#include <zephyr/usb/usb_device.h> |
|
#include <zephyr/drivers/clock_control/atmel_sam_pmc.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/barrier.h> |
|
#include <soc.h> |
|
#include <string.h> |
|
|
|
#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL |
|
#include <zephyr/logging/log.h> |
|
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; |
|
}
|
|
|