diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index a3cb877f18..a8c17b0202 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -166,6 +166,9 @@ config HAVE_STM32_USBFS config HAVE_STM32_USBOTG bool default y if MACH_STM32F2 || MACH_STM32F4 || MACH_STM32F7 || MACH_STM32H7 +config STM32_USB_DOUBLE_BUFFER_TX + bool + default y config HAVE_STM32_CANBUS bool default y if MACH_STM32F1 || MACH_STM32F2 || MACH_STM32F4x5 || MACH_STM32F446 || MACH_STM32F0x2 diff --git a/src/stm32/usbfs.c b/src/stm32/usbfs.c index a2fc6dd5a6..b1d4058cef 100644 --- a/src/stm32/usbfs.c +++ b/src/stm32/usbfs.c @@ -1,6 +1,6 @@ // Hardware interface to "fullspeed USB controller" // -// Copyright (C) 2018-2023 Kevin O'Connor +// Copyright (C) 2018-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -19,29 +19,27 @@ // Transfer memory is accessed with 32bits, but contains only 16bits of data typedef volatile uint32_t epmword_t; #define WSIZE 2 - #define USBx_IRQn USB_LP_IRQn -#elif CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32L4 +#elif CONFIG_MACH_STM32F0 || CONFIG_MACH_STM32L4 || CONFIG_MACH_STM32G4 // Transfer memory is accessed with 16bits and contains 16bits of data typedef volatile uint16_t epmword_t; #define WSIZE 2 - #define USBx_IRQn USB_IRQn -#elif CONFIG_MACH_STM32G4 - // Transfer memory is accessed with 16bits and contains 16bits of data - typedef volatile uint16_t epmword_t; - #define WSIZE 2 - #define USBx_IRQn USB_LP_IRQn #elif CONFIG_MACH_STM32G0 // Transfer memory is accessed with 32bits and contains 32bits of data typedef volatile uint32_t epmword_t; #define WSIZE 4 +#endif + +// Different chips have different names for the USB irq +#if CONFIG_MACH_STM32F1 || CONFIG_MACH_STM32G4 + #define USBx_IRQn USB_LP_IRQn +#elif CONFIG_MACH_STM32G0B1 + #define USBx_IRQn USB_UCPD1_2_IRQn +#else #define USBx_IRQn USB_IRQn #endif // The stm32g0 has slightly different register names #if CONFIG_MACH_STM32G0 - #if CONFIG_MACH_STM32G0B1 - #define USB_IRQn USB_UCPD1_2_IRQn - #endif #define USB USB_DRD_FS #define USB_PMAADDR USB_DRD_PMAADDR #define USB_EPADDR_FIELD USB_CHEP_ADDR @@ -55,8 +53,8 @@ // Some chip variants do not define these fields #ifndef USB_EP_DTOG_TX_Pos -#define USB_EP_DTOG_TX_Pos 6 -#define USB_EP_DTOG_RX_Pos 14 + #define USB_EP_DTOG_TX_Pos 6 + #define USB_EP_DTOG_RX_Pos 14 #endif @@ -244,8 +242,9 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) static uint32_t bulk_in_push_pos, bulk_in_pop_flag; #define BI_START 2 -int_fast8_t -usb_send_bulk_in(void *data, uint_fast8_t len) +// Send bulk packet to host with double buffering optimization +static int_fast8_t +usb_send_bulk_in_double_buffer(void *data, uint_fast8_t len) { if (readl(&bulk_in_pop_flag)) // No buffer space available @@ -278,6 +277,21 @@ usb_send_bulk_in(void *data, uint_fast8_t len) return len; } +// Send bulk usb packet to host +int_fast8_t +usb_send_bulk_in(void *data, uint_fast8_t len) +{ + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) + return usb_send_bulk_in_double_buffer(data, len); + uint32_t ep = USB_CDC_EP_BULK_IN, epr = USB_EPR[ep]; + if ((epr & USB_EPTX_STAT) != USB_EP_TX_NAK) + // No buffer space available + return -1; + btable_write_packet(ep, BUFTX, data, len); + USB_EPR[ep] = calc_epr_bits(epr, USB_EPTX_STAT, USB_EP_TX_VALID); + return len; +} + int_fast8_t usb_read_ep0(void *data, uint_fast8_t max_len) { @@ -336,9 +350,10 @@ usb_set_configure(void) bulk_out_pop_count = 0; USB_EPR[ep] = calc_epr_bits(USB_EPR[ep], USB_EPRX_STAT, USB_EP_RX_VALID); - ep = USB_CDC_EP_BULK_IN; - bulk_in_push_pos = BI_START; - writel(&bulk_in_pop_flag, 0); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + bulk_in_push_pos = BI_START; + writel(&bulk_in_pop_flag, 0); + } } @@ -363,9 +378,12 @@ usb_reset(void) bulk_out_push_flag = USB_EP_DTOG_TX; ep = USB_CDC_EP_BULK_IN; - USB_EPR[ep] = (USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_KIND - | USB_EP_TX_NAK); - bulk_in_pop_flag = USB_EP_DTOG_RX; + uint32_t bi_epr_flags = USB_CDC_EP_BULK_IN | USB_EP_BULK | USB_EP_TX_NAK; + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + bi_epr_flags |= USB_EP_KIND; + bulk_in_pop_flag = USB_EP_DTOG_RX; + } + USB_EPR[ep] = bi_epr_flags; USB->CNTR = USB_CNTR_CTRM | USB_CNTR_RESETM; USB->DADDR = USB_DADDR_EF; @@ -385,9 +403,12 @@ USB_IRQHandler(void) bulk_out_push_flag = 0; usb_notify_bulk_out(); } else if (ep == USB_CDC_EP_BULK_IN) { - USB_EPR[ep] = (calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0) - | bulk_in_pop_flag); - bulk_in_pop_flag = 0; + uint32_t ne = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) { + ne |= bulk_in_pop_flag; + bulk_in_pop_flag = 0; + } + USB_EPR[ep] = ne; usb_notify_bulk_in(); } else if (ep == 0) { USB_EPR[ep] = calc_epr_bits(epr, USB_EP_CTR_RX | USB_EP_CTR_TX, 0); diff --git a/src/stm32/usbotg.c b/src/stm32/usbotg.c index b2d52456d5..6e30836960 100644 --- a/src/stm32/usbotg.c +++ b/src/stm32/usbotg.c @@ -1,6 +1,6 @@ // Hardware interface to "USB OTG (on the go) controller" on stm32 // -// Copyright (C) 2019 Kevin O'Connor +// Copyright (C) 2019-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -119,6 +119,24 @@ fifo_write_packet(uint32_t ep, const uint8_t *src, uint32_t len) return len; } +// Write a packet to a tx fifo (optimized for already aligned data) +static int +fifo_write_packet_fast(uint32_t ep, const uint32_t *src, uint32_t len) +{ + void *fifo = EPFIFO(ep); + USB_OTG_INEndpointTypeDef *epi = EPIN(ep); + uint32_t ctl = epi->DIEPCTL; + if (ctl & USB_OTG_DIEPCTL_EPENA) + return -1; + epi->DIEPINT = USB_OTG_DIEPINT_XFRC; + epi->DIEPTSIZ = len | (1 << USB_OTG_DIEPTSIZ_PKTCNT_Pos); + epi->DIEPCTL = ctl | USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK; + uint32_t i; + for (i=0; i < DIV_ROUND_UP(len, sizeof(uint32_t)); i++) + writel(fifo, src[i]); + return 0; +} + // Read a packet from the rx queue static int_fast8_t fifo_read_packet(uint8_t *dest, uint_fast8_t max_len) @@ -208,6 +226,12 @@ usb_read_bulk_out(void *data, uint_fast8_t max_len) return ret; } +// Storage for "bulk in" transmissions for a kind of manual "double buffering" +static struct { + uint32_t len; + uint32_t buf[USB_CDC_EP_BULK_IN_SIZE / sizeof(uint32_t)]; +} TX_BUF; + int_fast8_t usb_send_bulk_in(void *data, uint_fast8_t len) { @@ -218,11 +242,23 @@ usb_send_bulk_in(void *data, uint_fast8_t len) usb_irq_enable(); return len; } - if (ctl & USB_OTG_DIEPCTL_EPENA) { - // Wait for space to transmit + int dbuf_busy = CONFIG_STM32_USB_DOUBLE_BUFFER_TX && TX_BUF.len; + if (ctl & USB_OTG_DIEPCTL_EPENA || dbuf_busy) { + if (!CONFIG_STM32_USB_DOUBLE_BUFFER_TX || TX_BUF.len || !len) { + // Wait for space to transmit + OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN; + usb_irq_enable(); + return -1; + } + // Buffer next packet for transmission from irq handler + len = len > USB_CDC_EP_BULK_IN_SIZE ? USB_CDC_EP_BULK_IN_SIZE : len; + uint32_t blocks = DIV_ROUND_UP(len, sizeof(uint32_t)); + TX_BUF.buf[blocks-1] = 0; + memcpy(TX_BUF.buf, data, len); + TX_BUF.len = len; OTGD->DAINTMSK |= 1 << USB_CDC_EP_BULK_IN; usb_irq_enable(); - return -1; + return len; } int_fast8_t ret = fifo_write_packet(USB_CDC_EP_BULK_IN, data, len); usb_irq_enable(); @@ -373,6 +409,8 @@ usb_set_configure(void) | USB_OTG_GRSTCTL_TXFFLSH); while (OTG->GRSTCTL & USB_OTG_GRSTCTL_TXFFLSH) ; + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX) + TX_BUF.len = 0; usb_irq_enable(); } @@ -401,8 +439,15 @@ OTG_FS_IRQHandler(void) OTGD->DAINTMSK = msk & ~daint; if (pend & (1 << 0)) usb_notify_ep0(); - if (pend & (1 << USB_CDC_EP_BULK_IN)) + if (pend & (1 << USB_CDC_EP_BULK_IN)) { usb_notify_bulk_in(); + if (CONFIG_STM32_USB_DOUBLE_BUFFER_TX && TX_BUF.len) { + int ret = fifo_write_packet_fast(USB_CDC_EP_BULK_IN + , TX_BUF.buf, TX_BUF.len); + if (!ret) + TX_BUF.len = 0; + } + } } }