1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/stm32/periph/usbdev.c

1157 lines
38 KiB
C
Raw Normal View History

/*
* 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.
*/
/**
2020-05-03 17:17:54 +02:00
* @ingroup cpu_stm32_usbdev
* @{
* @file
* @brief Low level USB interface functions for the stm32 FS/HS devices
*
* @author Koen Zandberg <koen@bergzand.net>
* @}
*/
#define USB_H_USER_IS_RIOT_INTERNAL
2020-10-21 15:56:42 +02:00
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include "bitarithm.h"
#include "ztimer.h"
#include "cpu.h"
#include "cpu_conf.h"
#include "periph/pm.h"
#include "periph/gpio.h"
#include "periph/usbdev.h"
#include "pm_layered.h"
#include "usbdev_stm32.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
*/
2020-10-22 11:34:00 +02:00
#define ENABLE_DEBUG 0
#include "debug.h"
#if defined(STM32_USB_OTG_FS_ENABLED) && defined(STM32_USB_OTG_HS_ENABLED)
#define _TOTAL_NUM_ENDPOINTS (STM32_USB_OTG_FS_NUM_EP + \
STM32_USB_OTG_HS_NUM_EP)
#elif defined(STM32_USB_OTG_FS_ENABLED)
#define _TOTAL_NUM_ENDPOINTS (STM32_USB_OTG_FS_NUM_EP)
#elif defined(STM32_USB_OTG_HS_ENABLED)
#define _TOTAL_NUM_ENDPOINTS (STM32_USB_OTG_HS_NUM_EP)
#endif
/* Mask for the set of interrupts used */
#define STM32_FSHS_USB_GINT_MASK \
(USB_OTG_GINTMSK_USBSUSPM | \
USB_OTG_GINTMSK_WUIM | \
USB_OTG_GINTMSK_ENUMDNEM | \
USB_OTG_GINTMSK_USBRST | \
USB_OTG_GINTMSK_OTGINT | \
USB_OTG_GINTMSK_IEPINT | \
USB_OTG_GINTMSK_OEPINT | \
USB_OTG_GINTMSK_RXFLVLM)
#define STM32_PKTSTS_GONAK 0x01 /**< Rx fifo global out nak */
#define STM32_PKTSTS_DATA_UPDT 0x02 /**< Rx fifo data update */
#define STM32_PKTSTS_XFER_COMP 0x03 /**< Rx fifo data complete */
#define STM32_PKTSTS_SETUP_COMP 0x04 /**< Rx fifo setup complete */
#define STM32_PKTSTS_SETUP_UPDT 0x06 /**< Rx fifo setup update */
/* Some device families (F7 and L4) forgot to define the FS device FIFO size *
* in their vendor headers. This define sets it to the value from the *
* reference manual */
#ifndef USB_OTG_FS_TOTAL_FIFO_SIZE
#define USB_OTG_FS_TOTAL_FIFO_SIZE (1280U)
#endif
/* Some device families (F7 and L4) forgot to define the HS device FIFO size *
* in their vendor headers. This define sets it to the value from the *
* reference manual */
#ifndef USB_OTG_HS_TOTAL_FIFO_SIZE
#define USB_OTG_HS_TOTAL_FIFO_SIZE (4096U)
#endif
/* minimum depth of an individual transmit FIFO */
#define STM32_USB_OTG_FIFO_MIN_WORD_SIZE (16U)
/* Offset for OUT endpoints in a shared IN/OUT endpoint bit flag register */
#define STM32_USB_OTG_REG_EP_OUT_OFFSET (16U)
/* Endpoint zero size values */
#define STM32_USB_OTG_EP0_SIZE_64 (0x0)
#define STM32_USB_OTG_EP0_SIZE_32 (0x1)
#define STM32_USB_OTG_EP0_SIZE_16 (0x2)
#define STM32_USB_OTG_EP0_SIZE_8 (0x3)
/* Endpoint type values */
#define STM32_USB_OTG_EP_TYPE_CONTROL (0x00 << USB_OTG_DOEPCTL_EPTYP_Pos)
#define STM32_USB_OTG_EP_TYPE_ISO (0x01 << USB_OTG_DOEPCTL_EPTYP_Pos)
#define STM32_USB_OTG_EP_TYPE_BULK (0x02 << USB_OTG_DOEPCTL_EPTYP_Pos)
#define STM32_USB_OTG_EP_TYPE_INTERRUPT (0x03 << USB_OTG_DOEPCTL_EPTYP_Pos)
/* List of instantiated USB peripherals */
static stm32_usb_otg_fshs_t _usbdevs[USBDEV_NUMOF] = { 0 };
static usbdev_ep_t _out[_TOTAL_NUM_ENDPOINTS];
static usbdev_ep_t _in[_TOTAL_NUM_ENDPOINTS];
/* Forward declaration for the usb device driver */
const usbdev_driver_t driver;
static void _flush_tx_fifo(const stm32_usb_otg_fshs_config_t *conf,
uint8_t fifo_num);
/*************************************************************************
* Conversion function from the base address to specific register blocks *
*************************************************************************/
static USB_OTG_GlobalTypeDef *_global_regs(
const stm32_usb_otg_fshs_config_t *conf)
{
return (USB_OTG_GlobalTypeDef *)(conf->periph + USB_OTG_GLOBAL_BASE);
}
static USB_OTG_DeviceTypeDef *_device_regs(
const stm32_usb_otg_fshs_config_t *conf)
{
return (USB_OTG_DeviceTypeDef *)(conf->periph + USB_OTG_DEVICE_BASE);
}
static USB_OTG_INEndpointTypeDef *_in_regs(
const stm32_usb_otg_fshs_config_t *conf,
size_t endpoint)
{
return (USB_OTG_INEndpointTypeDef *)(conf->periph +
USB_OTG_IN_ENDPOINT_BASE +
USB_OTG_EP_REG_SIZE * endpoint);
}
static USB_OTG_OUTEndpointTypeDef *_out_regs(
const stm32_usb_otg_fshs_config_t *conf,
size_t endpoint)
{
return (USB_OTG_OUTEndpointTypeDef *)(conf->periph +
USB_OTG_OUT_ENDPOINT_BASE +
USB_OTG_EP_REG_SIZE * endpoint);
}
static __I uint32_t *_rx_fifo(const stm32_usb_otg_fshs_config_t *conf)
{
return (__I uint32_t *)(conf->periph + USB_OTG_FIFO_BASE);
}
static __O uint32_t *_tx_fifo(const stm32_usb_otg_fshs_config_t *conf,
size_t num)
{
return (__O uint32_t *)(conf->periph +
USB_OTG_FIFO_BASE +
USB_OTG_FIFO_SIZE * num);
}
static __IO uint32_t *_pcgcctl_reg(const stm32_usb_otg_fshs_config_t *conf)
{
return (__IO uint32_t *)(conf->periph + USB_OTG_PCGCCTL_BASE);
}
/* end of conversion functions */
/**
* @brief Determine the number of available endpoints for the peripheral based
* on the type and the CID version
*
* @param config configuration struct
*/
static size_t _max_endpoints(const stm32_usb_otg_fshs_config_t *config)
{
return (config->type == STM32_USB_OTG_FS) ?
STM32_USB_OTG_FS_NUM_EP :
STM32_USB_OTG_HS_NUM_EP;
}
static bool _uses_dma(const stm32_usb_otg_fshs_config_t *config)
{
#if defined(STM32_USB_OTG_HS_ENABLED) && STM32_USB_OTG_HS_USE_DMA
return config->type == STM32_USB_OTG_HS;
#else
(void)config;
return false;
#endif
}
static size_t _setup(stm32_usb_otg_fshs_t *usbdev,
const stm32_usb_otg_fshs_config_t *config, size_t idx)
{
usbdev->usbdev.driver = &driver;
usbdev->config = config;
usbdev->out = &_out[idx];
usbdev->in = &_in[idx];
return _max_endpoints(config);
}
/**
* @brief Low level usbdev struct setup
*
* Distributes the available endpoints among the enabled peripherals
*/
void usbdev_init_lowlevel(void)
{
size_t ep_idx = 0;
for (size_t i = 0; i < USBDEV_NUMOF; i++) {
ep_idx += _setup(&_usbdevs[i], &stm32_usb_otg_fshs_config[i], ep_idx);
}
#ifdef NDEBUG
(void)ep_idx;
#endif
assert(ep_idx == _TOTAL_NUM_ENDPOINTS);
}
usbdev_t *usbdev_get_ctx(unsigned num)
{
assert(num < USBDEV_NUMOF);
return &_usbdevs[num].usbdev;
}
static void _enable_global_out_nak(const stm32_usb_otg_fshs_config_t *conf)
{
if (_device_regs(conf)->DCTL & USB_OTG_DCTL_GONSTS) {
return;
}
_device_regs(conf)->DCTL |= USB_OTG_DCTL_SGONAK;
while (!(_device_regs(conf)->DCTL & USB_OTG_DCTL_GONSTS)) {}
}
static void _disable_global_out_nak(const stm32_usb_otg_fshs_config_t *conf)
{
if (!(_device_regs(conf)->DCTL & USB_OTG_DCTL_GONSTS)) {
return;
}
_device_regs(conf)->DCTL |= USB_OTG_DCTL_CGONAK;
while ((_device_regs(conf)->DCTL & USB_OTG_DCTL_GONSTS)) {}
}
static void _enable_global_in_nak(const stm32_usb_otg_fshs_config_t *conf)
{
if (_device_regs(conf)->DCTL & USB_OTG_DCTL_GINSTS) {
return;
}
_device_regs(conf)->DCTL |= USB_OTG_DCTL_SGINAK;
while (!(_device_regs(conf)->DCTL & USB_OTG_DCTL_GINSTS)) {}
}
static void _disable_global_in_nak(const stm32_usb_otg_fshs_config_t *conf)
{
if (!(_device_regs(conf)->DCTL & USB_OTG_DCTL_GINSTS)) {
return;
}
_device_regs(conf)->DCTL |= USB_OTG_DCTL_CGINAK;
while ((_device_regs(conf)->DCTL & USB_OTG_DCTL_GINSTS)) {}
}
static void _disable_global_nak(const stm32_usb_otg_fshs_config_t *conf)
{
_disable_global_in_nak(conf);
_disable_global_out_nak(conf);
}
static uint32_t _type_to_reg(usb_ep_type_t type)
{
switch (type) {
case USB_EP_TYPE_CONTROL:
return STM32_USB_OTG_EP_TYPE_CONTROL;
case USB_EP_TYPE_ISOCHRONOUS:
return STM32_USB_OTG_EP_TYPE_ISO;
case USB_EP_TYPE_BULK:
return STM32_USB_OTG_EP_TYPE_BULK;
case USB_EP_TYPE_INTERRUPT:
return STM32_USB_OTG_EP_TYPE_INTERRUPT;
default:
assert(false);
return 0;
}
}
static uint32_t _ep0_size(size_t size)
{
switch (size) {
case 64:
return STM32_USB_OTG_EP0_SIZE_64;
case 32:
return STM32_USB_OTG_EP0_SIZE_32;
case 16:
return STM32_USB_OTG_EP0_SIZE_16;
case 8:
return STM32_USB_OTG_EP0_SIZE_8;
default:
assert(false);
return 0x00;
}
}
/**
* @brief Disables an IN type endpoint
*
* Endpoint is only deactivated if it was activated
*/
static void _ep_in_disable(const stm32_usb_otg_fshs_config_t *conf, size_t num)
{
if (_in_regs(conf, num)->DIEPCTL & USB_OTG_DIEPCTL_EPENA) {
DEBUG("otg_fs: Disabling IN %u\n", num);
/* Enable global nak according to procedure */
_enable_global_in_nak(conf);
/* Flush the fifo to clear pending data */
_flush_tx_fifo(conf, num);
/* disable endpoint and set NAK */
_in_regs(conf, num)->DIEPCTL = USB_OTG_DIEPCTL_EPDIS | USB_OTG_DIEPCTL_SNAK;
/* Wait for the disable to take effect */
while (_in_regs(conf, num)->DIEPCTL & USB_OTG_DIEPCTL_EPDIS) {}
/* Disable global nak according to procedure */
_disable_global_in_nak(conf);
}
}
/**
* @brief Disables an OUT type endpoint
*
* Endpoint is only deactivated if it was activated
*/
static void _ep_out_disable(const stm32_usb_otg_fshs_config_t *conf, size_t num)
{
if (_out_regs(conf, num)->DOEPCTL & USB_OTG_DOEPCTL_EPENA) {
DEBUG("otg_fs: Disabling OUT %u\n", num);
/* Enable global nak according to procedure */
_enable_global_out_nak(conf);
/* No need to flush the fifo here, this works(tm) */
/* disable endpoint and set NAK */
_out_regs(conf, num)->DOEPCTL = USB_OTG_DOEPCTL_EPDIS | USB_OTG_DOEPCTL_SNAK;
/* Wait for the disable to take effect */
while (_out_regs(conf, num)->DOEPCTL & USB_OTG_DOEPCTL_EPDIS) {}
/* Disable global nak according to procedure */
_disable_global_out_nak(conf);
}
}
static void _ep_deactivate(usbdev_ep_t *ep)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
if (ep->dir == USB_EP_DIR_IN) {
_ep_in_disable(conf, ep->num);
_in_regs(conf, ep->num)->DIEPCTL &= USB_OTG_DIEPCTL_USBAEP;
}
else {
_ep_out_disable(conf, ep->num);
_out_regs(conf, ep->num)->DOEPCTL &= USB_OTG_DOEPCTL_USBAEP;
}
}
static void _ep_activate(usbdev_ep_t *ep)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
if (ep->dir == USB_EP_DIR_IN) {
_ep_in_disable(conf, ep->num);
_device_regs(conf)->DAINTMSK |= 1 << ep->num;
uint32_t diepctl = USB_OTG_DIEPCTL_SNAK |
USB_OTG_DIEPCTL_USBAEP |
_type_to_reg(ep->type) |
ep->num << USB_OTG_DIEPCTL_TXFNUM_Pos;
if (ep->num == 0) {
diepctl |= _ep0_size(ep->len);
}
else {
diepctl |= ep->len;
diepctl |= USB_OTG_DIEPCTL_SD0PID_SEVNFRM;
}
_in_regs(conf, ep->num)->DIEPCTL |= diepctl;
}
else {
_ep_out_disable(conf, ep->num);
_device_regs(conf)->DAINTMSK |=
1 << (ep->num + STM32_USB_OTG_REG_EP_OUT_OFFSET);
_out_regs(conf, ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SNAK |
USB_OTG_DOEPCTL_USBAEP;
_type_to_reg(ep->type);
if (ep->num == 0) {
_out_regs(conf, ep->num)->DOEPCTL |= _ep0_size(ep->len);
}
else {
_out_regs(conf, ep->num)->DOEPCTL |= ep->len;
_out_regs(conf, ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
}
}
}
static inline void _usb_attach(stm32_usb_otg_fshs_t *usbdev)
{
DEBUG("otg_fs: Attaching to host\n");
/* Disable the soft disconnect feature */
_device_regs(usbdev->config)->DCTL &= ~USB_OTG_DCTL_SDIS;
}
static inline void _usb_detach(stm32_usb_otg_fshs_t *usbdev)
{
DEBUG("otg_fs: Detaching from host\n");
/* Enable the soft disconnect feature */
_device_regs(usbdev->config)->DCTL |= USB_OTG_DCTL_SDIS;
}
static void _set_address(stm32_usb_otg_fshs_t *usbdev, uint8_t address)
{
_device_regs(usbdev->config)->DCFG =
(_device_regs(usbdev->config)->DCFG & ~(USB_OTG_DCFG_DAD_Msk)) |
(address << USB_OTG_DCFG_DAD_Pos);
}
static usbdev_ep_t *_get_ep(stm32_usb_otg_fshs_t *usbdev, unsigned num,
usb_ep_dir_t dir)
{
if (num >= STM32_USB_OTG_FS_NUM_EP) {
return NULL;
}
return dir == USB_EP_DIR_IN ? &usbdev->in[num] : &usbdev->out[num];
}
#if defined(DEVELHELP) && !defined(NDEBUG)
static size_t _total_fifo_size(const stm32_usb_otg_fshs_config_t *conf)
{
if (conf->type == STM32_USB_OTG_FS) {
#ifdef STM32_USB_OTG_FS_ENABLED
return USB_OTG_FS_TOTAL_FIFO_SIZE;
#else
return 0;
#endif /* STM32_USB_OTG_FS_ENABLED */
}
else {
#ifdef STM32_USB_OTG_HS_ENABLED
return USB_OTG_HS_TOTAL_FIFO_SIZE;
#else
return 0;
#endif /* STM32_USB_OTG_HS_ENABLED */
}
}
#endif /* defined(DEVELHELP) && !defined(NDEBUG) */
static void _configure_tx_fifo(stm32_usb_otg_fshs_t *usbdev, size_t num,
size_t len)
{
/* TX Fifo size must be at least 16 words long and must be word aligned */
size_t wordlen = len < (STM32_USB_OTG_FIFO_MIN_WORD_SIZE * sizeof(uint32_t))
? STM32_USB_OTG_FIFO_MIN_WORD_SIZE
: (len + (sizeof(uint32_t) - 1)) / sizeof(uint32_t);
/* Check max size */
assert(usbdev->fifo_pos + wordlen <=
_total_fifo_size(usbdev->config) / sizeof(uint32_t));
/* FIFO Array starts at FIFO 1 at index 0, FIFO 0 is special and has a
* different register (DIEPTXF0_HNPTXFSIZ) */
_global_regs(usbdev->config)->DIEPTXF[num - 1] =
(wordlen << USB_OTG_TX0FD_Pos) |
(usbdev->fifo_pos);
usbdev->fifo_pos += wordlen;
}
static void _configure_fifo(stm32_usb_otg_fshs_t *usbdev)
{
/* TODO: cleanup, more dynamic, etc */
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
size_t rx_size = conf->type == STM32_USB_OTG_FS
? STM32_USB_OTG_FS_RX_FIFO_SIZE
: STM32_USB_OTG_HS_RX_FIFO_SIZE;
_global_regs(conf)->GRXFSIZ =
(_global_regs(conf)->GRXFSIZ & ~USB_OTG_GRXFSIZ_RXFD) |
rx_size;
_global_regs(conf)->DIEPTXF0_HNPTXFSIZ =
(STM32_USB_OTG_FIFO_MIN_WORD_SIZE << USB_OTG_TX0FD_Pos) |
rx_size;
usbdev->fifo_pos = (rx_size + STM32_USB_OTG_FIFO_MIN_WORD_SIZE);
}
static usbdev_ep_t *_usbdev_new_ep(usbdev_t *dev, usb_ep_type_t type,
usb_ep_dir_t dir, size_t buf_len)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)dev;
usbdev_ep_t *ep = NULL;
if (type == USB_EP_TYPE_CONTROL) {
if (dir == USB_EP_DIR_IN) {
ep = &usbdev->in[0];
}
else {
ep = &usbdev->out[0];
}
ep->num = 0;
}
else {
/* Find the first unassigned ep with matching direction */
for (unsigned idx = 1; idx < STM32_USB_OTG_FS_NUM_EP && !ep; idx++) {
usbdev_ep_t *candidate_ep = _get_ep(usbdev, idx, dir);
if (candidate_ep->type == USB_EP_TYPE_NONE) {
ep = candidate_ep;
ep->num = idx;
}
}
}
if (ep && ep->type == USB_EP_TYPE_NONE) {
if (usbdev->occupied + buf_len < STM32_USB_OTG_BUF_SPACE) {
ep->buf = usbdev->buffer + usbdev->occupied;
ep->dir = dir;
ep->type = type;
ep->dev = dev;
ep->len = buf_len;
usbdev->occupied += buf_len;
if (ep->dir == USB_EP_DIR_IN && ep->num != 0) {
_configure_tx_fifo(usbdev, ep->num, ep->len);
}
}
}
return ep;
}
/**
* @brief reset a TX fifo.
*
* @param conf usbdev context
* @param fifo_num fifo number to reset, 0x10 for all fifos
*/
static void _flush_tx_fifo(const stm32_usb_otg_fshs_config_t *conf,
uint8_t fifo_num)
{
uint32_t reg = _global_regs(conf)->GRSTCTL & ~(USB_OTG_GRSTCTL_TXFNUM);
reg |= fifo_num << USB_OTG_GRSTCTL_TXFNUM_Pos | USB_OTG_GRSTCTL_TXFFLSH;
_global_regs(conf)->GRSTCTL = reg;
/* Wait for flush to finish */
while (_global_regs(conf)->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH) {}
}
static void _flush_rx_fifo(const stm32_usb_otg_fshs_config_t *conf)
{
_global_regs(conf)->GRSTCTL |= USB_OTG_GRSTCTL_RXFFLSH;
while (_global_regs(conf)->GRSTCTL & USB_OTG_GRSTCTL_RXFFLSH) {}
}
static void _sleep_periph(const stm32_usb_otg_fshs_config_t *conf)
{
*_pcgcctl_reg(conf) |= USB_OTG_PCGCCTL_STOPCLK;
/* Unblocking STM32_PM_STOP during suspend on the stm32f446 breaks
* while (un)blocking works on the stm32f401, needs more
* investigation with a larger set of chips */
#ifdef STM32_USB_OTG_CID_1x
pm_unblock(STM32_PM_STOP);
#endif
}
static void _wake_periph(const stm32_usb_otg_fshs_config_t *conf)
{
#ifdef STM32_USB_OTG_CID_1x
pm_block(STM32_PM_STOP);
#endif
*_pcgcctl_reg(conf) &= ~USB_OTG_PCGCCTL_STOPCLK;
_flush_rx_fifo(conf);
_flush_tx_fifo(conf, 0x10);
}
static void _reset_eps(stm32_usb_otg_fshs_t *usbdev)
{
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
/* Set the NAK for all endpoints */
for (size_t i = 0; i < _max_endpoints(conf); i++) {
_out_regs(conf, i)->DOEPCTL |= USB_OTG_DOEPCTL_SNAK;
_in_regs(conf, i)->DIEPCTL |= USB_OTG_DIEPCTL_SNAK;
_in_regs(conf, i)->DIEPCTL |= (i) << USB_OTG_DIEPCTL_TXFNUM_Pos;
}
}
static void _reset_periph(stm32_usb_otg_fshs_t *usbdev)
{
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
/* Wait for AHB idle */
while (!(_global_regs(conf)->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL)) {}
_global_regs(conf)->GRSTCTL |= USB_OTG_GRSTCTL_CSRST;
/* Wait for reset done */
while (_global_regs(conf)->GRSTCTL & USB_OTG_GRSTCTL_CSRST) {}
}
static void _enable_gpio(const stm32_usb_otg_fshs_config_t *conf)
{
/* Enables clock on the GPIO bus */
gpio_init(conf->dp, GPIO_IN);
gpio_init(conf->dm, GPIO_IN);
/* Configure AF for the pins */
gpio_init_af(conf->dp, conf->af);
gpio_init_af(conf->dm, conf->af);
}
static void _set_mode_device(stm32_usb_otg_fshs_t *usbdev)
{
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
/* Force device mode */
_global_regs(conf)->GUSBCFG |= USB_OTG_GUSBCFG_FDMOD;
/* Spinlock to prevent a context switch here, needs a delay of 25 ms when
* force switching mode */
ztimer_spin(ZTIMER_MSEC, 25);
}
static void _usbdev_init(usbdev_t *dev)
{
/* Block both STOP and STANDBY, STOP is unblocked during USB suspend
* status */
pm_block(STM32_PM_STOP);
pm_block(STM32_PM_STANDBY);
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
/* Enable the clock to the peripheral */
periph_clk_en(conf->ahb, conf->rcc_mask);
_enable_gpio(conf);
/* TODO: implement ULPI mode when a board is available */
#ifdef STM32_USB_OTG_HS_ENABLED
if (conf->type == STM32_USB_OTG_HS) {
/* Disable the ULPI clock in low power mode, this is essential for the
* peripheral when using the built-in phy */
periph_lpclk_dis(conf->ahb, RCC_AHB1LPENR_OTGHSULPILPEN);
/* Only the built-in phy supported for now */
assert(conf->phy == STM32_USB_OTG_PHY_BUILTIN);
_global_regs(conf)->GUSBCFG |= USB_OTG_GUSBCFG_PHYSEL;
}
#endif
/* Reset the peripheral after phy selection */
_reset_periph(usbdev);
/* Reset clock */
*_pcgcctl_reg(conf) = 0;
/* Force the peripheral to device mode */
_set_mode_device(usbdev);
/* Disable Vbus detection and force the pull-up on */
#ifdef STM32_USB_OTG_CID_1x
/* Enable no Vbus sensing and enable 'Power Down Disable */
_global_regs(usbdev->config)->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS |
USB_OTG_GCCFG_PWRDWN;
#else
/* Enable no Vbus Detect enable and enable 'Power Down Disable */
_global_regs(usbdev->config)->GCCFG |= USB_OTG_GCCFG_VBDEN |
USB_OTG_GCCFG_PWRDWN;
/* Force Vbus Detect values and ID detect values to device mode */
_global_regs(usbdev->config)->GOTGCTL |= USB_OTG_GOTGCTL_VBVALOVAL |
USB_OTG_GOTGCTL_VBVALOEN |
USB_OTG_GOTGCTL_BVALOEN |
USB_OTG_GOTGCTL_BVALOVAL;
#endif
/* disable fancy USB features */
_global_regs(conf)->GUSBCFG &=
~(USB_OTG_GUSBCFG_HNPCAP | USB_OTG_GUSBCFG_SRPCAP);
/* Device mode init */
_device_regs(conf)->DCFG |= USB_OTG_DCFG_DSPD_Msk; /* Full speed! */
_configure_fifo(usbdev);
/* Reset the receive FIFO */
_flush_rx_fifo(conf);
/* Reset all TX FIFOs */
_flush_tx_fifo(conf, 0x10);
/* Values from the reference manual tables on TRDT configuration *
* 0x09 for 24Mhz ABH frequency, 0x06 for 32Mhz or higher AHB frequency */
uint8_t trdt = conf->type == STM32_USB_OTG_FS ? 0x06 : 0x09;
_global_regs(conf)->GUSBCFG =
(_global_regs(conf)->GUSBCFG & ~USB_OTG_GUSBCFG_TRDT) |
(trdt << USB_OTG_GUSBCFG_TRDT_Pos);
_reset_eps(usbdev);
/* Disable the global NAK for both directions */
_disable_global_nak(conf);
if (_uses_dma(conf)) {
_global_regs(usbdev->config)->GAHBCFG |=
/* Configure DMA */
USB_OTG_GAHBCFG_DMAEN |
/* DMA configured as 8 x 32bit accesses */
(0x05 << USB_OTG_GAHBCFG_HBSTLEN_Pos);
/* Unmask the transfer complete interrupts
* Only needed when using DMA, otherwise the RX FIFO not empty
* interrupt is used */
_device_regs(conf)->DOEPMSK |= USB_OTG_DOEPMSK_XFRCM;
_device_regs(conf)->DIEPMSK |= USB_OTG_DIEPMSK_XFRCM;
}
/* Clear the interrupt flags and unmask those interrupts */
_global_regs(conf)->GINTSTS |= STM32_FSHS_USB_GINT_MASK;
_global_regs(conf)->GINTMSK |= STM32_FSHS_USB_GINT_MASK;
DEBUG("otg_fs: USB peripheral currently in %s mode\n",
(_global_regs(
conf)->GINTSTS & USB_OTG_GINTSTS_CMOD) ? "host" : "device");
/* Enable interrupts and configure the TX level to interrupt on empty */
_global_regs(conf)->GAHBCFG |= USB_OTG_GAHBCFG_GINT |
USB_OTG_GAHBCFG_TXFELVL;
/* Unmask the interrupt in the NVIC */
NVIC_EnableIRQ(conf->irqn);
}
static int _usbdev_get(usbdev_t *dev, usbopt_t opt,
void *value, size_t max_len)
{
(void)dev;
(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("otg_fs: 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)
{
(void)value_len;
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)dev;
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("otg_fs: Unhandled set call: 0x%x\n", opt);
break;
}
return res;
}
static void _usbdev_esr(usbdev_t *dev)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
uint32_t int_status = _global_regs(conf)->GINTSTS;
uint32_t event = 0;
if (int_status & USB_OTG_GINTSTS_ENUMDNE) {
event = USB_OTG_GINTSTS_ENUMDNE;
/* Reset condition done */
DEBUG("otg_fs: Reset done\n");
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_RESET);
}
else if (int_status & USB_OTG_GINTSTS_USBRST) {
/* Start of reset condition */
event = USB_OTG_GINTSTS_USBRST;
DEBUG("otg_fs: Reset start\n");
if (usbdev->suspend) {
usbdev->suspend = false;
_wake_periph(conf);
DEBUG("otg_fs: PHY SUSP %lx\n", *_pcgcctl_reg(conf));
}
/* Reset all the things! */
_flush_rx_fifo(conf);
_flush_tx_fifo(conf, 0x10);
_reset_eps(usbdev);
_set_address(usbdev, 0);
}
else if (int_status & USB_OTG_GINTSTS_SRQINT) {
/* Reset done */
event = USB_OTG_GINTSTS_SRQINT;
DEBUG("otg_fs: Session request\n");
}
else if (int_status & USB_OTG_GINTSTS_USBSUSP) {
event = USB_OTG_GINTSTS_USBSUSP;
if (!usbdev->suspend) {
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_SUSPEND);
usbdev->suspend = true;
/* Disable USB clock */
_sleep_periph(conf);
}
}
else if (int_status & USB_OTG_GINTSTS_WKUINT) {
event = USB_OTG_GINTSTS_WKUINT;
if (usbdev->suspend) {
usbdev->suspend = false;
/* re-enable USB clock */
_wake_periph(conf);
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_RESUME);
}
}
_global_regs(conf)->GINTSTS |= event;
_global_regs(conf)->GAHBCFG |= USB_OTG_GAHBCFG_GINT;
}
static void _usbdev_ep_init(usbdev_ep_t *ep)
{
DEBUG("otg_fs: Initializing EP %u, %s\n", ep->num,
ep->dir == USB_EP_DIR_IN ? "IN" : "OUT");
}
static size_t _get_available(usbdev_ep_t *ep)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
return ep->len -
(_out_regs(conf, ep->num)->DOEPTSIZ & USB_OTG_DOEPTSIZ_XFRSIZ_Msk);
}
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_AVAILABLE:
assert(max_len == sizeof(size_t));
*(size_t *)value = _get_available(ep);
res = sizeof(size_t);
break;
default:
DEBUG("otg_fs: Unhandled endpoint get call: 0x%x\n", opt);
break;
}
return res;
}
static void _ep_set_stall(usbdev_ep_t *ep, bool enable)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
(void)enable;
if (ep->dir == USB_EP_DIR_IN) {
/* Disable first */
_ep_in_disable(conf, ep->num);
_in_regs(conf, ep->num)->DIEPCTL |= USB_OTG_DIEPCTL_STALL;
}
else {
/* Disable first */
_ep_out_disable(conf, ep->num);
_out_regs(conf, ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_STALL;
}
}
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_activate(ep);
}
else {
_ep_deactivate(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("otg_fs: Unhandled endpoint set call: 0x%x\n", opt);
break;
}
return res;
}
static int _usbdev_ep_ready(usbdev_ep_t *ep, size_t len)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
if (ep->dir == USB_EP_DIR_IN) {
/* Abort when the endpoint is not active, prevents hangs,
* could be an assert in the future maybe */
if (!(_in_regs(conf, ep->num)->DIEPCTL & USB_OTG_DIEPCTL_USBAEP)) {
return -1;
}
if (_uses_dma(conf)) {
_in_regs(conf, ep->num)->DIEPDMA = (uint32_t)ep->buf;
}
/* The order here is crucial (AFAIK), it is required to first set the
* size and the packet count, then clear the NAK and enable the
* endpoint, and finally fill the transmit FIFO with the packet data.
* When DMA is enabled, filling the transmit FIFO is handled by the DMA
* controller in the peripheral
*/
/* Packet count seems not to decrement below 1 and thus is broken in
* combination with the TXFE irq, it does however work with control
* transfers and when using DMA */
uint32_t dieptsiz = (len & USB_OTG_DIEPTSIZ_XFRSIZ_Msk);
if (ep->num == 0 || _uses_dma(conf)) {
dieptsiz |= (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos);
}
_in_regs(conf, ep->num)->DIEPTSIZ = dieptsiz;
/* Intentionally enabling this before the FIFO is filled, unmasking the
* interrupts after the FIFO is filled doesn't always trigger the ISR */
/* TX FIFO empty interrupt is only used in non-dma mode */
_device_regs(conf)->DAINTMSK |= 1 << ep->num;
_device_regs(conf)->DIEPEMPMSK |= 1 << ep->num;
_in_regs(conf, ep->num)->DIEPCTL |= USB_OTG_DIEPCTL_CNAK |
USB_OTG_DIEPCTL_EPENA;
if (len > 0 && !_uses_dma(conf)) {
/* The FIFO requires 32 bit word reads/writes */
size_t words = (len + 3) / 4;
uint32_t *ep_buf = (uint32_t *)ep->buf;
__O uint32_t *fifo = _tx_fifo(conf, ep->num);
for (size_t i = 0; i < words; i++) {
fifo[i] = ep_buf[i];
}
}
}
else {
/* Abort when the endpoint is not active, prevents hangs,
* could be an assert in the future maybe */
if (!(_out_regs(conf, ep->num)->DOEPCTL & USB_OTG_DOEPCTL_USBAEP)) {
return -1;
}
if (_uses_dma(conf)) {
_out_regs(conf, ep->num)->DOEPDMA = (uint32_t)ep->buf;
}
/* Configure to receive one packet with ep->len as max length */
uint32_t doeptsiz = (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos) |
(ep->len & USB_OTG_DOEPTSIZ_XFRSIZ_Msk);
doeptsiz |= (ep->num == 0) ? 1 << USB_OTG_DOEPTSIZ_STUPCNT_Pos : 0;
_out_regs(conf, ep->num)->DOEPTSIZ = doeptsiz;
_out_regs(conf, ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK |
USB_OTG_DOEPCTL_EPENA |
_type_to_reg(ep->type);
}
return 0;
}
static void _copy_rxfifo(stm32_usb_otg_fshs_t *usbdev, uint8_t *buf, size_t len)
{
/* The FIFO requires 32 bit word reads/writes */
uint32_t *buf32 = (uint32_t *)buf;
__I uint32_t *fifo32 = _rx_fifo(usbdev->config);
size_t count = (len + 3) / 4;
for (size_t i = 0; i < count; i++) {
buf32[i] = fifo32[i];
}
}
static void _read_packet(usbdev_ep_t *ep)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
/* Pop status from the receive fifo status register */
uint32_t status = _global_regs(conf)->GRXSTSP;
/* Packet status code */
unsigned pkt_status = (status & USB_OTG_GRXSTSP_PKTSTS_Msk) >>
USB_OTG_GRXSTSP_PKTSTS_Pos;
size_t len = (status & USB_OTG_GRXSTSP_BCNT_Msk) >>
USB_OTG_GRXSTSP_BCNT_Pos;
/* Packet is copied on the update status and copied on the transfer
* complete status*/
if (pkt_status == STM32_PKTSTS_DATA_UPDT ||
pkt_status == STM32_PKTSTS_SETUP_UPDT) {
_copy_rxfifo(usbdev, ep->buf, len);
#ifdef STM32_USB_OTG_CID_2x
/* CID 2x doesn't signal SETUP_COMP on non-zero length packets, signal
* the TR_COMPLETE event immediately */
if (ep->num == 0 && len) {
usbdev->usbdev.epcb(&usbdev->out[ep->num],
USBDEV_EVENT_TR_COMPLETE);
}
#endif /* STM32_USB_OTG_CID_2x */
}
/* On zero length frames, only the COMP status is signalled and the UPDT
* status is skipped */
else if (pkt_status == STM32_PKTSTS_XFER_COMP ||
pkt_status == STM32_PKTSTS_SETUP_COMP) {
usbdev->usbdev.epcb(&usbdev->out[ep->num], USBDEV_EVENT_TR_COMPLETE);
}
}
/* This signals to the upper stack a completed transfer. Control transfers
* behave slightly different with the interrupts, so a number of conditionals
* filter interrupts to events
*/
static void _usbdev_ep_esr(usbdev_ep_t *ep)
{
stm32_usb_otg_fshs_t *usbdev = (stm32_usb_otg_fshs_t *)ep->dev;
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
if (ep->dir == USB_EP_DIR_IN) {
uint32_t status = _in_regs(conf, ep->num)->DIEPINT;
/* XFRC interrupt is used for all endpoints when DMA is enabled */
if (status & USB_OTG_DIEPINT_XFRC && _uses_dma(conf)) {
_in_regs(conf, ep->num)->DIEPINT = USB_OTG_DIEPINT_XFRC;
if (ep->num != 0) {
usbdev->usbdev.epcb(ep, USBDEV_EVENT_TR_COMPLETE);
}
}
else
/* TXFE empty interrupt is only used with DMA disabled */
if (status & USB_OTG_DIEPINT_TXFE) {
_device_regs(conf)->DIEPEMPMSK &= ~(1 << ep->num);
usbdev->usbdev.epcb(ep, USBDEV_EVENT_TR_COMPLETE);
}
}
else {
/* RX FIFO not empty and the endpoint matches the function argument */
if ((_global_regs(conf)->GINTSTS & USB_OTG_GINTSTS_RXFLVL) &&
(_global_regs(conf)->GRXSTSR & USB_OTG_GRXSTSP_EPNUM_Msk) == ep->num &&
!_uses_dma(conf)) {
_read_packet(ep);
}
/* Transfer complete seems only reliable when used with DMA */
else if (_out_regs(conf, ep->num)->DOEPINT & USB_OTG_DOEPINT_XFRC) {
_out_regs(conf, ep->num)->DOEPINT = USB_OTG_DOEPINT_XFRC;
if (_uses_dma(conf)) {
usbdev->usbdev.epcb(ep, USBDEV_EVENT_TR_COMPLETE);
}
}
}
/* Enable the peripheral interrupts again */
_global_regs(conf)->GAHBCFG |= USB_OTG_GAHBCFG_GINT;
}
static void _isr_ep(stm32_usb_otg_fshs_t *usbdev)
{
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
/* Top 16 bits of the register is OUT endpoints, bottom 16 is IN
* endpoints */
uint32_t active_ep = _device_regs(conf)->DAINT;
if (active_ep) {
unsigned epnum = bitarithm_lsb(active_ep);
if (epnum >= STM32_USB_OTG_REG_EP_OUT_OFFSET) {
usbdev->usbdev.epcb(&usbdev->out[epnum - STM32_USB_OTG_REG_EP_OUT_OFFSET],
USBDEV_EVENT_ESR);
}
else {
usbdev->usbdev.epcb(&usbdev->in[epnum], USBDEV_EVENT_ESR);
}
}
}
void _isr_common(stm32_usb_otg_fshs_t *usbdev)
{
const stm32_usb_otg_fshs_config_t *conf = usbdev->config;
uint32_t status = _global_regs(conf)->GINTSTS;
if (status) {
if (status & USB_OTG_GINTSTS_RXFLVL) {
unsigned epnum = _global_regs(conf)->GRXSTSR &
USB_OTG_GRXSTSP_EPNUM_Msk;
usbdev->usbdev.epcb(&usbdev->out[epnum], USBDEV_EVENT_ESR);
}
else if (_global_regs(conf)->GINTSTS &
(USB_OTG_GINTSTS_OEPINT | USB_OTG_GINTSTS_IEPINT)) {
_isr_ep(usbdev);
}
else {
/* Global interrupt */
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_ESR);
}
_global_regs(conf)->GAHBCFG &= ~USB_OTG_GAHBCFG_GINT;
}
cortexm_isr_end();
}
#ifdef STM32_USB_OTG_FS_ENABLED
void isr_otg_fs(void)
{
/* Take the first device from the list */
stm32_usb_otg_fshs_t *usbdev = &_usbdevs[0];
_isr_common(usbdev);
}
#endif /* STM32_USB_OTG_FS_ENABLED */
#ifdef STM32_USB_OTG_HS_ENABLED
void isr_otg_hs(void)
{
/* Take the last usbdev device from the list */
stm32_usb_otg_fshs_t *usbdev = &_usbdevs[USBDEV_NUMOF - 1];
_isr_common(usbdev);
}
#endif /* STM32_USB_OTG_HS_ENABLED */
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,
};