Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
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.
 
 
 
 
 
 

1257 lines
31 KiB

/*
* Copyright Google LLC.
* Copyright Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "udc_common.h"
#include <string.h>
#include <stdio.h>
#include <soc.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/logging/log.h>
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_sof_event(dev);
}
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)