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.
1314 lines
35 KiB
1314 lines
35 KiB
/* |
|
* Copyright (c) 2024 Nordic Semiconductor ASA |
|
* Copyright (c) 2021 Pete Johanson |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include "udc_common.h" |
|
|
|
#include <string.h> |
|
#include <stdio.h> |
|
|
|
#include <soc.h> |
|
#include <hardware/structs/usb.h> |
|
#include <hardware/resets.h> |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/mem_blocks.h> |
|
#include <zephyr/drivers/usb/udc.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
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; |
|
struct pinctrl_dev_config *const pcfg; |
|
clock_control_subsys_t clk_sys; |
|
}; |
|
|
|
struct rpi_pico_ep_data { |
|
void *buf; |
|
uint8_t next_pid; |
|
}; |
|
|
|
enum rpi_pico_event_type { |
|
/* Setup packet received */ |
|
RPI_PICO_EVT_SETUP, |
|
/* Trigger new transfer (except control OUT) */ |
|
RPI_PICO_EVT_XFER_NEW, |
|
/* Transfer for specific endpoint is finished */ |
|
RPI_PICO_EVT_XFER_FINISHED, |
|
}; |
|
|
|
struct rpi_pico_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 RPI_PICO_EVT_XFER_NEW or RPI_PICO_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; |
|
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]; |
|
}; |
|
|
|
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 >= 16) { |
|
return USB_EP_DIR_IN | (bit - 16); |
|
} else { |
|
return USB_EP_DIR_OUT | bit; |
|
} |
|
} |
|
|
|
/* 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 sie_dp_pullup(const struct device *dev, const bool enable) |
|
{ |
|
const struct rpi_pico_config *config = dev->config; |
|
usb_hw_t *base = config->base; |
|
|
|
if (enable) { |
|
rpi_pico_bit_set((mm_reg_t)&base->sie_ctrl, USB_SIE_CTRL_PULLUP_EN_BITS); |
|
} else { |
|
rpi_pico_bit_clr((mm_reg_t)&base->sie_ctrl, USB_SIE_CTRL_PULLUP_EN_BITS); |
|
} |
|
} |
|
|
|
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; |
|
struct udc_ep_config *ep_cfg = udc_get_ep_cfg(dev, ep); |
|
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 */ |
|
udc_ep_set_busy(ep_cfg, false); |
|
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); |
|
} |
|
|
|
udc_ep_set_busy(ep_cfg, false); |
|
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); |
|
unsigned int lock_key; |
|
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); |
|
|
|
lock_key = irq_lock(); |
|
|
|
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); |
|
|
|
irq_unlock(lock_key); |
|
|
|
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); |
|
unsigned int lock_key; |
|
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; |
|
} |
|
|
|
lock_key = irq_lock(); |
|
|
|
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); |
|
|
|
irq_unlock(lock_key); |
|
|
|
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 udc_ep_config *cfg_out = udc_get_ep_cfg(dev, USB_CONTROL_EP_OUT); |
|
struct udc_ep_config *cfg_in = udc_get_ep_cfg(dev, USB_CONTROL_EP_IN); |
|
struct net_buf *buf; |
|
|
|
buf = udc_buf_get_all(cfg_out); |
|
if (buf != NULL) { |
|
net_buf_unref(buf); |
|
} |
|
|
|
buf = udc_buf_get_all(cfg_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(cfg); |
|
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(cfg, 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(cfg); |
|
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(cfg); |
|
udc_ep_set_busy(cfg, 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(cfg); |
|
if (buf == NULL) { |
|
return; |
|
} |
|
|
|
if (USB_EP_DIR_IS_OUT(cfg->addr)) { |
|
if (cfg->stat.halted) { |
|
return; |
|
} |
|
|
|
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(cfg, true); |
|
} |
|
} |
|
|
|
static ALWAYS_INLINE void rpi_pico_thread_handler(void *const arg) |
|
{ |
|
const struct device *dev = (const struct device *)arg; |
|
struct rpi_pico_data *priv = udc_get_private(dev); |
|
struct udc_ep_config *ep_cfg; |
|
uint32_t evt; |
|
uint32_t eps; |
|
uint8_t ep; |
|
|
|
evt = k_event_wait(&priv->events, UINT32_MAX, false, K_FOREVER); |
|
udc_lock_internal(dev, K_FOREVER); |
|
|
|
if (evt & BIT(RPI_PICO_EVT_XFER_FINISHED)) { |
|
k_event_clear(&priv->events, BIT(RPI_PICO_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)) { |
|
rpi_pico_handle_evt_din(dev, ep_cfg); |
|
} else { |
|
rpi_pico_handle_evt_dout(dev, ep_cfg); |
|
} |
|
|
|
if (!udc_ep_is_busy(ep_cfg)) { |
|
rpi_pico_handle_xfer_next(dev, ep_cfg); |
|
} else { |
|
LOG_ERR("Endpoint 0x%02x busy", ep); |
|
} |
|
} |
|
} |
|
|
|
if (evt & BIT(RPI_PICO_EVT_XFER_NEW)) { |
|
k_event_clear(&priv->events, BIT(RPI_PICO_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_DBG("New transfer ep 0x%02x in the queue", ep); |
|
|
|
if (!udc_ep_is_busy(ep_cfg)) { |
|
rpi_pico_handle_xfer_next(dev, ep_cfg); |
|
} else { |
|
LOG_ERR("Endpoint 0x%02x busy", ep); |
|
} |
|
} |
|
} |
|
|
|
if (evt & BIT(RPI_PICO_EVT_SETUP)) { |
|
k_event_clear(&priv->events, BIT(RPI_PICO_EVT_SETUP)); |
|
LOG_DBG("SETUP event"); |
|
rpi_pico_handle_evt_setup(dev); |
|
} |
|
|
|
udc_unlock_internal(dev); |
|
} |
|
|
|
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; |
|
/* |
|
* 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_event_post(&priv->events, BIT(RPI_PICO_EVT_SETUP)); |
|
} |
|
|
|
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_data *priv = udc_get_private(dev); |
|
__maybe_unused int err; |
|
struct net_buf *buf; |
|
size_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 = 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 { |
|
atomic_set_bit(&priv->xfer_finished, udc_ep_to_bnum(ep)); |
|
k_event_post(&priv->events, BIT(RPI_PICO_EVT_XFER_FINISHED)); |
|
} |
|
} |
|
} |
|
|
|
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_data *priv = udc_get_private(dev); |
|
struct net_buf *buf; |
|
size_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 = 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) && 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 { |
|
atomic_set_bit(&priv->xfer_finished, udc_ep_to_bnum(ep)); |
|
k_event_post(&priv->events, BIT(RPI_PICO_EVT_XFER_FINISHED)); |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
rpi_pico_bit_clr((mm_reg_t)&base->buf_status, BIT(i)); |
|
/* 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); |
|
} |
|
|
|
buf_status &= ~BIT(i); |
|
} |
|
} |
|
|
|
static void rpi_pico_isr_handler(const struct device *dev) |
|
{ |
|
const struct rpi_pico_config *config = dev->config; |
|
const struct pinctrl_dev_config *const pcfg = config->pcfg; |
|
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; |
|
udc_submit_sof_event(dev); |
|
} |
|
|
|
if (status & USB_INTS_DEV_CONN_DIS_BITS) { |
|
uint32_t sie_status; |
|
|
|
sie_status = sys_read32((mm_reg_t)&base->sie_status); |
|
LOG_DBG("CONNECTED bit %u, VBUS_DETECTED bit %u", |
|
(bool)(sie_status & USB_SIE_STATUS_CONNECTED_BITS), |
|
(bool)(sie_status & USB_SIE_STATUS_VBUS_DETECTED_BITS)); |
|
|
|
if (pcfg != NULL && !(sie_status & USB_SIE_STATUS_CONNECTED_BITS) && |
|
!(sie_status & USB_SIE_STATUS_VBUS_DETECTED_BITS)) { |
|
sie_dp_pullup(dev, false); |
|
udc_submit_event(dev, UDC_EVT_VBUS_REMOVED, 0); |
|
} |
|
|
|
handled |= USB_INTS_DEV_CONN_DIS_BITS; |
|
sie_status_clr(dev, USB_SIE_STATUS_CONNECTED_BITS); |
|
} |
|
|
|
if (status & USB_INTS_VBUS_DETECT_BITS) { |
|
uint32_t sie_status; |
|
|
|
sie_status = sys_read32((mm_reg_t)&base->sie_status); |
|
LOG_DBG("VBUS_DETECTED bit %u", |
|
(bool)(sie_status & USB_SIE_STATUS_VBUS_DETECTED_BITS)); |
|
|
|
if (pcfg != NULL && (sie_status & USB_SIE_STATUS_VBUS_DETECTED_BITS)) { |
|
sie_dp_pullup(dev, true); |
|
udc_submit_event(dev, UDC_EVT_VBUS_READY, 0); |
|
} |
|
|
|
handled |= USB_INTS_VBUS_DETECT_BITS; |
|
sie_status_clr(dev, USB_SIE_STATUS_VBUS_DETECTED_BITS); |
|
} |
|
|
|
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_set_suspended(dev, 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_set_suspended(dev, true); |
|
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); |
|
/* |
|
* This can be triggered before the STALL handshake response |
|
* to the OUT DATAx. Handling IRQ_ON_STALL to fix the expected |
|
* DATA PID is too much overhead since the endpoint is halted |
|
* anyway. |
|
*/ |
|
LOG_WRN("Data Sequence Error"); |
|
} |
|
|
|
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_data *priv = udc_get_private(dev); |
|
|
|
udc_buf_put(cfg, buf); |
|
|
|
if (!cfg->stat.halted) { |
|
atomic_set_bit(&priv->xfer_new, udc_ep_to_bnum(cfg->addr)); |
|
k_event_post(&priv->events, BIT(RPI_PICO_EVT_XFER_NEW)); |
|
} |
|
|
|
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(cfg); |
|
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; |
|
unsigned int lock_key; |
|
uint32_t bits; |
|
|
|
lock_key = irq_lock(); |
|
if (USB_EP_GET_IDX(cfg->addr) == 0) { |
|
bits = 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, bits); |
|
} |
|
|
|
if (USB_EP_DIR_IS_OUT(cfg->addr)) { |
|
/* |
|
* Cancel any transfer in progress. The available bit must be |
|
* set for the controller to respond to OUT DATAx with a STALL |
|
* handshake. |
|
*/ |
|
rpi_pico_ep_cancel(dev, cfg->addr); |
|
bits = USB_BUF_CTRL_STALL | USB_BUF_CTRL_AVAIL; |
|
} else { |
|
/* Only STALL bit needs to be set here. */ |
|
bits = USB_BUF_CTRL_STALL; |
|
} |
|
|
|
rpi_pico_bit_set(buf_ctrl_reg, bits); |
|
|
|
if (USB_EP_GET_IDX(cfg->addr) != 0) { |
|
cfg->stat.halted = true; |
|
} |
|
|
|
irq_unlock(lock_key); |
|
LOG_DBG("Set halt ep 0x%02x buf_ctrl 0x%08x busy %u", |
|
cfg->addr, sys_read32(buf_ctrl_reg), udc_ep_is_busy(cfg)); |
|
|
|
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); |
|
struct rpi_pico_data *priv = udc_get_private(dev); |
|
mem_addr_t buf_ctrl_reg = get_buf_ctrl_reg(dev, cfg->addr); |
|
unsigned int lock_key; |
|
|
|
if (USB_EP_GET_IDX(cfg->addr) == 0) { |
|
return 0; |
|
} |
|
|
|
lock_key = irq_lock(); |
|
if (USB_EP_DIR_IS_OUT(cfg->addr)) { |
|
/* Cancel responds with a STALL handshake.*/ |
|
rpi_pico_ep_cancel(dev, cfg->addr); |
|
} else { |
|
rpi_pico_bit_clr(buf_ctrl_reg, USB_BUF_CTRL_STALL); |
|
} |
|
|
|
ep_data->next_pid = 0; |
|
cfg->stat.halted = false; |
|
irq_unlock(lock_key); |
|
|
|
if (udc_ep_is_busy(cfg)) { |
|
rpi_pico_handle_xfer_next(dev, cfg); |
|
} else if (udc_buf_peek(cfg)) { |
|
atomic_set_bit(&priv->xfer_new, udc_ep_to_bnum(cfg->addr)); |
|
k_event_post(&priv->events, BIT(RPI_PICO_EVT_XFER_NEW)); |
|
} |
|
|
|
LOG_DBG("Clear halt ep 0x%02x buf_ctrl 0x%08x busy %u", |
|
cfg->addr, sys_read32(buf_ctrl_reg), udc_ep_is_busy(cfg)); |
|
|
|
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; |
|
const struct pinctrl_dev_config *const pcfg = config->pcfg; |
|
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); |
|
|
|
if (pcfg == NULL) { |
|
/* 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(IF_ENABLED(CONFIG_UDC_ENABLE_SOF, (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); |
|
|
|
if (sys_read32((mm_reg_t)&base->sie_status) & USB_SIE_STATUS_VBUS_DETECTED_BITS) { |
|
/* Present full speed device by enabling pull up on DP */ |
|
sie_dp_pullup(dev, true); |
|
} |
|
|
|
/* 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("Disable device %p", dev); |
|
|
|
return 0; |
|
} |
|
|
|
static int udc_rpi_pico_init(const struct device *dev) |
|
{ |
|
const struct rpi_pico_config *config = dev->config; |
|
const struct pinctrl_dev_config *const pcfg = config->pcfg; |
|
int err; |
|
|
|
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; |
|
} |
|
|
|
if (pcfg != NULL) { |
|
err = pinctrl_apply_state(pcfg, PINCTRL_STATE_DEFAULT); |
|
if (err) { |
|
LOG_ERR("Failed to apply default pinctrl state (%d)", err); |
|
return err; |
|
} |
|
} |
|
|
|
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 rpi_pico_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_rpi_pico_lock(const struct device *dev) |
|
{ |
|
k_sched_lock(); |
|
udc_lock_internal(dev, K_FOREVER); |
|
} |
|
|
|
static void udc_rpi_pico_unlock(const struct device *dev) |
|
{ |
|
udc_unlock_internal(dev); |
|
k_sched_unlock(); |
|
} |
|
|
|
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_PINCTRL_DT_INST_DEFINE(n) \ |
|
COND_CODE_1(DT_INST_PINCTRL_HAS_NAME(n, default), \ |
|
(PINCTRL_DT_INST_DEFINE(n)), ()) |
|
|
|
#define UDC_RPI_PICO_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_RPI_PICO_DEVICE_DEFINE(n) \ |
|
UDC_RPI_PICO_PINCTRL_DT_INST_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, \ |
|
.pcfg = UDC_RPI_PICO_PINCTRL_DT_INST_DEV_CONFIG_GET(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)
|
|
|