stm32: Initial support for USB On-The-Go (OTG) driver on stm32f4

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2019-08-11 22:40:28 -04:00
parent 841150ff00
commit aac51bdb0a
3 changed files with 411 additions and 3 deletions

View File

@ -19,7 +19,6 @@ choice
config MACH_STM32F103 config MACH_STM32F103
bool "STM32F103" bool "STM32F103"
select MACH_STM32F1 select MACH_STM32F1
select HAVE_STM32_USBFS
config MACH_STM32F405 config MACH_STM32F405
bool "STM32F405" bool "STM32F405"
select MACH_STM32F4 select MACH_STM32F4
@ -37,6 +36,12 @@ config MACH_STM32F4
bool bool
config HAVE_STM32_USBFS config HAVE_STM32_USBFS
bool bool
default y if MACH_STM32F103
default n
config HAVE_STM32_USBOTG
bool
default y if MACH_STM32F4
default n
config MCU config MCU
string string
@ -97,7 +102,7 @@ config CLOCK_REF_8M
config USBSERIAL config USBSERIAL
bool "Use USB for communication (instead of serial)" bool "Use USB for communication (instead of serial)"
depends on HAVE_STM32_USBFS depends on HAVE_STM32_USBFS || HAVE_STM32_USBOTG
default y default y
config SERIAL config SERIAL
depends on !USBSERIAL depends on !USBSERIAL

View File

@ -26,7 +26,9 @@ src-$(CONFIG_MACH_STM32F4) += ../lib/stm32f4/system_stm32f4xx.c
src-$(CONFIG_MACH_STM32F4) += stm32/stm32f4.c src-$(CONFIG_MACH_STM32F4) += stm32/stm32f4.c
src-$(CONFIG_HAVE_GPIO_ADC) += stm32/adc.c src-$(CONFIG_HAVE_GPIO_ADC) += stm32/adc.c
src-$(CONFIG_HAVE_GPIO_SPI) += stm32/spi.c src-$(CONFIG_HAVE_GPIO_SPI) += stm32/spi.c
src-$(CONFIG_USBSERIAL) += stm32/usbfs.c generic/usb_cdc.c usb-src-$(CONFIG_HAVE_STM32_USBFS) := stm32/usbfs.c
usb-src-$(CONFIG_HAVE_STM32_USBOTG) := stm32/usbotg.c
src-$(CONFIG_USBSERIAL) += $(usb-src-y) generic/usb_cdc.c
src-$(CONFIG_SERIAL) += stm32/serial.c generic/serial_irq.c src-$(CONFIG_SERIAL) += stm32/serial.c generic/serial_irq.c
# Add assembler build rules # Add assembler build rules

401
src/stm32/usbotg.c Normal file
View File

