zephyr/drivers/usb/device/usb_dc_sam0.c
Johann Fischer 52eacf16a2 driver: usb: add check for endpoint capabilities
Add function to check capabilities of an endpoint.
Only basic properties are checked, especially on STM32
capabilities of different USB controller configurations
have to be considered in the future.

Signed-off-by: Johann Fischer <j.fischer@phytec.de>
2018-06-15 11:02:05 +02:00

567 lines
13 KiB
C

/*
* Copyright (c) 2018 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define SYS_LOG_DOMAIN "usb/dc"
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_USB_DRIVER_LEVEL
#include <logging/sys_log.h>
#include <drivers/usb/usb_dc.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 *)CONFIG_USB_DC_SAM0_BASE_ADDRESS)
#define USB_NUM_ENDPOINTS CONFIG_USB_DC_SAM0_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) != 0) {
/* Setup */
data->ep_cb[0][ep](ep, USB_DC_EP_SETUP);
}
if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT0) != 0) {
/* Out (to device) data received */
data->ep_cb[0][ep](ep, USB_DC_EP_DATA_OUT);
}
if ((intflag & USB_DEVICE_EPINTFLAG_TRCPT1) != 0) {
/* In (to host) transmit complete */
data->ep_cb[1][ep](ep | USB_SAM0_IN_EP, USB_DC_EP_DATA_IN);
if (data->addr != 0) {
/* 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 = 0;
}
}
}
/* 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) != 0) {
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 = 0; epint != 0; epint >>= 1) {
/* Scan bit-by-bit as the Cortex-M0 doesn't have ffs */
if ((epint & 1) != 0) {
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;
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 = 5;
}
regs->PADCAL.bit.TRANSN = pad_transn;
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 = 29;
}
regs->PADCAL.bit.TRANSP = pad_transp;
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 = 3;
}
regs->PADCAL.bit.TRIM = pad_trim;
}
/* Attach by initializing the device */
int usb_dc_attach(void)
{
UsbDevice *regs = &REGS->DEVICE;
struct usb_sam0_data *data = usb_sam0_get_data();
/* 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) {
}
/* 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;
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 */
IRQ_CONNECT(CONFIG_USB_DC_SAM0_IRQ, CONFIG_USB_DC_SAM0_IRQ_PRIORITY,
usb_sam0_isr, 0, 0);
irq_enable(CONFIG_USB_DC_SAM0_IRQ);
/* 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(CONFIG_USB_DC_SAM0_IRQ);
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;
}
int usb_dc_set_status_callback(const usb_dc_status_callback cb)
{
struct usb_sam0_data *data = usb_sam0_get_data();
data->cb = cb;
return 0;
}
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) {
SYS_LOG_ERR("invalid endpoint configuration");
return -1;
}
if (ep_idx > CONFIG_USB_DC_SAM0_NUM_BIDIR_ENDPOINTS) {
SYS_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 (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 (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 (for_in) {
*stalled = endpoint->EPSTATUS.bit.STALLRQ1;
} else {
*stalled = endpoint->EPSTATUS.bit.STALLRQ0;
}
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 (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;
}
/* 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 (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 (!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 = 0;
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 = 0;
}
} 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];
endpoint->EPSTATUSCLR.bit.BK0RDY = 1;
data->out_at = 0;
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;
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();
u8_t for_in = ep & USB_EP_DIR_MASK;
u8_t ep_num = ep & ~USB_EP_DIR_MASK;
UsbDeviceDescriptor *desc = &data->descriptors[ep_num];
int size;
if (for_in) {
size = desc->DeviceDescBank[1].PCKSIZE.bit.SIZE;
} else {
size = desc->DeviceDescBank[0].PCKSIZE.bit.SIZE;
}
if (size >= 7) {
return 1023;
}
/* 0 -> 8, 1 -> 16, 2 -> 32 etc */
return 1 << (size + 3);
}