diff --git a/boards/common/stm32/include/cfg_usb_otg_fs.h b/boards/common/stm32/include/cfg_usb_otg_fs.h new file mode 100644 index 0000000000..323ca091f3 --- /dev/null +++ b/boards/common/stm32/include/cfg_usb_otg_fs.h @@ -0,0 +1,62 @@ +/* + * 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 boards_common_stm32 + * @{ + * + * @file + * @brief Common configuration for STM32 OTG FS peripheral + * + * @author Koen Zandberg + */ + +#ifndef CFG_USB_OTG_FS_H +#define CFG_USB_OTG_FS_H + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enable the full speed USB OTG peripheral + */ +#define STM32_USB_OTG_FS_ENABLED + +/** + * @name common USB OTG FS configuration + * @{ + */ +static const stm32_usb_otg_fshs_config_t stm32_usb_otg_fshs_config[] = { + { + .periph = (uint8_t *)USB_OTG_FS_PERIPH_BASE, + .rcc_mask = RCC_AHB2ENR_OTGFSEN, + .phy = STM32_USB_OTG_PHY_BUILTIN, + .type = STM32_USB_OTG_FS, + .irqn = OTG_FS_IRQn, + .ahb = AHB2, + .dm = GPIO_PIN(PORT_A, 11), + .dp = GPIO_PIN(PORT_A, 12), + .af = GPIO_AF10, + } +}; +/** @} */ + +/** + * @brief Number of available USB OTG peripherals + */ +#define USBDEV_NUMOF ARRAY_SIZE(stm32_usb_otg_fshs_config) + +#ifdef __cplusplus +} +#endif + +#endif /* CFG_USB_OTG_FS_H */ +/** @} */ diff --git a/boards/common/stm32/include/cfg_usb_otg_hs_fs.h b/boards/common/stm32/include/cfg_usb_otg_hs_fs.h new file mode 100644 index 0000000000..b47f05bb6f --- /dev/null +++ b/boards/common/stm32/include/cfg_usb_otg_hs_fs.h @@ -0,0 +1,62 @@ +/* + * 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 boards_common_stm32 + * @{ + * + * @file + * @brief Common configuration for STM32 OTG HS peripheral with FS phy + * + * @author Koen Zandberg + */ + +#ifndef CFG_USB_OTG_HS_FS_H +#define CFG_USB_OTG_HS_FS_H + +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enable the full speed USB OTG peripheral + */ +#define STM32_USB_OTG_HS_ENABLED + +/** + * @name common USB OTG FS configuration + * @{ + */ +static const stm32_usb_otg_fshs_config_t stm32_usb_otg_fshs_config[] = { + { + .periph = (uint8_t *)USB_OTG_HS_PERIPH_BASE, + .rcc_mask = RCC_AHB1ENR_OTGHSEN, + .phy = STM32_USB_OTG_PHY_BUILTIN, + .type = STM32_USB_OTG_HS, + .irqn = OTG_HS_IRQn, + .ahb = AHB1, + .dm = GPIO_PIN(PORT_B, 14), + .dp = GPIO_PIN(PORT_B, 15), + .af = GPIO_AF12, + } +}; +/** @} */ + +/** + * @brief Number of available USB OTG peripherals + */ +#define USBDEV_NUMOF ARRAY_SIZE(stm32_usb_otg_fshs_config) + +#ifdef __cplusplus +} +#endif + +#endif /* CFG_USB_OTG_HS_FS_H */ +/** @} */ diff --git a/boards/nucleo-f207zg/Makefile.features b/boards/nucleo-f207zg/Makefile.features index 5d64a5ac77..7cb40b2855 100644 --- a/boards/nucleo-f207zg/Makefile.features +++ b/boards/nucleo-f207zg/Makefile.features @@ -10,6 +10,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # Put other features for this board (in alphabetical order) FEATURES_PROVIDED += riotboot diff --git a/boards/nucleo-f207zg/include/periph_conf.h b/boards/nucleo-f207zg/include/periph_conf.h index a1f39e0594..d1bb7296cb 100644 --- a/boards/nucleo-f207zg/include/periph_conf.h +++ b/boards/nucleo-f207zg/include/periph_conf.h @@ -24,6 +24,7 @@ #include "periph_cpu.h" #include "f2/cfg_clock_120_8_1.h" #include "cfg_i2c1_pb8_pb9.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f412zg/Makefile.features b/boards/nucleo-f412zg/Makefile.features index 128b38e895..20126aaf30 100644 --- a/boards/nucleo-f412zg/Makefile.features +++ b/boards/nucleo-f412zg/Makefile.features @@ -9,6 +9,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # load the common Makefile.features for Nucleo-144 boards include $(RIOTBOARD)/common/nucleo144/Makefile.features diff --git a/boards/nucleo-f412zg/include/periph_conf.h b/boards/nucleo-f412zg/include/periph_conf.h index c2ce73dedd..ac195cd510 100644 --- a/boards/nucleo-f412zg/include/periph_conf.h +++ b/boards/nucleo-f412zg/include/periph_conf.h @@ -25,6 +25,7 @@ #include "f4/cfg_clock_100_8_1.h" #include "cfg_i2c1_pb8_pb9.h" #include "cfg_timer_tim5.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f413zh/Makefile.features b/boards/nucleo-f413zh/Makefile.features index 11e3cdab8f..73b48b6501 100644 --- a/boards/nucleo-f413zh/Makefile.features +++ b/boards/nucleo-f413zh/Makefile.features @@ -12,6 +12,7 @@ FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # load the common Makefile.features for Nucleo boards include $(RIOTBOARD)/common/nucleo144/Makefile.features diff --git a/boards/nucleo-f413zh/include/periph_conf.h b/boards/nucleo-f413zh/include/periph_conf.h index 29224547d0..f0b2436e92 100644 --- a/boards/nucleo-f413zh/include/periph_conf.h +++ b/boards/nucleo-f413zh/include/periph_conf.h @@ -25,6 +25,7 @@ #include "f4/cfg_clock_100_8_1.h" #include "cfg_i2c1_pb8_pb9.h" #include "cfg_timer_tim5.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f429zi/Makefile.features b/boards/nucleo-f429zi/Makefile.features index f67ac9bf03..536c6eaf8f 100644 --- a/boards/nucleo-f429zi/Makefile.features +++ b/boards/nucleo-f429zi/Makefile.features @@ -9,6 +9,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # load the common Makefile.features for Nucleo boards include $(RIOTBOARD)/common/nucleo144/Makefile.features diff --git a/boards/nucleo-f429zi/include/periph_conf.h b/boards/nucleo-f429zi/include/periph_conf.h index bd6f165774..269027bb2b 100644 --- a/boards/nucleo-f429zi/include/periph_conf.h +++ b/boards/nucleo-f429zi/include/periph_conf.h @@ -24,6 +24,7 @@ #include "cfg_i2c1_pb8_pb9.h" #include "cfg_spi_divtable.h" #include "cfg_timer_tim5.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f446ze/Makefile.features b/boards/nucleo-f446ze/Makefile.features index e0fbb813d1..d9f6d7e060 100644 --- a/boards/nucleo-f446ze/Makefile.features +++ b/boards/nucleo-f446ze/Makefile.features @@ -8,6 +8,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # load the common Makefile.features for Nucleo boards include $(RIOTBOARD)/common/nucleo144/Makefile.features diff --git a/boards/nucleo-f446ze/include/periph_conf.h b/boards/nucleo-f446ze/include/periph_conf.h index 70bfcf2c35..94fc584ae0 100644 --- a/boards/nucleo-f446ze/include/periph_conf.h +++ b/boards/nucleo-f446ze/include/periph_conf.h @@ -24,6 +24,7 @@ #include "cfg_i2c1_pb8_pb9.h" #include "cfg_spi_divtable.h" #include "cfg_timer_tim5.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f722ze/Makefile.features b/boards/nucleo-f722ze/Makefile.features index 48804fb1a4..4bf07a0527 100644 --- a/boards/nucleo-f722ze/Makefile.features +++ b/boards/nucleo-f722ze/Makefile.features @@ -7,6 +7,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # Put other features for this board (in alphabetical order) FEATURES_PROVIDED += riotboot diff --git a/boards/nucleo-f722ze/include/periph_conf.h b/boards/nucleo-f722ze/include/periph_conf.h index 3869d9bf1b..28c9f74097 100644 --- a/boards/nucleo-f722ze/include/periph_conf.h +++ b/boards/nucleo-f722ze/include/periph_conf.h @@ -24,6 +24,7 @@ #include "cfg_i2c1_pb8_pb9.h" #include "cfg_rtt_default.h" #include "cfg_timer_tim2.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f746zg/Makefile.features b/boards/nucleo-f746zg/Makefile.features index 23385d3dad..8215d8c153 100644 --- a/boards/nucleo-f746zg/Makefile.features +++ b/boards/nucleo-f746zg/Makefile.features @@ -7,6 +7,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # Put other features for this board (in alphabetical order) FEATURES_PROVIDED += riotboot diff --git a/boards/nucleo-f746zg/include/periph_conf.h b/boards/nucleo-f746zg/include/periph_conf.h index 34f92f1a4f..860762daf9 100644 --- a/boards/nucleo-f746zg/include/periph_conf.h +++ b/boards/nucleo-f746zg/include/periph_conf.h @@ -24,6 +24,7 @@ #include "cfg_i2c1_pb8_pb9.h" #include "cfg_rtt_default.h" #include "cfg_timer_tim2.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/nucleo-f767zi/Makefile.features b/boards/nucleo-f767zi/Makefile.features index 3ed5517e5d..75bf9c80c0 100644 --- a/boards/nucleo-f767zi/Makefile.features +++ b/boards/nucleo-f767zi/Makefile.features @@ -9,6 +9,7 @@ FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev FEATURES_PROVIDED += periph_eth # Put other features for this board (in alphabetical order) diff --git a/boards/nucleo-f767zi/include/periph_conf.h b/boards/nucleo-f767zi/include/periph_conf.h index 2404f4707a..cc47880721 100644 --- a/boards/nucleo-f767zi/include/periph_conf.h +++ b/boards/nucleo-f767zi/include/periph_conf.h @@ -25,6 +25,7 @@ #include "cfg_spi_divtable.h" #include "cfg_rtt_default.h" #include "cfg_timer_tim2.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/pyboard/Makefile.features b/boards/pyboard/Makefile.features index e2a8fd95a7..30544feeb6 100644 --- a/boards/pyboard/Makefile.features +++ b/boards/pyboard/Makefile.features @@ -7,3 +7,4 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev diff --git a/boards/pyboard/Makefile.include b/boards/pyboard/Makefile.include index 57c0f9c079..ec39ee098d 100644 --- a/boards/pyboard/Makefile.include +++ b/boards/pyboard/Makefile.include @@ -1,3 +1,6 @@ +# we use shared STM32 configuration snippets +INCLUDES += -I$(RIOTBOARD)/common/stm32/include + # define the default port depending on the host OS PORT_LINUX ?= /dev/ttyUSB0 PORT_DARWIN ?= $(firstword $(sort $(wildcard /dev/tty.SLAB_USBtoUART*))) diff --git a/boards/pyboard/include/periph_conf.h b/boards/pyboard/include/periph_conf.h index b3a265f232..883aa2f906 100644 --- a/boards/pyboard/include/periph_conf.h +++ b/boards/pyboard/include/periph_conf.h @@ -22,6 +22,7 @@ #define PERIPH_CONF_H #include "periph_cpu.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/stm32f429i-disc1/Makefile.features b/boards/stm32f429i-disc1/Makefile.features index 082931f6f9..a7d89bbd78 100644 --- a/boards/stm32f429i-disc1/Makefile.features +++ b/boards/stm32f429i-disc1/Makefile.features @@ -6,6 +6,7 @@ FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # Put other features for this board (in alphabetical order) FEATURES_PROVIDED += riotboot diff --git a/boards/stm32f429i-disc1/include/periph_conf.h b/boards/stm32f429i-disc1/include/periph_conf.h index 0e1b61145e..346fdb8514 100644 --- a/boards/stm32f429i-disc1/include/periph_conf.h +++ b/boards/stm32f429i-disc1/include/periph_conf.h @@ -23,6 +23,7 @@ #include "f4/cfg_clock_168_8_1.h" #include "cfg_spi_divtable.h" #include "cfg_timer_tim5.h" +#include "cfg_usb_otg_hs_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/stm32f4discovery/Makefile.features b/boards/stm32f4discovery/Makefile.features index b2d4746186..568c013ad4 100644 --- a/boards/stm32f4discovery/Makefile.features +++ b/boards/stm32f4discovery/Makefile.features @@ -10,6 +10,7 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev # Various other features (if any) FEATURES_PROVIDED += arduino diff --git a/boards/stm32f4discovery/include/periph_conf.h b/boards/stm32f4discovery/include/periph_conf.h index 547ef2c9b6..3c1e1dba11 100644 --- a/boards/stm32f4discovery/include/periph_conf.h +++ b/boards/stm32f4discovery/include/periph_conf.h @@ -23,6 +23,7 @@ #include "periph_cpu.h" #include "f4/cfg_clock_168_8_0.h" #include "cfg_spi_divtable.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/stm32f723e-disco/Makefile.features b/boards/stm32f723e-disco/Makefile.features index ccf8f03c99..f40649a235 100644 --- a/boards/stm32f723e-disco/Makefile.features +++ b/boards/stm32f723e-disco/Makefile.features @@ -9,3 +9,4 @@ FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev diff --git a/boards/stm32f723e-disco/include/periph_conf.h b/boards/stm32f723e-disco/include/periph_conf.h index afe93b1688..d57a8eeb1a 100644 --- a/boards/stm32f723e-disco/include/periph_conf.h +++ b/boards/stm32f723e-disco/include/periph_conf.h @@ -21,6 +21,7 @@ #include "periph_cpu.h" #include "cfg_rtt_default.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/boards/stm32f769i-disco/Makefile.features b/boards/stm32f769i-disco/Makefile.features index ed0d7b23cb..28cba0d161 100644 --- a/boards/stm32f769i-disco/Makefile.features +++ b/boards/stm32f769i-disco/Makefile.features @@ -6,3 +6,4 @@ FEATURES_PROVIDED += periph_rtc FEATURES_PROVIDED += periph_rtt FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +FEATURES_PROVIDED += periph_usbdev diff --git a/boards/stm32f769i-disco/include/periph_conf.h b/boards/stm32f769i-disco/include/periph_conf.h index dda83fac6c..2ad1524527 100644 --- a/boards/stm32f769i-disco/include/periph_conf.h +++ b/boards/stm32f769i-disco/include/periph_conf.h @@ -22,6 +22,7 @@ #include "periph_cpu.h" #include "cfg_rtt_default.h" #include "cfg_timer_tim2.h" +#include "cfg_usb_otg_fs.h" #ifdef __cplusplus extern "C" { diff --git a/cpu/stm32_common/Makefile.dep b/cpu/stm32_common/Makefile.dep index 378eb8ebc7..2a2c82fa34 100644 --- a/cpu/stm32_common/Makefile.dep +++ b/cpu/stm32_common/Makefile.dep @@ -3,3 +3,7 @@ USEMODULE += pm_layered # include stm32 common functions and stm32 common periph drivers USEMODULE += stm32_common stm32_common_periph + +ifneq (,$(filter periph_usbdev,$(FEATURES_USED))) + USEMODULE += xtimer +endif diff --git a/cpu/stm32_common/include/periph_cpu_common.h b/cpu/stm32_common/include/periph_cpu_common.h index de34d426f9..9507935a01 100644 --- a/cpu/stm32_common/include/periph_cpu_common.h +++ b/cpu/stm32_common/include/periph_cpu_common.h @@ -573,6 +573,46 @@ typedef struct { } i2c_timing_param_t; #endif +/** + * @brief USB OTG peripheral type. + * + * High speed peripheral is assumed to have DMA support available. + * + * @warning Only one of each type is supported at the moment, it is not + * supported to have two FS type or two HS type peripherals enabled on a + * single MCU. + */ +typedef enum { + STM32_USB_OTG_FS = 0, /**< Full speed peripheral */ + STM32_USB_OTG_HS = 1, /**< High speed peripheral */ +} stm32_usb_otg_fshs_type_t; + +/** + * @brief Type of USB OTG peripheral phy. + * + * The FS type only supports the built-in type, the HS phy can have either the + * FS built-in phy enabled or the HS ULPI interface enabled. + */ +typedef enum { + STM32_USB_OTG_PHY_BUILTIN, + STM32_USB_OTG_PHY_ULPI, +} stm32_usb_otg_fshs_phy_t; + +/** + * @brief stm32 USB OTG configuration + */ +typedef struct { + uint8_t *periph; /**< USB peripheral base address */ + uint32_t rcc_mask; /**< bit in clock enable register */ + stm32_usb_otg_fshs_phy_t phy; /**< Built-in or ULPI phy */ + stm32_usb_otg_fshs_type_t type; /**< FS or HS type */ + uint8_t irqn; /**< IRQ channel */ + uint8_t ahb; /**< AHB bus */ + gpio_t dm; /**< Data- gpio */ + gpio_t dp; /**< Data+ gpio */ + gpio_af_t af; /**< Alternative function */ +} stm32_usb_otg_fshs_config_t; + /** * @brief Get the actual bus clock frequency for the APB buses * @@ -745,6 +785,10 @@ int dma_configure(dma_t dma, int chan, const volatile void *src, volatile void * #include "candev_stm32.h" #endif +#ifdef MODULE_PERIPH_USBDEV +#include "usbdev_stm32.h" +#endif + /** * @brief STM32 Ethernet configuration mode */ diff --git a/cpu/stm32_common/include/usbdev_stm32.h b/cpu/stm32_common/include/usbdev_stm32.h new file mode 100644 index 0000000000..c67a424f95 --- /dev/null +++ b/cpu/stm32_common/include/usbdev_stm32.h @@ -0,0 +1,138 @@ +/* + * 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_stm32_common_usbdev stm32 USB OTG FS/HS peripheral + * @ingroup cpu_stm32_common + * @brief USB interface functions for the stm32 class devices + * + * @{ + * + * @file + * @brief USB interface functions for the stm32 OTG FS/HS class devices + * + * The stm32f2, stm32f4 and stm32f7 have a common USB OTG FS capable USB + * peripheral. + * + * Two versions are currently known to exist with subtle differences + * in some registers. The CID register of the peripheral indicates this version, + * 0x00001200 for one version of the full speed peripheral and 0x00002000 for + * the other version of the full speed peripheral. + * The main difference is in the GCCFG register, where the 1.2 version has a + * NOVBUSSENS bit and the 2.0 version has a VBDEN bit. This difference is used + * to detect the IP version. + * The 2.0 version also has more advanced USB low power mode support. + * + * For the end user, the main difference is the 1.2 version having 4 endpoints + * and the 2.0 version having 6 endpoints. The 2.0 version also supports a + * number of USB low power modes. + * + * @author Koen Zandberg + */ + +#ifndef USBDEV_STM32_H +#define USBDEV_STM32_H + +#include +#include +#include "periph_cpu.h" +#include "periph/usbdev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Detect the IP version based on the available register define */ +#if defined(USB_OTG_GCCFG_NOVBUSSENS) +#define STM32_USB_OTG_CID_1x /**< USB OTG FS version 0x00001200 */ +#elif defined(USB_OTG_GCCFG_VBDEN) +#define STM32_USB_OTG_CID_2x /**< USB OTG FS version 0x00002000 */ +#else +#error Unknown USB peripheral version +#endif + +/** + * @brief Buffer space available for endpoint TX/RX data + */ +#ifndef STM32_USB_OTG_BUF_SPACE +#define STM32_USB_OTG_BUF_SPACE USBDEV_EP_BUF_SPACE +#endif + +/** + * @brief Number of endpoints available with the OTG FS peripheral + * including the control endpoint + */ +#ifdef STM32_USB_OTG_CID_1x +#define STM32_USB_OTG_FS_NUM_EP (4) /**< OTG FS with 4 endpoints */ +#elif defined(STM32_USB_OTG_CID_2x) +#define STM32_USB_OTG_FS_NUM_EP (6) /**< OTG FS with 6 endpoints */ +#endif + +/** + * @brief Number of endpoints available with the OTG HS peripheral + * including the control endpoint + */ +#ifdef STM32_USB_OTG_CID_1x +#define STM32_USB_OTG_HS_NUM_EP (6) /**< OTG HS with 6 endpoints */ +#elif defined(STM32_USB_OTG_CID_2x) +#define STM32_USB_OTG_HS_NUM_EP (9) /**< OTG HS with 9 endpoints */ +#endif + +/** + * @brief USB OTG FS FIFO reception buffer space in 32-bit words + * + * Used as shared FIFO for reception of all OUT transfers + * + * @note The application might have to increase this when dealing with large + * isochronous transfers + */ +#ifndef STM32_USB_OTG_FS_RX_FIFO_SIZE +#define STM32_USB_OTG_FS_RX_FIFO_SIZE (128U) +#endif + +/** + * @brief USB OTG HS FIFO reception buffer space in 32-bit words + * + * Used as shared FIFO for reception of all OUT transfers from the host + */ +#ifndef STM32_USB_OTG_HS_RX_FIFO_SIZE +#define STM32_USB_OTG_HS_RX_FIFO_SIZE (512U) +#endif + +/** + * @brief Use the built-in DMA controller of the HS peripheral when possible + */ +#ifndef STM32_USB_OTG_HS_USE_DMA +#ifdef STM32_USB_OTG_CID_1x +/* FIXME: It should be possible to use DMA with the 1.x version of the * + * peripheral, but somehow it doesn't work. */ +#define STM32_USB_OTG_HS_USE_DMA (0) +#else +#define STM32_USB_OTG_HS_USE_DMA (1) +#endif +#endif + +/** + * @brief stm32 USB OTG peripheral device context + */ +typedef struct { + usbdev_t usbdev; /**< Inherited usbdev struct */ + const stm32_usb_otg_fshs_config_t *config; /**< USB peripheral config */ + uint8_t buffer[STM32_USB_OTG_BUF_SPACE]; /**< Buffer space for endpoints */ + size_t occupied; /**< Buffer space occupied */ + size_t fifo_pos; /**< FIFO space occupied */ + usbdev_ep_t *in; /**< In endpoints */ + usbdev_ep_t *out; /**< Out endpoints */ + bool suspend; /**< Suspend status */ +} stm32_usb_otg_fshs_t; + +#ifdef __cplusplus +} +#endif +#endif /* USBDEV_STM32_H */ +/** @} */ diff --git a/cpu/stm32_common/periph/usbdev.c b/cpu/stm32_common/periph/usbdev.c new file mode 100644 index 0000000000..f6135c4807 --- /dev/null +++ b/cpu/stm32_common/periph/usbdev.c @@ -0,0 +1,1152 @@ +/* + * 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_stm32_common_usbdev + * @{ + * @file + * @brief Low level USB interface functions for the stm32 FS/HS devices + * + * @author Koen Zandberg + * @} + */ + +#define USB_H_USER_IS_RIOT_INTERNAL + +#include +#include +#include + +#include "bitarithm.h" +#include "xtimer.h" +#include "cpu.h" +#include "cpu_conf.h" +#include "periph/pm.h" +#include "periph/gpio.h" +#include "periph/usbdev.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 + */ +#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); + } + 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]; +} + +#ifdef DEVELHELP +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 /* DEVELHELP */ + +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 */ + xtimer_spin(xtimer_ticks_from_usec(25 * US_PER_MS)); +} + +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, +};