mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
707fecbc38
Signed-off-by: Dylan Laduranty <dylan.laduranty@mesotic.com>
809 lines
23 KiB
C
809 lines
23 KiB
C
/*
|
|
* Copyright (C) 2019 Koen Zandberg
|
|
* 2022 SSV Software Systems GmbH
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_sam0_common_usb
|
|
* @{
|
|
* @file
|
|
* @brief Low level USB interface functions for the sam0 class devices
|
|
*
|
|
* @author Koen Zandberg <koen@bergzand.net>
|
|
* @author Juergen Fitschen <me@jue.yt>
|
|
* @}
|
|
*/
|
|
|
|
#define USB_H_USER_IS_RIOT_INTERNAL
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include "architecture.h"
|
|
#include "cpu.h"
|
|
#include "cpu_conf.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph/usbdev.h"
|
|
#include "pm_layered.h"
|
|
#include "sam_usb.h"
|
|
|
|
#include "bitarithm.h"
|
|
|
|
/**
|
|
* Be careful with enabling debug here. As with all timing critical systems it
|
|
* is able to interfere with USB functionality and you might see different
|
|
* errors than debug disabled
|
|
*/
|
|
#define ENABLE_DEBUG 0
|
|
#include "debug.h"
|
|
|
|
/* List of instantiated USB peripherals */
|
|
static sam0_common_usb_t _usbdevs[USB_INST_NUM];
|
|
|
|
/* Forward declaration for the usb device driver */
|
|
const usbdev_driver_t driver;
|
|
|
|
static void _usbdev_ep_init(usbdev_ep_t *ep);
|
|
static int _usbdev_ep_xmit(usbdev_ep_t *ep, uint8_t *buf, size_t len);
|
|
static usbdev_ep_t *_usbdev_new_ep(usbdev_t *dev, usb_ep_type_t type,
|
|
usb_ep_dir_t dir, size_t len);
|
|
|
|
static int _bank_set_size(usbdev_ep_t *ep);
|
|
|
|
static inline unsigned _ep_num(unsigned num, usb_ep_dir_t dir)
|
|
{
|
|
return 2 * num + (dir == USB_EP_DIR_OUT ? 0 : 1);
|
|
}
|
|
|
|
static inline UsbDeviceDescBank *_bank_from_ep(usbdev_ep_t *ep)
|
|
{
|
|
sam0_common_usb_t *usbdev = (sam0_common_usb_t *)ep->dev;
|
|
|
|
return &usbdev->banks[_ep_num(ep->num, ep->dir)];
|
|
}
|
|
|
|
static inline usbdev_ep_t *_get_ep(sam0_common_usb_t *dev,
|
|
unsigned num, usb_ep_dir_t dir)
|
|
{
|
|
return &dev->endpoints[_ep_num(num, dir)];
|
|
}
|
|
|
|
static inline UsbDeviceEndpoint *_ep_reg_from_ep(usbdev_ep_t *ep)
|
|
{
|
|
sam0_common_usb_t *usbdev = (sam0_common_usb_t *)ep->dev;
|
|
|
|
return &usbdev->config->device->DeviceEndpoint[ep->num];
|
|
}
|
|
|
|
static inline void _enable_irq(sam0_common_usb_t *dev)
|
|
{
|
|
if (dev->suspended) {
|
|
dev->config->device->INTENSET.reg = USB_DEVICE_INTENSET_WAKEUP |
|
|
USB_DEVICE_INTENCLR_EORST;
|
|
}
|
|
else {
|
|
dev->config->device->INTENSET.reg = USB_DEVICE_INTENSET_SUSPEND |
|
|
USB_DEVICE_INTENSET_EORST;
|
|
}
|
|
}
|
|
|
|
static inline void _disable_irq(sam0_common_usb_t *dev)
|
|
{
|
|
dev->config->device->INTENCLR.reg = USB_DEVICE_INTENCLR_EORST |
|
|
USB_DEVICE_INTENCLR_SUSPEND |
|
|
USB_DEVICE_INTENCLR_WAKEUP;
|
|
}
|
|
|
|
static void _enable_ep_irq_out(sam0_common_usb_t *usbdev,
|
|
UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
ep_reg->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT0 |
|
|
USB_DEVICE_EPINTENSET_STALL0;
|
|
if (ep_reg == &usbdev->config->device->DeviceEndpoint[0]) {
|
|
ep_reg->EPINTENSET.reg = USB_DEVICE_EPINTENSET_RXSTP;
|
|
}
|
|
}
|
|
|
|
static void _enable_ep_irq_in(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
ep_reg->EPINTENSET.reg = USB_DEVICE_EPINTENSET_TRCPT1 |
|
|
USB_DEVICE_EPINTENSET_STALL1;
|
|
}
|
|
|
|
/* Enable the endpoint specific interrupts */
|
|
static void _enable_ep_irq(usbdev_ep_t *ep)
|
|
{
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
_enable_ep_irq_out((sam0_common_usb_t *)ep->dev, ep_reg);
|
|
}
|
|
else {
|
|
_enable_ep_irq_in(ep_reg);
|
|
}
|
|
}
|
|
|
|
static void _disable_ep_irq_out(sam0_common_usb_t *usbdev,
|
|
UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
DEBUG("Disabling OUT irq\n");
|
|
ep_reg->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT0 |
|
|
USB_DEVICE_EPINTENCLR_STALL0;
|
|
if (ep_reg == &usbdev->config->device->DeviceEndpoint[0]) {
|
|
ep_reg->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_RXSTP;
|
|
}
|
|
}
|
|
|
|
static void _disable_ep_irq_in(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
DEBUG("Disabling IN irq\n");
|
|
ep_reg->EPINTENCLR.reg = USB_DEVICE_EPINTENCLR_TRCPT1 |
|
|
USB_DEVICE_EPINTENCLR_STALL1;
|
|
}
|
|
|
|
static int _bank_set_size(usbdev_ep_t *ep)
|
|
{
|
|
UsbDeviceDescBank *bank = _bank_from_ep(ep);
|
|
unsigned val = 0x00;
|
|
|
|
switch (ep->len) {
|
|
case 8:
|
|
val = 0x0;
|
|
break;
|
|
case 16:
|
|
val = 0x1;
|
|
break;
|
|
case 32:
|
|
val = 0x2;
|
|
break;
|
|
case 64:
|
|
val = 0x3;
|
|
break;
|
|
case 128:
|
|
val = 0x4;
|
|
break;
|
|
case 256:
|
|
val = 0x5;
|
|
break;
|
|
case 512:
|
|
val = 0x6;
|
|
break;
|
|
case 1023:
|
|
val = 0x7;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
uint32_t reg = bank->PCKSIZE.reg;
|
|
bank->PCKSIZE.reg = ((reg & ~USB_DEVICE_PCKSIZE_SIZE_Msk) | USB_DEVICE_PCKSIZE_SIZE(val));
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Checks if one of the endpoints interrupts flags for the OUT direction are
|
|
* set
|
|
*/
|
|
static bool _ep_out_flags_set(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
return ep_reg->EPINTFLAG.reg &
|
|
ep_reg->EPINTENSET.reg &
|
|
(USB_DEVICE_EPINTENSET_TRCPT0 |
|
|
USB_DEVICE_EPINTENSET_RXSTP |
|
|
USB_DEVICE_EPINTENSET_STALL0);
|
|
}
|
|
|
|
/**
|
|
* Checks if one of the endpoints interrupts flags for the IN direction are
|
|
* set
|
|
*/
|
|
static bool _ep_in_flags_set(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
return ep_reg->EPINTFLAG.reg &
|
|
ep_reg->EPINTENSET.reg &
|
|
(USB_DEVICE_EPINTENSET_TRCPT1 |
|
|
USB_DEVICE_EPINTENSET_STALL1);
|
|
}
|
|
|
|
static void _set_address(sam0_common_usb_t *dev, uint8_t addr)
|
|
{
|
|
uint32_t dadd = dev->config->device->DADD.reg;
|
|
/* Only enable the address if it is nonzero */
|
|
if (addr) {
|
|
dadd |= USB_DEVICE_DADD_ADDEN;
|
|
}
|
|
else {
|
|
dadd &= ~USB_DEVICE_DADD_ADDEN;
|
|
}
|
|
|
|
dev->config->device->DADD.reg = (dadd & ~USB_DEVICE_DADD_DADD_Msk)
|
|
| USB_DEVICE_DADD_DADD(addr);
|
|
}
|
|
|
|
static bool _syncbusy_enable(sam0_common_usb_t *dev)
|
|
{
|
|
return dev->config->device->SYNCBUSY.reg & USB_SYNCBUSY_ENABLE;
|
|
}
|
|
|
|
static bool _syncbusy_swrst(sam0_common_usb_t *dev)
|
|
{
|
|
return dev->config->device->SYNCBUSY.reg & USB_SYNCBUSY_SWRST;
|
|
}
|
|
|
|
static inline void _poweron(sam0_common_usb_t *dev)
|
|
{
|
|
sam0_gclk_enable(dev->config->gclk_src);
|
|
|
|
#if defined(MCLK)
|
|
MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB;
|
|
MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB;
|
|
#else
|
|
PM->AHBMASK.reg |= PM_AHBMASK_USB;
|
|
PM->APBBMASK.reg |= PM_APBBMASK_USB;
|
|
#endif
|
|
|
|
#if defined(CPU_COMMON_SAMD21)
|
|
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN
|
|
| GCLK_CLKCTRL_GEN(dev->config->gclk_src)
|
|
| GCLK_CLKCTRL_ID(USB_GCLK_ID);
|
|
#else
|
|
GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_CHEN
|
|
| GCLK_PCHCTRL_GEN(dev->config->gclk_src);
|
|
#endif
|
|
}
|
|
|
|
static usbdev_ep_t *_usbdev_new_ep(usbdev_t *dev, usb_ep_type_t type,
|
|
usb_ep_dir_t dir, size_t size)
|
|
{
|
|
sam0_common_usb_t *usbdev = (sam0_common_usb_t *)dev;
|
|
/* The IP supports all types for all endpoints */
|
|
usbdev_ep_t *res = NULL;
|
|
|
|
/* Always return endpoint 0 for control types */
|
|
if (type == USB_EP_TYPE_CONTROL) {
|
|
res = _get_ep(usbdev, 0, dir);
|
|
res->num = 0;
|
|
}
|
|
else {
|
|
/* Find the first unassigned ep with proper dir */
|
|
for (unsigned idx = 1; idx < SAM_USB_NUM_EP && !res; idx++) {
|
|
usbdev_ep_t *ep = _get_ep(usbdev, idx, dir);
|
|
if (ep->type == USB_EP_TYPE_NONE) {
|
|
res = ep;
|
|
res->num = idx;
|
|
}
|
|
}
|
|
}
|
|
if (res) {
|
|
res->dev = dev;
|
|
res->dir = dir;
|
|
res->type = type;
|
|
res->len = size;
|
|
_bank_set_size(res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void _setup(sam0_common_usb_t *usbdev,
|
|
const sam0_common_usb_config_t *config)
|
|
{
|
|
usbdev->usbdev.driver = &driver;
|
|
usbdev->config = config;
|
|
}
|
|
|
|
void usbdev_init_lowlevel(void)
|
|
{
|
|
for (size_t i = 0; i < USB_INST_NUM; i++) {
|
|
_setup(&_usbdevs[i], &sam_usbdev_config[i]);
|
|
}
|
|
}
|
|
|
|
usbdev_t *usbdev_get_ctx(unsigned num)
|
|
{
|
|
assert(num < USB_INST_NUM);
|
|
return &_usbdevs[num].usbdev;
|
|
}
|
|
|
|
static void _usbdev_init(usbdev_t *dev)
|
|
{
|
|
DEBUG("Initializing sam0 usb peripheral\n");
|
|
/* Only one usb device on this board */
|
|
sam0_common_usb_t *usbdev = (sam0_common_usb_t *)dev;
|
|
|
|
/* clear previously set pm blockers */
|
|
if (usbdev->config->device->CTRLA.reg & USB_CTRLA_ENABLE) {
|
|
#if IS_ACTIVE(MODULE_PM_LAYERED) && defined(SAM0_USB_IDLE_PM_BLOCK)
|
|
pm_unblock(SAM0_USB_IDLE_PM_BLOCK);
|
|
#endif
|
|
if (!usbdev->suspended) {
|
|
#if IS_ACTIVE(MODULE_PM_LAYERED) && defined(SAM0_USB_ACTIVE_PM_BLOCK)
|
|
pm_unblock(SAM0_USB_ACTIVE_PM_BLOCK);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Set GPIO */
|
|
gpio_init(usbdev->config->dp, GPIO_IN);
|
|
gpio_init(usbdev->config->dm, GPIO_IN);
|
|
gpio_init_mux(usbdev->config->dm, usbdev->config->d_mux);
|
|
gpio_init_mux(usbdev->config->dp, usbdev->config->d_mux);
|
|
_poweron(usbdev);
|
|
|
|
/* Reset peripheral */
|
|
usbdev->config->device->CTRLA.reg |= USB_CTRLA_SWRST;
|
|
while (_syncbusy_swrst(usbdev)) {}
|
|
|
|
/* Enable USB device */
|
|
usbdev->config->device->DESCADD.reg = (uint32_t)&usbdev->banks;
|
|
usbdev->config->device->CTRLA.reg |= USB_CTRLA_ENABLE;
|
|
while (_syncbusy_enable(usbdev)) {}
|
|
|
|
/* Calibration values */
|
|
usbdev->config->device->PADCAL.reg =
|
|
USB_PADCAL_TRANSP((*(uint32_t *)USB_FUSES_TRANSP_ADDR >>
|
|
USB_FUSES_TRANSP_Pos)) |
|
|
USB_PADCAL_TRANSN((*(uint32_t *)USB_FUSES_TRANSN_ADDR >>
|
|
USB_FUSES_TRANSN_Pos)) |
|
|
USB_PADCAL_TRIM((*(uint32_t *)USB_FUSES_TRIM_ADDR >>
|
|
USB_FUSES_TRIM_Pos));
|
|
|
|
uint32_t ctrlb = usbdev->config->device->CTRLB.reg;
|
|
usbdev->config->device->CTRLB.reg = (ctrlb & ~USB_DEVICE_CTRLB_SPDCONF_Msk)
|
|
| USB_DEVICE_CTRLB_SPDCONF_FS;
|
|
|
|
_enable_irq(usbdev);
|
|
|
|
#if IS_ACTIVE(MODULE_PM_LAYERED) && defined(SAM0_USB_IDLE_PM_BLOCK)
|
|
pm_block(SAM0_USB_IDLE_PM_BLOCK);
|
|
#endif
|
|
#if IS_ACTIVE(MODULE_PM_LAYERED) && defined(SAM0_USB_ACTIVE_PM_BLOCK)
|
|
pm_block(SAM0_USB_ACTIVE_PM_BLOCK);
|
|
#endif
|
|
|
|
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_HOST_CONNECT);
|
|
/* Interrupt configuration */
|
|
#ifdef CPU_COMMON_SAMD5X
|
|
NVIC_EnableIRQ(USB_0_IRQn);
|
|
NVIC_EnableIRQ(USB_1_IRQn);
|
|
NVIC_EnableIRQ(USB_2_IRQn);
|
|
NVIC_EnableIRQ(USB_3_IRQn);
|
|
#else
|
|
NVIC_EnableIRQ(USB_IRQn);
|
|
#endif
|
|
}
|
|
|
|
static void usb_attach(sam0_common_usb_t *dev)
|
|
{
|
|
DEBUG("sam_usb: Attaching to host\n");
|
|
dev->config->device->CTRLB.reg &= ~USB_DEVICE_CTRLB_DETACH;
|
|
}
|
|
|
|
static void usb_detach(sam0_common_usb_t *dev)
|
|
{
|
|
DEBUG("sam_usb: detaching to host\n");
|
|
dev->config->device->CTRLB.reg |= USB_DEVICE_CTRLB_DETACH;
|
|
}
|
|
|
|
static int _usbdev_get(usbdev_t *usbdev, usbopt_t opt,
|
|
void *value, size_t max_len)
|
|
{
|
|
(void)usbdev;
|
|
(void)max_len;
|
|
int res = -ENOTSUP;
|
|
|
|
switch (opt) {
|
|
case USBOPT_MAX_VERSION:
|
|
assert(max_len == sizeof(usb_version_t));
|
|
*(usb_version_t *)value = USB_VERSION_20;
|
|
res = sizeof(usb_version_t);
|
|
break;
|
|
case USBOPT_MAX_SPEED:
|
|
assert(max_len == sizeof(usb_speed_t));
|
|
*(usb_speed_t *)value = USB_SPEED_FULL;
|
|
res = sizeof(usb_speed_t);
|
|
break;
|
|
default:
|
|
DEBUG("Unhandled get call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int _usbdev_set(usbdev_t *dev, usbopt_t opt,
|
|
const void *value, size_t value_len)
|
|
{
|
|
sam0_common_usb_t *usbdev = (sam0_common_usb_t *)dev;
|
|
|
|
(void)value_len;
|
|
int res = -ENOTSUP;
|
|
|
|
switch (opt) {
|
|
case USBOPT_ADDRESS:
|
|
assert(value_len == sizeof(uint8_t));
|
|
uint8_t addr = (*((uint8_t *)value));
|
|
_set_address(usbdev, addr);
|
|
break;
|
|
case USBOPT_ATTACH:
|
|
assert(value_len == sizeof(usbopt_enable_t));
|
|
if (*((usbopt_enable_t *)value)) {
|
|
usb_attach(usbdev);
|
|
}
|
|
else {
|
|
usb_detach(usbdev);
|
|
}
|
|
res = sizeof(usbopt_enable_t);
|
|
break;
|
|
default:
|
|
DEBUG("Unhandled set call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void _ep_disable(usbdev_ep_t *ep)
|
|
{
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
ep_reg->EPCFG.reg &= ~USB_DEVICE_EPCFG_EPTYPE0_Msk;
|
|
}
|
|
else {
|
|
ep_reg->EPCFG.reg &= ~USB_DEVICE_EPCFG_EPTYPE1_Msk;
|
|
}
|
|
}
|
|
|
|
static void _ep_enable(usbdev_ep_t *ep)
|
|
{
|
|
DEBUG("sam_usb: Enabling endpoint %d, dir %s\n", ep->num,
|
|
ep->dir == USB_EP_DIR_OUT ? "OUT" : "IN");
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
uint8_t type = 0;
|
|
uint32_t epcfg;
|
|
|
|
switch (ep->type) {
|
|
case USB_EP_TYPE_CONTROL:
|
|
type = 0x01;
|
|
break;
|
|
case USB_EP_TYPE_ISOCHRONOUS:
|
|
type = 0x02;
|
|
break;
|
|
case USB_EP_TYPE_BULK:
|
|
type = 0x03;
|
|
break;
|
|
case USB_EP_TYPE_INTERRUPT:
|
|
type = 0x04;
|
|
break;
|
|
default:
|
|
/* Must never happen */
|
|
assert(false);
|
|
}
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
epcfg = ep_reg->EPCFG.reg;
|
|
ep_reg->EPCFG.reg = (epcfg & ~USB_DEVICE_EPCFG_EPTYPE0_Msk)
|
|
| USB_DEVICE_EPCFG_EPTYPE0(type);
|
|
}
|
|
else {
|
|
epcfg = ep_reg->EPCFG.reg;
|
|
ep_reg->EPCFG.reg = (epcfg & ~USB_DEVICE_EPCFG_EPTYPE1_Msk)
|
|
| USB_DEVICE_EPCFG_EPTYPE1(type);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sam0 USB interrupt service routine
|
|
*/
|
|
void isr_usb(void)
|
|
{
|
|
/* TODO: make a bit more elegant for multi-periph support */
|
|
sam0_common_usb_t *dev = &_usbdevs[0];
|
|
|
|
if (dev->config->device->EPINTSMRY.reg) {
|
|
/* Endpoint specific interrupt */
|
|
unsigned ep_num = bitarithm_lsb(dev->config->device->EPINTSMRY.reg);
|
|
UsbDeviceEndpoint *ep_reg =
|
|
&dev->config->device->DeviceEndpoint[ep_num];
|
|
if (_ep_in_flags_set(ep_reg)) {
|
|
usbdev_ep_t *ep = _get_ep(dev, ep_num, USB_EP_DIR_IN);
|
|
_disable_ep_irq_in(ep_reg);
|
|
dev->usbdev.epcb(ep, USBDEV_EVENT_ESR);
|
|
}
|
|
else if (_ep_out_flags_set(ep_reg)) {
|
|
usbdev_ep_t *ep = _get_ep(dev, ep_num, USB_EP_DIR_OUT);
|
|
_disable_ep_irq_out(dev, ep_reg);
|
|
dev->usbdev.epcb(ep, USBDEV_EVENT_ESR);
|
|
}
|
|
else {
|
|
DEBUG("sam_usb: Unhandled EP interrupt\n");
|
|
}
|
|
}
|
|
else {
|
|
/* Device interrupt */
|
|
_disable_irq(dev);
|
|
dev->usbdev.cb(&dev->usbdev, USBDEV_EVENT_ESR);
|
|
}
|
|
cortexm_isr_end();
|
|
}
|
|
|
|
void isr_usb0(void)
|
|
{
|
|
isr_usb();
|
|
}
|
|
|
|
void isr_usb1(void)
|
|
{
|
|
isr_usb();
|
|
}
|
|
void isr_usb2(void)
|
|
{
|
|
isr_usb();
|
|
}
|
|
void isr_usb3(void)
|
|
{
|
|
isr_usb();
|
|
}
|
|
|
|
static void _usbdev_esr(usbdev_t *dev)
|
|
{
|
|
sam0_common_usb_t *usbdev = (sam0_common_usb_t *)dev;
|
|
|
|
if (usbdev->config->device->INTFLAG.reg) {
|
|
if (usbdev->config->device->INTFLAG.reg & USB_DEVICE_INTFLAG_EORST) {
|
|
/* Clear flag */
|
|
usbdev->config->device->INTFLAG.reg = USB_DEVICE_INTFLAG_EORST;
|
|
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_RESET);
|
|
usbdev->config->device->INTFLAG.reg = USB_DEVICE_INTFLAG_EORST;
|
|
}
|
|
else if ((usbdev->config->device->INTFLAG.reg & USB_DEVICE_INTFLAG_SUSPEND) &&
|
|
!usbdev->suspended) {
|
|
usbdev->config->device->INTFLAG.reg = USB_DEVICE_INTFLAG_WAKEUP |
|
|
USB_DEVICE_INTFLAG_SUSPEND;
|
|
usbdev->suspended = true;
|
|
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_SUSPEND);
|
|
#if IS_ACTIVE(MODULE_PM_LAYERED) && defined(SAM0_USB_ACTIVE_PM_BLOCK)
|
|
/* Low power modes are available while suspended */
|
|
pm_unblock(SAM0_USB_ACTIVE_PM_BLOCK);
|
|
#endif
|
|
}
|
|
else if ((usbdev->config->device->INTFLAG.reg & USB_DEVICE_INTFLAG_WAKEUP) &&
|
|
usbdev->suspended) {
|
|
usbdev->config->device->INTFLAG.reg = USB_DEVICE_INTFLAG_WAKEUP |
|
|
USB_DEVICE_INTFLAG_SUSPEND;
|
|
usbdev->suspended = false;
|
|
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_RESUME);
|
|
#if IS_ACTIVE(MODULE_PM_LAYERED) && defined(SAM0_USB_ACTIVE_PM_BLOCK)
|
|
/* Device wakeup detected, blocking low power modes */
|
|
pm_block(SAM0_USB_ACTIVE_PM_BLOCK);
|
|
#endif
|
|
}
|
|
else {
|
|
DEBUG("sam_usb: Unhandled interrupt\n");
|
|
}
|
|
/* Re-enable the USB IRQ */
|
|
_enable_irq(usbdev);
|
|
}
|
|
}
|
|
|
|
static inline void _enable_ep_stall_out(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
ep_reg->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ0;
|
|
}
|
|
|
|
static inline void _enable_ep_stall_in(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
ep_reg->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1;
|
|
}
|
|
|
|
static inline void _disable_ep_stall_out(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
ep_reg->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0;
|
|
}
|
|
|
|
static inline void _disable_ep_stall_in(UsbDeviceEndpoint *ep_reg)
|
|
{
|
|
ep_reg->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1;
|
|
}
|
|
|
|
static void _usbdev_ep0_stall(usbdev_t *usbdev)
|
|
{
|
|
sam0_common_usb_t *sam_usbdev = (sam0_common_usb_t *)usbdev;
|
|
UsbDeviceEndpoint *ep0_reg = &sam_usbdev->config->device->DeviceEndpoint[0];
|
|
|
|
ep0_reg->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_STALLRQ1 |
|
|
USB_DEVICE_EPSTATUSSET_STALLRQ0;
|
|
}
|
|
|
|
static void _ep_set_stall(usbdev_ep_t *ep, usbopt_enable_t enable)
|
|
{
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
|
|
if (ep->dir == USB_EP_DIR_IN) {
|
|
enable ? _enable_ep_stall_in(ep_reg)
|
|
: _disable_ep_stall_in(ep_reg);
|
|
}
|
|
else {
|
|
enable ? _enable_ep_stall_out(ep_reg)
|
|
: _disable_ep_stall_out(ep_reg);
|
|
}
|
|
}
|
|
|
|
usbopt_enable_t _ep_get_stall(usbdev_ep_t *ep)
|
|
{
|
|
usbopt_enable_t res;
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
|
|
if (ep->dir == USB_EP_DIR_IN) {
|
|
res = (ep_reg->EPSTATUSSET.reg & USB_DEVICE_EPSTATUSSET_STALLRQ1)
|
|
? USBOPT_ENABLE
|
|
: USBOPT_DISABLE;
|
|
}
|
|
else {
|
|
res = (ep_reg->EPSTATUSSET.reg & USB_DEVICE_EPSTATUSSET_STALLRQ0)
|
|
? USBOPT_ENABLE
|
|
: USBOPT_DISABLE;
|
|
}
|
|
return res;
|
|
|
|
}
|
|
|
|
static void _usbdev_ep_stall(usbdev_ep_t *ep, bool enable)
|
|
{
|
|
_ep_set_stall(ep, enable);
|
|
}
|
|
|
|
static void _usbdev_ep_init(usbdev_ep_t *ep)
|
|
{
|
|
_enable_ep_irq(ep);
|
|
}
|
|
|
|
static size_t _ep_get_available(usbdev_ep_t *ep)
|
|
{
|
|
return ((_bank_from_ep(ep)->PCKSIZE.reg & USB_DEVICE_PCKSIZE_BYTE_COUNT_Msk)
|
|
>> USB_DEVICE_PCKSIZE_BYTE_COUNT_Pos);
|
|
}
|
|
|
|
static int _usbdev_ep_get(usbdev_ep_t *ep, usbopt_ep_t opt,
|
|
void *value, size_t max_len)
|
|
{
|
|
(void)max_len;
|
|
int res = -ENOTSUP;
|
|
|
|
switch (opt) {
|
|
case USBOPT_EP_STALL:
|
|
assert(max_len == sizeof(usbopt_enable_t));
|
|
*(usbopt_enable_t *)value = _ep_get_stall(ep);
|
|
res = sizeof(usbopt_enable_t);
|
|
break;
|
|
case USBOPT_EP_AVAILABLE:
|
|
assert(max_len == sizeof(size_t));
|
|
*(size_t *)value = _ep_get_available(ep);
|
|
res = sizeof(size_t);
|
|
break;
|
|
default:
|
|
DEBUG("sam_usb: Unhandled get call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int _usbdev_ep_set(usbdev_ep_t *ep, usbopt_ep_t opt,
|
|
const void *value, size_t value_len)
|
|
{
|
|
(void)value_len;
|
|
int res = -ENOTSUP;
|
|
|
|
switch (opt) {
|
|
case USBOPT_EP_ENABLE:
|
|
assert(value_len == sizeof(usbopt_enable_t));
|
|
if (*((usbopt_enable_t *)value)) {
|
|
_ep_enable(ep);
|
|
_usbdev_ep_init(ep);
|
|
}
|
|
else {
|
|
_ep_disable(ep);
|
|
}
|
|
res = sizeof(usbopt_enable_t);
|
|
break;
|
|
case USBOPT_EP_STALL:
|
|
assert(value_len == sizeof(usbopt_enable_t));
|
|
_ep_set_stall(ep, *(usbopt_enable_t *)value);
|
|
res = sizeof(usbopt_enable_t);
|
|
break;
|
|
default:
|
|
DEBUG("sam_usb: Unhandled set call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static int _usbdev_ep_xmit(usbdev_ep_t *ep, uint8_t *buf, size_t len)
|
|
{
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
/* Assert the alignment required for the buffers */
|
|
assert(HAS_ALIGNMENT_OF(buf, USBDEV_CPU_DMA_ALIGNMENT));
|
|
|
|
_bank_from_ep(ep)->ADDR.reg = (uint32_t)(intptr_t)buf;
|
|
if (ep->dir == USB_EP_DIR_IN) {
|
|
_disable_ep_stall_in(ep_reg);
|
|
uint32_t reg = _bank_from_ep(ep)->PCKSIZE.reg;
|
|
_bank_from_ep(ep)->PCKSIZE.reg = (reg & ~USB_DEVICE_PCKSIZE_BYTE_COUNT_Msk)
|
|
| USB_DEVICE_PCKSIZE_BYTE_COUNT(len);
|
|
ep_reg->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK1RDY;
|
|
}
|
|
else {
|
|
_disable_ep_stall_out(ep_reg);
|
|
ep_reg->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK0RDY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Endpoint event handler
|
|
*
|
|
* Calls the endpoint callback to report the event to the USB stack
|
|
*/
|
|
static void _usbdev_ep_esr(usbdev_ep_t *ep)
|
|
{
|
|
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
|
signed event = -1;
|
|
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
if (ep_reg->EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT0) {
|
|
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0;
|
|
event = USBDEV_EVENT_TR_COMPLETE;
|
|
}
|
|
else if (ep_reg->EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_RXSTP) {
|
|
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP;
|
|
event = USBDEV_EVENT_TR_COMPLETE;
|
|
}
|
|
else if (ep_reg->EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL0) {
|
|
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL0;
|
|
event = USBDEV_EVENT_TR_STALL;
|
|
}
|
|
else {
|
|
DEBUG("Unhandled event: EP OUT %u: 0x%x\n", ep->num,
|
|
ep_reg->EPINTFLAG.reg);
|
|
}
|
|
}
|
|
else {
|
|
if (ep_reg->EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_TRCPT1) {
|
|
DEBUG("sam_usb: Transfer IN complete\n");
|
|
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT1;
|
|
event = USBDEV_EVENT_TR_COMPLETE;
|
|
}
|
|
else if (ep_reg->EPINTFLAG.reg & USB_DEVICE_EPINTFLAG_STALL1) {
|
|
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_STALL1;
|
|
event = USBDEV_EVENT_TR_STALL;
|
|
}
|
|
else {
|
|
DEBUG("Unhandled event: EP IN %u: 0x%x\n", ep->num,
|
|
ep_reg->EPINTFLAG.reg);
|
|
}
|
|
}
|
|
if (event >= 0) {
|
|
ep->dev->epcb(ep, event);
|
|
}
|
|
ep->dir == USB_EP_DIR_OUT ? _enable_ep_irq_out((sam0_common_usb_t *)ep->dev,
|
|
ep_reg)
|
|
: _enable_ep_irq_in(ep_reg);
|
|
}
|
|
|
|
const usbdev_driver_t driver = {
|
|
.init = _usbdev_init,
|
|
.new_ep = _usbdev_new_ep,
|
|
.get = _usbdev_get,
|
|
.set = _usbdev_set,
|
|
.esr = _usbdev_esr,
|
|
.ep0_stall = _usbdev_ep0_stall,
|
|
.ep_init = _usbdev_ep_init,
|
|
.ep_stall = _usbdev_ep_stall,
|
|
.ep_get = _usbdev_ep_get,
|
|
.ep_set = _usbdev_ep_set,
|
|
.ep_esr = _usbdev_ep_esr,
|
|
.xmit = _usbdev_ep_xmit,
|
|
};
|