mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
sam0_common: Add USB peripheral driver
This commit is contained in:
parent
c697a3bfd8
commit
d7804823db
@ -392,6 +392,17 @@ typedef struct {
|
||||
uint32_t muxpos; /**< ADC channel pin multiplexer value */
|
||||
} adc_conf_chan_t;
|
||||
|
||||
/**
|
||||
* @brief USB peripheral parameters
|
||||
*/
|
||||
#if defined(USB_INST_NUM) || defined(DOXYGEN)
|
||||
typedef struct {
|
||||
gpio_t dm; /**< D- line gpio */
|
||||
gpio_t dp; /**< D+ line gpio */
|
||||
gpio_mux_t d_mux; /**< alternate function (mux) for data pins */
|
||||
UsbDevice *device; /**< ptr to the device registers */
|
||||
} sam0_common_usb_config_t;
|
||||
#endif /* USB_INST_NUM */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
64
cpu/sam0_common/include/sam_usb.h
Normal file
64
cpu/sam0_common/include/sam_usb.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Koen Zandberg
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup cpu_sam0_common_usb sam0 USB peripheral
|
||||
* @ingroup cpu_sam0_common
|
||||
* @brief USB interface functions for the sam0 class devices
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief USB interface functions for the sam0 class devices
|
||||
*
|
||||
* @author Koen Zandberg <koen@bergzand.net>
|
||||
*/
|
||||
|
||||
#ifndef SAM_USB_H
|
||||
#define SAM_USB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "periph_cpu.h"
|
||||
#include "periph/usbdev.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* USB endpoint buffer space
|
||||
*/
|
||||
#define SAM_USB_BUF_SPACE USBDEV_EP_BUF_SPACE
|
||||
|
||||
/**
|
||||
* Number of USB IN and OUT endpoints
|
||||
*/
|
||||
#define SAM_USB_NUM_EP USBDEV_NUM_ENDPOINTS
|
||||
|
||||
/**
|
||||
* @brief sam0 usb peripheral device context
|
||||
*/
|
||||
typedef struct {
|
||||
usbdev_t usbdev; /**< Inherited usbdev struct */
|
||||
const sam0_common_usb_config_t *config; /**< USB peripheral config */
|
||||
UsbDeviceDescBank banks[2 * SAM_USB_NUM_EP]; /**< Device descriptor banks */
|
||||
usbdev_ep_t endpoints[2 * SAM_USB_NUM_EP]; /**< Endpoints */
|
||||
size_t used; /**< Number of bytes from the
|
||||
buffer that are used */
|
||||
__attribute__ ((aligned(4)))
|
||||
uint8_t buffer[SAM_USB_BUF_SPACE]; /**< Buffer space, must be
|
||||
32-bit aligned */
|
||||
bool suspended; /**< Suspend active */
|
||||
} sam0_common_usb_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* SAM_USB_H */
|
||||
/** @} */
|
768
cpu/sam0_common/periph/usbdev.c
Normal file
768
cpu/sam0_common/periph/usbdev.c
Normal file
@ -0,0 +1,768 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Koen Zandberg
|
||||
*
|
||||
* 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>
|
||||
* @}
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.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_ready(usbdev_ep_t *ep, 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 buf_len);
|
||||
|
||||
static int _bank_set_size(usbdev_ep_t *ep);
|
||||
static int _ep_unready(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)
|
||||
{
|
||||
dev->config->device->INTENSET.reg = USB_DEVICE_INTENSET_EORST;
|
||||
if (dev->suspended) {
|
||||
dev->config->device->INTENSET.reg = USB_DEVICE_INTENSET_WAKEUP;
|
||||
}
|
||||
else {
|
||||
dev->config->device->INTENSET.reg = USB_DEVICE_INTENSET_SUSPEND;
|
||||
}
|
||||
}
|
||||
|
||||
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 void _bank_set_address(usbdev_ep_t *ep)
|
||||
{
|
||||
UsbDeviceDescBank *bank = _bank_from_ep(ep);
|
||||
|
||||
bank->ADDR.reg = (uint32_t)ep->buf;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
bank->PCKSIZE.bit.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_TRFAIL0 |
|
||||
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_TRFAIL1 |
|
||||
USB_DEVICE_EPINTENSET_TRCPT1 |
|
||||
USB_DEVICE_EPINTENSET_STALL1);
|
||||
}
|
||||
|
||||
static void _set_address(sam0_common_usb_t *dev, uint8_t addr)
|
||||
{
|
||||
dev->config->device->DADD.bit.DADD = addr;
|
||||
/* Only enable the address if it is nonzero */
|
||||
dev->config->device->DADD.bit.ADDEN = addr ? 1 : 0;
|
||||
}
|
||||
|
||||
static bool _syncbusy_enable(sam0_common_usb_t *dev)
|
||||
{
|
||||
return dev->config->device->SYNCBUSY.bit.ENABLE;
|
||||
}
|
||||
|
||||
static bool _syncbusy_swrst(sam0_common_usb_t *dev)
|
||||
{
|
||||
return dev->config->device->SYNCBUSY.bit.SWRST;
|
||||
}
|
||||
|
||||
static inline void _poweron(void)
|
||||
{
|
||||
#if defined(CPU_FAM_SAMD21)
|
||||
PM->AHBMASK.reg |= PM_AHBMASK_USB;
|
||||
PM->APBBMASK.reg |= PM_APBBMASK_USB;
|
||||
GCLK->CLKCTRL.reg = (uint32_t)(GCLK_CLKCTRL_CLKEN |
|
||||
GCLK_CLKCTRL_GEN_GCLK0 |
|
||||
(GCLK_CLKCTRL_ID(USB_GCLK_ID)));
|
||||
#elif defined(CPU_FAM_SAML21)
|
||||
MCLK->AHBMASK.reg |= MCLK_AHBMASK_USB;
|
||||
MCLK->APBBMASK.reg |= MCLK_APBBMASK_USB;
|
||||
GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |
|
||||
GCLK_PCHCTRL_GEN_GCLK0;
|
||||
#else
|
||||
#error "Unknown CPU family for sam0 common usbdev driver"
|
||||
#endif
|
||||
}
|
||||
|
||||
static usbdev_ep_t *_usbdev_new_ep(usbdev_t *dev, usb_ep_type_t type,
|
||||
usb_ep_dir_t dir, size_t buf_len)
|
||||
{
|
||||
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;
|
||||
if (usbdev->used + buf_len < SAM_USB_BUF_SPACE) {
|
||||
res->buf = usbdev->buffer + usbdev->used;
|
||||
res->len = buf_len;
|
||||
if (_bank_set_size(res) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
usbdev->used += buf_len;
|
||||
_bank_set_address(res);
|
||||
res->type = type;
|
||||
res->dev = dev;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void _block_pm(void)
|
||||
{
|
||||
#if defined(CPU_FAM_SAMD21)
|
||||
pm_block(SAMD21_PM_IDLE_1);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _unblock_pm(void)
|
||||
{
|
||||
#if defined(CPU_FAM_SAMD21)
|
||||
pm_unblock(SAMD21_PM_IDLE_1);
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
usbdev->used = 0;
|
||||
/* 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();
|
||||
|
||||
/* 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)) {}
|
||||
|
||||
/* Callibration 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));
|
||||
|
||||
usbdev->config->device->CTRLB.bit.SPDCONF = USB_DEVICE_CTRLB_SPDCONF_FS;
|
||||
_enable_irq(usbdev);
|
||||
|
||||
_block_pm();
|
||||
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_HOST_CONNECT);
|
||||
/* Interrupt configuration */
|
||||
NVIC_EnableIRQ(USB_IRQn);
|
||||
}
|
||||
|
||||
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.bit.EPTYPE0 = 0;
|
||||
}
|
||||
else {
|
||||
ep_reg->EPCFG.bit.EPTYPE1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
case default:
|
||||
/* Must never happen */
|
||||
assert(false);
|
||||
}
|
||||
if (ep->dir == USB_EP_DIR_OUT) {
|
||||
ep_reg->EPCFG.bit.EPTYPE0 = type;
|
||||
}
|
||||
else {
|
||||
ep_reg->EPCFG.bit.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();
|
||||
}
|
||||
|
||||
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.bit.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.bit.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);
|
||||
/* Low power modes are available while suspended */
|
||||
_unblock_pm();
|
||||
}
|
||||
else if (usbdev->config->device->INTFLAG.bit.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);
|
||||
/* Device wakeup detected, blocking low power modes */
|
||||
_block_pm();
|
||||
}
|
||||
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 _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.bit.STALLRQ1
|
||||
? USBOPT_ENABLE
|
||||
: USBOPT_DISABLE;
|
||||
}
|
||||
else {
|
||||
res = ep_reg->EPSTATUSSET.bit.STALLRQ0
|
||||
? USBOPT_ENABLE
|
||||
: USBOPT_DISABLE;
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
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.bit.BYTE_COUNT;
|
||||
}
|
||||
|
||||
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;
|
||||
case USBOPT_EP_READY:
|
||||
assert(value_len == sizeof(usbopt_enable_t));
|
||||
if (*((usbopt_enable_t *)value)) {
|
||||
_ep_unready(ep);
|
||||
}
|
||||
else {
|
||||
_usbdev_ep_ready(ep, 0);
|
||||
}
|
||||
res = sizeof(usbopt_enable_t);
|
||||
break;
|
||||
default:
|
||||
DEBUG("sam_usb: Unhandled set call: 0x%x\n", opt);
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int _ep_unready(usbdev_ep_t *ep)
|
||||
{
|
||||
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
||||
|
||||
if (ep->dir == USB_EP_DIR_IN) {
|
||||
ep_reg->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_BK1RDY;
|
||||
}
|
||||
else {
|
||||
ep_reg->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _usbdev_ep_ready(usbdev_ep_t *ep, size_t len)
|
||||
{
|
||||
UsbDeviceEndpoint *ep_reg = _ep_reg_from_ep(ep);
|
||||
|
||||
if (ep->dir == USB_EP_DIR_IN) {
|
||||
_disable_ep_stall_in(ep_reg);
|
||||
_bank_from_ep(ep)->PCKSIZE.bit.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.bit.TRCPT0) {
|
||||
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRCPT0;
|
||||
event = USBDEV_EVENT_TR_COMPLETE;
|
||||
}
|
||||
else if (ep_reg->EPINTFLAG.bit.RXSTP) {
|
||||
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_RXSTP;
|
||||
event = USBDEV_EVENT_TR_COMPLETE;
|
||||
}
|
||||
else if (ep_reg->EPINTFLAG.bit.TRFAIL0) {
|
||||
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL0;
|
||||
event = USBDEV_EVENT_TR_FAIL;
|
||||
}
|
||||
else if (ep_reg->EPINTFLAG.bit.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.bit.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.bit.TRFAIL1) {
|
||||
ep_reg->EPINTFLAG.reg = USB_DEVICE_EPINTFLAG_TRFAIL1;
|
||||
event = USBDEV_EVENT_TR_FAIL;
|
||||
}
|
||||
else if (ep_reg->EPINTFLAG.bit.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,
|
||||
.ep_init = _usbdev_ep_init,
|
||||
.ep_get = _usbdev_ep_get,
|
||||
.ep_set = _usbdev_ep_set,
|
||||
.ep_esr = _usbdev_ep_esr,
|
||||
.ready = _usbdev_ep_ready,
|
||||
};
|
Loading…
Reference in New Issue
Block a user