@ -0,0 +1,401 @@
// Hardware interface to "USB OTG (on the go) controller" on stm32
//
// Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include <string.h> // NULL
#include "autoconf.h" // CONFIG_MACH_STM32F446
#include "board/io.h" // writel
#include "board/usb_cdc.h" // usb_notify_ep0
#include "board/usb_cdc_ep.h" // USB_CDC_EP_BULK_IN
#include "command.h" // DECL_CONSTANT_STR
#include "internal.h" // GPIO
#include "sched.h" // DECL_INIT
/****************************************************************
* USB transfer memory
****************************************************************/
#define OTG ((USB_OTG_GlobalTypeDef*)USB_OTG_FS_PERIPH_BASE)
#define OTGD ((USB_OTG_DeviceTypeDef*) \
(USB_OTG_FS_PERIPH_BASE + USB_OTG_DEVICE_BASE))
#define EPFIFO(EP) ((void*)(USB_OTG_FS_PERIPH_BASE + USB_OTG_FIFO_BASE \
+ ((EP) << 12)))
#define EPIN(EP) ((USB_OTG_INEndpointTypeDef*) \
(USB_OTG_FS_PERIPH_BASE + USB_OTG_IN_ENDPOINT_BASE \
+ ((EP) << 5)))
#define EPOUT(EP) ((USB_OTG_OUTEndpointTypeDef*) \
(USB_OTG_FS_PERIPH_BASE + USB_OTG_OUT_ENDPOINT_BASE \
+ ((EP) << 5)))
// Setup the USB fifos
static void
fifo_configure(void)
{
// Reserve memory for Rx fifo
uint32_t sz = ((4 * 1 + 6)
+ 4 * ((USB_CDC_EP_BULK_OUT_SIZE / 4) + 1)
+ (2 * 1));
OTG->GRXFSIZ = sz;
// Tx fifos
uint32_t fpos = sz, ep_size = 0x10;
OTG->DIEPTXF0_HNPTXFSIZ = ((fpos << USB_OTG_TX0FSA_Pos)
| (ep_size << USB_OTG_TX0FD_Pos));
fpos += ep_size;
OTG->DIEPTXF[USB_CDC_EP_ACM - 1] = (
(fpos << USB_OTG_DIEPTXF_INEPTXSA_Pos)
| (ep_size << USB_OTG_DIEPTXF_INEPTXFD_Pos));
fpos += ep_size;
OTG->DIEPTXF[USB_CDC_EP_BULK_IN - 1] = (
(fpos << USB_OTG_DIEPTXF_INEPTXSA_Pos)
| (ep_size << USB_OTG_DIEPTXF_INEPTXFD_Pos));
fpos += ep_size;
}
// Inspect the next packet on the rx queue
static uint32_t
peek_rx_queue(uint32_t ep)
{
for (;;) {
USB_OTG_OUTEndpointTypeDef *epo = EPOUT(ep);
uint32_t ctl = epo->DOEPCTL;
if (!(ctl & USB_OTG_DOEPCTL_EPENA) || ctl & USB_OTG_DOEPCTL_NAKSTS) {
// Reenable packet reception if it got disabled by controller
epo->DOEPTSIZ = 64 | (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos);
epo->DOEPCTL = ctl | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
}
uint32_t sts = OTG->GINTSTS;
if (!(sts & USB_OTG_GINTSTS_RXFLVL))
// No packet ready
return 0;
uint32_t grx = OTG->GRXSTSR;
uint32_t pktsts = ((grx & USB_OTG_GRXSTSP_PKTSTS_Msk)
>> USB_OTG_GRXSTSP_PKTSTS_Pos);
if (pktsts != 1 && pktsts != 3 && pktsts != 4) {
// A packet is ready
if ((grx & USB_OTG_GRXSTSP_EPNUM_Msk) != ep)
return 0;
return grx;
}
// Discard informational entries from queue
grx = OTG->GRXSTSP;
}
}
// Read a packet from the rx queue
static int_fast8_t
fifo_read_packet(uint8_t *dest, uint_fast8_t max_len)
{
void *fifo = EPFIFO(0);
uint32_t grx = OTG->GRXSTSP;
uint32_t bcnt = (grx & USB_OTG_GRXSTSP_BCNT) >> USB_OTG_GRXSTSP_BCNT_Pos;
uint32_t xfer = bcnt > max_len ? max_len : bcnt, count = xfer;
while (count >= 4) {
uint32_t data = readl(fifo);
memcpy(dest, &data, 4);
count -= 4;
dest += 4;
}
if (count) {
uint32_t data = readl(fifo);
memcpy(dest, &data, count);
}
uint32_t extra = DIV_ROUND_UP(bcnt, 4) - DIV_ROUND_UP(xfer, 4);
while (extra--)
readl(fifo);
return xfer;
}
// Write a packet to a tx fifo
static void
fifo_write_packet(void *fifo, const uint8_t *src, uint32_t count)
{
while (count >= 4) {
uint32_t data;
memcpy(&data, src, 4);
writel(fifo, data);
count -= 4;
src += 4;
}
if (count) {
uint32_t data = 0;
memcpy(&data, src, count);
writel(fifo, data);
}
}
/****************************************************************
* USB interface
****************************************************************/
int_fast8_t
usb_read_bulk_out(void *data, uint_fast8_t max_len)
{
uint32_t grx = peek_rx_queue(USB_CDC_EP_BULK_OUT);
if (!grx) {
// Wait for packet
OTG->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM;
return -1;
}
return fifo_read_packet(data, max_len);
}
int_fast8_t
usb_send_bulk_in(void *data, uint_fast8_t len)
{
USB_OTG_INEndpointTypeDef *epi = EPIN(USB_CDC_EP_BULK_IN);
uint32_t len_d4 = DIV_ROUND_UP(len, 4);
uint32_t ctl = epi->DIEPCTL;
if (!(ctl & USB_OTG_DIEPCTL_USBAEP))
return -2;
if (ctl & USB_OTG_DIEPCTL_EPENA || len_d4 > epi->DTXFSTS) {
// Wait for space to transmit
OTGD->DIEPEMPMSK |= (1 << USB_CDC_EP_BULK_IN);
return -1;
}
epi->DIEPTSIZ = len | (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos);
epi->DIEPCTL |= USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK;
fifo_write_packet(EPFIFO(USB_CDC_EP_BULK_IN), data, len);
return len;
}
int_fast8_t
usb_read_ep0(void *data, uint_fast8_t max_len)
{
uint32_t grx = peek_rx_queue(0);
if (!grx) {
// Wait for packet
OTG->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM;
return -1;
}
uint32_t pktsts = ((grx & USB_OTG_GRXSTSP_PKTSTS_Msk)
>> USB_OTG_GRXSTSP_PKTSTS_Pos);
if (pktsts != 2)
// Transfer interrupted
return -2;
return fifo_read_packet(data, max_len);
}
int_fast8_t
usb_read_ep0_setup(void *data, uint_fast8_t max_len)
{
for (;;) {
uint32_t grx = peek_rx_queue(0);
if (!grx) {
// Wait for packet
OTG->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM;
return -1;
}
uint32_t pktsts = ((grx & USB_OTG_GRXSTSP_PKTSTS_Msk)
>> USB_OTG_GRXSTSP_PKTSTS_Pos);
if (pktsts == 6)
// Found a setup packet
break;
// Discard other packets
fifo_read_packet(NULL, 0);
}
uint32_t ctl = EPIN(0)->DIEPCTL;
if (ctl & USB_OTG_DIEPCTL_EPENA) {
// Flush any pending tx packets
EPIN(0)->DIEPCTL = ctl | USB_OTG_DIEPCTL_EPDIS | USB_OTG_DIEPCTL_SNAK;
while (EPIN(0)->DIEPCTL & USB_OTG_DIEPCTL_EPENA)
;
OTG->GRSTCTL = USB_OTG_GRSTCTL_TXFFLSH;
while (OTG->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH)
;
}
return fifo_read_packet(data, max_len);
}
int_fast8_t
usb_send_ep0(const void *data, uint_fast8_t len)
{
uint32_t grx = peek_rx_queue(0);
if (grx) {
// Transfer interrupted
return -2;
}
USB_OTG_INEndpointTypeDef *epi = EPIN(0);
uint32_t len_d4 = DIV_ROUND_UP(len, 4);
uint32_t ctl = epi->DIEPCTL;
if (ctl & USB_OTG_DIEPCTL_EPENA || len_d4 > epi->DTXFSTS) {
// Wait for space to transmit
OTGD->DIEPEMPMSK |= (1 << 0);
OTG->GINTMSK |= USB_OTG_GINTMSK_RXFLVLM;
return -1;
}
epi->DIEPTSIZ = len | (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos);
epi->DIEPCTL = ((ctl & ~USB_OTG_DIEPCTL_STALL) | USB_OTG_DIEPCTL_EPENA
| USB_OTG_DIEPCTL_CNAK);
fifo_write_packet(EPFIFO(0), data, len);
return len;
}
void
usb_stall_ep0(void)
{
EPIN(0)->DIEPCTL |= USB_OTG_DIEPCTL_STALL;
usb_notify_ep0(); // XXX - wake from main usb_cdc.c code?
}
void
usb_set_address(uint_fast8_t addr)
{
OTGD->DCFG |= addr << USB_OTG_DCFG_DAD_Pos;
usb_send_ep0(NULL, 0);
usb_notify_ep0();
}
void
usb_set_configure(void)
{
}
void
usb_request_bootloader(void)
{
}
/****************************************************************
* Setup and interrupts
****************************************************************/
DECL_CONSTANT_STR("RESERVE_PINS_USB", "PA11,PA12");
// Initialize the usb controller
void
usb_init(void)
{
// Enable USB clock
RCC->AHB2ENR |= RCC_AHB2ENR_OTGFSEN;
while (!(OTG->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL))
;
// Configure USB in full-speed device mode
OTG->GUSBCFG = (USB_OTG_GUSBCFG_FDMOD | USB_OTG_GUSBCFG_PHYSEL
| (6 << USB_OTG_GUSBCFG_TRDT_Pos));
OTGD->DCFG |= (3 << USB_OTG_DCFG_DSPD_Pos);
#if CONFIG_MACH_STM32F446
OTG->GOTGCTL = USB_OTG_GOTGCTL_BVALOEN | USB_OTG_GOTGCTL_BVALOVAL;
#else
OTG->GCCFG |= USB_OTG_GCCFG_NOVBUSSENS;
#endif
// Route pins
gpio_peripheral(GPIO('A', 11), GPIO_FUNCTION(10), 0);
gpio_peripheral(GPIO('A', 12), GPIO_FUNCTION(10), 0);
// Setup USB packet memory
fifo_configure();
// Enable interrupts
OTGD->DAINTMSK = (1 << 0) | (1 << USB_CDC_EP_BULK_IN);
OTG->GINTMSK = (USB_OTG_GINTMSK_USBRST | USB_OTG_GINTSTS_USBSUSP
| USB_OTG_GINTMSK_RXFLVLM | USB_OTG_GINTMSK_IEPINT);
OTG->GAHBCFG = USB_OTG_GAHBCFG_GINT;
NVIC_SetPriority(OTG_FS_IRQn, 1);
NVIC_EnableIRQ(OTG_FS_IRQn);
// Enable USB
OTG->GCCFG |= USB_OTG_GCCFG_PWRDWN;
OTGD->DCTL = 0;
}
DECL_INIT(usb_init);
// Configure interface after a USB reset event
static void
usb_reset(void)
{
// Flush Rx queue
OTG->GRSTCTL = USB_OTG_GRSTCTL_RXFFLSH;
while (OTG->GRSTCTL & USB_OTG_GRSTCTL_RXFFLSH)
;
// Flush Tx queues
OTG->GRSTCTL = (16 << USB_OTG_GRSTCTL_TXFNUM_Pos) | USB_OTG_GRSTCTL_TXFFLSH;
while (OTG->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH)
;
// Configure and enable endpoints
uint32_t mpsize_ep0 = 2;
USB_OTG_INEndpointTypeDef *epi = EPIN(0);
USB_OTG_OUTEndpointTypeDef *epo = EPOUT(0);
epi->DIEPCTL = mpsize_ep0 | USB_OTG_DIEPCTL_SNAK;
epo->DOEPTSIZ = (64 | (1 << USB_OTG_DOEPTSIZ_STUPCNT_Pos)
| (1 << USB_OTG_DOEPTSIZ_PKTCNT_Pos));
epo->DOEPCTL = mpsize_ep0 | USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK;
epi = EPIN(USB_CDC_EP_ACM);
epi->DIEPTSIZ = (USB_CDC_EP_ACM_SIZE
| (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos));
epi->DIEPCTL = (
USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP
| (0x03 << USB_OTG_DIEPCTL_EPTYP_Pos) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM
| (USB_CDC_EP_ACM << USB_OTG_DIEPCTL_TXFNUM_Pos)
| (USB_CDC_EP_ACM_SIZE << USB_OTG_DIEPCTL_MPSIZ_Pos));
epo = EPOUT(USB_CDC_EP_BULK_OUT);
epo->DOEPCTL = (
USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_USBAEP | USB_OTG_DOEPCTL_EPENA
| (0x02 << USB_OTG_DOEPCTL_EPTYP_Pos) | USB_OTG_DOEPCTL_SD0PID_SEVNFRM
| (USB_CDC_EP_BULK_OUT_SIZE << USB_OTG_DOEPCTL_MPSIZ_Pos));
epi = EPIN(USB_CDC_EP_BULK_IN);
epi->DIEPTSIZ = (USB_CDC_EP_BULK_IN_SIZE
| (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos));
epi->DIEPCTL = (
USB_OTG_DIEPCTL_SNAK | USB_OTG_DIEPCTL_USBAEP
| (0x02 << USB_OTG_DIEPCTL_EPTYP_Pos) | USB_OTG_DIEPCTL_SD0PID_SEVNFRM
| (USB_CDC_EP_BULK_IN << USB_OTG_DIEPCTL_TXFNUM_Pos)
| (USB_CDC_EP_BULK_IN_SIZE << USB_OTG_DIEPCTL_MPSIZ_Pos));
// Set address to zero
OTGD->DCFG &= ~USB_OTG_DCFG_DAD;
}
// Handle a USB disconnect
static void
usb_suspend(void)
{
EPIN(USB_CDC_EP_BULK_IN)->DIEPCTL &= ~USB_OTG_DIEPCTL_USBAEP;
}
// Main irq handler
void __visible
OTG_FS_IRQHandler(void)
{
uint32_t sts = OTG->GINTSTS;
if (sts & USB_OTG_GINTSTS_USBRST) {
OTG->GINTSTS = USB_OTG_GINTSTS_USBRST;
usb_reset();
}
if (sts & USB_OTG_GINTSTS_USBSUSP) {
OTG->GINTSTS = USB_OTG_GINTSTS_USBSUSP;
usb_suspend();
}
if (sts & USB_OTG_GINTSTS_RXFLVL) {
// Received data - disable irq and notify endpoint
OTG->GINTMSK &= ~USB_OTG_GINTMSK_RXFLVLM;
uint32_t grx = OTG->GRXSTSR, ep = grx & USB_OTG_GRXSTSP_EPNUM_Msk;
if (ep == 0)
usb_notify_ep0();
else
usb_notify_bulk_out();
}
if (sts & USB_OTG_GINTSTS_IEPINT) {
// Can transmit data - disable irq and notify endpoint
uint32_t daint = OTGD->DAINT;
OTGD->DIEPEMPMSK &= ~daint;
if (daint & (1 << 0))
usb_notify_ep0();
if (daint & (1 << USB_CDC_EP_BULK_IN))
usb_notify_bulk_in();
}
}