zephyr/drivers/usb/device/usb_dc_sam0.c
Benjamin Valentin 2ffb8dd9f1 usb: sam0: use TRIM registers when available
Newer SoCs like SAME54 provide dedicated registers to store
the USB trim configuration.

Use those registers if they are defined in ASF to access the
calibration data.

Signed-off-by: Benjamin Valentin <benpicco@googlemail.com>
2019-12-11 11:13:24 +01:00

711 lines
16 KiB
C

/*
* Copyright (c) 2018 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(usb_dc_sam0);
#include <usb/usb_device.h>
#include <soc.h>
#include <string.h>
#define NVM_USB_PAD_TRANSN_POS 45
#define NVM_USB_PAD_TRANSN_SIZE 5
#define NVM_USB_PAD_TRANSP_POS 50
#define NVM_USB_PAD_TRANSP_SIZE 5
#define NVM_USB_PAD_TRIM_POS 55
#define NVM_USB_PAD_TRIM_SIZE 3
#define USB_SAM0_IN_EP 0x80
#define REGS ((Usb *)DT_INST_0_ATMEL_SAM0_USB_BASE_ADDRESS)
#define USB_NUM_ENDPOINTS DT_INST_0_ATMEL_SAM0_USB_NUM_BIDIR_ENDPOINTS
struct usb_sam0_data {
UsbDeviceDescriptor descriptors[USB_NUM_ENDPOINTS];
usb_dc_status_callback cb;
usb_dc_ep_callback ep_cb[2][USB_NUM_ENDPOINTS];
u8_t addr;
u32_t out_at;
/* Memory used as a simple heap for the endpoint buffers */
u32_t ep_buf[USB_NUM_ENDPOINTS * 64 / 4];
int brk;
};
static struct usb_sam0_data usb_sam0_data_0;
static struct usb_sam0_data *usb_sam0_get_data(void)
{
return &usb_sam0_data_0;
}
/* Handles interrupts on an endpoint */
static void usb_sam0_ep_isr(u8_t ep)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep];
u32_t intflag = endpoint->EPINTFLAG.reg;
endpoint->EPINTFLAG.reg = intflag;
if ((intflag & USB_DEVICE_EPINTFLAG_RXSTP) != 0U) {
/* Setup */
data->ep_cb[0][ep](ep, USB_DC_EP_SETUP);
}
if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT0) != 0U) {
/* Out (to device) data received */
data->ep_cb[0][ep](ep, USB_DC_EP_DATA_OUT);
}
if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT1) != 0U) {
/* In (to host) transmit complete */
data->ep_cb[1][ep](ep | USB_SAM0_IN_EP, USB_DC_EP_DATA_IN);
if (data->addr != 0U) {
/* Commit the pending address update. This
* must be done after the ack to the host
* completes else the ack will get dropped.
*/
regs->DADD.reg = data->addr;
data->addr = 0U;
}
}
}
/* Top level interrupt handler */
static void usb_sam0_isr(void)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
u32_t intflag = regs->INTFLAG.reg;
u32_t epint = regs->EPINTSMRY.reg;
u8_t ep;
/* Acknowledge all interrupts */
regs->INTFLAG.reg = intflag;
if ((intflag & USB_DEVICE_INTFLAG_EORST) != 0U) {
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[0];
/* The device clears some of the configuration of EP0
* when it receives the EORST. Re-enable interrupts.
*/
endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 |
USB_DEVICE_EPINTENSET_TRCPT1 |
USB_DEVICE_EPINTENSET_RXSTP;
data->cb(USB_DC_RESET, NULL);
}
/* Dispatch the endpoint interrupts */
for (ep = 0U; epint != 0U; epint >>= 1) {
/* Scan bit-by-bit as the Cortex-M0 doesn't have ffs */
if ((epint & 1) != 0U) {
usb_sam0_ep_isr(ep);
}
ep++;
}
}
/* Wait for the device to process the last config write */
static void usb_sam0_wait_syncbusy(void)
{
UsbDevice *regs = &REGS->DEVICE;
while (regs->SYNCBUSY.reg != 0) {
}
}
/* Load the pad calibration from the built-in fuses */
static void usb_sam0_load_padcal(void)
{
UsbDevice *regs = &REGS->DEVICE;
u32_t pad_transn;
u32_t pad_transp;
u32_t pad_trim;
#ifdef USB_FUSES_TRANSN_ADDR
pad_transn = *(uint32_t *)USB_FUSES_TRANSN_ADDR;
#else
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
regs->PADCAL.bit.TRANSN = pad_transn;
#ifdef USB_FUSES_TRANSP_ADDR
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
regs->PADCAL.bit.TRANSP = pad_transp;
#ifdef USB_FUSES_TRIM_ADDR
pad_trim = *(uint32_t *)USB_FUSES_TRIM_ADDR;
#else
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
regs->PADCAL.bit.TRIM = pad_trim;
}
#define SAM0_USB_IRQ_CONNECT(n) \
do { \
IRQ_CONNECT(DT_INST_0_ATMEL_SAM0_USB_IRQ_##n, \
DT_INST_0_ATMEL_SAM0_USB_IRQ_##n##_PRIORITY,\
usb_sam0_isr, 0, 0); \
irq_enable(DT_INST_0_ATMEL_SAM0_USB_IRQ_##n); \
} while (0)
/* Attach by initializing the device */
int usb_dc_attach(void)
{
UsbDevice *regs = &REGS->DEVICE;
struct usb_sam0_data *data = usb_sam0_get_data();
#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 /* !MCLK */
/* Configure */
regs->CTRLA.bit.SWRST = 1;
usb_sam0_wait_syncbusy();
/* Change QOS values to have the best performance and correct USB
* behaviour
*/
regs->QOSCTRL.bit.CQOS = 2;
regs->QOSCTRL.bit.DQOS = 2;
usb_sam0_load_padcal();
regs->CTRLA.reg = USB_CTRLA_MODE_DEVICE | USB_CTRLA_RUNSTDBY;
regs->CTRLB.reg = USB_DEVICE_CTRLB_SPDCONF_HS;
(void)memset(data->descriptors, 0, sizeof(data->descriptors));
regs->DESCADD.reg = (uintptr_t)&data->descriptors[0];
regs->INTENSET.reg = USB_DEVICE_INTENSET_EORST;
/* Connect and enable the interrupt */
#ifdef DT_INST_0_ATMEL_SAM0_USB_IRQ_0
SAM0_USB_IRQ_CONNECT(0);
#endif
#ifdef DT_INST_0_ATMEL_SAM0_USB_IRQ_1
SAM0_USB_IRQ_CONNECT(1);
#endif
#ifdef DT_INST_0_ATMEL_SAM0_USB_IRQ_2
SAM0_USB_IRQ_CONNECT(2);
#endif
#ifdef DT_INST_0_ATMEL_SAM0_USB_IRQ_3
SAM0_USB_IRQ_CONNECT(3);
#endif
/* Enable and attach */
regs->CTRLA.bit.ENABLE = 1;
usb_sam0_wait_syncbusy();
regs->CTRLB.bit.DETACH = 0;
return 0;
}
/* Detach from the bus */
int usb_dc_detach(void)
{
UsbDevice *regs = &REGS->DEVICE;
regs->CTRLB.bit.DETACH = 1;
usb_sam0_wait_syncbusy();
return 0;
}
/* Remove the interrupt and reset the device */
int usb_dc_reset(void)
{
UsbDevice *regs = &REGS->DEVICE;
irq_disable(DT_INST_0_ATMEL_SAM0_USB_IRQ_0);
regs->CTRLA.bit.SWRST = 1;
usb_sam0_wait_syncbusy();
return 0;
}
/* Queue a change in address. This is processed later when the
* current transfers are compelete.
*/
int usb_dc_set_address(const u8_t addr)
{
struct usb_sam0_data *data = usb_sam0_get_data();
data->addr = addr | USB_DEVICE_DADD_ADDEN;
return 0;
}
void usb_dc_set_status_callback(const usb_dc_status_callback cb)
{
struct usb_sam0_data *data = usb_sam0_get_data();
data->cb = cb;
}
int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg)
{
u8_t ep_idx = cfg->ep_addr & ~USB_EP_DIR_MASK;
if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) {
LOG_ERR("invalid endpoint configuration");
return -1;
}
if (ep_idx > USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address too high");
return -1;
}
return 0;
}
int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data *const cfg)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
u8_t for_in = cfg->ep_addr & USB_EP_DIR_MASK;
u8_t ep = cfg->ep_addr & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep];
UsbDeviceDescriptor *desc = &data->descriptors[ep];
int type;
int size;
/* Map the type to native type */
switch (cfg->ep_type) {
case USB_DC_EP_CONTROL:
type = 1;
break;
case USB_DC_EP_ISOCHRONOUS:
type = 2;
break;
case USB_DC_EP_BULK:
type = 3;
break;
case USB_DC_EP_INTERRUPT:
type = 4;
break;
default:
return -EINVAL;
}
/* Map the endpoint size to native size */
switch (cfg->ep_mps) {
case 8:
size = 0;
break;
case 16:
size = 1;
break;
case 32:
size = 2;
break;
case 64:
size = 3;
break;
case 128:
size = 4;
break;
case 256:
size = 5;
break;
case 512:
size = 6;
break;
case 1023:
size = 7;
break;
default:
return -EINVAL;
}
if (for_in) {
endpoint->EPCFG.bit.EPTYPE1 = type;
desc->DeviceDescBank[1].PCKSIZE.bit.SIZE = size;
desc->DeviceDescBank[1].ADDR.reg =
(uintptr_t)&data->ep_buf[data->brk];
endpoint->EPSTATUSCLR.bit.BK1RDY = 1;
} else {
endpoint->EPCFG.bit.EPTYPE0 = type;
desc->DeviceDescBank[0].PCKSIZE.bit.SIZE = size;
desc->DeviceDescBank[0].ADDR.reg =
(uintptr_t)&data->ep_buf[data->brk];
endpoint->EPSTATUSCLR.bit.BK0RDY = 1;
}
data->brk += cfg->ep_mps / sizeof(u32_t);
return 0;
}
int usb_dc_ep_set_stall(const u8_t ep)
{
UsbDevice *regs = &REGS->DEVICE;
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (for_in) {
endpoint->EPSTATUSSET.bit.STALLRQ1 = 1;
} else {
endpoint->EPSTATUSSET.bit.STALLRQ0 = 1;
}
return 0;
}
int usb_dc_ep_clear_stall(const u8_t ep)
{
UsbDevice *regs = &REGS->DEVICE;
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (for_in) {
endpoint->EPSTATUSCLR.bit.STALLRQ1 = 1;
} else {
endpoint->EPSTATUSCLR.bit.STALLRQ0 = 1;
}
return 0;
}
int usb_dc_ep_is_stalled(const u8_t ep, u8_t *stalled)
{
UsbDevice *regs = &REGS->DEVICE;
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (stalled == NULL) {
LOG_ERR("parameter must not be NULL");
return -1;
}
if (for_in) {
*stalled = endpoint->EPSTATUS.bit.STALLRQ1;
} else {
*stalled = endpoint->EPSTATUS.bit.STALLRQ0;
}
return 0;
}
/* Halt the selected endpoint */
int usb_dc_ep_halt(u8_t ep)
{
return usb_dc_ep_set_stall(ep);
}
/* Flush the selected endpoint */
int usb_dc_ep_flush(u8_t ep)
{
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
/* TODO */
LOG_WRN("flush not implemented");
return 0;
}
/* Enable an endpoint and the endpoint interrupts */
int usb_dc_ep_enable(const u8_t ep)
{
UsbDevice *regs = &REGS->DEVICE;
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (for_in) {
endpoint->EPSTATUSCLR.bit.BK1RDY = 1;
} else {
endpoint->EPSTATUSCLR.bit.BK0RDY = 1;
}
endpoint->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 |
USB_DEVICE_EPINTENSET_TRCPT1 |
USB_DEVICE_EPINTENSET_RXSTP;
return 0;
}
/* Disable the selected endpoint */
int usb_dc_ep_disable(u8_t ep)
{
UsbDevice *regs = &REGS->DEVICE;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
endpoint->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0
| USB_DEVICE_EPINTENCLR_TRCPT1
| USB_DEVICE_EPINTENCLR_RXSTP;
return 0;
}
/* Write a single payload to the IN buffer on the endpoint */
int usb_dc_ep_write(u8_t ep, const u8_t *buf, u32_t len, u32_t *ret_bytes)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
UsbDeviceDescriptor *desc = &data->descriptors[ep_num];
u32_t addr = desc->DeviceDescBank[1].ADDR.reg;
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (endpoint->EPSTATUS.bit.BK1RDY) {
/* Write in progress, drop */
return -EAGAIN;
}
/* Note that this code does not use the hardware's
* multi-packet and automatic zero-length packet features as
* the upper layers in Zephyr implement these in code.
*/
memcpy((void *)addr, buf, len);
desc->DeviceDescBank[1].PCKSIZE.bit.MULTI_PACKET_SIZE = 0;
desc->DeviceDescBank[1].PCKSIZE.bit.BYTE_COUNT = len;
endpoint->EPINTFLAG.reg =
USB_DEVICE_EPINTFLAG_TRCPT1 | USB_DEVICE_EPINTFLAG_TRFAIL1;
endpoint->EPSTATUSSET.bit.BK1RDY = 1;
*ret_bytes = len;
return 0;
}
/* Read data from an OUT endpoint */
int usb_dc_ep_read_ex(u8_t ep, u8_t *buf, u32_t max_data_len,
u32_t *read_bytes, bool wait)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
UsbDeviceDescriptor *desc = &data->descriptors[ep_num];
u32_t addr = desc->DeviceDescBank[0].ADDR.reg;
u32_t bytes = desc->DeviceDescBank[0].PCKSIZE.bit.BYTE_COUNT;
u32_t take;
int remain;
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (!endpoint->EPSTATUS.bit.BK0RDY) {
return -EAGAIN;
}
/* The code below emulates the Quark FIFO which the Zephyr USB
* API is based on. Reading with buf == NULL returns the
* number of bytes available and starts the read. The caller
* then keeps calling until all bytes are consumed which
* also marks the OUT buffer as freed.
*/
if (buf == NULL) {
data->out_at = 0U;
if (read_bytes != NULL) {
*read_bytes = bytes;
}
return 0;
}
remain = bytes - data->out_at;
take = MIN(max_data_len, remain);
memcpy(buf, (u8_t *)addr + data->out_at, take);
if (read_bytes != NULL) {
*read_bytes = take;
}
if (take == remain) {
if (!wait) {
endpoint->EPSTATUSCLR.bit.BK0RDY = 1;
data->out_at = 0U;
}
} else {
data->out_at += take;
}
return 0;
}
int usb_dc_ep_read(u8_t ep, u8_t *buf, u32_t max_data_len, u32_t *read_bytes)
{
return usb_dc_ep_read_ex(ep, buf, max_data_len, read_bytes, false);
}
int usb_dc_ep_read_wait(u8_t ep, u8_t *buf, u32_t max_data_len,
u32_t *read_bytes)
{
return usb_dc_ep_read_ex(ep, buf, max_data_len, read_bytes, true);
}
int usb_dc_ep_read_continue(u8_t ep)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep_num];
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
endpoint->EPSTATUSCLR.bit.BK0RDY = 1;
data->out_at = 0U;
return 0;
}
int usb_dc_ep_set_callback(const u8_t ep, const usb_dc_ep_callback cb)
{
struct usb_sam0_data *data = usb_sam0_get_data();
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
data->ep_cb[for_in ? 1 : 0][ep_num] = cb;
return 0;
}
int usb_dc_ep_mps(const u8_t ep)
{
struct usb_sam0_data *data = usb_sam0_get_data();
UsbDevice *regs = &REGS->DEVICE;
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceDescriptor *desc = &data->descriptors[ep_num];
UsbDeviceEndpoint *endpoint = &regs->DeviceEndpoint[ep];
int size;
if (ep_num >= USB_NUM_ENDPOINTS) {
LOG_ERR("endpoint index/address out of range");
return -1;
}
if (for_in) {
/* if endpoint is not configured, this should return 0 */
if (endpoint->EPCFG.bit.EPTYPE1 == 0) {
return 0;
}
size = desc->DeviceDescBank[1].PCKSIZE.bit.SIZE;
} else {
/* if endpoint is not configured, this should return 0 */
if (endpoint->EPCFG.bit.EPTYPE0 == 0) {
return 0;
}
size = desc->DeviceDescBank[0].PCKSIZE.bit.SIZE;
}
if (size >= 7) {
return 1023;
}
/* 0 -> 8, 1 -> 16, 2 -> 32 etc */
return 1 << (size + 3);
}