From c8eaa11a0b0a8209437f2f08aa7d001ed0a5db2d Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Wed, 26 Feb 2025 22:14:56 +0100 Subject: [PATCH] drivers: udc: add new UDC driver for SAM0 USB controller The UDC driver for this beautiful USB controller is mostly rewritten from scratch. USB Pad Calibration and clock handling are copied from the usb_dc_sam0 driver. The driver uses multipacket transfers for all endpoints except the OUT control endpoint. The OUT control endpoint has a buffer that is always mapped to the endpoint buffer register so that it always has a valid buffer. The driver provides up to 7 IN and 7 OUT endpoints that support any type of transfer. Double buffering is not used, for the possible case of isochronous transfers some changes would be required in the future. Signed-off-by: Johann Fischer --- drivers/usb/udc/CMakeLists.txt | 1 + drivers/usb/udc/Kconfig | 1 + drivers/usb/udc/Kconfig.sam0 | 28 + drivers/usb/udc/udc_sam0.c | 1257 ++++++++++++++++++++++++++++++++ 4 files changed, 1287 insertions(+) create mode 100644 drivers/usb/udc/Kconfig.sam0 create mode 100644 drivers/usb/udc/udc_sam0.c diff --git a/drivers/usb/udc/CMakeLists.txt b/drivers/usb/udc/CMakeLists.txt index c8fbc9b6011..c9e5b6e79d0 100644 --- a/drivers/usb/udc/CMakeLists.txt +++ b/drivers/usb/udc/CMakeLists.txt @@ -21,3 +21,4 @@ zephyr_library_sources_ifdef(CONFIG_UDC_RPI_PICO udc_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_UDC_AMBIQ udc_ambiq.c) zephyr_library_sources_ifdef(CONFIG_UDC_RENESAS_RA udc_renesas_ra.c) zephyr_library_sources_ifdef(CONFIG_UDC_MAX32 udc_max32.c) +zephyr_library_sources_ifdef(CONFIG_UDC_SAM0 udc_sam0.c) diff --git a/drivers/usb/udc/Kconfig b/drivers/usb/udc/Kconfig index 997da20d7cb..b6c49e6b3e7 100644 --- a/drivers/usb/udc/Kconfig +++ b/drivers/usb/udc/Kconfig @@ -78,5 +78,6 @@ source "drivers/usb/udc/Kconfig.rpi_pico" source "drivers/usb/udc/Kconfig.ambiq" source "drivers/usb/udc/Kconfig.renesas_ra" source "drivers/usb/udc/Kconfig.max32" +source "drivers/usb/udc/Kconfig.sam0" endif # UDC_DRIVER diff --git a/drivers/usb/udc/Kconfig.sam0 b/drivers/usb/udc/Kconfig.sam0 new file mode 100644 index 00000000000..7d4a870cc5a --- /dev/null +++ b/drivers/usb/udc/Kconfig.sam0 @@ -0,0 +1,28 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config UDC_SAM0 + bool "Driver for SAM0 family USB device controller" + default y + depends on DT_HAS_ATMEL_SAM0_USB_ENABLED + select PINCTRL + select SYS_MEM_BLOCKS + select EVENTS + help + Driver for SAM0 family USB device controller. + +if UDC_SAM0 + +config UDC_SAM0_STACK_SIZE + int "UDC controller driver internal thread stack size" + default 512 + help + Device controller driver internal thread stack size. + +config UDC_SAM0_THREAD_PRIORITY + int "UDC controller driver thread priority" + default 8 + help + Device controller driver thread priority. + +endif # UDC_SAM0 diff --git a/drivers/usb/udc/udc_sam0.c b/drivers/usb/udc/udc_sam0.c new file mode 100644 index 00000000000..dab462a03fa --- /dev/null +++ b/drivers/usb/udc/udc_sam0.c @@ -0,0 +1,1257 @@ +/* + * Copyright Google LLC. + * Copyright Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "udc_common.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(udc_sam0, CONFIG_UDC_DRIVER_LOG_LEVEL); + +/* + * Although the manual refers to this as an "Endpoint Descriptor structure", it + * is actually an endpoint buffer descriptor and has a similar function to the + * buffer descriptor in the UDC Kinetis driver. Do not use the ASF definition + * as it is incorrect, cumbersome and has a very misleading name. + */ +struct sam0_ebd_bank0 { + uint32_t addr; + /* PCKSIZE offset 0x04 */ + unsigned int byte_count : 14; + unsigned int multi_packet_size : 14; + unsigned int size : 3; + unsigned int auto_zlp : 1; + /* EXTREG offset 0x08*/ + unsigned int subpid : 4; + unsigned int variable : 11; + unsigned int reserved0 : 1; + /* STATUS_BK offset 0x0A*/ + unsigned int erroflow : 1; + unsigned int crcerr : 1; + unsigned int reserved1 : 6; + /* RESERVED */ + uint8_t reserved2[5]; +} __packed; + +struct sam0_ebd_bank1 { + uint32_t addr; + /* PCKSIZE offset 0x14 */ + unsigned int byte_count : 14; + unsigned int multi_packet_size : 14; + unsigned int size : 3; + unsigned int auto_zlp : 1; + /* RESERVED, no EXTREG */ + uint8_t reserved0[2]; + /* STATUS_BK offset 0x1A*/ + unsigned int erroflow : 1; + unsigned int crcerr : 1; + unsigned int reserved1 : 6; + /* RESERVED */ + uint8_t reserved2[5]; +} __packed; + +struct sam0_ep_buffer_desc { + /* Used for OUT endpoints 0x00, 0x01 ... 0x08 */ + struct sam0_ebd_bank0 bank0; + /* Used for IN endpoints 0x80, 0x81 ... 0x88 */ + struct sam0_ebd_bank1 bank1; +} __packed; + +BUILD_ASSERT(sizeof(struct sam0_ep_buffer_desc) == 32, "Broken endpoint buffer descriptor"); + +struct udc_sam0_config { + UsbDevice *base; + struct sam0_ep_buffer_desc *bdt; + size_t num_of_eps; + struct udc_ep_config *ep_cfg_in; + struct udc_ep_config *ep_cfg_out; + struct pinctrl_dev_config *const pcfg; + void (*irq_enable_func)(const struct device *dev); + void (*irq_disable_func)(const struct device *dev); + void (*make_thread)(const struct device *dev); +}; + +enum sam0_event_type { + /* Setup packet received */ + SAM0_EVT_SETUP, + /* Trigger new transfer (except control OUT) */ + SAM0_EVT_XFER_NEW, + /* Transfer for specific endpoint is finished */ + SAM0_EVT_XFER_FINISHED, +}; + +struct udc_sam0_data { + struct k_thread thread_data; + /* + * events are events that the driver thread waits. + * xfer_new and xfer_finished contain information on which endpoints + * events SAM0_EVT_XFER_NEW or SAM0_EVT_XFER_FINISHED are triggered. + * The mapping is bits 31..16 for IN endpoints and bits 15..0 for OUT + * endpoints. + */ + struct k_event events; + atomic_t xfer_new; + atomic_t xfer_finished; + /* + * This control OUT endpoint buffer is persistent because we have no + * control over when the host sends a setup packet. All other endpoints + * use multi-packet transfers and transfer buffers directly. + */ + uint8_t ctrl_out_buf[64]; + uint8_t setup[8]; +}; + +static inline int udc_ep_to_bnum(const uint8_t ep) +{ + if (USB_EP_DIR_IS_IN(ep)) { + return 16UL + USB_EP_GET_IDX(ep); + } + + return USB_EP_GET_IDX(ep); +} + +static inline uint8_t udc_pull_ep_from_bmsk(uint32_t *const bitmap) +{ + unsigned int bit; + + __ASSERT_NO_MSG(bitmap && *bitmap); + + bit = find_lsb_set(*bitmap) - 1; + *bitmap &= ~BIT(bit); + + if (bit >= 16U) { + return USB_EP_DIR_IN | (bit - 16U); + } else { + return USB_EP_DIR_OUT | bit; + } +} + +/* For CTRLA.ENABLE and CTRLA.SWRST */ +static void sam0_wait_syncbusy(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + + while (base->SYNCBUSY.reg != 0) { + } +} + +static void sam0_load_padcal(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + uint32_t pad_transn; + uint32_t pad_transp; + uint32_t pad_trim; + +#ifdef USB_FUSES_TRANSN_ADDR + pad_transn = *(uint32_t *)USB_FUSES_TRANSN_ADDR; +#else +#define NVM_USB_PAD_TRANSN_POS 45 +#define NVM_USB_PAD_TRANSN_SIZE 5 + pad_transn = (*((uint32_t *)(NVMCTRL_OTP4) + + (NVM_USB_PAD_TRANSN_POS / 32)) >> + (NVM_USB_PAD_TRANSN_POS % 32)) & + ((1 << NVM_USB_PAD_TRANSN_SIZE) - 1); + + if (pad_transn == 0x1F) { + pad_transn = 5U; + } +#endif + + base->PADCAL.bit.TRANSN = pad_transn; + +#ifdef USB_FUSES_TRANSP_ADDR +#define NVM_USB_PAD_TRANSP_POS 50 +#define NVM_USB_PAD_TRANSP_SIZE 5 + pad_transp = *(uint32_t *)USB_FUSES_TRANSP_ADDR; +#else + pad_transp = (*((uint32_t *)(NVMCTRL_OTP4) + + (NVM_USB_PAD_TRANSP_POS / 32)) >> + (NVM_USB_PAD_TRANSP_POS % 32)) & + ((1 << NVM_USB_PAD_TRANSP_SIZE) - 1); + + if (pad_transp == 0x1F) { + pad_transp = 29U; + } +#endif + + base->PADCAL.bit.TRANSP = pad_transp; + +#ifdef USB_FUSES_TRIM_ADDR + pad_trim = *(uint32_t *)USB_FUSES_TRIM_ADDR; +#else +#define NVM_USB_PAD_TRIM_POS 55 +#define NVM_USB_PAD_TRIM_SIZE 3 + pad_trim = (*((uint32_t *)(NVMCTRL_OTP4) + + (NVM_USB_PAD_TRIM_POS / 32)) >> + (NVM_USB_PAD_TRIM_POS % 32)) & + ((1 << NVM_USB_PAD_TRIM_SIZE) - 1); + + if (pad_trim == 0x7) { + pad_trim = 3U; + } +#endif + + base->PADCAL.bit.TRIM = pad_trim; +} + +static uint8_t sam0_get_bd_size(const uint16_t mps) +{ + switch (mps) { + case 8: + return 0; + case 16: + return 1; + case 32: + return 2; + case 64: + return 3; + case 128: + return 4; + case 256: + return 5; + case 512: + return 6; + case 1023: + return 7; + default: + __ASSERT(true, "Wrong maximum packet size value"); + return 0; + } +} + +static struct sam0_ep_buffer_desc *sam0_get_ebd(const struct device *dev, const uint8_t ep) +{ + const struct udc_sam0_config *config = dev->config; + + return &config->bdt[USB_EP_GET_IDX(ep)]; +} + +static UsbDeviceEndpoint *sam0_get_ep_reg(const struct device *dev, const uint8_t ep) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + + return &base->DeviceEndpoint[USB_EP_GET_IDX(ep)]; +} + +static int sam0_prep_out(const struct device *dev, + struct net_buf *const buf, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + struct sam0_ep_buffer_desc *const bd = sam0_get_ebd(dev, ep_cfg->addr); + const uint16_t size = MIN(16383U, net_buf_tailroom(buf)); + unsigned int lock_key; + + if (!endpoint->EPSTATUS.bit.BK0RDY) { + LOG_ERR("ep 0x%02x buffer is used by the controller", ep_cfg->addr); + return -EBUSY; + } + + lock_key = irq_lock(); + if (ep_cfg->addr != USB_CONTROL_EP_OUT) { + bd->bank0.addr = (uintptr_t)buf->data; + bd->bank0.byte_count = 0; + + bd->bank0.multi_packet_size = size; + bd->bank0.size = sam0_get_bd_size(udc_mps_ep_size(ep_cfg)); + } + + endpoint->EPSTATUSCLR.bit.BK0RDY = 1; + irq_unlock(lock_key); + + LOG_DBG("Prepare OUT ep 0x%02x size %u", ep_cfg->addr, size); + + return 0; +} + +static int sam0_prep_in(const struct device *dev, + struct net_buf *const buf, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + struct sam0_ep_buffer_desc *const bd = sam0_get_ebd(dev, ep_cfg->addr); + const uint16_t len = MIN(16383U, buf->len); + unsigned int lock_key; + + if (endpoint->EPSTATUS.bit.BK1RDY) { + LOG_ERR("ep 0x%02x buffer is used by the controller", ep_cfg->addr); + return -EAGAIN; + } + + lock_key = irq_lock(); + + bd->bank1.addr = (uintptr_t)buf->data; + bd->bank1.size = sam0_get_bd_size(udc_mps_ep_size(ep_cfg)); + + bd->bank1.multi_packet_size = 0; + bd->bank1.byte_count = len; + bd->bank1.auto_zlp = 0; + + endpoint->EPSTATUSSET.bit.BK1RDY = 1; + irq_unlock(lock_key); + + LOG_DBG("Prepare IN ep 0x%02x length %u", ep_cfg->addr, len); + + return 0; +} + +static int sam0_ctrl_feed_dout(const struct device *dev, const size_t length) +{ + struct udc_ep_config *const ep_cfg = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); + struct net_buf *buf; + + buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, length); + if (buf == NULL) { + return -ENOMEM; + } + + udc_buf_put(ep_cfg, buf); + + return sam0_prep_out(dev, buf, ep_cfg); +} + +static void drop_control_transfers(const struct device *dev) +{ + struct net_buf *buf; + + buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT)); + if (buf != NULL) { + net_buf_unref(buf); + } + + buf = udc_buf_get_all(udc_get_ep_cfg(dev, USB_CONTROL_EP_IN)); + if (buf != NULL) { + net_buf_unref(buf); + } +} + +static int sam0_handle_evt_setup(const struct device *dev) +{ + struct udc_sam0_data *const priv = udc_get_private(dev); + struct net_buf *buf; + int err; + + drop_control_transfers(dev); + + buf = udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, 8); + if (buf == NULL) { + return -ENOMEM; + } + + net_buf_add_mem(buf, priv->setup, sizeof(priv->setup)); + udc_ep_buf_set_setup(buf); + + /* Update to next stage of control transfer */ + udc_ctrl_update_stage(dev, buf); + + if (udc_ctrl_stage_is_data_out(dev)) { + /* Allocate and feed buffer for data OUT stage */ + LOG_DBG("s:%p|feed for -out-", (void *)buf); + + err = sam0_ctrl_feed_dout(dev, udc_data_stage_length(buf)); + if (err == -ENOMEM) { + udc_submit_ep_event(dev, buf, err); + } else { + return err; + } + } else if (udc_ctrl_stage_is_data_in(dev)) { + LOG_DBG("s:%p|feed for -in-status", (void *)buf); + err = udc_ctrl_submit_s_in_status(dev); + } else { + LOG_DBG("s:%p|no data", (void *)buf); + err = udc_ctrl_submit_s_status(dev); + } + + return err; +} + +static int sam0_handle_evt_din(const struct device *dev, + struct udc_ep_config *const ep_cfg) +{ + struct net_buf *buf; + + buf = udc_buf_get(ep_cfg); + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep_cfg->addr); + return -ENOBUFS; + } + + udc_ep_set_busy(ep_cfg, false); + + if (ep_cfg->addr == USB_CONTROL_EP_IN) { + if (udc_ctrl_stage_is_status_in(dev) || + udc_ctrl_stage_is_no_data(dev)) { + /* Status stage finished, notify upper layer */ + udc_ctrl_submit_status(dev, buf); + } + + /* Update to next stage of control transfer */ + udc_ctrl_update_stage(dev, buf); + + if (udc_ctrl_stage_is_status_out(dev)) { + int err; + + /* IN transfer finished, submit buffer for status stage */ + net_buf_unref(buf); + + err = sam0_ctrl_feed_dout(dev, 0); + if (err == -ENOMEM) { + udc_submit_ep_event(dev, buf, err); + } else { + return err; + } + } + + return 0; + } + + return udc_submit_ep_event(dev, buf, 0); +} + +static inline int sam0_handle_evt_dout(const struct device *dev, + struct udc_ep_config *const ep_cfg) +{ + struct net_buf *buf; + int err = 0; + + buf = udc_buf_get(ep_cfg); + if (buf == NULL) { + LOG_ERR("No buffer for OUT ep 0x%02x", ep_cfg->addr); + return -ENODATA; + } + + udc_ep_set_busy(ep_cfg, false); + + if (ep_cfg->addr == USB_CONTROL_EP_OUT) { + if (udc_ctrl_stage_is_status_out(dev)) { + LOG_DBG("dout:%p|status, feed >s", (void *)buf); + + /* Status stage finished, notify upper layer */ + udc_ctrl_submit_status(dev, buf); + } + + /* Update to next stage of control transfer */ + udc_ctrl_update_stage(dev, buf); + + if (udc_ctrl_stage_is_status_in(dev)) { + err = udc_ctrl_submit_s_out_status(dev, buf); + } + } else { + err = udc_submit_ep_event(dev, buf, 0); + } + + return err; +} + +static void sam0_handle_xfer_next(const struct device *dev, + struct udc_ep_config *const ep_cfg) +{ + struct net_buf *buf; + int err; + + buf = udc_buf_peek(ep_cfg); + if (buf == NULL) { + return; + } + + if (USB_EP_DIR_IS_OUT(ep_cfg->addr)) { + err = sam0_prep_out(dev, buf, ep_cfg); + } else { + err = sam0_prep_in(dev, buf, ep_cfg); + } + + if (err != 0) { + buf = udc_buf_get(ep_cfg); + udc_submit_ep_event(dev, buf, -ECONNREFUSED); + } else { + udc_ep_set_busy(ep_cfg, true); + } +} + +static ALWAYS_INLINE void sam0_thread_handler(const struct device *const dev) +{ + struct udc_sam0_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg; + uint32_t evt; + uint32_t eps; + uint8_t ep; + int err; + + evt = k_event_wait(&priv->events, UINT32_MAX, false, K_FOREVER); + udc_lock_internal(dev, K_FOREVER); + + if (evt & BIT(SAM0_EVT_XFER_FINISHED)) { + k_event_clear(&priv->events, BIT(SAM0_EVT_XFER_FINISHED)); + + eps = atomic_clear(&priv->xfer_finished); + + while (eps) { + ep = udc_pull_ep_from_bmsk(&eps); + ep_cfg = udc_get_ep_cfg(dev, ep); + LOG_DBG("Finished event ep 0x%02x", ep); + + if (USB_EP_DIR_IS_IN(ep)) { + err = sam0_handle_evt_din(dev, ep_cfg); + } else { + err = sam0_handle_evt_dout(dev, ep_cfg); + } + + if (err) { + udc_submit_event(dev, UDC_EVT_ERROR, err); + } + + if (!udc_ep_is_busy(ep_cfg)) { + sam0_handle_xfer_next(dev, ep_cfg); + } else { + LOG_ERR("Endpoint 0x%02x busy", ep); + } + } + } + + if (evt & BIT(SAM0_EVT_XFER_NEW)) { + k_event_clear(&priv->events, BIT(SAM0_EVT_XFER_NEW)); + + eps = atomic_clear(&priv->xfer_new); + + while (eps) { + ep = udc_pull_ep_from_bmsk(&eps); + ep_cfg = udc_get_ep_cfg(dev, ep); + LOG_INF("New transfer ep 0x%02x in the queue", ep); + + if (!udc_ep_is_busy(ep_cfg)) { + sam0_handle_xfer_next(dev, ep_cfg); + } else { + LOG_ERR("Endpoint 0x%02x busy", ep); + } + } + } + + if (evt & BIT(SAM0_EVT_SETUP)) { + k_event_clear(&priv->events, BIT(SAM0_EVT_SETUP)); + err = sam0_handle_evt_setup(dev); + if (err) { + udc_submit_event(dev, UDC_EVT_ERROR, err); + } + } + + udc_unlock_internal(dev); +} + +static void sam0_handle_setup_isr(const struct device *dev) +{ + struct sam0_ep_buffer_desc *const bd = sam0_get_ebd(dev, 0); + struct udc_sam0_data *const priv = udc_get_private(dev); + + if (bd->bank0.byte_count != 8) { + LOG_ERR("Wrong byte count %u for setup packet", + bd->bank0.byte_count); + } + + memcpy(priv->setup, priv->ctrl_out_buf, sizeof(priv->setup)); + k_event_post(&priv->events, BIT(SAM0_EVT_SETUP)); +} + +static void sam0_handle_out_isr(const struct device *dev, const uint8_t ep) +{ + struct sam0_ep_buffer_desc *const bd = sam0_get_ebd(dev, ep); + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep); + struct udc_sam0_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); + struct net_buf *buf; + uint32_t size; + + buf = udc_buf_peek(ep_cfg); + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return; + } + + LOG_DBG("ISR ep 0x%02x byte_count %u room %u mps %u", + ep, bd->bank0.byte_count, net_buf_tailroom(buf), udc_mps_ep_size(ep_cfg)); + + size = MIN(bd->bank0.byte_count, net_buf_tailroom(buf)); + if (ep == USB_CONTROL_EP_OUT) { + net_buf_add_mem(buf, priv->ctrl_out_buf, size); + } else { + net_buf_add(buf, size); + } + + /* + * The remaining buffer size should actually be at least equal to MPS, + * if (net_buf_tailroom(buf) >= udc_mps_ep_size(ep_cfg) && ..., + * otherwise the controller may write outside the buffer, this must be + * fixed in the UDC buffer allocation. + */ + if (net_buf_tailroom(buf) && size == udc_mps_ep_size(ep_cfg)) { + __maybe_unused int err; + + if (ep == USB_CONTROL_EP_OUT) { + /* This is the same as sam0_prep_out() would do for the + * control OUT endpoint, but shorter. + */ + endpoint->EPSTATUSCLR.bit.BK0RDY = 1; + } else { + err = sam0_prep_out(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new OUT transaction"); + } + } else { + atomic_set_bit(&priv->xfer_finished, udc_ep_to_bnum(ep)); + k_event_post(&priv->events, BIT(SAM0_EVT_XFER_FINISHED)); + } +} + +static void sam0_handle_in_isr(const struct device *dev, const uint8_t ep) +{ + struct sam0_ep_buffer_desc *const bd = sam0_get_ebd(dev, ep); + struct udc_sam0_data *const priv = udc_get_private(dev); + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); + __maybe_unused int err = 0; + struct net_buf *buf; + uint32_t len; + + buf = udc_buf_peek(ep_cfg); + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return; + } + + len = bd->bank1.byte_count; + LOG_DBG("ISR ep 0x%02x byte_count %u", ep, len); + net_buf_pull(buf, len); + + if (buf->len) { + err = sam0_prep_in(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new IN transaction"); + } else { + if (udc_ep_buf_has_zlp(buf)) { + err = sam0_prep_in(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new IN transaction"); + udc_ep_buf_clear_zlp(buf); + return; + } + + atomic_set_bit(&priv->xfer_finished, udc_ep_to_bnum(ep)); + k_event_post(&priv->events, BIT(SAM0_EVT_XFER_FINISHED)); + } +} + +static void ALWAYS_INLINE handle_ep_isr(const struct device *dev, const uint8_t idx) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, idx); + uint32_t intflag; + + intflag = endpoint->EPINTFLAG.reg; + /* Clear endpoint interrupt flags */ + endpoint->EPINTFLAG.reg = intflag; + + if (intflag & USB_DEVICE_EPINTFLAG_TRCPT1) { + sam0_handle_in_isr(dev, idx | USB_EP_DIR_IN); + } + + if (intflag & USB_DEVICE_EPINTFLAG_TRCPT0) { + sam0_handle_out_isr(dev, idx); + } + + if (intflag & USB_DEVICE_EPINTFLAG_RXSTP) { + sam0_handle_setup_isr(dev); + } + +} + +static void sam0_isr_handler(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + uint32_t epintsmry = base->EPINTSMRY.reg; + uint32_t intflag; + + /* Check endpoint interrupts bit-by-bit */ + for (uint8_t idx = 0U; epintsmry != 0U; epintsmry >>= 1) { + if ((epintsmry & 1) != 0U) { + handle_ep_isr(dev, idx); + } + + idx++; + } + + intflag = base->INTFLAG.reg; + /* Clear interrupt flags */ + base->INTFLAG.reg = intflag; + + if (intflag & USB_DEVICE_INTFLAG_SOF) { + udc_submit_event(dev, UDC_EVT_SOF, 0); + } + + if (intflag & USB_DEVICE_INTFLAG_EORST) { + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, 0); + + /* Re-enable control endpoint interrupts */ + endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 | + USB_DEVICE_EPINTENSET_TRCPT1 | + USB_DEVICE_EPINTENSET_RXSTP; + + udc_submit_event(dev, UDC_EVT_RESET, 0); + } + + if (intflag & USB_DEVICE_INTFLAG_SUSPEND) { + if (!udc_is_suspended(dev)) { + udc_set_suspended(dev, true); + udc_submit_event(dev, UDC_EVT_SUSPEND, 0); + } + } + + if (intflag & USB_DEVICE_INTFLAG_EORSM) { + if (udc_is_suspended(dev)) { + udc_set_suspended(dev, false); + udc_submit_event(dev, UDC_EVT_RESUME, 0); + } + } + + /* + * This controller does not support VBUS status detection. To work + * smoothly, we should consider whether it would be possible to use the + * GPIO pin for VBUS state detection (e.g. PA7 on SAM R21 Xplained Pro). + */ + + if (intflag & USB_DEVICE_INTFLAG_RAMACER) { + udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } +} + +static int udc_sam0_ep_enqueue(const struct device *dev, + struct udc_ep_config *const ep_cfg, struct net_buf *buf) +{ + struct udc_sam0_data *const priv = udc_get_private(dev); + + LOG_DBG("%s enqueue 0x%02x %p", dev->name, ep_cfg->addr, (void *)buf); + udc_buf_put(ep_cfg, buf); + + if (!ep_cfg->stat.halted) { + atomic_set_bit(&priv->xfer_new, udc_ep_to_bnum(ep_cfg->addr)); + k_event_post(&priv->events, BIT(SAM0_EVT_XFER_NEW)); + } + + return 0; +} + +static int udc_sam0_ep_dequeue(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + unsigned int lock_key; + struct net_buf *buf; + + lock_key = irq_lock(); + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->EPSTATUSCLR.bit.BK1RDY = 1; + } else { + endpoint->EPSTATUSSET.bit.BK0RDY = 1; + } + + buf = udc_buf_get_all(ep_cfg); + if (buf) { + udc_submit_ep_event(dev, buf, -ECONNABORTED); + udc_ep_set_busy(ep_cfg, false); + } + + irq_unlock(lock_key); + + return 0; +} + +static void setup_control_out_ep(const struct device *dev) +{ + struct sam0_ep_buffer_desc *const bd = sam0_get_ebd(dev, 0); + struct udc_sam0_data *const priv = udc_get_private(dev); + + /* It will never be reassigned to anything else during device runtime. */ + bd->bank0.addr = (uintptr_t)priv->ctrl_out_buf; + bd->bank0.multi_packet_size = 0; + bd->bank0.size = sam0_get_bd_size(64); + bd->bank0.auto_zlp = 0; +} + +static int udc_sam0_ep_enable(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + uint8_t type; + + switch (ep_cfg->attributes & USB_EP_TRANSFER_TYPE_MASK) { + case USB_EP_TYPE_CONTROL: + type = 1; + break; + case USB_EP_TYPE_ISO: + type = 2; + break; + case USB_EP_TYPE_BULK: + type = 3; + break; + case USB_EP_TYPE_INTERRUPT: + type = 4; + break; + default: + return -EINVAL; + } + + if (ep_cfg->addr == USB_CONTROL_EP_OUT) { + setup_control_out_ep(dev); + endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_RXSTP; + } + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->EPCFG.bit.EPTYPE1 = type; + endpoint->EPSTATUSCLR.bit.BK1RDY = 1; + endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT1; + } else { + endpoint->EPCFG.bit.EPTYPE0 = type; + endpoint->EPSTATUSSET.bit.BK0RDY = 1; + endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0; + } + + LOG_DBG("Enable ep 0x%02x", ep_cfg->addr); + + return 0; +} + +static int udc_sam0_ep_disable(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + + if (ep_cfg->addr == USB_CONTROL_EP_OUT) { + endpoint->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_RXSTP; + } + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT1; + endpoint->EPCFG.bit.EPTYPE1 = 0; + } else { + endpoint->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0; + endpoint->EPCFG.bit.EPTYPE0 = 0; + } + + LOG_DBG("Disable ep 0x%02x", ep_cfg->addr); + + return 0; +} + +static int udc_sam0_ep_set_halt(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->EPSTATUSSET.bit.STALLRQ1 = 1; + } else { + endpoint->EPSTATUSSET.bit.STALLRQ0 = 1; + } + + LOG_DBG("Set halt ep 0x%02x", ep_cfg->addr); + if (USB_EP_GET_IDX(ep_cfg->addr) != 0) { + ep_cfg->stat.halted = true; + } + + return 0; +} + +static int udc_sam0_ep_clear_halt(const struct device *dev, struct udc_ep_config *const ep_cfg) +{ + UsbDeviceEndpoint *const endpoint = sam0_get_ep_reg(dev, ep_cfg->addr); + struct udc_sam0_data *const priv = udc_get_private(dev); + + if (USB_EP_GET_IDX(ep_cfg->addr) == 0) { + return 0; + } + + if (USB_EP_DIR_IS_IN(ep_cfg->addr)) { + endpoint->EPSTATUSCLR.bit.STALLRQ1 = 1; + endpoint->EPSTATUSCLR.bit.DTGLIN = 1; + } else { + endpoint->EPSTATUSCLR.bit.STALLRQ0 = 1; + endpoint->EPSTATUSCLR.bit.DTGLOUT = 1; + } + + if (USB_EP_GET_IDX(ep_cfg->addr) != 0 && !udc_ep_is_busy(ep_cfg)) { + if (udc_buf_peek(ep_cfg)) { + atomic_set_bit(&priv->xfer_new, udc_ep_to_bnum(ep_cfg->addr)); + k_event_post(&priv->events, BIT(SAM0_EVT_XFER_NEW)); + } + } + + LOG_DBG("Clear halt ep 0x%02x", ep_cfg->addr); + ep_cfg->stat.halted = false; + + return 0; +} + +static int udc_sam0_set_address(const struct device *dev, const uint8_t addr) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + + LOG_DBG("Set new address %u for %s", addr, dev->name); + if (addr != 0) { + base->DADD.reg = addr | USB_DEVICE_DADD_ADDEN; + } else { + base->DADD.reg = 0; + } + + return 0; +} + +static int udc_sam0_host_wakeup(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + + LOG_DBG("Remote wakeup from %s", dev->name); + base->CTRLB.bit.UPRSM = 1; + + return 0; +} + +static enum udc_bus_speed udc_sam0_device_speed(const struct device *dev) +{ + struct udc_data *data = dev->data; + + return data->caps.hs ? UDC_BUS_SPEED_HS : UDC_BUS_SPEED_FS; +} + +static int udc_sam0_enable(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + const struct pinctrl_dev_config *const pcfg = config->pcfg; + UsbDevice *const base = config->base; + int ret; + +#ifdef MCLK + /* Enable the clock in MCLK */ + MCLK->APBBMASK.bit.USB_ = 1; + + /* Enable the GCLK - use 48 MHz source */ + GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_GEN(2) | GCLK_PCHCTRL_CHEN; + + while (GCLK->SYNCBUSY.reg) { + } +#else + /* Enable the clock in PM */ + PM->APBBMASK.bit.USB_ = 1; + + /* Enable the GCLK */ + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_USB | GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_CLKEN; + + while (GCLK->STATUS.bit.SYNCBUSY) { + } +#endif + + /* Reset controller */ + base->CTRLA.bit.SWRST = 1; + sam0_wait_syncbusy(dev); + + /* + * Change QOS values to have the best performance and correct USB + * behaviour. + */ + base->QOSCTRL.bit.CQOS = 2; + base->QOSCTRL.bit.DQOS = 2; + + ret = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); + if (ret) { + LOG_ERR("Failed to apply default pinctrl state (%d)", ret); + return ret; + } + + sam0_load_padcal(dev); + + base->CTRLA.reg = USB_CTRLA_MODE_DEVICE | USB_CTRLA_RUNSTDBY; + base->CTRLB.reg = USB_DEVICE_CTRLB_SPDCONF_FS; + + base->DESCADD.reg = (uintptr_t)config->bdt; + + if (udc_ep_enable_internal(dev, USB_CONTROL_EP_OUT, + USB_EP_TYPE_CONTROL, 64, 0)) { + LOG_ERR("Failed to enable control endpoint"); + return -EIO; + } + + if (udc_ep_enable_internal(dev, USB_CONTROL_EP_IN, + USB_EP_TYPE_CONTROL, 64, 0)) { + LOG_ERR("Failed to enable control endpoint"); + return -EIO; + } + + base->INTENSET.reg = USB_DEVICE_INTENSET_EORSM | + USB_DEVICE_INTENSET_EORST | + USB_DEVICE_INTENSET_SUSPEND; + + base->CTRLA.bit.ENABLE = 1; + sam0_wait_syncbusy(dev); + base->CTRLB.bit.DETACH = 0; + + config->irq_enable_func(dev); + LOG_DBG("Enable device %s", dev->name); + + return 0; +} + +static int udc_sam0_disable(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + UsbDevice *const base = config->base; + + config->irq_disable_func(dev); + base->CTRLB.bit.DETACH = 1; + base->CTRLA.bit.ENABLE = 0; + sam0_wait_syncbusy(dev); + + if (udc_ep_disable_internal(dev, USB_CONTROL_EP_OUT)) { + LOG_ERR("Failed to disable control endpoint"); + return -EIO; + } + + if (udc_ep_disable_internal(dev, USB_CONTROL_EP_IN)) { + LOG_ERR("Failed to disable control endpoint"); + return -EIO; + } + +#ifdef MCLK + /* Disable 48 MHz clock source in GCLK */ + GCLK->PCHCTRL[USB_GCLK_ID].reg = 0; + + /* Disable the clock in MCLK */ + MCLK->APBBMASK.bit.USB_ = 0; + + while (GCLK->SYNCBUSY.reg) { + } +#else + /* Disable clock source in GCLK */ + GCLK->CLKCTRL.reg = 0; + + /* Disable the clock in PM */ + PM->APBBMASK.bit.USB_ = 0; + + while (GCLK->STATUS.bit.SYNCBUSY) { + } +#endif + + LOG_DBG("Disable device %s", dev->name); + + return 0; +} + +/* + * Nothing to do here as the controller does not support VBUS state change + * detection and there is nothing to initialize in the controller to do this. + */ +static int udc_sam0_init(const struct device *dev) +{ + LOG_DBG("Init device %s", dev->name); + + return 0; +} + +static int udc_sam0_shutdown(const struct device *dev) +{ + LOG_DBG("Shutdown device %s", dev->name); + + return 0; +} + +static int udc_sam0_driver_preinit(const struct device *dev) +{ + const struct udc_sam0_config *config = dev->config; + struct udc_sam0_data *priv = udc_get_private(dev); + struct udc_data *data = dev->data; + uint16_t mps = 1023; + int err; + + k_mutex_init(&data->mutex); + k_event_init(&priv->events); + atomic_clear(&priv->xfer_new); + atomic_clear(&priv->xfer_finished); + + data->caps.rwup = true; + data->caps.mps0 = UDC_MPS0_64; + + for (int i = 0; i < config->num_of_eps; i++) { + config->ep_cfg_out[i].caps.out = 1; + if (i == 0) { + config->ep_cfg_out[i].caps.control = 1; + config->ep_cfg_out[i].caps.mps = 64; + } else { + config->ep_cfg_out[i].caps.bulk = 1; + config->ep_cfg_out[i].caps.interrupt = 1; + config->ep_cfg_out[i].caps.iso = 1; + config->ep_cfg_out[i].caps.mps = mps; + } + + config->ep_cfg_out[i].addr = USB_EP_DIR_OUT | i; + err = udc_register_ep(dev, &config->ep_cfg_out[i]); + if (err != 0) { + LOG_ERR("Failed to register endpoint"); + return err; + } + } + + for (int i = 0; i < config->num_of_eps; i++) { + config->ep_cfg_in[i].caps.in = 1; + if (i == 0) { + config->ep_cfg_in[i].caps.control = 1; + config->ep_cfg_in[i].caps.mps = 64; + } else { + config->ep_cfg_in[i].caps.bulk = 1; + config->ep_cfg_in[i].caps.interrupt = 1; + config->ep_cfg_in[i].caps.iso = 1; + config->ep_cfg_in[i].caps.mps = mps; + } + + config->ep_cfg_in[i].addr = USB_EP_DIR_IN | i; + err = udc_register_ep(dev, &config->ep_cfg_in[i]); + if (err != 0) { + LOG_ERR("Failed to register endpoint"); + return err; + } + } + + config->make_thread(dev); + + return 0; +} + +static void udc_sam0_lock(const struct device *dev) +{ + k_sched_lock(); + udc_lock_internal(dev, K_FOREVER); +} + +static void udc_sam0_unlock(const struct device *dev) +{ + udc_unlock_internal(dev); + k_sched_unlock(); +} + +static const struct udc_api udc_sam0_api = { + .lock = udc_sam0_lock, + .unlock = udc_sam0_unlock, + .device_speed = udc_sam0_device_speed, + .init = udc_sam0_init, + .enable = udc_sam0_enable, + .disable = udc_sam0_disable, + .shutdown = udc_sam0_shutdown, + .set_address = udc_sam0_set_address, + .host_wakeup = udc_sam0_host_wakeup, + .ep_enable = udc_sam0_ep_enable, + .ep_disable = udc_sam0_ep_disable, + .ep_set_halt = udc_sam0_ep_set_halt, + .ep_clear_halt = udc_sam0_ep_clear_halt, + .ep_enqueue = udc_sam0_ep_enqueue, + .ep_dequeue = udc_sam0_ep_dequeue, +}; + +#define DT_DRV_COMPAT atmel_sam0_usb + +#define UDC_SAM0_IRQ_ENABLE(i, n) \ + IRQ_CONNECT(DT_INST_IRQ_BY_IDX(n, i, irq), \ + DT_INST_IRQ_BY_IDX(n, i, priority), \ + sam0_isr_handler, DEVICE_DT_INST_GET(n), 0); \ + irq_enable(DT_INST_IRQ_BY_IDX(n, i, irq)); + +#define UDC_SAM0_IRQ_DISABLE(i, n) \ + irq_disable(DT_INST_IRQ_BY_IDX(n, i, irq)); + +#define UDC_SAM0_IRQ_ENABLE_DEFINE(i, n) \ +static void udc_sam0_irq_enable_func_##n(const struct device *dev) \ +{ \ + LISTIFY(DT_INST_NUM_IRQS(n), UDC_SAM0_IRQ_ENABLE, (), n) \ +} + +#define UDC_SAM0_IRQ_DISABLE_DEFINE(i, n) \ +static void udc_sam0_irq_disable_func_##n(const struct device *dev) \ +{ \ + LISTIFY(DT_INST_NUM_IRQS(n), UDC_SAM0_IRQ_DISABLE, (), n) \ +} + +#define UDC_SAM0_PINCTRL_DT_INST_DEFINE(n) \ + COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ + (PINCTRL_DT_INST_DEFINE(n)), ()) + +#define UDC_SAM0_PINCTRL_DT_INST_DEV_CONFIG_GET(n) \ + COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ + ((void *)PINCTRL_DT_INST_DEV_CONFIG_GET(n)), (NULL)) + +#define UDC_SAM0_DEVICE_DEFINE(n) \ + UDC_SAM0_PINCTRL_DT_INST_DEFINE(n); \ + UDC_SAM0_IRQ_ENABLE_DEFINE(i, n); \ + UDC_SAM0_IRQ_DISABLE_DEFINE(i, n); \ + \ + K_THREAD_STACK_DEFINE(udc_sam0_stack_##n, CONFIG_UDC_SAM0_STACK_SIZE); \ + \ + static __aligned(sizeof(void *)) struct sam0_ep_buffer_desc \ + sam0_bdt_##n[DT_INST_PROP(n, num_bidir_endpoints)]; \ + \ + static void udc_sam0_thread_##n(void *dev, void *arg1, void *arg2) \ + { \ + while (true) { \ + sam0_thread_handler(dev); \ + } \ + } \ + \ + static void udc_sam0_make_thread_##n(const struct device *dev) \ + { \ + struct udc_sam0_data *priv = udc_get_private(dev); \ + \ + k_thread_create(&priv->thread_data, \ + udc_sam0_stack_##n, \ + K_THREAD_STACK_SIZEOF(udc_sam0_stack_##n), \ + udc_sam0_thread_##n, \ + (void *)dev, NULL, NULL, \ + K_PRIO_COOP(CONFIG_UDC_SAM0_THREAD_PRIORITY), \ + K_ESSENTIAL, \ + K_NO_WAIT); \ + k_thread_name_set(&priv->thread_data, dev->name); \ + } \ + \ + static struct udc_ep_config \ + ep_cfg_out[DT_INST_PROP(n, num_bidir_endpoints)]; \ + static struct udc_ep_config \ + ep_cfg_in[DT_INST_PROP(n, num_bidir_endpoints)]; \ + \ + static const struct udc_sam0_config udc_sam0_config_##n = { \ + .base = (UsbDevice *)DT_INST_REG_ADDR(n), \ + .bdt = sam0_bdt_##n, \ + .num_of_eps = DT_INST_PROP(n, num_bidir_endpoints), \ + .ep_cfg_in = ep_cfg_out, \ + .ep_cfg_out = ep_cfg_in, \ + .irq_enable_func = udc_sam0_irq_enable_func_##n, \ + .irq_disable_func = udc_sam0_irq_disable_func_##n, \ + .pcfg = UDC_SAM0_PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .make_thread = udc_sam0_make_thread_##n, \ + }; \ + \ + static struct udc_sam0_data udc_priv_##n = { \ + }; \ + \ + static struct udc_data udc_data_##n = { \ + .mutex = Z_MUTEX_INITIALIZER(udc_data_##n.mutex), \ + .priv = &udc_priv_##n, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, udc_sam0_driver_preinit, NULL, \ + &udc_data_##n, &udc_sam0_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &udc_sam0_api); + +DT_INST_FOREACH_STATUS_OKAY(UDC_SAM0_DEVICE_DEFINE)