From c41071d2ac868eb14a5725987c36f522dc4e8585 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Mon, 19 Aug 2024 19:13:28 +0200 Subject: [PATCH] drivers: udc: add UDC driver for RP2040 USB device controller This driver is mostly rewritten from scratch, with some parts borrowed from the usb_dc_rpi_pico driver implemented by Pete Johanson. This driver does not use any function from RPI PICO HAL and actually could be furter improved to not use any defines or types from the HAL as they are confusing and overdo it with volatile qualifiers. Signed-off-by: Johann Fischer --- drivers/usb/udc/CMakeLists.txt | 1 + drivers/usb/udc/Kconfig | 1 + drivers/usb/udc/Kconfig.rpi_pico | 26 + drivers/usb/udc/udc_rpi_pico.c | 1157 ++++++++++++++++++++++++++++++ 4 files changed, 1185 insertions(+) create mode 100644 drivers/usb/udc/Kconfig.rpi_pico create mode 100644 drivers/usb/udc/udc_rpi_pico.c diff --git a/drivers/usb/udc/CMakeLists.txt b/drivers/usb/udc/CMakeLists.txt index 44c9bf023ad..afadbe8786c 100644 --- a/drivers/usb/udc/CMakeLists.txt +++ b/drivers/usb/udc/CMakeLists.txt @@ -16,3 +16,4 @@ zephyr_library_sources_ifdef(CONFIG_UDC_IT82XX2 udc_it82xx2.c) zephyr_library_sources_ifdef(CONFIG_UDC_NXP_EHCI udc_mcux_ehci.c) zephyr_library_sources_ifdef(CONFIG_UDC_NXP_IP3511 udc_mcux_ip3511.c) zephyr_library_sources_ifdef(CONFIG_UDC_NUMAKER udc_numaker.c) +zephyr_library_sources_ifdef(CONFIG_UDC_RPI_PICO udc_rpi_pico.c) diff --git a/drivers/usb/udc/Kconfig b/drivers/usb/udc/Kconfig index 59b80620817..4c96fccf71a 100644 --- a/drivers/usb/udc/Kconfig +++ b/drivers/usb/udc/Kconfig @@ -63,5 +63,6 @@ source "drivers/usb/udc/Kconfig.stm32" source "drivers/usb/udc/Kconfig.it82xx2" source "drivers/usb/udc/Kconfig.mcux" source "drivers/usb/udc/Kconfig.numaker" +source "drivers/usb/udc/Kconfig.rpi_pico" endif # UDC_DRIVER diff --git a/drivers/usb/udc/Kconfig.rpi_pico b/drivers/usb/udc/Kconfig.rpi_pico new file mode 100644 index 00000000000..3656a754afc --- /dev/null +++ b/drivers/usb/udc/Kconfig.rpi_pico @@ -0,0 +1,26 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config UDC_RPI_PICO + bool "Driver for an Raspberry PI Pico USB device controller" + default y + depends on DT_HAS_RASPBERRYPI_PICO_USBD_ENABLED + select SYS_MEM_BLOCKS + help + Driver for an Raspberry PI Pico USB device controller. + +if UDC_RPI_PICO + +config UDC_RPI_PICO_STACK_SIZE + int "UDC controller driver internal thread stack size" + default 512 + help + Device controller driver internal thread stack size. + +config UDC_RPI_PICO_THREAD_PRIORITY + int "UDC controller driver thread priority" + default 8 + help + Device controller driver thread priority. + +endif # UDC_RPI_PICO diff --git a/drivers/usb/udc/udc_rpi_pico.c b/drivers/usb/udc/udc_rpi_pico.c new file mode 100644 index 00000000000..f109418baa9 --- /dev/null +++ b/drivers/usb/udc/udc_rpi_pico.c @@ -0,0 +1,1157 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * Copyright (c) 2021 Pete Johanson + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "udc_common.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(udc_rpi_pico, CONFIG_UDC_DRIVER_LOG_LEVEL); + +struct rpi_pico_config { + usb_hw_t *base; + usb_device_dpram_t *dpram; + sys_mem_blocks_t *mem_block; + size_t num_of_eps; + struct udc_ep_config *ep_cfg_in; + struct udc_ep_config *ep_cfg_out; + void (*make_thread)(const struct device *dev); + void (*irq_enable_func)(const struct device *dev); + void (*irq_disable_func)(const struct device *dev); + const struct device *clk_dev; + clock_control_subsys_t clk_sys; +}; + +struct rpi_pico_ep_data { + void *buf; + uint8_t next_pid; +}; + +struct rpi_pico_data { + struct k_thread thread_data; + struct rpi_pico_ep_data out_ep[USB_NUM_ENDPOINTS]; + struct rpi_pico_ep_data in_ep[USB_NUM_ENDPOINTS]; + bool rwu_pending; + uint8_t setup[8]; +}; + +enum rpi_pico_event_type { + /* Trigger next transfer, must not be used for control OUT */ + RPI_PICO_EVT_XFER, + /* Setup packet received */ + RPI_PICO_EVT_SETUP, + /* OUT transaction for specific endpoint is finished */ + RPI_PICO_EVT_DOUT, + /* IN transaction for specific endpoint is finished */ + RPI_PICO_EVT_DIN, +}; + +struct rpi_pico_event { + const struct device *dev; + enum rpi_pico_event_type type; + uint8_t ep; +}; + +K_MSGQ_DEFINE(drv_msgq, sizeof(struct rpi_pico_event), 16, sizeof(void *)); + +/* Use Atomic Register Access to set bits */ +static void ALWAYS_INLINE rpi_pico_bit_set(const mm_reg_t reg, const uint32_t bit) +{ + sys_write32(bit, REG_ALIAS_SET_BITS | reg); +} + +/* Use Atomic Register Access to clear bits */ +static void ALWAYS_INLINE rpi_pico_bit_clr(const mm_reg_t reg, const uint32_t bit) +{ + sys_write32(bit, REG_ALIAS_CLR_BITS | reg); +} + +static void ALWAYS_INLINE sie_status_clr(const struct device *dev, const uint32_t bit) +{ + const struct rpi_pico_config *config = dev->config; + usb_hw_t *base = config->base; + + rpi_pico_bit_clr((mm_reg_t)&base->sie_status, bit); +} + +static inline uint32_t get_ep_mask(const uint8_t ep) +{ + const int idx = USB_EP_GET_IDX(ep) * 2 + USB_EP_DIR_IS_OUT(ep); + + return BIT(idx); +} + +/* Get the address of an endpoint control register */ +static mem_addr_t get_ep_ctrl_reg(const struct device *dev, const uint8_t ep) +{ + const struct rpi_pico_config *config = dev->config; + usb_device_dpram_t *dpram = config->dpram; + + if (USB_EP_GET_IDX(ep) == 0) { + return 0UL; + } + + if (USB_EP_DIR_IS_OUT(ep)) { + return (uintptr_t)&dpram->ep_ctrl[USB_EP_GET_IDX(ep) - 1].out; + } + + return (uintptr_t)&dpram->ep_ctrl[USB_EP_GET_IDX(ep) - 1].in; +} + +/* Get the address of an endpoint buffer control register */ +static mem_addr_t get_buf_ctrl_reg(const struct device *dev, const uint8_t ep) +{ + const struct rpi_pico_config *config = dev->config; + usb_device_dpram_t *dpram = config->dpram; + + if (USB_EP_DIR_IS_OUT(ep)) { + return (uintptr_t)&dpram->ep_buf_ctrl[USB_EP_GET_IDX(ep)].out; + } + + return (uintptr_t)&dpram->ep_buf_ctrl[USB_EP_GET_IDX(ep)].in; +} + +/* Get the address of an endpoint buffer control register */ +static struct rpi_pico_ep_data *get_ep_data(const struct device *dev, const uint8_t ep) +{ + struct rpi_pico_data *priv = udc_get_private(dev); + + if (USB_EP_DIR_IS_OUT(ep)) { + return &priv->out_ep[USB_EP_GET_IDX(ep)]; + } + + return &priv->in_ep[USB_EP_GET_IDX(ep)]; +} + +static uint32_t read_buf_ctrl_reg(const struct device *dev, const uint8_t ep) +{ + mm_reg_t buf_ctrl_reg = get_buf_ctrl_reg(dev, ep); + + return sys_read32(buf_ctrl_reg); +} + +static void write_buf_ctrl_reg(const struct device *dev, const uint8_t ep, + const uint32_t buf_ctrl) +{ + mm_reg_t buf_ctrl_reg = get_buf_ctrl_reg(dev, ep); + + sys_write32(buf_ctrl, buf_ctrl_reg); +} + +static void write_ep_ctrl_reg(const struct device *dev, const uint8_t ep, + const uint32_t ep_ctrl) +{ + mm_reg_t ep_ctrl_reg = get_ep_ctrl_reg(dev, ep); + + sys_write32(ep_ctrl, ep_ctrl_reg); +} + +static void rpi_pico_ep_cancel(const struct device *dev, const uint8_t ep) +{ + bool abort_handshake_supported = rp2040_chip_version() >= 2; + const struct rpi_pico_config *config = dev->config; + usb_hw_t *base = config->base; + mm_reg_t abort_done_reg = (mm_reg_t)&base->abort_done; + mm_reg_t abort_reg = (mm_reg_t)&base->abort; + const uint32_t ep_mask = get_ep_mask(ep); + uint32_t buf_ctrl; + + buf_ctrl = read_buf_ctrl_reg(dev, ep); + if (!(buf_ctrl & USB_BUF_CTRL_AVAIL)) { + /* The buffer is not used by the controller */ + return; + } + + if (abort_handshake_supported) { + rpi_pico_bit_set(abort_reg, ep_mask); + while ((sys_read32(abort_done_reg) & ep_mask) != ep_mask) { + } + } + + buf_ctrl &= ~USB_BUF_CTRL_AVAIL; + write_buf_ctrl_reg(dev, ep, buf_ctrl); + + if (abort_handshake_supported) { + rpi_pico_bit_clr(abort_reg, ep_mask); + } + + LOG_INF("Canceled ep 0x%02x transaction", ep); +} + +static int rpi_pico_prep_rx(const struct device *dev, + struct net_buf *const buf, struct udc_ep_config *const cfg) +{ + struct rpi_pico_ep_data *const ep_data = get_ep_data(dev, cfg->addr); + uint32_t buf_ctrl; + + buf_ctrl = read_buf_ctrl_reg(dev, cfg->addr); + if (buf_ctrl & USB_BUF_CTRL_AVAIL) { + LOG_ERR("ep 0x%02x buffer is used by the controller", cfg->addr); + return -EBUSY; + } + + LOG_DBG("Prepare RX ep 0x%02x len %u pid: %u", + cfg->addr, net_buf_tailroom(buf), ep_data->next_pid); + + buf_ctrl = cfg->mps; + buf_ctrl |= ep_data->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; + ep_data->next_pid ^= 1U; + + write_buf_ctrl_reg(dev, cfg->addr, buf_ctrl); + /* + * By default, clk_sys runs at 125MHz, wait 3 nop instructions before + * setting the AVAILABLE bit. See 4.1.2.5.1. Concurrent access. + */ + arch_nop(); + arch_nop(); + arch_nop(); + write_buf_ctrl_reg(dev, cfg->addr, buf_ctrl | USB_BUF_CTRL_AVAIL); + + return 0; +} + +static int rpi_pico_prep_tx(const struct device *dev, + struct net_buf *const buf, struct udc_ep_config *const cfg) +{ + struct rpi_pico_ep_data *const ep_data = get_ep_data(dev, cfg->addr); + uint32_t buf_ctrl; + size_t len; + + buf_ctrl = read_buf_ctrl_reg(dev, cfg->addr); + if (buf_ctrl & USB_BUF_CTRL_AVAIL) { + LOG_ERR("ep 0x%02x buffer is used by the controller", cfg->addr); + return -EBUSY; + } + + len = MIN(cfg->mps, buf->len); + memcpy(ep_data->buf, buf->data, len); + + LOG_DBG("Prepare TX ep 0x%02x len %u pid: %u", + cfg->addr, len, ep_data->next_pid); + + buf_ctrl = len; + buf_ctrl |= ep_data->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; + buf_ctrl |= USB_BUF_CTRL_FULL; + ep_data->next_pid ^= 1U; + + write_buf_ctrl_reg(dev, cfg->addr, buf_ctrl); + /* + * By default, clk_sys runs at 125MHz, wait 3 nop instructions before + * setting the AVAILABLE bit. See 4.1.2.5.1. Concurrent access. + */ + arch_nop(); + arch_nop(); + arch_nop(); + write_buf_ctrl_reg(dev, cfg->addr, buf_ctrl | USB_BUF_CTRL_AVAIL); + + return 0; +} + +static int rpi_pico_ctrl_feed_dout(const struct device *dev, const size_t length) +{ + struct udc_ep_config *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 rpi_pico_prep_rx(dev, buf, ep_cfg); +} + +static void drop_control_transfers(const struct device *dev) +{ + struct net_buf *buf; + + buf = udc_buf_get_all(dev, USB_CONTROL_EP_OUT); + if (buf != NULL) { + net_buf_unref(buf); + } + + buf = udc_buf_get_all(dev, USB_CONTROL_EP_IN); + if (buf != NULL) { + net_buf_unref(buf); + } +} + +static int rpi_pico_handle_evt_setup(const struct device *dev) +{ + struct rpi_pico_data *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) { + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return -ENOMEM; + } + + net_buf_add_mem(buf, priv->setup, sizeof(priv->setup)); + udc_ep_buf_set_setup(buf); + LOG_HEXDUMP_DBG(buf->data, buf->len, "setup"); + + /* 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-", buf); + + err = rpi_pico_ctrl_feed_dout(dev, udc_data_stage_length(buf)); + if (err != 0) { + err = udc_submit_ep_event(dev, buf, err); + } + } else if (udc_ctrl_stage_is_data_in(dev)) { + LOG_DBG("s:%p|feed for -in-status", buf); + err = udc_ctrl_submit_s_in_status(dev); + } else { + LOG_DBG("s:%p|no data", buf); + err = udc_ctrl_submit_s_status(dev); + } + + return err; +} + +static inline int rpi_pico_handle_evt_dout(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct net_buf *buf; + int err = 0; + + buf = udc_buf_get(dev, cfg->addr); + if (buf == NULL) { + LOG_ERR("No buffer for OUT ep 0x%02x", cfg->addr); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return -ENODATA; + } + + udc_ep_set_busy(dev, cfg->addr, false); + + if (cfg->addr == USB_CONTROL_EP_OUT) { + if (udc_ctrl_stage_is_status_out(dev)) { + LOG_DBG("dout:%p|status, feed >s", 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 int rpi_pico_handle_evt_din(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct net_buf *buf; + int err; + + buf = udc_buf_peek(dev, cfg->addr); + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", cfg->addr); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return -ENOBUFS; + } + + buf = udc_buf_get(dev, cfg->addr); + udc_ep_set_busy(dev, cfg->addr, false); + + if (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)) { + /* IN transfer finished, submit buffer for status stage */ + net_buf_unref(buf); + + err = rpi_pico_ctrl_feed_dout(dev, 0); + if (err == -ENOMEM) { + err = udc_submit_ep_event(dev, buf, err); + } + } + + return 0; + } + + return udc_submit_ep_event(dev, buf, 0); +} + +static void rpi_pico_handle_xfer_next(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct net_buf *buf; + int err; + + buf = udc_buf_peek(dev, cfg->addr); + if (buf == NULL) { + return; + } + + if (USB_EP_DIR_IS_OUT(cfg->addr)) { + err = rpi_pico_prep_rx(dev, buf, cfg); + } else { + err = rpi_pico_prep_tx(dev, buf, cfg); + } + + if (err != 0) { + udc_submit_ep_event(dev, buf, -ECONNREFUSED); + } else { + udc_ep_set_busy(dev, cfg->addr, true); + } +} + +static ALWAYS_INLINE void rpi_pico_thread_handler(void *const arg) +{ + const struct device *dev = (const struct device *)arg; + struct udc_ep_config *ep_cfg; + struct rpi_pico_event evt; + + k_msgq_get(&drv_msgq, &evt, K_FOREVER); + ep_cfg = udc_get_ep_cfg(dev, evt.ep); + + switch (evt.type) { + case RPI_PICO_EVT_XFER: + LOG_DBG("New transfer in the queue"); + break; + case RPI_PICO_EVT_SETUP: + LOG_DBG("SETUP event"); + rpi_pico_handle_evt_setup(dev); + break; + case RPI_PICO_EVT_DOUT: + LOG_DBG("DOUT event ep 0x%02x", ep_cfg->addr); + rpi_pico_handle_evt_dout(dev, ep_cfg); + break; + case RPI_PICO_EVT_DIN: + LOG_DBG("DIN event"); + rpi_pico_handle_evt_din(dev, ep_cfg); + break; + } + + if (ep_cfg->addr != USB_CONTROL_EP_OUT && !udc_ep_is_busy(dev, ep_cfg->addr)) { + rpi_pico_handle_xfer_next(dev, ep_cfg); + } +} + +static void rpi_pico_handle_setup(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + struct rpi_pico_data *priv = udc_get_private(dev); + usb_device_dpram_t *dpram = config->dpram; + struct rpi_pico_event evt = { + .type = RPI_PICO_EVT_SETUP, + .ep = USB_CONTROL_EP_OUT, + }; + + /* + * Host may issue a new setup packet even if the previous control transfer + * did not complete. Cancel any active transaction. + */ + rpi_pico_ep_cancel(dev, USB_CONTROL_EP_IN); + rpi_pico_ep_cancel(dev, USB_CONTROL_EP_OUT); + + sys_put_le32(sys_read32((uintptr_t)&dpram->setup_packet[0]), &priv->setup[0]); + sys_put_le32(sys_read32((uintptr_t)&dpram->setup_packet[4]), &priv->setup[4]); + + /* Set DATA1 PID for the next (data or status) stage */ + get_ep_data(dev, USB_CONTROL_EP_IN)->next_pid = 1; + get_ep_data(dev, USB_CONTROL_EP_OUT)->next_pid = 1; + + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); +} + +static void rpi_pico_handle_buff_status_in(const struct device *dev, const uint8_t ep) +{ + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); + struct rpi_pico_event evt = { + .ep = ep, + .type = RPI_PICO_EVT_DIN, + }; + __maybe_unused int err; + struct net_buf *buf; + size_t len; + + buf = udc_buf_peek(dev, ep); + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return; + } + + len = read_buf_ctrl_reg(dev, ep) & USB_BUF_CTRL_LEN_MASK; + net_buf_pull(buf, len); + + if (buf->len) { + err = rpi_pico_prep_tx(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new IN transaction"); + } else { + if (udc_ep_buf_has_zlp(buf)) { + err = rpi_pico_prep_tx(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new IN transaction"); + udc_ep_buf_clear_zlp(buf); + } else { + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + } + } +} + +static void rpi_pico_handle_buff_status_out(const struct device *dev, const uint8_t ep) +{ + struct rpi_pico_ep_data *ep_data = get_ep_data(dev, ep); + struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); + struct rpi_pico_event evt = { + .ep = ep, + .type = RPI_PICO_EVT_DOUT, + }; + struct net_buf *buf; + size_t len; + + buf = udc_buf_peek(dev, ep); + if (buf == NULL) { + LOG_ERR("No buffer for ep 0x%02x", ep); + udc_submit_event(dev, UDC_EVT_ERROR, -ENOBUFS); + return; + } + + len = read_buf_ctrl_reg(dev, ep) & USB_BUF_CTRL_LEN_MASK; + net_buf_add_mem(buf, ep_data->buf, MIN(len, net_buf_tailroom(buf))); + + if (net_buf_tailroom(buf) >= udc_mps_ep_size(ep_cfg) && + len == udc_mps_ep_size(ep_cfg)) { + __unused int err; + + err = rpi_pico_prep_rx(dev, buf, ep_cfg); + __ASSERT(err == 0, "Failed to start new OUT transaction"); + } else { + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + } +} + +static void rpi_pico_handle_buff_status(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + usb_hw_t *base = config->base; + uint32_t buf_status; + uint8_t ep; + + buf_status = sys_read32((mm_reg_t)&base->buf_status); + + for (unsigned int i = 0; buf_status && i < USB_NUM_ENDPOINTS * 2; i++) { + if (!(buf_status & BIT(i))) { + continue; + } + + /* Odd bits in the register correspond to OUT endpoints */ + if (i & 1U) { + ep = USB_EP_DIR_OUT | (i >> 1U); + rpi_pico_handle_buff_status_out(dev, ep); + } else { + ep = USB_EP_DIR_IN | (i >> 1U); + rpi_pico_handle_buff_status_in(dev, ep); + } + + rpi_pico_bit_clr((mm_reg_t)&base->buf_status, BIT(i)); + buf_status &= ~BIT(i); + } +} + +static void rpi_pico_isr_handler(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + struct rpi_pico_data *priv = udc_get_private(dev); + usb_hw_t *base = config->base; + uint32_t status = base->ints; + uint32_t handled = 0; + + if (status & USB_INTS_DEV_SOF_BITS) { + handled |= USB_INTS_DEV_SOF_BITS; + sys_read32((mm_reg_t)&base->sof_rd); + } + + if (status & USB_INTS_DEV_CONN_DIS_BITS) { + uint32_t sie_status; + + handled |= USB_INTS_DEV_CONN_DIS_BITS; + sie_status_clr(dev, USB_SIE_STATUS_CONNECTED_BITS); + + sie_status = sys_read32((mm_reg_t)&base->sie_status); + if (sie_status & USB_SIE_STATUS_CONNECTED_BITS) { + udc_submit_event(dev, UDC_EVT_VBUS_READY, 0); + } else { + udc_submit_event(dev, UDC_EVT_VBUS_REMOVED, 0); + } + } + + if ((status & (USB_INTS_BUFF_STATUS_BITS | USB_INTS_SETUP_REQ_BITS)) && + priv->rwu_pending) { + /* The rpi pico USB device does not appear to be sending + * USB_INTR_DEV_RESUME_FROM_HOST interrupts when the resume is + * a result of a remote wakeup request sent by us. + * This will simulate a resume event if bus activity is observed + */ + + priv->rwu_pending = false; + udc_submit_event(dev, UDC_EVT_RESUME, 0); + } + + if (status & USB_INTR_DEV_RESUME_FROM_HOST_BITS) { + handled |= USB_INTR_DEV_RESUME_FROM_HOST_BITS; + sie_status_clr(dev, USB_SIE_STATUS_RESUME_BITS); + + priv->rwu_pending = false; + udc_submit_event(dev, UDC_EVT_RESUME, 0); + } + + if (status & USB_INTS_DEV_SUSPEND_BITS) { + handled |= USB_INTS_DEV_SUSPEND_BITS; + sie_status_clr(dev, USB_SIE_STATUS_SUSPENDED_BITS); + + udc_submit_event(dev, UDC_EVT_SUSPEND, 0); + } + + if (status & USB_INTS_BUS_RESET_BITS) { + handled |= USB_INTS_BUS_RESET_BITS; + sie_status_clr(dev, USB_SIE_STATUS_BUS_RESET_BITS); + + sys_write32(0, (mm_reg_t)&base->dev_addr_ctrl); + udc_submit_event(dev, UDC_EVT_RESET, 0); + } + + if (status & USB_INTS_ERROR_DATA_SEQ_BITS) { + handled |= USB_INTS_ERROR_DATA_SEQ_BITS; + sie_status_clr(dev, USB_SIE_STATUS_DATA_SEQ_ERROR_BITS); + + LOG_ERR("Data Sequence Error"); + udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } + + if (status & USB_INTS_ERROR_RX_TIMEOUT_BITS) { + handled |= USB_INTS_ERROR_RX_TIMEOUT_BITS; + sie_status_clr(dev, USB_SIE_STATUS_RX_TIMEOUT_BITS); + + LOG_ERR("RX timeout"); + udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } + + if (status & USB_INTS_ERROR_RX_OVERFLOW_BITS) { + sie_status_clr(dev, USB_SIE_STATUS_RX_OVERFLOW_BITS); + handled |= USB_INTS_ERROR_RX_OVERFLOW_BITS; + + LOG_ERR("RX overflow"); + udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } + + if (status & USB_INTS_ERROR_BIT_STUFF_BITS) { + handled |= USB_INTS_ERROR_BIT_STUFF_BITS; + sie_status_clr(dev, USB_SIE_STATUS_BIT_STUFF_ERROR_BITS); + + LOG_ERR("Bit Stuff Error"); + udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } + + if (status & USB_INTS_ERROR_CRC_BITS) { + handled |= USB_INTS_ERROR_CRC_BITS; + sie_status_clr(dev, USB_SIE_STATUS_CRC_ERROR_BITS); + + LOG_ERR("CRC Error"); + udc_submit_event(dev, UDC_EVT_ERROR, -EINVAL); + } + + /* + * Here both interrupt flags BUF_STATUS and SETUP_REQ may be set at + * the same time, e.g. because of the interrupt latency. Check + * BUF_STATUS interrupt first to get the notifications in the right + * order. + */ + if (status & USB_INTS_BUFF_STATUS_BITS) { + handled |= USB_INTS_BUFF_STATUS_BITS; + rpi_pico_handle_buff_status(dev); + } + + if (status & USB_INTS_SETUP_REQ_BITS) { + handled |= USB_INTS_SETUP_REQ_BITS; + sie_status_clr(dev, USB_SIE_STATUS_SETUP_REC_BITS); + + rpi_pico_handle_setup(dev); + } + + if (status ^ handled) { + LOG_ERR("Unhandled IRQ: 0x%x", status ^ handled); + } +} + +static int udc_rpi_pico_ep_enqueue(const struct device *dev, + struct udc_ep_config *const cfg, + struct net_buf *buf) +{ + struct rpi_pico_event evt = { + .ep = cfg->addr, + .type = RPI_PICO_EVT_XFER, + }; + + udc_buf_put(cfg, buf); + + if (!cfg->stat.halted) { + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + } + + return 0; +} + +static int udc_rpi_pico_ep_dequeue(const struct device *dev, + struct udc_ep_config *const cfg) +{ + unsigned int lock_key; + struct net_buf *buf; + + lock_key = irq_lock(); + + rpi_pico_ep_cancel(dev, cfg->addr); + buf = udc_buf_get_all(dev, cfg->addr); + if (buf) { + udc_submit_ep_event(dev, buf, -ECONNABORTED); + } + + irq_unlock(lock_key); + + return 0; +} + +static int udc_rpi_pico_ep_enable(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct rpi_pico_ep_data *const ep_data = get_ep_data(dev, cfg->addr); + const struct rpi_pico_config *config = dev->config; + uint8_t type = cfg->attributes & USB_EP_TRANSFER_TYPE_MASK; + usb_device_dpram_t *dpram = config->dpram; + int err; + + write_buf_ctrl_reg(dev, cfg->addr, USB_BUF_CTRL_DATA0_PID); + ep_data->next_pid = 0; + + if (USB_EP_GET_IDX(cfg->addr) != 0) { + uint32_t ep_ctrl = EP_CTRL_ENABLE_BITS | + EP_CTRL_INTERRUPT_PER_BUFFER | + (type << EP_CTRL_BUFFER_TYPE_LSB); + size_t blocks = DIV_ROUND_UP(cfg->mps, 64U); + + err = sys_mem_blocks_alloc(config->mem_block, blocks, &ep_data->buf); + if (err != 0) { + LOG_ERR("Failed to allocate %zu memory blocks for ep 0x%02x", + cfg->addr, blocks); + return err; + } + + ep_ctrl |= (uintptr_t)ep_data->buf & 0xFFFFUL; + write_ep_ctrl_reg(dev, cfg->addr, ep_ctrl); + } else { + ep_data->buf = dpram->ep0_buf_a; + } + + LOG_DBG("Enable ep 0x%02x", cfg->addr); + + return 0; +} + +static int udc_rpi_pico_ep_disable(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct rpi_pico_ep_data *const ep_data = get_ep_data(dev, cfg->addr); + const struct rpi_pico_config *config = dev->config; + int err; + + rpi_pico_ep_cancel(dev, cfg->addr); + + if (USB_EP_GET_IDX(cfg->addr) != 0) { + size_t blocks = DIV_ROUND_UP(cfg->mps, 64U); + + write_ep_ctrl_reg(dev, cfg->addr, 0UL); + err = sys_mem_blocks_free(config->mem_block, blocks, &ep_data->buf); + if (err != 0) { + LOG_ERR("Failed to free memory blocks"); + return err; + } + } + + LOG_DBG("Disable ep 0x%02x", cfg->addr); + + return 0; +} + +static int udc_rpi_pico_ep_set_halt(const struct device *dev, + struct udc_ep_config *const cfg) +{ + const struct rpi_pico_config *config = dev->config; + mem_addr_t buf_ctrl_reg = get_buf_ctrl_reg(dev, cfg->addr); + usb_hw_t *base = config->base; + + if (USB_EP_GET_IDX(cfg->addr) == 0) { + uint32_t bit = USB_EP_DIR_IS_OUT(cfg->addr) ? + USB_EP_STALL_ARM_EP0_OUT_BITS : USB_EP_STALL_ARM_EP0_IN_BITS; + + rpi_pico_bit_set((mm_reg_t)&base->ep_stall_arm, bit); + } + + rpi_pico_bit_set(buf_ctrl_reg, USB_BUF_CTRL_STALL); + + LOG_INF("Set halt ep 0x%02x", cfg->addr); + if (USB_EP_GET_IDX(cfg->addr) != 0) { + cfg->stat.halted = true; + } + + return 0; +} + +static int udc_rpi_pico_ep_clear_halt(const struct device *dev, + struct udc_ep_config *const cfg) +{ + struct rpi_pico_ep_data *const ep_data = get_ep_data(dev, cfg->addr); + mem_addr_t buf_ctrl_reg = get_buf_ctrl_reg(dev, cfg->addr); + struct rpi_pico_event evt = { + .ep = cfg->addr, + .type = RPI_PICO_EVT_XFER, + }; + + if (USB_EP_GET_IDX(cfg->addr) != 0) { + ep_data->next_pid = 0; + rpi_pico_bit_clr(buf_ctrl_reg, USB_BUF_CTRL_DATA1_PID); + /* + * By default, clk_sys runs at 125MHz, wait 3 nop instructions + * before clearing the CTRL_STALL bit. See 4.1.2.5.4. + */ + arch_nop(); + arch_nop(); + arch_nop(); + rpi_pico_bit_clr(buf_ctrl_reg, USB_BUF_CTRL_STALL); + + if (udc_buf_peek(dev, cfg->addr)) { + k_msgq_put(&drv_msgq, &evt, K_NO_WAIT); + } + } + + cfg->stat.halted = false; + LOG_INF("Clear halt ep 0x%02x buf_ctrl 0x%08x", + cfg->addr, sys_read32(buf_ctrl_reg)); + + return 0; +} + +static int udc_rpi_pico_set_address(const struct device *dev, const uint8_t addr) +{ + const struct rpi_pico_config *config = dev->config; + usb_hw_t *base = config->base; + + sys_write32(addr, (mm_reg_t)&base->dev_addr_ctrl); + LOG_DBG("Set new address %u for %s", addr, dev->name); + + return 0; +} + +static int udc_rpi_pico_host_wakeup(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + struct rpi_pico_data *priv = udc_get_private(dev); + usb_hw_t *base = config->base; + + rpi_pico_bit_set((mm_reg_t)&base->sie_ctrl, USB_SIE_CTRL_RESUME_BITS); + priv->rwu_pending = true; + + LOG_DBG("Remote wakeup from %s", dev->name); + + return 0; +} + +static int udc_rpi_pico_enable(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + usb_device_dpram_t *dpram = config->dpram; + usb_hw_t *base = config->base; + + /* Reset USB controller */ + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + /* Clear registers and DPRAM */ + memset(base, 0, sizeof(usb_hw_t)); + memset(dpram, 0, sizeof(usb_device_dpram_t)); + + /* Connect USB controller to the onboard PHY */ + sys_write32(USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS, + (mm_reg_t)&base->muxing); + + /* Force VBUS detect so the device thinks it is plugged into a host */ + sys_write32(USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS, + (mm_reg_t)&base->pwr); + + /* Enable an interrupt per EP0 transaction */ + sys_write32(USB_SIE_CTRL_EP0_INT_1BUF_BITS, (mm_reg_t)&base->sie_ctrl); + + /* Enable interrupts */ + sys_write32(USB_INTE_DEV_SOF_BITS | + USB_INTE_SETUP_REQ_BITS | + USB_INTE_DEV_RESUME_FROM_HOST_BITS | + USB_INTE_DEV_SUSPEND_BITS | + USB_INTE_DEV_CONN_DIS_BITS | + USB_INTE_BUS_RESET_BITS | + USB_INTE_VBUS_DETECT_BITS | + USB_INTE_ERROR_CRC_BITS | + USB_INTE_ERROR_BIT_STUFF_BITS | + USB_INTE_ERROR_RX_OVERFLOW_BITS | + USB_INTE_ERROR_RX_TIMEOUT_BITS | + USB_INTE_ERROR_DATA_SEQ_BITS | + USB_INTE_BUFF_STATUS_BITS, + (mm_reg_t)&base->inte); + + /* Present full speed device by enabling pull up on DP */ + rpi_pico_bit_set((mm_reg_t)&base->sie_ctrl, USB_SIE_CTRL_PULLUP_EN_BITS); + + /* Enable the USB controller in device mode. */ + sys_write32(USB_MAIN_CTRL_CONTROLLER_EN_BITS, (mm_reg_t)&base->main_ctrl); + + config->irq_enable_func(dev); + + LOG_DBG("Enable device %s %p", dev->name, (void *)base); + + return 0; +} + +static int udc_rpi_pico_disable(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + + config->irq_disable_func(dev); + LOG_DBG("Enable device %p", dev); + + return 0; +} + +static int udc_rpi_pico_init(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + + 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; + } + + return clock_control_on(config->clk_dev, config->clk_sys); +} + +static int udc_rpi_pico_shutdown(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + + 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; + } + + return clock_control_off(config->clk_dev, config->clk_sys); +} + +static int udc_rpi_pico_driver_preinit(const struct device *dev) +{ + const struct rpi_pico_config *config = dev->config; + struct udc_data *data = dev->data; + uint16_t mps = 1023; + int err; + + k_mutex_init(&data->mutex); + + 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 int udc_rpi_pico_lock(const struct device *dev) +{ + return udc_lock_internal(dev, K_FOREVER); +} + +static int udc_rpi_pico_unlock(const struct device *dev) +{ + return udc_unlock_internal(dev); +} + +static const struct udc_api udc_rpi_pico_api = { + .lock = udc_rpi_pico_lock, + .unlock = udc_rpi_pico_unlock, + .init = udc_rpi_pico_init, + .enable = udc_rpi_pico_enable, + .disable = udc_rpi_pico_disable, + .shutdown = udc_rpi_pico_shutdown, + .set_address = udc_rpi_pico_set_address, + .host_wakeup = udc_rpi_pico_host_wakeup, + .ep_enable = udc_rpi_pico_ep_enable, + .ep_disable = udc_rpi_pico_ep_disable, + .ep_set_halt = udc_rpi_pico_ep_set_halt, + .ep_clear_halt = udc_rpi_pico_ep_clear_halt, + .ep_enqueue = udc_rpi_pico_ep_enqueue, + .ep_dequeue = udc_rpi_pico_ep_dequeue, +}; + +#define DT_DRV_COMPAT raspberrypi_pico_usbd + +#define UDC_RPI_PICO_DEVICE_DEFINE(n) \ + K_THREAD_STACK_DEFINE(udc_rpi_pico_stack_##n, CONFIG_UDC_RPI_PICO_STACK_SIZE); \ + \ + SYS_MEM_BLOCKS_DEFINE_STATIC_WITH_EXT_BUF(rpi_pico_mb_##n, \ + 64U, 58U, \ + usb_dpram->epx_data) \ + \ + static void udc_rpi_pico_thread_##n(void *dev, void *arg1, void *arg2) \ + { \ + while (true) { \ + rpi_pico_thread_handler(dev); \ + } \ + } \ + \ + static void udc_rpi_pico_make_thread_##n(const struct device *dev) \ + { \ + struct rpi_pico_data *priv = udc_get_private(dev); \ + \ + k_thread_create(&priv->thread_data, \ + udc_rpi_pico_stack_##n, \ + K_THREAD_STACK_SIZEOF(udc_rpi_pico_stack_##n), \ + udc_rpi_pico_thread_##n, \ + (void *)dev, NULL, NULL, \ + K_PRIO_COOP(CONFIG_UDC_RPI_PICO_THREAD_PRIORITY), \ + K_ESSENTIAL, \ + K_NO_WAIT); \ + k_thread_name_set(&priv->thread_data, dev->name); \ + } \ + \ + static void udc_rpi_pico_irq_enable_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), \ + DT_INST_IRQ(n, priority), \ + rpi_pico_isr_handler, \ + DEVICE_DT_INST_GET(n), \ + 0); \ + \ + irq_enable(DT_INST_IRQN(n)); \ + } \ + \ + static void udc_rpi_pico_irq_disable_func_##n(const struct device *dev) \ + { \ + irq_disable(DT_INST_IRQN(n)); \ + } \ + \ + static struct udc_ep_config ep_cfg_out[USB_NUM_ENDPOINTS]; \ + static struct udc_ep_config ep_cfg_in[USB_NUM_ENDPOINTS]; \ + \ + static const struct rpi_pico_config rpi_pico_config_##n = { \ + .base = (usb_hw_t *)DT_INST_REG_ADDR(n), \ + .dpram = (usb_device_dpram_t *)USBCTRL_DPRAM_BASE, \ + .mem_block = &rpi_pico_mb_##n, \ + .num_of_eps = DT_INST_PROP(n, num_bidir_endpoints), \ + .ep_cfg_in = ep_cfg_out, \ + .ep_cfg_out = ep_cfg_in, \ + .make_thread = udc_rpi_pico_make_thread_##n, \ + .irq_enable_func = udc_rpi_pico_irq_enable_func_##n, \ + .irq_disable_func = udc_rpi_pico_irq_disable_func_##n, \ + .clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ + .clk_sys = (void *)DT_INST_PHA_BY_IDX(n, clocks, 0, clk_id), \ + }; \ + \ + static struct rpi_pico_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_rpi_pico_driver_preinit, NULL, \ + &udc_data_##n, &rpi_pico_config_##n, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &udc_rpi_pico_api); + +DT_INST_FOREACH_STATUS_OKAY(UDC_RPI_PICO_DEVICE_DEFINE)