mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
df044f4f56
Signed-off-by: Dylan Laduranty <dylan.laduranty@mesotic.com>
820 lines
26 KiB
C
820 lines
26 KiB
C
/*
|
|
* Copyright (C) 2021 Mesotic SAS
|
|
*
|
|
* 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
|
|
* @{
|
|
* @file
|
|
* @brief Low level USB interface functions for the stm32 FS devices
|
|
*
|
|
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
|
|
* @}
|
|
*/
|
|
|
|
#define USB_H_USER_IS_RIOT_INTERNAL
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
|
|
#include "bitarithm.h"
|
|
#include "cpu.h"
|
|
#include "cpu_conf.h"
|
|
#include "periph/pm.h"
|
|
#include "periph/gpio.h"
|
|
#include "periph/usbdev.h"
|
|
#include "usbdev_stm32.h"
|
|
#include "pm_layered.h"
|
|
#include "ztimer.h"
|
|
#include "busy_wait.h"
|
|
|
|
#include <string.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"
|
|
|
|
#ifndef USB1_PMAADDR
|
|
#define USB1_PMAADDR USB_PMAADDR
|
|
#endif /* ndef USB1_PMAADDR */
|
|
|
|
/**
|
|
* There are two schemes for accessing the packet buffer area (PMA) from the CPU:
|
|
*
|
|
* - 2 x 16 bit/word access scheme where two 16-bit half-words per word can be
|
|
* accessed. With this scheme the access can be half-word aligned and the
|
|
* PMA address offset corresponds therefore to the local USB IP address.
|
|
* The size of the PMA SRAM is usually 1024 byte.
|
|
* - 1 x 16 bit/word access scheme where one 16-bit half word per word can be
|
|
* accessed. With this scheme the access can only be word-aligned and the
|
|
* PMA address offset to a half-word is therefore twice the local USB IP
|
|
* address. The size of the PMA SRAM is usually 512 byte.
|
|
*
|
|
* Which access scheme is used depends on the STM32 model.
|
|
*/
|
|
#if defined(CPU_LINE_STM32F303xD) || defined(CPU_LINE_STM32F303xE) || \
|
|
defined(CPU_LINE_STM32WB35xx) || defined(CPU_LINE_STM32WB55xx)
|
|
|
|
#define _ENDPOINT_NUMOF (8) /* Number of endpoints */
|
|
#define _PMA_SRAM_SIZE (1024) /* Size of Packet buffer Memory Area (PMA) SRAM */
|
|
#define _PMA_ACCESS_SCHEME (2) /* 2 x 16-bit/word */
|
|
#define _PMA_ACCESS_STEP_SIZE (1) /* Step size in 16-bit half-words when
|
|
* accessing the PMA SRAM from CPU */
|
|
#elif defined(CPU_LINE_STM32F103xB) || \
|
|
defined(CPU_LINE_STM32F303xB) || defined(CPU_LINE_STM32F303xC)
|
|
|
|
#define _ENDPOINT_NUMOF (8) /* Number of endpoints */
|
|
#define _PMA_SRAM_SIZE (512) /* Size of Packet buffer Memory Area (PMA) SRAM */
|
|
#define _PMA_ACCESS_SCHEME (1) /* 1 x 16-bit/word */
|
|
#define _PMA_ACCESS_STEP_SIZE (2) /* Step size in 16-bit half-words when
|
|
* accessing the PMA SRAM from CPU */
|
|
#else
|
|
#error "STM32 line is not supported"
|
|
#endif
|
|
|
|
/* USB IP local base address for the endpoints buffers in PMA SRAM */
|
|
#define _PMA_BUF_BASE_OFFSET (_ENDPOINT_NUMOF * 8)
|
|
|
|
/* List of instantiated USB peripherals */
|
|
static stm32_usbdev_fs_t _usbdevs[USBDEV_NUMOF];
|
|
|
|
static usbdev_ep_t _ep_in[_ENDPOINT_NUMOF];
|
|
static usbdev_ep_t _ep_out[_ENDPOINT_NUMOF];
|
|
|
|
/* 16-bit addresses used by the USB IP as endpoint buffers in the PMA SRAM */
|
|
static uint16_t _ep_in_buf[_ENDPOINT_NUMOF];
|
|
static uint16_t _ep_out_buf[_ENDPOINT_NUMOF];
|
|
|
|
/* Buffers used for receiving packets on OUT EPs. */
|
|
static uint8_t* _app_pbuf[_ENDPOINT_NUMOF];
|
|
|
|
/* Forward declaration for the usb device driver */
|
|
const usbdev_driver_t driver;
|
|
|
|
static USB_TypeDef *_global_regs(const stm32_usbdev_fs_config_t *conf)
|
|
{
|
|
return (USB_TypeDef *)conf->base_addr;
|
|
}
|
|
|
|
#if _PMA_ACCESS_SCHEME == 1
|
|
|
|
/* Endpoint Buffer Descriptor for the 1 x 16-bit/word access scheme
|
|
* (word-aligned access). Even though the members define 16-bit USB IP local
|
|
* addresses, they have to be defined as 32-bit unsigned for word-aligned
|
|
* access */
|
|
typedef struct {
|
|
uint32_t addr_tx; /* buffer address (16-bit USB IP local address) */
|
|
uint32_t count_tx; /* byte count (16-bit) */
|
|
uint32_t addr_rx; /* buffer address (16-bit USB IP local address) */
|
|
uint32_t count_rx; /* byte count (16-bit) */
|
|
} ep_buf_desc_t;
|
|
|
|
#elif _PMA_ACCESS_SCHEME == 2
|
|
|
|
/* Endpoint Buffer Descriptor for the 2 x 16-bits/word access scheme
|
|
* (half-word-aligned access) */
|
|
typedef struct {
|
|
uint16_t addr_tx;
|
|
uint16_t count_tx;
|
|
uint16_t addr_rx;
|
|
uint16_t count_rx;
|
|
} ep_buf_desc_t;
|
|
|
|
#else
|
|
#error "_PMA_ACCESS_SCHEME is not defined"
|
|
#endif
|
|
|
|
/* Bit mask containing all toggleable bits in a EP_REG(x) register */
|
|
#define _EP_REG_TOGGLE_ONLY_BITS_MASK (USB_EP_DTOG_TX | USB_EP_DTOG_RX | \
|
|
USB_EPTX_STAT | USB_EPRX_STAT )
|
|
|
|
#define EP_DESC ((ep_buf_desc_t*)USB1_PMAADDR)
|
|
#define EP_REG(x) (*((volatile uint32_t*) (((uintptr_t)(USB)) + (4*x))))
|
|
|
|
usbdev_t *usbdev_get_ctx(unsigned num)
|
|
{
|
|
assert(num < USBDEV_NUMOF);
|
|
return &_usbdevs[num].usbdev;
|
|
}
|
|
|
|
static inline void _enable_irq(void)
|
|
{
|
|
stm32_usbdev_fs_t *usbdev = &_usbdevs[0];
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
_global_regs(conf)->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM |
|
|
USB_CNTR_ERRM | USB_CNTR_PMAOVRM;
|
|
}
|
|
|
|
static void _setup(stm32_usbdev_fs_t *usbdev,
|
|
const stm32_usbdev_fs_config_t *config)
|
|
{
|
|
usbdev->usbdev.driver = &driver;
|
|
usbdev->config = config;
|
|
usbdev->out = _ep_out;
|
|
usbdev->in = _ep_in;
|
|
}
|
|
|
|
static uint32_t _type_to_reg(usb_ep_type_t type)
|
|
{
|
|
switch (type) {
|
|
case USB_EP_TYPE_CONTROL:
|
|
return USB_EP_CONTROL;
|
|
case USB_EP_TYPE_ISOCHRONOUS:
|
|
return USB_EP_ISOCHRONOUS;
|
|
case USB_EP_TYPE_BULK:
|
|
return USB_EP_BULK;
|
|
case USB_EP_TYPE_INTERRUPT:
|
|
return USB_EP_INTERRUPT;
|
|
default:
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void usbdev_init_lowlevel(void)
|
|
{
|
|
for (size_t i = 0; i < USBDEV_NUMOF; i++) {
|
|
_setup(&_usbdevs[i], &stm32_usbdev_fs_config[i]);
|
|
}
|
|
}
|
|
|
|
/*TODO: rework this function (maybe move it outside this file)
|
|
for other ST families support */
|
|
static void _enable_usb_clk(void)
|
|
{
|
|
#if defined(PWR_CR2_USV)
|
|
/* Validate USB Supply */
|
|
PWR->CR2 |= PWR_CR2_USV;
|
|
#endif
|
|
|
|
#if defined(RCC_APB1SMENR_USBSMEN)
|
|
RCC->APB1SMENR |= RCC_APB1SMENR_USBSMEN;
|
|
#elif defined(RCC_APB1SMENR1_USBSMEN)
|
|
RCC->APB1SMENR1 |= RCC_APB1SMENR1_USBSMEN;
|
|
#endif
|
|
|
|
#if defined(CRS_CR_AUTOTRIMEN) && defined(CRS_CR_CEN)
|
|
/* Enable CRS with auto trim enabled */
|
|
CRS->CR |= (CRS_CR_AUTOTRIMEN | CRS_CR_CEN);
|
|
#endif
|
|
}
|
|
|
|
static void _enable_gpio(const stm32_usbdev_fs_config_t *conf)
|
|
{
|
|
if (conf->af == GPIO_AF_UNDEF && conf->disconn == GPIO_UNDEF) {
|
|
/* If the MCU does not have an internal D+ pullup and there is no
|
|
* dedicated GPIO on the board to simulate a USB disconnect, the D+ GPIO
|
|
* is temporarily configured as an output and pushed down to simulate
|
|
* a disconnect/connect cycle to allow the host to recognize the device.
|
|
* This requires an external pullup on D+ signal to work. */
|
|
gpio_init(conf->dp, GPIO_OUT);
|
|
gpio_clear(conf->dp);
|
|
/* wait a 1 ms */
|
|
if (IS_USED(MODULE_ZTIMER_MSEC)) {
|
|
ztimer_sleep(ZTIMER_MSEC, 1);
|
|
}
|
|
else if(IS_USED(MODULE_ZTIMER_USEC)) {
|
|
ztimer_sleep(ZTIMER_USEC, 1 * US_PER_MS);
|
|
}
|
|
else {
|
|
busy_wait_us(1 * US_PER_MS);
|
|
}
|
|
gpio_init(conf->dp, GPIO_IN);
|
|
}
|
|
if (conf->af != GPIO_AF_UNDEF) {
|
|
/* Configure AF for the pins */
|
|
gpio_init_af(conf->dp, conf->af);
|
|
gpio_init_af(conf->dm, conf->af);
|
|
}
|
|
|
|
if (conf->disconn != GPIO_UNDEF) {
|
|
/* In case the MCU has no internal D+ pullup, a GPIO is used to
|
|
* connect/disconnect from USB bus */
|
|
gpio_init(conf->disconn, GPIO_OUT);
|
|
gpio_clear(conf->disconn);
|
|
}
|
|
}
|
|
|
|
static void _set_ep_in_status(uint16_t *val, uint16_t mask)
|
|
{
|
|
/* status endpoints bits can only be toggled, writing 0
|
|
as no effect. Thus, check which bits should be set
|
|
then XOR it with their current value */
|
|
if (mask & USB_EPTX_DTOG1) {
|
|
*val ^= USB_EPTX_DTOG1;
|
|
}
|
|
if (mask & USB_EPTX_DTOG2) {
|
|
*val ^= USB_EPTX_DTOG2;
|
|
}
|
|
}
|
|
|
|
static void _set_ep_out_status(uint16_t *val, uint16_t mask)
|
|
{
|
|
/* status endpoints bits can only be toggled, writing 0
|
|
* as no effect. Thus, check which bits should be set
|
|
* then XOR it with their current value */
|
|
|
|
if (mask & USB_EPRX_DTOG1) {
|
|
*val ^= USB_EPRX_DTOG1;
|
|
}
|
|
if (mask & USB_EPRX_DTOG2) {
|
|
*val ^= USB_EPRX_DTOG2;
|
|
}
|
|
}
|
|
|
|
static inline void _usb_attach(stm32_usbdev_fs_t *usbdev)
|
|
{
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
|
|
/* Some ST USB IP doesn't have this feature */
|
|
#ifdef USB_BCDR_DPPU
|
|
/* Enable DP pullup to signal connection */
|
|
_global_regs(conf)->BCDR |= USB_BCDR_DPPU;
|
|
while (!(CRS->ISR & CRS_ISR_ESYNCF)) {}
|
|
#else
|
|
/* If configuration uses a GPIO for USB connect/disconnect */
|
|
if (conf->disconn != GPIO_UNDEF) {
|
|
gpio_set(conf->disconn);
|
|
}
|
|
#endif /* USB_BCDR_DPPU */
|
|
}
|
|
|
|
static inline void _usb_detach(stm32_usbdev_fs_t *usbdev)
|
|
{
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
|
|
/* Some ST USB IP doesn't have this feature */
|
|
#ifdef USB_BCDR_DPPU
|
|
DEBUG_PUTS("usbdev_fs: Detaching from host");
|
|
|
|
/* Disable DP pullup to signal disconnection */
|
|
CLRBIT(_global_regs(conf)->BCDR, USB_BCDR_DPPU);
|
|
#else
|
|
/* If configuration uses a GPIO for USB connect/disconnect */
|
|
if (conf->disconn != GPIO_UNDEF) {
|
|
gpio_clear(conf->disconn);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void _set_address(stm32_usbdev_fs_t *usbdev, uint8_t address)
|
|
{
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
_global_regs(conf)->DADDR = USB_DADDR_EF | (address & USB_DADDR_ADD);
|
|
}
|
|
|
|
void USBDEV_ISR(void) {
|
|
stm32_usbdev_fs_t *usbdev = &_usbdevs[0];
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
int irq_status = _global_regs(conf)->ISTR;
|
|
/* Disable interrupts temporarily */
|
|
_global_regs(conf)->CNTR = 0;
|
|
/* Correct transfer Interrupt */
|
|
if (_global_regs(conf)->ISTR & USB_ISTR_CTR) {
|
|
/* Clear pending interrupts */
|
|
_global_regs(conf)->ISTR = 0;
|
|
/* Get endpoint number and associated ep pointer */
|
|
unsigned epnum = irq_status & 0x000F;
|
|
usbdev_ep_t *ep = (irq_status & USB_ISTR_DIR) ? &_ep_out[epnum] : &_ep_in[epnum];
|
|
/* get type and add endpoint address */
|
|
uint16_t reg = (_type_to_reg(ep->type) | ep->num);
|
|
/* Avoid toggling by mistake these bits */
|
|
CLRBIT(reg, (_EP_REG_TOGGLE_ONLY_BITS_MASK));
|
|
if (irq_status & USB_ISTR_DIR) {
|
|
/* Clear RX CTR by writing 0, avoid clear TX CTR so leave it to one */
|
|
EP_REG(epnum) = reg | USB_EP_CTR_TX;
|
|
usbdev->usbdev.epcb(ep, USBDEV_EVENT_ESR);
|
|
} else {
|
|
/* Clear TX CTR by writing 0, avoid clear RX CTR so leave it to one */
|
|
EP_REG(epnum) = reg | USB_EP_CTR_RX;
|
|
usbdev->usbdev.epcb(ep, USBDEV_EVENT_ESR);
|
|
}
|
|
} else if (_global_regs(conf)->ISTR & USB_ISTR_RESET) {
|
|
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_ESR);
|
|
} else if (_global_regs(conf)->ISTR & USB_ISTR_PMAOVR) {
|
|
_global_regs(conf)->ISTR = 0;
|
|
} else {
|
|
_global_regs(conf)->ISTR = 0;
|
|
}
|
|
cortexm_isr_end();
|
|
}
|
|
|
|
static void _usbdev_init(usbdev_t *dev)
|
|
{
|
|
stm32_usbdev_fs_t *usbdev = (stm32_usbdev_fs_t *)dev;
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
|
|
DEBUG_PUTS("usbdev_fs: initialization");
|
|
/* Block STOP/STANDBY */
|
|
pm_block(STM32_PM_STOP);
|
|
pm_block(STM32_PM_STANDBY);
|
|
|
|
#if defined(RCC_CFGR_USBPRE)
|
|
/* If `RCC_CFGR_USBPRE` is defined, the USB device FS clock of 48 MHz is
|
|
* derived from the PLL clock. In this case the PLL clock must be configured
|
|
* and must be either 48 MHz or 72 MHz. If the PLL clock is 72 MHz it is
|
|
* pre-divided by 1.5, the PLL clock of 48 MHz is used directly. */
|
|
#if defined(CLOCK_PLL_SRC) && (CLOCK_CORECLOCK == MHZ(72))
|
|
RCC->CFGR &= ~RCC_CFGR_USBPRE;
|
|
#elif defined(CLOCK_PLL_SRC) && (CLOCK_CORECLOCK == MHZ(48))
|
|
RCC->CFGR |= RCC_CFGR_USBPRE;
|
|
#else
|
|
#error "PLL must be configured to output 48 or 72 MHz"
|
|
#endif
|
|
#endif
|
|
|
|
/* Enable the clock to the peripheral */
|
|
periph_clk_en(conf->apb, conf->rcc_mask);
|
|
/* Enable USB clock */
|
|
_enable_usb_clk();
|
|
/* Configure GPIOs */
|
|
_enable_gpio(conf);
|
|
|
|
/* Reset and power down USB IP */
|
|
_global_regs(conf)->CNTR = USB_CNTR_FRES | USB_CNTR_PDWN;
|
|
/* Clear power down */
|
|
_global_regs(conf)->CNTR &= ~USB_CNTR_PDWN;
|
|
/* Clear reset */
|
|
_global_regs(conf)->CNTR &= ~USB_CNTR_FRES;
|
|
/* Clear interrupt register */
|
|
_global_regs(conf)->ISTR = 0x0000;
|
|
/* Set BTABLE at start of USB SRAM */
|
|
_global_regs(conf)->BTABLE = 0x0000;
|
|
/* Set default address after reset */
|
|
_global_regs(conf)->DADDR = USB_DADDR_EF;
|
|
/* Unmask the interrupt in the NVIC */
|
|
_enable_irq();
|
|
|
|
/* Disable remapping of USB IRQs if remapping defined */
|
|
#ifdef SYSCFG_CFGR1_USB_IT_RMP
|
|
SYSCFG->CFGR1 &= ~SYSCFG_CFGR1_USB_IT_RMP;
|
|
#endif
|
|
|
|
/* Enable USB IRQ */
|
|
NVIC_EnableIRQ(conf->irqn);
|
|
/* fill USB SRAM with zeroes */
|
|
memset((uint8_t*)USB1_PMAADDR, 0, 1024);
|
|
}
|
|
|
|
static usbdev_ep_t *_get_ep(stm32_usbdev_fs_t *usbdev, unsigned num,
|
|
usb_ep_dir_t dir)
|
|
{
|
|
if (num >= _ENDPOINT_NUMOF) {
|
|
return NULL;
|
|
}
|
|
return dir == USB_EP_DIR_IN ? &usbdev->in[num] : &usbdev->out[num];
|
|
}
|
|
|
|
static bool _ep_check(stm32_usbdev_fs_t *usbdev, unsigned idx,
|
|
usb_ep_dir_t dir, usb_ep_type_t type)
|
|
{
|
|
usbdev_ep_t* ep_in = &usbdev->in[idx];
|
|
usbdev_ep_t* ep_out = &usbdev->out[idx];
|
|
|
|
if (dir == USB_EP_DIR_IN) {
|
|
if (ep_out->type == type || ep_out->type == USB_EP_TYPE_NONE) {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
if (ep_in->type == type || ep_in->type == USB_EP_TYPE_NONE) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void _ep_enable(usbdev_ep_t *ep)
|
|
{
|
|
uint16_t reg = EP_REG(ep->num);
|
|
/* Avoid modification of the following bits in the register as writing
|
|
1 has no effect on these bits */
|
|
SETBIT(reg, USB_EP_CTR_RX | USB_EP_CTR_TX);
|
|
CLRBIT(reg, USB_EP_DTOG_RX | USB_EP_DTOG_TX);
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
_set_ep_out_status(®, USB_EP_RX_NAK);
|
|
/* Avoid toggling the other direction */
|
|
CLRBIT(reg, USB_EP_TX_VALID);
|
|
}
|
|
else {
|
|
_set_ep_in_status(®, USB_EP_TX_NAK);
|
|
/* Avoid toggling the other direction */
|
|
CLRBIT(reg, USB_EP_RX_VALID);
|
|
}
|
|
|
|
EP_REG(ep->num) = reg;
|
|
}
|
|
|
|
static void _ep_disable(usbdev_ep_t *ep)
|
|
{
|
|
uint16_t reg = EP_REG(ep->num);
|
|
/* Avoid modification of the following bits in the register as writing
|
|
1 has no effect on these bits */
|
|
SETBIT(reg, USB_EP_CTR_RX | USB_EP_CTR_TX);
|
|
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
_set_ep_out_status(®, USB_EP_RX_DIS);
|
|
/* Avoid toggling the other direction */
|
|
CLRBIT(reg, USB_EP_TX_VALID);
|
|
}
|
|
else {
|
|
_set_ep_in_status(®, USB_EP_TX_DIS);
|
|
/* Avoid toggling the other direction */
|
|
CLRBIT(reg, USB_EP_RX_VALID);
|
|
}
|
|
EP_REG(ep->num) = reg;
|
|
}
|
|
|
|
static usbdev_ep_t *_usbdev_new_ep(usbdev_t *dev, usb_ep_type_t type,
|
|
usb_ep_dir_t dir, size_t len)
|
|
{
|
|
stm32_usbdev_fs_t *usbdev = (stm32_usbdev_fs_t *)dev;
|
|
/* The IP supports all types for all endpoints */
|
|
usbdev_ep_t *new_ep = NULL;
|
|
|
|
/* Always return endpoint 0 for control types as requested by IP */
|
|
if (type == USB_EP_TYPE_CONTROL) {
|
|
new_ep = _get_ep(usbdev, 0, dir);
|
|
new_ep->num = 0;
|
|
}
|
|
else {
|
|
/* Find the first unassigned ep with proper dir */
|
|
for (unsigned idx = 1; idx < _ENDPOINT_NUMOF && !new_ep; idx++) {
|
|
usbdev_ep_t *ep = _get_ep(usbdev, idx, dir);
|
|
/* Check if endpoint is available */
|
|
if (ep->type == USB_EP_TYPE_NONE) {
|
|
bool avail = _ep_check(usbdev, idx, dir, type);
|
|
if (avail) {
|
|
new_ep = ep;
|
|
new_ep->num = idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (new_ep) {
|
|
if (usbdev->used + len < _PMA_SRAM_SIZE) {
|
|
if (dir == USB_EP_DIR_IN) {
|
|
_ep_in_buf[new_ep->num] = (_PMA_BUF_BASE_OFFSET + usbdev->used);
|
|
} else {
|
|
_ep_out_buf[new_ep->num] = (_PMA_BUF_BASE_OFFSET + usbdev->used);
|
|
}
|
|
usbdev->used += len;
|
|
new_ep->len = len;
|
|
new_ep->type = type;
|
|
new_ep->dev = dev;
|
|
new_ep->dir = dir;
|
|
DEBUG("usbdev_fs: register new %s endpoint as EP%d\n",
|
|
new_ep->dir == USB_EP_DIR_IN ? "IN": "OUT", new_ep->num);
|
|
DEBUG("usbdev_fs: %d bytes used\n", usbdev->used);
|
|
}
|
|
else {
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
return new_ep;
|
|
}
|
|
|
|
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)
|
|
{
|
|
(void)value_len;
|
|
stm32_usbdev_fs_t *usbdev = (stm32_usbdev_fs_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("usbdev_fs: Unhandled set call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void _usbdev_esr(usbdev_t *dev)
|
|
{
|
|
stm32_usbdev_fs_t *usbdev = (stm32_usbdev_fs_t *)dev;
|
|
const stm32_usbdev_fs_config_t *conf = usbdev->config;
|
|
|
|
if (_global_regs(conf)->ISTR & USB_ISTR_RESET) {
|
|
usbdev->usbdev.cb(&usbdev->usbdev, USBDEV_EVENT_RESET);
|
|
_global_regs(conf)->ISTR = (uint16_t)~USB_ISTR_RESET;
|
|
}
|
|
else {
|
|
_global_regs(conf)->ISTR = 0x0000;
|
|
}
|
|
_enable_irq();
|
|
}
|
|
|
|
static void _usbdev_ep_init(usbdev_ep_t *ep)
|
|
{
|
|
uint16_t reg = EP_REG(ep->num);
|
|
/* Set endpoint type */
|
|
reg &= ~(USB_EP_TYPE_MASK);
|
|
reg |= _type_to_reg(ep->type);
|
|
/* Keep the state ouf the other endpoint direction if already sets */
|
|
CLRBIT(reg, _EP_REG_TOGGLE_ONLY_BITS_MASK);
|
|
/* Assign EP address */
|
|
reg |= (ep->num & 0x000F);
|
|
/* Ensure interrupts are cleared */
|
|
reg &= ~(USB_EP_CTR_RX | USB_EP_CTR_TX);
|
|
/* Write the configuration to the register */
|
|
EP_REG(ep->num) = reg;
|
|
if (ep->dir == USB_EP_DIR_IN) {
|
|
EP_DESC[ep->num].addr_tx = _ep_in_buf[ep->num];
|
|
EP_DESC[ep->num].count_tx = 64;
|
|
} else {
|
|
EP_DESC[ep->num].addr_rx = _ep_out_buf[ep->num];
|
|
/* Set BLSIZE + NUM_BLOCK (1) */
|
|
EP_DESC[ep->num].count_rx = (1 << 10) | (1 << 15);
|
|
}
|
|
}
|
|
|
|
static usbopt_enable_t _ep_get_stall(usbdev_ep_t *ep)
|
|
{
|
|
usbopt_enable_t res;
|
|
|
|
if (ep->dir == USB_EP_DIR_IN) {
|
|
res = ((EP_REG(ep->num) & USB_EPTX_STAT) == USB_EP_TX_STALL) ?
|
|
USBOPT_ENABLE : USBOPT_DISABLE;
|
|
} else {
|
|
res = ((EP_REG(ep->num) & USB_EPRX_STAT) == USB_EP_RX_STALL) ?
|
|
USBOPT_ENABLE : USBOPT_DISABLE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static size_t _ep_get_available(usbdev_ep_t *ep)
|
|
{
|
|
size_t size;
|
|
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
size = EP_DESC[ep->num].count_rx & 0x03FF;
|
|
} else {
|
|
size = EP_DESC[ep->num].count_tx;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
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("usbdev_fs: Unhandled get call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void _ep_set_stall(usbdev_ep_t *ep, usbopt_enable_t enable)
|
|
{
|
|
uint16_t reg = EP_REG(ep->num);
|
|
/* Avoid modification of the following bits in the register as writing
|
|
1 has no effect on these bits */
|
|
SETBIT(reg, USB_EP_CTR_RX | USB_EP_CTR_TX);
|
|
|
|
if (ep->dir == USB_EP_DIR_IN) {
|
|
_set_ep_in_status(®, enable ? USB_EP_TX_STALL : USB_EP_TX_NAK);
|
|
/* Avoid toggling the other direction */
|
|
CLRBIT(reg, USB_EP_RX_VALID);
|
|
}
|
|
else {
|
|
_set_ep_out_status(®, enable ? USB_EP_RX_STALL : USB_EP_RX_NAK);
|
|
/* Avoid toggling the other direction */
|
|
CLRBIT(reg, USB_EP_TX_VALID);
|
|
}
|
|
|
|
EP_REG(ep->num) = reg;
|
|
}
|
|
|
|
static void _usbdev_ep0_stall(usbdev_t *usbdev)
|
|
{
|
|
(void)usbdev;
|
|
_ep_set_stall(&_ep_in[0], true);
|
|
_ep_set_stall(&_ep_out[0], true);
|
|
}
|
|
|
|
static void _usbdev_ep_stall(usbdev_ep_t *ep, bool enable)
|
|
{
|
|
_ep_set_stall(ep, enable);
|
|
}
|
|
|
|
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)) {
|
|
_usbdev_ep_init(ep);
|
|
_ep_enable(ep);
|
|
}
|
|
else {
|
|
_ep_disable(ep);
|
|
}
|
|
res = sizeof(usbopt_enable_t);
|
|
break;
|
|
case USBOPT_EP_STALL:
|
|
assert(value_len == sizeof(usbopt_enable_t));
|
|
_ep_set_stall(ep, *(usbopt_enable_t *)value);
|
|
res = sizeof(usbopt_enable_t);
|
|
break;
|
|
default:
|
|
DEBUG("usbdev_fs: Unhandled endpoint set call: 0x%x\n", opt);
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void _usbdev_ep_esr(usbdev_ep_t *ep)
|
|
{
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
/* Copy PMA SRAM buffer to OUT buffer */
|
|
/* `addr_rx` is the USB IP local address offset in half-words. The PMA
|
|
* address offset depends on the access scheme.
|
|
* - 2 x 16 bits/word access scheme: `_PMA_ACCESS_STEP_SIZE` is 1
|
|
* and the PMA address offset corresponds to USB IP local address.
|
|
* - 1 x 16 bits/word access scheme: `_PMA_ACCESS_STEP_SIZE` is 2
|
|
* and the PMA address offset is the double of the USB IP local
|
|
* address. */
|
|
uint16_t* pma_ptr = (uint16_t *)(USB1_PMAADDR +
|
|
(EP_DESC[ep->num].addr_rx * _PMA_ACCESS_STEP_SIZE));
|
|
uint16_t hword;
|
|
uint8_t cpt = 0;
|
|
while (cpt < 64) {
|
|
hword = *pma_ptr;
|
|
_app_pbuf[ep->num][cpt++] = hword & 0x00ff;
|
|
_app_pbuf[ep->num][cpt++] = (hword & 0xff00) >> 8;
|
|
pma_ptr += _PMA_ACCESS_STEP_SIZE;
|
|
}
|
|
}
|
|
ep->dev->epcb(ep, USBDEV_EVENT_TR_COMPLETE);
|
|
_enable_irq();
|
|
}
|
|
|
|
static int _usbdev_ep_xmit(usbdev_ep_t *ep, uint8_t* buf, size_t len)
|
|
{
|
|
unsigned irq = irq_disable();
|
|
uint16_t reg = EP_REG(ep->num);
|
|
/* Avoid modification of the following bits in the register as writing
|
|
1 has no effect on these bits */
|
|
SETBIT(reg, USB_EP_CTR_RX | USB_EP_CTR_TX);
|
|
if (ep->dir == USB_EP_DIR_OUT) {
|
|
_set_ep_out_status(®, USB_EP_RX_VALID);
|
|
CLRBIT(reg, USB_EPTX_STAT);
|
|
if (len == 0) {
|
|
len = ep->len;
|
|
}
|
|
if (len < 63) {
|
|
/* Should write len / 2 in count_rx if len <= 62 */
|
|
EP_DESC[ep->num].count_rx = (len >> 1) << 10;
|
|
} else {
|
|
/* BL_SIZE = 1, NUM_BLOCK = 1 */
|
|
EP_DESC[ep->num].count_rx = 1 << 15 | 1 << 10;
|
|
}
|
|
CLRBIT(reg, USB_EP_DTOG_TX | USB_EP_DTOG_RX);
|
|
_app_pbuf[ep->num] = buf;
|
|
} else {
|
|
_set_ep_in_status(®, USB_EP_TX_VALID);
|
|
CLRBIT(reg, USB_EPRX_STAT);
|
|
/* Transfer IN buffer content to USB PMA SRAM */
|
|
uint16_t* pma_ptr = (uint16_t *)(USB1_PMAADDR +
|
|
(EP_DESC[ep->num].addr_tx * _PMA_ACCESS_STEP_SIZE));
|
|
for (size_t i = 0; i < len; i += 2) {
|
|
*pma_ptr = (buf[i + 1] << 8) | buf[i];
|
|
pma_ptr += _PMA_ACCESS_STEP_SIZE;
|
|
}
|
|
/* Manage odd transfer size */
|
|
if (len % 2 == 1) {
|
|
*pma_ptr = 0x00ff & buf[len - 1];
|
|
}
|
|
|
|
EP_DESC[ep->num].count_tx = len;
|
|
CLRBIT(reg, USB_EP_DTOG_TX | USB_EP_DTOG_RX);
|
|
}
|
|
|
|
EP_REG(ep->num) = reg;
|
|
irq_restore(irq);
|
|
return 0;
|
|
}
|
|
|
|
const usbdev_driver_t driver = {
|
|
.init = _usbdev_init,
|
|
.new_ep = _usbdev_new_ep,
|
|
.get = _usbdev_get,
|
|
.set = _usbdev_set,
|
|
.esr = _usbdev_esr,
|
|
.ep0_stall = _usbdev_ep0_stall,
|
|
.ep_init = _usbdev_ep_init,
|
|
.ep_stall = _usbdev_ep_stall,
|
|
.ep_get = _usbdev_ep_get,
|
|
.ep_set = _usbdev_ep_set,
|
|
.ep_esr = _usbdev_ep_esr,
|
|
.xmit = _usbdev_ep_xmit,
|
|
};
|