diff --git a/CMakeLists.txt b/CMakeLists.txt index a7774b1..f8f24c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,19 +34,24 @@ elseif(NOT FLATUSB_DESC_SRC) message("flatUSB: No source of descriptors passed! Please either set FLATUSB_DESC_DIR to the directory containing the descriptor files OR pass descriptor files directly by defining FLATUSB_DESC_SRC and FLATUSB_DESC_HEADER!") endif() +if (NOT FLATUSB_DRIVER_SRC) + message("flatUSB: No USB driver passed! It's likely an error and not intentional. Please populate FLATUSB_DRIVER_SRC with the list of the USB driver files!") +endif() + message("flatUSB classes selected: ${FLATUSB_CLASSES}") set(FLATUSB_SRC ${FLATUSB_CLASSES_SRC} ${FLATUSB_DESC_SRC} + ${FLATUSB_DRIVER_SRC} usb.c usb_callback_event.h usb_common.h usb_common_types.h usb_device_types.h - usb_driver.c - usb_driver.h + #usb_driver.c + #usb_driver.h usb.h # utils/gen_queue.c diff --git a/class/cdc.c b/class/cdc.c index 7f7e04e..261f984 100644 --- a/class/cdc.c +++ b/class/cdc.c @@ -5,7 +5,6 @@ #include "../usb_device_types.h" #include "../usb.h" -#include "flatUSB/usb_driver.h" #include @@ -56,6 +55,10 @@ void usb_cdc_init(const USB_CdcAssignments *as) { // static uint8_t replyBuf[sizeof(USB_Cdc_LineCodingStruct)]; +// static void usbcore_setup_cplt_cb(uint8_t in) { +// usbcore_wake_up_endpoint(cdcs.ep_assignments.data_ep, USB_IN); +// } + static void usb_cdc_review_comm_init() { // check if line coding was initialized USB_Cdc_LineCodingStruct *lc = &cdcs.line_coding; @@ -67,10 +70,10 @@ static void usb_cdc_review_comm_init() { // combine the above criteria cdcs.commInit = lcOk && clsOk; - // wake up endpoint if initialized - if (cdcs.commInit) { - usbcore_wake_up_endpoint(cdcs.ep_assignments.data_ep, USB_IN); - } + // // wake up endpoint if initialized + // if (cdcs.commInit) { + // usbcore_register_IN_callback(0, usbcore_setup_cplt_cb); + // } } int usb_cdc_process_and_return(USB_CallbackEvent *cbevt) { diff --git a/driver/stm32/usb_common_defs.h b/driver/stm32/common_defs.h similarity index 76% rename from driver/stm32/usb_common_defs.h rename to driver/stm32/common_defs.h index 29dc60b..8640ab8 100644 --- a/driver/stm32/usb_common_defs.h +++ b/driver/stm32/common_defs.h @@ -29,4 +29,19 @@ #define USBFIFO(ep) ((uint32_t *)(((uint32_t)(USBG)) + USB_OTG_FIFO_BASE + (USB_OTG_FIFO_SIZE) * (ep))) #define USBPCGCCTL ((uint32_t *)(((uint32_t)(USBG)) + USB_OTG_PCGCCTL_BASE)) +// Set TOCAL and TRDT values (see Ref. Man.) +#if defined(USB_STM32H7) /*|| defined(USB_HIGH_SPEED)*/ +#define TOCAL_VALUE (0x00) +#define TRDT_VALUE (0x05) +#elif defined(USB_STM32F4) +#if !defined(USB_HIGH_SPEED) +#define TOCAL_VALUE (0x07) +#define TRDT_VALUE (0x06) +#else +#define TOCAL_VALUE (0x07) +#define TRDT_VALUE (0x09) +#endif +#endif + + #endif /* CORE_USB_USB_COMMON_DEFS */ diff --git a/driver/stm32/usb_core_types.h b/driver/stm32/usb_core_types.h index d2b1409..e74de8e 100644 --- a/driver/stm32/usb_core_types.h +++ b/driver/stm32/usb_core_types.h @@ -1,16 +1,16 @@ #ifndef CORE_USB_USB_CORE_TYPES #define CORE_USB_USB_CORE_TYPES -#include "usb_common_types.h" +// #include "usb_common_types.h" -/* USB linespeed */ -typedef enum { USB_SPD_UNKNOWN = 0b00, USB_SPD_LOW = 0b01, USB_SPD_FULL = 0b11 } USB_Speed; +// /* USB linespeed */ +// typedef enum { USB_SPD_UNKNOWN = 0b00, USB_SPD_LOW = 0b01, USB_SPD_FULL = 0b11 } USB_Speed; -/* USB core state */ -typedef struct -{ - uint8_t linespeed; // USB linespeed -} USB_CoreState; +// /* USB core state */ +// typedef struct +// { +// uint8_t linespeed; // USB linespeed +// } USB_CoreState; #endif /* CORE_USB_USB_CORE_TYPES */ diff --git a/driver/stm32/usb_drv.c b/driver/stm32/usb_drv.c index 0ee7bdc..a39f4a4 100644 --- a/driver/stm32/usb_drv.c +++ b/driver/stm32/usb_drv.c @@ -1,11 +1,40 @@ #include "usb_drv.h" -#include "flatUSB_config.h" +#include +#include #include "stm32f4xx_hal.h" +#include "flatUSB_config.h" +#include "usb_driver_common.h" + +#include FLATUSB_DESCRIPTOR_HEADER + +#include "common_defs.h" #include "usb_common.h" -#include "usb_common_defs.h" + +// ------------------------ + +#define USB_RX_BUF_SIZE (512) + +static USBDRV_GlobalState gs; // global USB state +static uint8_t rx_buf[USB_RX_BUF_SIZE] DWORD_ALIGN; // receive buffer +static USBDRV_IN_cb cbs[USB_NUM_OF_ENDPOINTS]; // callbacks for IN completion + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +// ------------------------ + +#ifdef USBDBGMSG +static const char *FIFO_STATUS_STR[6] = { + "GLOBAL OUT NAK", + "OUT DATA RECV", + "OUT TRANSFER CPLT", + "OUT SETUP CPLT", + "", + "OUT SETUP RECV"}; +#endif // ------------------------ @@ -17,54 +46,90 @@ __weak void usbdrv_ulpi_init() { // ------------------------ +void usbdrv_register_IN_complete_cb(uint8_t ep, USBDRV_IN_cb cb) { + cbs[ep] = cb; +} + +// ------------------------ + +static USB_DrvIntf drvIntf; + +void usbdrv_init_intf() { + drvIntf.init = usbdrv_init; + drvIntf.reset = usbdrv_reset; + drvIntf.stall = usbdrv_stall_endpoint; + drvIntf.arm_IN = usbdrv_arm_IN_endpoint; + drvIntf.arm_OUT = usbdrv_arm_OUT_endpoint; + drvIntf.set_address = usbdrv_set_address; + drvIntf.set_config = usbdrv_fetch_endpoint_configuration; + drvIntf.autoarm = usbdrv_autoarm_OUT_endpoint; + drvIntf.en_ep_irq = usbdrv_enable_endpoint_interrupt; + drvIntf.reg_IN_cb = usbdrv_register_IN_complete_cb; +} + +USB_DrvIntf *usbdrv_get_intf() { + return &drvIntf; +} + +// ------------------------ + #if USB_INTERNAL && USB_UPLI -#error "Select just one from USB_INTERNAL and USB_ULPI" +#error "Select just one from USB_INTERNAL and USB_ULPI!" #endif +// issue a hardware reset +static void usbdrv_hw_reset() { + WAIT_FOR_nBIT_DELAY(USBG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL, 1); // wait for AHB transactions to cease + SET_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); // reset the USB core + WAIT_FOR_BIT_DELAY(USBG->GRSTCTL, USB_OTG_GRSTCTL_CSRST, 1); + // SET_BIT(USBG->GINTSTS, USB_OTG_GINTSTS_USBRST); +} + // initialize USB peripheral -void usbdrv_periph_init() { - // enable USB clock - USB_CLOCK_ENABLE(); +void usbdrv_periph_init(bool reset) { + if (!reset) { + // enable USB clock + USB_CLOCK_ENABLE(); - // select either the internal or the external ULPI transceiver + // select either the internal or the external ULPI transceiver #if USB_INTERNAL - SET_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_PHYSEL); // select the internal FS PHY + SET_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_PHYSEL); // select the internal FS PHY #endif #if USB_ULPI - CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_PHYSEL); // select the external HS PHY - CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_TSDPS); - CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_ULPIFSLS); + CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_PHYSEL); // select the external HS PHY + CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_TSDPS); + CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_ULPIFSLS); - CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_ULPIEVBUSD); - CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_ULPIEVBUSI); + CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_ULPIEVBUSD); + CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_ULPIEVBUSI); #endif - WAIT_FOR_nBIT_DELAY(USBG->GRSTCTL, USB_OTG_GRSTCTL_AHBIDL, 1); // wait for AHB transactions to cease - SET_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_CSRST); // reset the USB core - WAIT_FOR_BIT_DELAY(USBG->GRSTCTL, USB_OTG_GRSTCTL_CSRST, 1); + usbdrv_hw_reset(); // commence a hardware reset #if USB_ULPI -usbdrv_ulpi_init(); // initialize PHY + usbdrv_ulpi_init(); // initialize PHY #endif - CLEAR_BIT(USBG->GCCFG, USB_OTG_GCCFG_PWRDWN); // power the internal transceiver peripheral + CLEAR_BIT(USBG->GCCFG, USB_OTG_GCCFG_PWRDWN); // power the internal transceiver peripheral + } + + CLEAR_BIT(USBG->GAHBCFG, USB_OTG_GAHBCFG_GINT); // mask all interrupts for now - CLEAR_BIT(USBG->GAHBCFG, USB_OTG_GAHBCFG_GINT); // mask all interrupts for now CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_HNPCAP | USB_OTG_GUSBCFG_SRPCAP); // disable HNP and SRP WRITE_FIELD(USBG->GUSBCFG, USB_OTG_GUSBCFG_TRDT, TRDT_VALUE); // set TRDT according to the RM WRITE_FIELD(USBG->GUSBCFG, USB_OTG_GUSBCFG_TOCAL, TOCAL_VALUE); // set TOCAL - CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_FHMOD); // clear Host mode forcing - SET_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_FDMOD); // force Device mode + CLEAR_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_FHMOD); // clear Host mode forcing + SET_BIT(USBG->GUSBCFG, USB_OTG_GUSBCFG_FDMOD); // force Device mode - HAL_Delay(25); // wait for Device mode forcing propagation + USB_DELAY(25); // wait for Device mode forcing propagation WAIT_FOR_BIT(USBG->GINTSTS, 0b1); // SET_BIT(USBD->DCTL, USB_OTG_DCTL_SDIS); // soft disconnect peripheral (should be upper, but since it's controlled by a Device register, cannot be set before switching to device mode) #if defined(USB_STM32H7) #if !USB_VBUSSENSE - CLEAR_BIT(USBG->GCCFG, USB_OTG_GCCFG_VBDEN); // turn off VBUSSENSE + CLEAR_BIT(USBG->GCCFG, USB_OTG_GCCFG_VBDEN); // turn off VBUSSENSE #endif SET_BIT(USBG->GOTGCTL, USB_OTG_GOTGCTL_BVALOEN | USB_OTG_GOTGCTL_BVALOVAL); // force B-session #elif defined(USB_STM32F4) @@ -74,15 +139,17 @@ usbdrv_ulpi_init(); // initialize PHY CLEAR_BIT(USBG->GCCFG, USB_OTG_GCCFG_VBUSBSEN | USB_OTG_GCCFG_VBUSASEN); #endif - HAL_Delay(25); // it takes time to forcing Device mode takes effect + USB_DELAY(25); // it takes time to forcing Device mode takes effect + if (!reset) { #ifdef USB_HIGH_SPEED - // WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_FULL_SPEED); - // WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_HIGH_SPEED_ULPI); - WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_FULL_SPEED_ULPI); + // WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_FULL_SPEED); + WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_HIGH_SPEED_ULPI); + // WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_FULL_SPEED_ULPI); #else - WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_FULL_SPEED); // there's no other possible option + WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD, USB_LINESPEED_FULL_SPEED); // there's no other possible option #endif + } // allow specific interrupts uint32_t intmask = /*USB_OTG_GINTMSK_WUIM | // Wake up */ @@ -94,16 +161,11 @@ usbdrv_ulpi_init(); // initialize PHY USB_OTG_GINTMSK_RXFLVLM; // RX FIFO level (signal if non-empty) USBG->GINTMSK = intmask; - // set global NAK on all Endpoints - // usbdrv_set_global_NAK(USB_IN, true); - // usbdrv_set_global_NAK(USB_OUT, true); - - // flush Tx and Rx FIFOs - usbdrv_flush_rx_fifo(); - usbdrv_flush_tx_fifo(USB_FLUSH_TX_FIFO_ALL); - - // make Tx FIFO empty interrupt fire when Tx FIFO is emtpy - // SET_BIT(USBG->GAHBCFG, USB_OTG_GAHBCFG_TXFELVL); + if (!reset) { + // flush Tx and Rx FIFOs + usbdrv_flush_rx_fifo(); + usbdrv_flush_tx_fifo(USB_FLUSH_TX_FIFO_ALL); + } // set masks for endpoint interrupts SET_BIT(USBD->DIEPMSK, USB_OTG_DIEPMSK_XFRCM | USB_OTG_DIEPMSK_TOM | USB_OTG_DIEPMSK_ITTXFEMSK); // transfer complete, timeout and Tx FIFO empty while receiving IN for IN EPs @@ -117,16 +179,806 @@ usbdrv_ulpi_init(); // initialize PHY SET_BIT(USBG->GAHBCFG, USB_OTG_GAHBCFG_GINT); } +// initialize global state +void usbdrv_init_global_state() { + // clear state + memset(&gs, 0, sizeof(USBDRV_GlobalState)); + + // clear IN complete callbacks + memset(&cbs, 0, sizeof(USBDRV_IN_cb) * USB_NUM_OF_ENDPOINTS); + + // initialize receive buffer + gs.rx_buf = rx_buf; + gs.rx_buf_level = 0; +} + // initialize USB subsystem void usbdrv_init() { - HAL_NVIC_DisableIRQ(USB_IRQn); + USB_IRQ_DISABLE(USB_IRQ_N); // disable USB interrupts - //usbdrv_init_global_state(); + usbdrv_init_global_state(); + usbdrv_init_intf(); usbdrv_gpio_init(); - usbdrv_periph_init(); + usbdrv_periph_init(false); usbdrv_initial_ep0_setup(); usbdrv_power_and_connect(true); - HAL_NVIC_SetPriority(USB_IRQn, 8, 0); - HAL_NVIC_EnableIRQ(USB_IRQn); + USB_IRQ_SET_PRIORITY(USB_IRQ_N, USB_IRQ_PRIORITY); + USB_IRQ_ENABLE(USB_IRQ_N); +} + +void usbdrv_reset() { + USB_IRQ_DISABLE(USB_IRQ_N); // disable USB interrupts + + usbdrv_hw_reset(); // hardware reset + usbdrv_init_global_state(); + usbdrv_periph_init(true); + usbdrv_initial_ep0_setup(); + usbdrv_power_and_connect(true); + + USB_IRQ_SET_PRIORITY(USB_IRQ_N, USB_IRQ_PRIORITY); + USB_IRQ_ENABLE(USB_IRQ_N); +} + +// connect to or disconnect from the bus +void usbdrv_power_and_connect(bool en) { + if (en) { // ON + CLEAR_BIT(USBD->DCTL, USB_OTG_DCTL_SDIS); +#ifdef USB_INTERNAL + SET_BIT(USBG->GCCFG, USB_OTG_GCCFG_PWRDWN); // actually, this is power UP and affects only the built-in FS transciever +#endif + } else { // OFF + SET_BIT(USBD->DCTL, USB_OTG_DCTL_SDIS); +#ifdef USB_INTERNAL + CLEAR_BIT(USBG->GCCFG, USB_OTG_GCCFG_PWRDWN); +#endif + } +} + +// ------------------- + +// flush specific or all Tx FIFOs +void usbdrv_flush_tx_fifo(uint8_t n) { + WAIT_FOR_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); // wait for previous request to conclude + WRITE_FIELD(USBG->GRSTCTL, USB_OTG_GRSTCTL_TXFNUM, n); // issue flush + WAIT_FOR_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_TXFFLSH); // wait for our request to conclude +} + +// flush the Rx FIFO +void usbdrv_flush_rx_fifo() { + WAIT_FOR_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); + SET_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); // issue flush + WAIT_FOR_BIT(USBG->GRSTCTL, USB_OTG_GRSTCTL_RXFFLSH); +} + +// --------------------- + +// addresses of specific DIEPTXF registers, addresses from the RM +static uint32_t *USB_pDIEPTXF[USB_NUM_OF_ENDPOINTS] = { + // FS + (uint32_t *)(((uint32_t)USBG) + 0x028), // DIEPTXF0 + (uint32_t *)(((uint32_t)USBG) + 0x104), // DIEPTXF1 + (uint32_t *)(((uint32_t)USBG) + 0x108), // DIEPTXF2 + (uint32_t *)(((uint32_t)USBG) + 0x10C), // DIEPTXF3 + // HS + (uint32_t *)(((uint32_t)USBG) + 0x120), // DIEPTXF4 + (uint32_t *)(((uint32_t)USBG) + 0x124), // DIEPTXF5 +}; + +// configure USB endpoint +void usbdrv_configure_endpoint(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg) { + if (dir == USB_OUT) { // ---- OUT ---- + + if (ep == 0) { // SPECIAL handling on EP0 + WRITE_FIELD(USBOUTEP[0].DOEPCTL, USB_OTG_DOEPCTL_MPSIZ, 0); // fix in 64 bytes + WRITE_FIELD(USBOUTEP[0].DOEPTSIZ, USB_OTG_DOEPTSIZ_STUPCNT, 3); // SETUP transaction stands of three packets + } else { + WRITE_FIELD(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_EPTYP, cfg->type); // program endpoint type + WRITE_FIELD(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_MPSIZ, cfg->max_packet_size); // program maximum packet size + SET_BIT(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_USBAEP); // the endpoint is active in the current configuration + } + + // ---- common for all endpoints ---- + + // enable interrupt + SET_BIT(USBD->DAINTMSK, 1 << (USB_OTG_DAINTMSK_OEPM_Pos + ep)); + + // NAK processing + if (cfg->responding_NAK) { + SET_BIT(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_SNAK); // send NAK + } else { + usbdrv_arm_OUT_endpoint(ep, cfg->max_packet_size); + } + } else { // ---- IN ---- + + if (ep == 0) { // SPECIAL handling on EP0 + WRITE_FIELD(USBINEP[0].DIEPCTL, USB_OTG_DIEPCTL_MPSIZ, 0); // fix in 64 bytes + } else { + WRITE_FIELD(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_EPTYP, cfg->type); // program endpoint type + WRITE_FIELD(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_MPSIZ, cfg->max_packet_size); // program maximum packet size + SET_BIT(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_USBAEP); // the endpoint is active in the current configuration + } + + // ---- common for all endpoints ---- + + // program FIFO corresponding FIFO number + WRITE_FIELD(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_TXFNUM, ep); + + // store Tx FIFO size (both fields are WORD units, NOT bytes, RM is missing this information!) + uint32_t tx_fifo_config = ((cfg->fifo_size >> 2) << USB_OTG_DIEPTXF_INEPTXFD_Pos) | (cfg->fifo_address >> 2); // combine size in DWORDs and address + *(USB_pDIEPTXF[ep]) = tx_fifo_config; // save + + // NAK processing + if (cfg->responding_NAK) { + SET_BIT(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_SNAK); // send NAK + } else { + SET_BIT(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_CNAK); // clear sending NAK + } + } +} + +// deconfigure USB endpoint +void usbdrv_deconfigure_endpoint(uint8_t ep, uint8_t dir) { + if (ep == 0) { // EP0 cannot be deconfigured + return; + } + + if (dir == USB_OUT) { // ---- OUT ---- + CLEAR_BIT(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_USBAEP); // deactivate endpoint + CLEAR_BIT(USBD->DAINTMSK, 1 << (USB_OTG_DAINTMSK_OEPM_Pos + ep)); // disable interrupt + } else { // ---- IN ---- + CLEAR_BIT(USBD->DAINTMSK, 1 << ep); // disable interrupt + usbdrv_flush_tx_fifo(ep); // flush Tx FIFO + SET_BIT(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_USBAEP); // deactivate endpoint + } +} + +// --------------------- + +// stall endpoint +void usbdrv_stall_endpoint(uint8_t ep, uint8_t dir, bool stall) { + USB_OTG_INEndpointTypeDef *inep = USBINEP + ep; + // USB_OTG_OUTEndpointTypeDef *outep = USBOUTEP + ep; + + if (stall) { + if (dir == USB_IN) { + if (ep != 0) { // special treatment for EP0 + SET_BIT(inep->DIEPCTL, USB_OTG_DIEPCTL_EPDIS); // disable endpoint + } + SET_BIT(inep->DIEPCTL, USB_OTG_DIEPCTL_STALL); // stall endpoint + } + + if (ep != 0) { // EP0 cannot be disabled + // wait for endpoint disable to get effective + WAIT_FOR_nBIT(inep->DIEPINT, USB_OTG_DIEPINT_EPDISD); + CLEAR_BIT(inep->DIEPINT, USB_OTG_DIEPINT_EPDISD); + } + + // flush trnasmit FIFO + usbdrv_flush_tx_fifo(ep); + } else { + if (dir == USB_IN) { + if (ep != 0) { // special treatment for EP0 + SET_BIT(inep->DIEPCTL, USB_OTG_DIEPCTL_EPENA); // enable endpoint + } + CLEAR_BIT(inep->DIEPCTL, USB_OTG_DIEPCTL_STALL); // clear endpoint stall + } + } +} + +// set global NAK +void usbdrv_set_global_NAK(uint8_t dir, bool en) { + if (en) { + if (dir == USB_IN) { + SET_BIT(USBD->DCTL, USB_OTG_DCTL_SGINAK); + } else { + SET_BIT(USBD->DCTL, USB_OTG_DCTL_SGONAK); + } + } else { + if (dir == USB_IN) { + SET_BIT(USBD->DCTL, USB_OTG_DCTL_CGINAK); + } else { + SET_BIT(USBD->DCTL, USB_OTG_DCTL_CGONAK); + } + } +} + +// --------------------- + +// fetch received data from RX FIFO to receive buffer +void usbdrv_fetch_received_data(uint8_t ep, uint16_t len) { + if (len > 0) { + volatile uint32_t *p = USBFIFO(ep); + uint16_t len_dwords = CEILDIV4(len); + for (uint16_t i = 0; i < len_dwords; i++) { + uint32_t dword = p[i]; + uint16_t i0 = i * 4; + gs.rx_buf[i0] = dword & 0xFF; + gs.rx_buf[i0 + 1] = (dword >> 8) & 0xFF; + gs.rx_buf[i0 + 2] = (dword >> 16) & 0xFF; + gs.rx_buf[i0 + 3] = (dword >> 24) & 0xFF; + } + } + gs.rx_buf_level = len; +} + +// ---------------- + +// write data to specific endpoint FIFO +uint32_t usbdrv_arm_IN_endpoint(uint8_t ep, const uint8_t *data, uint16_t len) { + // determine if a transmission is in progress or not + bool txp = READ_BIT(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_EPENA); + + uint16_t mps = gs.ep_IN[ep].max_packet_size; // fetch maximum packet size + + // determine final write size + uint32_t freeSize = USBINEP[ep].DTXFSTS * sizeof(uint32_t); // get free transmit buffer size + uint16_t writeSize = 0; + + if (txp) { // transaction is in progress + uint32_t remainingBytes = USBINEP[ep].DIEPTSIZ & USB_OTG_DIEPTSIZ_XFRSIZ; // acquire remaining bytes + if (len >= mps) { // write only full-sized packets, or a short packet + uint16_t mws = MIN(freeSize, len); // maximum possible write size + uint16_t fpc = mws / mps; // full packet count + writeSize = fpc * mps; // form write size + } else { // length is less then a full packet size + if (len <= freeSize) { // packet can be written + writeSize = len; + } else { + writeSize = 0; + } + } + // adjust write size to remaining size + writeSize = MIN(remainingBytes, writeSize); + } else { // no transaction is in progress + writeSize = MIN(freeSize, len); + } + + // if no transmission is in progress + if (!txp) { + // calculate packet count based on max packet size + uint16_t packet_count = 1; // for ZLPs + if (len > 0) { // if length is nonzero + packet_count = len / mps + (((len % mps) > 0) ? 1 : 0); // a transfer may contain multiple packets + } + + // set zlp_next if transfer size is integer multiple of max packet size and auto ZLP is on + gs.ep_IN[ep].zlp_next = (len > 0) && ((len % mps) == 0); + + // program DIEPTSIZ with transfer length + USBINEP[ep].DIEPTSIZ = /*(packet_count << USB_OTG_DIEPTSIZ_MULCNT_Pos) |*/ (packet_count << USB_OTG_DIEPTSIZ_PKTCNT_Pos) | len; + + // enable endpoint and cancel responding NAK + SET_BIT(USBINEP[ep].DIEPCTL, USB_OTG_DIEPCTL_EPENA | USB_OTG_DIEPCTL_CNAK); + + // enable endpoint interrupts + SET_BIT(USBD->DAINTMSK, 1 << ep); + } + + // turn on interrupt generation only, if this is NOT the last FIFO write considering the current transfer + if (len > writeSize) { + USBD->DIEPEMPMSK |= ((uint32_t)(1 << ep)); + } + + // disable ALL USB interrupts to prevent access to specific registers (see errata) + CLEAR_BIT(USBG->GAHBCFG, USB_OTG_GAHBCFG_GINT); + + // WAIT_FOR_nBIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_TXFE); + + // https://github.com/iliasam/STM32F4_USB_MICROPHONE/blob/master/Libraries/STM32_USB_OTG_Driver/src/usb_dcd_int.c#L655 + // https://github.com/iliasam/STM32F4_USB_MICROPHONE/blob/master/Libraries/STM32_USB_OTG_Driver/src/usb_core.c#L168 + + // push full dwords + volatile uint32_t *p = (uint32_t *)USBFIFO(ep); + uint32_t floorlen_dwords = writeSize >> 2; + for (uint16_t i = 0; i < floorlen_dwords; i++) { + uint16_t i0 = 4 * i; + uint32_t dword = data[i0] | (data[i0 + 1] << 8) | (data[i0 + 2] << 16) | (data[i0 + 3] << 24); + *p = dword; + } + + // push the remaining partial dword + uint8_t rem_bytes = writeSize & 0b11; + if (rem_bytes > 0) { + uint32_t rem_dword = 0; + uint16_t rem_start = writeSize - rem_bytes; + for (int16_t i = writeSize - 1; i >= rem_start; i--) { + rem_dword = (rem_dword << 8) | data[i]; + } + *p = rem_dword; + } + + // unmask USB interrupts + SET_BIT(USBG->GAHBCFG, USB_OTG_GAHBCFG_GINT); + + // return with written size + return writeSize; +} + +// arm OUT endpoint +uint32_t usbdrv_arm_OUT_endpoint(uint8_t ep, uint16_t size) { + // arm endpoint only if it was not armed before + if (READ_BIT(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_EPENA)) { + return 0; + } + + // cap size at max packet size defined for this endpoint + size = MIN(gs.ep_OUT[ep].max_packet_size, size); + + // write registers + uint32_t doeptsiz = USBOUTEP[ep].DOEPTSIZ; + doeptsiz &= ~(USB_OTG_DOEPTSIZ_XFRSIZ); // clear XFERSIZ bits + doeptsiz |= USB_OTG_DOEPTSIZ_PKTCNT | size; // program DIEPTSIZ with maximum (expected) transfer length and set PCKTCNT to make ready for reception + USBOUTEP[ep].DOEPTSIZ = doeptsiz; // write value to the actual register + + SET_BIT(USBOUTEP[ep].DOEPCTL, USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK); // enable endpoint and clear NAK + + // return with armed size + return size; +} + +void usbdrv_autoarm_OUT_endpoint(uint8_t ep) { + gs.ep_OUT[ep].autoarm = true; +} + +void usbdrv_enable_endpoint_interrupt(uint8_t ep, uint8_t dir, bool en) { + uint32_t mask = 1 << (ep + ((dir == USB_OUT) ? USB_OTG_DAINTMSK_OEPM_Pos : 0)); + if (en) { + SET_BIT(USBD->DAINTMSK, mask); + } else { + CLEAR_BIT(USBD->DAINTMSK, mask); + } +} + +// --------------------- + +// set Rx FIFO size +void usbdrv_set_rx_fifo_size(uint16_t size) { + USBG->GRXFSIZ = CEILDIV4(size); +} + +// ---------------- + +// preload usb endpoint config +void usbdrv_preload_endpoint_config(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg) { + USBMSG("PRELOAD: %u %s\n", ep, dir ? "IN" : "OUT"); + if (dir == USB_OUT) { + gs.ep_OUT[ep] = *cfg; + gs.ep_OUT[ep].is_configured = true; + } else { + gs.ep_IN[ep] = *cfg; + gs.ep_IN[ep].is_configured = true; + } +} + +// clear endpoint config +void usbdrv_clear_endpoint_config() { + memset(&gs.ep_OUT, 0, USB_NUM_OF_ENDPOINTS * sizeof(USBDRV_EpConfig)); + memset(&gs.ep_IN, 0, USB_NUM_OF_ENDPOINTS * sizeof(USBDRV_EpConfig)); +} + +// apply preloaded endpoint configuration +void usbdrv_apply_endpoint_config() { + for (uint8_t i = 0; i < USB_NUM_OF_ENDPOINTS; i++) { + // OUT EPs + if (gs.ep_OUT[i].is_configured) { + usbdrv_configure_endpoint(i, USB_OUT, gs.ep_OUT + i); + } + + // IN EPs + if (gs.ep_IN[i].is_configured) { + usbdrv_configure_endpoint(i, USB_IN, gs.ep_IN + i); + } + } +} + +// fetch endpoint configuration from descriptor dump +void usbdrv_fetch_endpoint_configuration(uint8_t config_index) { + const uint8_t *fullConfDesc = (const uint8_t *)confDescs[config_index]; // point an array to the beginning of the full configuration + const USB_ConfigurationDesc *confDesc = (const USB_ConfigurationDesc *)confDescs[config_index]; // fetch the leading configuration descriptor + + // look up endpoint descriptors + const uint8_t *iter = fullConfDesc; + while (iter < (fullConfDesc + confDesc->wTotalLength)) { + if (iter[1] == UD_Endpoint) { // Endpoint descriptor found + USB_EndpointDesc epDesc; + memcpy(&epDesc, iter, iter[0]); // fetch EP descriptor by copy, since desciptor start address is NOT aligned + USBDRV_EpConfig cfg; // fill-in configuration + cfg.max_packet_size = epDesc.wMaxPacketSize; + cfg.responding_NAK = false; + cfg.type = epDesc.bmAttributes & 0b11; + cfg.service_interval = epDesc.bInterval; + + // fetch endpoint address + uint8_t dir = (epDesc.bEndpointAddress >> 7); + uint8_t n = epDesc.bEndpointAddress & 0x7F; + + // apply configuration + usbdrv_preload_endpoint_config(n, dir, &cfg); + } + + // advance iterator using the bLength field (first byte in a descriptor) + iter += iter[0]; + } + + usbdrv_build_fifo(); + + usbdrv_apply_endpoint_config(); +} + +#define USB_MIN_FIFO_SIZE (64) +#define USB_FIFO_MARGIN (8) +#define USB_RX_FIFO_SETUP_RESERVATION_DWORDS (10) +#define USB_MIN_GROSS_TX_FIFO_SIZE (2 * USB_MIN_EP_FIFO_SIZE) + +#if defined(USB_STM32F4) +#if USB_INTERNAL +#define USB_MIN_GROSS_RX_FIFO_SIZE (2 * USB_MIN_EP_FIFO_SIZE + USB_RX_FIFO_SETUP_RESERVATION_DWORDS * 4) +#else +#define USB_MIN_GROSS_RX_FIFO_SIZE (1024) +#endif +#elif defined(USB_STM32H7) +#define USB_MIN_GROSS_RX_FIFO_SIZE (256) +#endif + +#define USBDRV_ADDR_TABLE_STR_LEN (255) +static char usbdrv_addr_table[USBDRV_ADDR_TABLE_STR_LEN + 1]; + +// build FIFO (compute addresses) +void usbdrv_build_fifo() { + // ---- OUT ---- + uint16_t next_fifo_addr = 0x00; // Rx FIFO begins at address zero + uint16_t largest_packet_size = 0; // largest packet size + uint16_t control_ep_count = 0; // number of control endpoints + uint16_t out_ep_count = 0; // count of OUT pipes + for (uint8_t i = 0; i < USB_NUM_OF_ENDPOINTS; i++) { // gather config information + // look for largest packet size + if (gs.ep_OUT[i].is_configured) { // examine OUT EPs + largest_packet_size = MAX(largest_packet_size, gs.ep_OUT[i].max_packet_size); + out_ep_count++; + } + if (gs.ep_IN[i].is_configured) { // examine IN EPs + largest_packet_size = MAX(largest_packet_size, gs.ep_IN[i].max_packet_size); + } + + // count control endpoints + if (((gs.ep_OUT[i].is_configured) && (gs.ep_OUT[i].type == UT_Control)) || + ((gs.ep_IN[i].is_configured) && (gs.ep_IN[i].type == UT_Control))) { + control_ep_count++; + } + } + + // RX FIFO size calculation expression from the RM + uint16_t fifo_size_dwords = (5 * control_ep_count + 8) + (CEILDIV4(largest_packet_size) + 1) + (2 * out_ep_count) + 1; // calculate RX FIFO size in DWORDS + uint16_t fifo_size = fifo_size_dwords * 4; // calculate RX FIFO size in bytes + fifo_size = CEIL4(MAX(fifo_size, USB_MIN_GROSS_RX_FIFO_SIZE)); // RX FIFO should be at least this large + + // fifo_size *= 2; // TODO: + gs.rx_fifo_size = fifo_size; // save Rx FIFO size for later + next_fifo_addr += fifo_size; // advance next FIFO address + usbdrv_set_rx_fifo_size(fifo_size); // set Rx FIFO size in hardware + + uint32_t str_offset = SNPRINTF(usbdrv_addr_table, USBDRV_ADDR_TABLE_STR_LEN, "RX: 000-%03x (%u)\r\n", fifo_size - 1, fifo_size); + + // ---- IN ---- + for (uint8_t i = 0; i < USB_NUM_OF_ENDPOINTS; i++) { + USBDRV_EpConfig *cfg = &gs.ep_IN[i]; + if (cfg->is_configured) { + cfg->fifo_size = CEIL4(MAX(USB_MIN_GROSS_TX_FIFO_SIZE, cfg->max_packet_size + 64)); // correct FIFO size if necessary + cfg->fifo_address = next_fifo_addr; // store FIFO address + cfg->zlp_next = false; + + str_offset += SNPRINTF(usbdrv_addr_table + str_offset, USBDRV_ADDR_TABLE_STR_LEN - str_offset, "TX%u: %03x-%03x (%u)\r\n", i, next_fifo_addr, next_fifo_addr + cfg->fifo_size - 1, cfg->fifo_size); + + // cfg->txp = false; // no transfer is in progress + next_fifo_addr += cfg->fifo_size; // advance next address + } + } +} + +const char *usbdrv_get_fifo_addr_table() { + return usbdrv_addr_table; +} + +// --------------------- + +// create an initial setup for EP0 in both directions +void usbdrv_initial_ep0_setup() { + // setup EP0 OUT and IN + USBDRV_EpConfig ep_cfg; + ep_cfg.max_packet_size = 64; + ep_cfg.responding_NAK = false; + usbdrv_preload_endpoint_config(0, USB_OUT, &ep_cfg); + usbdrv_preload_endpoint_config(0, USB_IN, &ep_cfg); + + // build FIFO + usbdrv_build_fifo(); + + // configure endpoints + usbdrv_apply_endpoint_config(); + + // turn off global NAK + usbdrv_set_global_NAK(USB_IN, false); + usbdrv_set_global_NAK(USB_OUT, false); +} + +// --------------------- + +void usbdrv_set_address(uint8_t addr) { + gs.address = addr; + WRITE_FIELD(USBD->DCFG, USB_OTG_DCFG_DAD, gs.address); +} + +// get endpoint interrupt flag +bool usbdrv_get_endpoint_interrupt_flag(uint8_t ep, uint8_t dir) { + return (USBD->DAINT & (1 << (ep + ((dir == USB_OUT) ? USB_OTG_DAINT_OEPINT_Pos : 0)))) != 0; +} + +// --------------------- + +// receive packet +void usbdrv_process_rx_fifo_top(USBDRV_EventData *evt_data) { + uint32_t rxstat = USBG->GRXSTSP; // POP (not just read) latest FIFO status word + + uint8_t pckt_status = READ_FIELD(rxstat, USB_OTG_GRXSTSP_PKTSTS); // read packet status + uint8_t data_pid = READ_FIELD(rxstat, USB_OTG_GRXSTSP_DPID); // read data PID + uint8_t byte_count = READ_FIELD(rxstat, USB_OTG_GRXSTSP_BCNT); // byte count + uint8_t ep_num = READ_FIELD(rxstat, USB_OTG_GRXSTSP_EPNUM); // read endpoint number + + // copy to output structure + evt_data->rx.pckt_status = pckt_status; + evt_data->rx.data_pid = data_pid; + evt_data->rx.byte_count = byte_count; + evt_data->rx.ep_num = ep_num; + + USBMSG("%s [%u] %u\n", FIFO_STATUS_STR[pckt_status - 1], ep_num, byte_count); +} + +// always pass ALIGNED data! +__weak void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stage) { + return; +} + +__weak void usbcore_process_nonsetup_event(USBDRV_CallbackCompound *cbcpd) { + return; +} + +// process USB event +void usbdrv_process_event(uint8_t evt_code, USBDRV_EventData *evt_data) { + if (evt_code == USB_EVT_USB_RESET) { // reset takes precedence over anything else TODO + usbdrv_reset(); + + // invoke Reset Done notification + if (drvIntf.rst_notify != NULL) { + drvIntf.rst_notify(); + } + + USBMSG("RESET\n"); + return; + } + + switch (gs.state) { + case USB_FSM_INITIAL_WAIT_SPEEDNEG: // wait for speed negotitation to conclude + if (evt_code == USB_EVT_SPEEDNEG_DONE) { + gs.state = USB_FSM_SETUP_OPERATE; // wait for speed negotiation + } + + // invoke Enumeration Done notification + if (drvIntf.enum_notify != NULL) { + uint8_t spd; + uint32_t dspd = READ_FIELD(USBD->DCFG, USB_OTG_DCFG_DSPD); + switch (dspd) { + case USB_LINESPEED_FULL_SPEED_ULPI: + case USB_LINESPEED_FULL_SPEED: + spd = USB_SPD_FULL; + break; + case USB_LINESPEED_HIGH_SPEED_ULPI: + spd = USB_SPD_HIGH; + break; + default: + spd = USB_SPD_UKWN; + break; + } + drvIntf.enum_notify(spd); + } + + break; + case USB_FSM_SETUP_OPERATE: { // expect SETUP transactions first, then everything else as well + switch (evt_code) { + case USB_EVT_RECEPTION_DONE: { // reception done + USBDRV_EventData evt_data = {0}; + usbdrv_process_rx_fifo_top(&evt_data); // process rx fifo top + + // fetch data if data are available + if ((evt_data.rx.pckt_status == USB_PCKT_STATUS_SETUP_DATA_RECV) || + (evt_data.rx.pckt_status == USB_PCKT_STATUS_OUT_DATA_RECV)) { + usbdrv_fetch_received_data(evt_data.rx.ep_num, evt_data.rx.byte_count); // fetch the data + } + + // act according to what we have received + if (evt_data.rx.ep_num == 0) { // EP0 special treatment + int stage = -1; + if (evt_data.rx.pckt_status == USB_PCKT_STATUS_SETUP_CPLT) { + stage = UST_SETUP; + USBMSG("--SETUP\n"); + } else if (evt_data.rx.pckt_status == USB_PCKT_STATUS_OUT_TRANSFER_CPLT) { + stage = UST_DATA; + + USBMSG("--DATA\n"); + } + + // process setup packet + if (stage != -1) { + usbcore_process_setup_pckt(gs.rx_buf, gs.rx_buf_level, stage); + } + + // SET_BIT(USBG->GINTMSK, USB_OTG_GINTMSK_RXFLVLM); // unmask interrupt + } else { // not EP0 + if (evt_data.rx.pckt_status == USB_PCKT_STATUS_OUT_DATA_RECV) { + USBDRV_CallbackCompound cbcpd; + cbcpd.ep = evt_data.rx.ep_num; + cbcpd.dir = USB_OUT; + cbcpd.code = USB_CBC_OUT; + cbcpd.data = gs.rx_buf; + cbcpd.size = gs.rx_buf_level; + usbcore_process_nonsetup_event(&cbcpd); + } + } + break; + } + + case USB_EVT_OUT_DONE: { // some OUT operations have finished + for (uint8_t ep = 0; ep < USB_NUM_OF_ENDPOINTS; ep++) { + if (gs.ep_OUT[ep].is_configured) { // if the endpoint is running + if (READ_BIT(USBOUTEP[ep].DOEPINT, USB_OTG_DOEPINT_STUP)) { // setup done + SET_BIT(USBOUTEP[ep].DOEPINT, USB_OTG_DOEPINT_STUP); + USBMSG("SETUP\n"); + } + + if (READ_BIT(USBOUTEP[ep].DOEPINT, USB_OTG_DOEPINT_XFRC)) { // OUT transaction done + SET_BIT(USBOUTEP[ep].DOEPINT, USB_OTG_DOEPINT_XFRC); + USBMSG("OUT\n"); + + if ((ep == 0) || (gs.ep_OUT[ep].autoarm)) { // EP0 must always be armed + usbdrv_arm_OUT_endpoint(ep, gs.ep_OUT[ep].max_packet_size); // arm endpoint + gs.ep_OUT[ep].autoarm = false; // clear autoarm flag + } + } + } + } + break; + } + + case USB_EVT_IN_DONE: { // some IN operations have finished + + // callback compound + USBDRV_CallbackCompound cbcpd; + cbcpd.dir = USB_IN; + cbcpd.data = NULL; + cbcpd.size = 0; + + for (uint8_t ep = 0; ep < USB_NUM_OF_ENDPOINTS; ep++) { + cbcpd.ep = ep; + bool ep_on = USBD->DAINTMSK & (1 << ep); // decide if this endpoint is currently enable for transmission based on its endpoint mask + if (gs.ep_IN[ep].is_configured && ep_on) { // if the endpoint is running + if (READ_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_TOC)) { // timeout done + SET_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_TOC); + USBMSG("TO\n"); + } else if ((USBD->DIEPEMPMSK & (1 << ep)) && READ_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_TXFE)) { + // disable FIFO empty interrupt + USBD->DIEPEMPMSK &= ~((uint32_t)(1 << ep)); + + cbcpd.code = USB_CBC_IN_FIFOEMPTY; + usbcore_process_nonsetup_event(&cbcpd); + } else if (READ_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_XFRC)) { // IN transaction done + SET_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_XFRC); + + // disable FIFO empty interrupt + USBD->DIEPEMPMSK &= ~((uint32_t)(1 << ep)); + + // transfer finished + // gs.ep_IN[ep].txp = false; + + // see if a ZLP transmission was queued + if (gs.ep_IN[ep].zlp_next) { + usbdrv_arm_IN_endpoint(ep, NULL, 0); // send ZLP + } else { // no ZLP + USBMSG("IN [%d]\n", ep); + + // invoke callback if registered + if (cbs[ep] != NULL) { + USBDRV_IN_cb cb = cbs[ep]; // fetch function pointer + cbs[ep] = NULL; // clear callback + cb(ep); // invoke the callback + } + + cbcpd.code = USB_CBC_IN_DONE; + usbcore_process_nonsetup_event(&cbcpd); + } + + } else if (READ_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_ITTXFE)) { // IN endpoint IN token received with Tx FIFO empty interrupt + SET_BIT(USBINEP[ep].DIEPINT, USB_OTG_DIEPINT_ITTXFE); + + // disable endpoint interrupt + usbdrv_enable_endpoint_interrupt(ep, USB_IN, false); + + cbcpd.code = USB_CBC_IN_FIFOEMPTY; + usbcore_process_nonsetup_event(&cbcpd); + } + } + } + } + + default: + break; + } + } + + default: + break; + } +} + +// --------------------- + +#define PROCESS_EVENT(evt, data) usbdrv_process_event((evt), (data)) + +void USB_IRQ_HANDLER() { + + uint32_t ints = USBG->GINTSTS; + + // USB reset + if (ints & USB_OTG_GINTSTS_USBRST) { + SET_BIT(USBG->GINTSTS, USB_OTG_GINTSTS_USBRST); // clear interrupt + PROCESS_EVENT(USB_EVT_USB_RESET, NULL); // process event + } + + // End of enumeration (meaning NOT the USB ENUMERATION PROCESS, + // ST calls speed negotiation the enumeration, normally this + // interrupt fires only before even communication is commenced) + if (ints & USB_OTG_GINTSTS_ENUMDNE) { + SET_BIT(USBG->GINTSTS, USB_OTG_GINTSTS_ENUMDNE); // clear interrupt + PROCESS_EVENT(USB_EVT_SPEEDNEG_DONE, NULL); // process event + // usbdrv_process_event(USB_EVT_SPEEDNEG_DONE, NULL); // process event + // usbdrv_push_event(USB_EVT_SPEEDNEG_DONE, NULL); // push event + } + + // Start of Frame received (like Keep-Alive in LS mode) + if (ints & USB_OTG_GINTSTS_SOF) { + SET_BIT(USBG->GINTSTS, USB_OTG_GINTSTS_SOF); // clear interrupt + } + + // USB Suspend + if (ints & USB_OTG_GINTSTS_USBSUSP) { + SET_BIT(USBG->GINTSTS, USB_OTG_GINTSTS_USBSUSP); // clear interrupt + USBMSG("SUSPEND\n"); + } + + // OUT endpoint interrupt + if (ints & USB_OTG_GINTSTS_OEPINT) { + PROCESS_EVENT(USB_EVT_OUT_DONE, NULL); + // usbdrv_push_event(USB_EVT_OUT_DONE, NULL); + // if (USBD->DAINT & (1 << 16)) { + // if (USBOUTEP[0].DOEPINT & USB_OTG_DOEPINT_STUP) { + // CLEAR_BIT(USBOUTEP[0].DOEPINT, USB_OTG_DOEPINT_STUP); + // } + // } + } + + // RX FIFO non-empty interrupt + if (ints & USB_OTG_GINTSTS_RXFLVL) { + // SET_BIT(USBG->GINTSTS, USB_OTG_GINTSTS_RXFLVL); // clear interrupt + USBMSG("RX DONE\n"); + // CLEAR_BIT(USBG->GINTMSK, USB_OTG_GINTMSK_RXFLVLM); // mask interrupt until processing is done + PROCESS_EVENT(USB_EVT_RECEPTION_DONE, NULL); // process event + // usbdrv_push_event(USB_EVT_RECEPTION_DONE, NULL); // push event + } + + // IN endpoint interrupt + if (ints & USB_OTG_GINTSTS_IEPINT) { + PROCESS_EVENT(USB_EVT_IN_DONE, NULL); + // usbdrv_push_event(USB_EVT_IN_DONE, NULL); + } + + return; } \ No newline at end of file diff --git a/driver/stm32/usb_drv.h b/driver/stm32/usb_drv.h index 495c188..cd00b18 100644 --- a/driver/stm32/usb_drv.h +++ b/driver/stm32/usb_drv.h @@ -1,35 +1,309 @@ #ifndef STM32_USB_DRV #define STM32_USB_DRV -#include "usb_common_defs.h" +#include "common_defs.h" -// Select IO crossbar advanced function -#if defined(USB_STM32H7) -#ifndef STM32H723xx -#define USB_GPIO_AF (GPIO_AF10_OTG2_FS) -#else -// #define USB_GPIO_AF (GPIO_AF10_OTG1_HS) -#endif -#elif defined(USB_STM32F4) -#ifdef USB_HIGH_SPEED -#define USB_GPIO_AF (GPIO_AF10_OTG_HS) -#else -#define USB_GPIO_AF (GPIO_AF10_OTG_FS) -#endif +#include "../../usb_common_types.h" + +#include "../../usb_driver_common.h" + +// number of supported endpoints +#define USB_NUM_OF_ENDPOINTS (6) // set it to the maximum that this type of module can support + +// non isochronous transfers +#define USB_MAX_FS_PCKT_SIZE_NON_ISOCHRONOUS (64) +#define USB_MAX_HS_PCKT_SIZE_NON_ISOCHRONOUS (512) + +// isochronous transfers +#define USB_MAX_FS_PCKT_SIZE_ISOCHRONOUS (1023) + +#define USB_MIN_EP_FIFO_SIZE (64) + +// Unfortunately this cannot be enabled, since the USB controller +// generates such enormous amounts of interrupts, so that no other +// task can run along the USB processing. +#define USB_EVENT_PROCESSING_IN_OS_THREAD (0) + +#if USB_EVENT_PROCESSING_IN_OS_THREAD +#include #endif -// Set TOCAL and TRDT values (see Ref. Man.) -#if defined(USB_STM32H7) /*|| defined(USB_HIGH_SPEED)*/ -#define TOCAL_VALUE (0x00) -#define TRDT_VALUE (0x05) -#elif defined(USB_STM32F4) -#if !defined(USB_HIGH_SPEED) -#define TOCAL_VALUE (0x07) -#define TRDT_VALUE (0x06) -#else -#define TOCAL_VALUE (0x07) -#define TRDT_VALUE (0x09) -#endif +// endpoint configuration structure +typedef struct { + uint16_t max_packet_size; // maximum packet size + bool8_t responding_NAK; // indicates if endpoint responds with NAK + uint8_t type; // endpoint type + uint8_t service_interval; // service interval + + // calculated values + + uint8_t is_configured; // the endpoint is in use + uint16_t fifo_address; // address in the FIFO + uint16_t fifo_size; // FIFO size + bool8_t zlp_next; // indicates, that ZLP should be transmitted at the end of the current transfer (for IN endpoints) + bool8_t autoarm; // automatically arm endpoint (for OUT endpoints) +} USBDRV_EpConfig; + +typedef enum { + USB_FSM_INITIAL_WAIT_SPEEDNEG = 0, // initial state, right after reset, wait for speed negotiation, must be ZERO! + USB_FSM_SETUP_OPERATE, // ready to perform setup operations +} USBDRV_FsmState; + +typedef enum { + USB_EVT_USB_RESET, // bus reset received + USB_EVT_SPEEDNEG_DONE, // linespeed negotiation (ST calls "enumeration") done + USB_EVT_RECEPTION_DONE, // received packet is waiting to be fetched from the Rx FIFO + USB_EVT_OUT_DONE, // something has happened on an OUT endpoint + USB_EVT_IN_DONE, // previously armed IN endpoint has finished packet transmission +} USBDRV_EventCode; + +// endpoint event +typedef enum { + USB_EPEVT_IDLE = 0x00, // endpoint is IDLE + USB_EPEVT_ARMED, // endpoint is armed for transmission + USB_EPEVT_STALLED, // endpoint is stalled + USB_EPEVT_NAK // endpoint responds NAK regardless of FIFO level +} USBDRV_EndpointEvent; + +// USB device state +typedef struct { + USBDRV_EpConfig ep_OUT[USB_NUM_OF_ENDPOINTS]; // OUT endpoint configs + USBDRV_EpConfig ep_IN[USB_NUM_OF_ENDPOINTS]; // IN endpoint configs + uint16_t rx_fifo_size; // Rx FIFO size in bytes + uint16_t state; // FSM state + uint8_t *rx_buf; // pointer to the receive buffer (this way declaration can be separated) + uint16_t rx_buf_level; // fill level of the rx buffer + uint8_t address; // device address +#if USB_EVENT_PROCESSING_IN_OS_THREAD + osMessageQueueId_t event_queue; // event queue + osThreadId_t th; // event processing thread #endif +} USBDRV_GlobalState; + +// USB received packet status +typedef enum { USB_PCKT_STATUS_GLOBAL_OUT_NAK = 0b0001, + USB_PCKT_STATUS_OUT_DATA_RECV = 0b0010, + USB_PCKT_STATUS_OUT_TRANSFER_CPLT = 0b0011, + USB_PCKT_STATUS_SETUP_CPLT = 0b0100, + USB_PCKT_STATUS_SETUP_DATA_RECV = 0b0110, +} USBDRV_PcktStatus; + +#define USB_FLUSH_TX_FIFO_ALL (0b10000) + +// event data +typedef union { + struct { + uint8_t pckt_status; // read packet status + uint8_t data_pid; // data PID + uint8_t byte_count; // byte count + uint8_t ep_num; // read endpoint number + } rx; // receive event data +} USBDRV_EventData; + +// event compound for queueing +typedef struct { + uint32_t code; // event code + USBDRV_EventData data; // accompaining event data +} USBDRV_EventCompound; + +// ------------ + +/** + * Fully initialize USB driver. + */ +void usbdrv_init(); + +/** + * Reset USB driver. + */ +void usbdrv_reset(); + +/** + * Initialize driver interface. + */ +void usbdrv_init_intf(); + +/** + * USB peripheral initialization. + * + * @param reset only perform an after-reset reinitialization + */ +void usbdrv_periph_init(bool reset); + +/** + * Flush specific TX FIFO. + * + * @param n index of the TX FIFO + */ +void usbdrv_flush_tx_fifo(uint8_t n); + +/** + * Flush specific RX FIFO. + */ +void usbdrv_flush_rx_fifo(); + +/** + * Power down or up the USB peripheral. Also control built-in transciever. + * + * @param en power on/off + */ +void usbdrv_power_and_connect(bool en); + +/** + * Configure USB Endpoint. + * + * @param ep index of the endpoint + * @param dir direction of the endpoint to be configured + * @param cfg pointer to the configuration details + */ +void usbdrv_configure_endpoint(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg); + +/** + * Deconfigure a specific USB Endpoint. + * + * @param ep index of the endpoint + * @param dir direction of the endpoint to be deconfigured + */ +void usbdrv_deconfigure_endpoint(uint8_t ep, uint8_t dir); + +/** + * Stall a particular USB Endpoint. + * + * @param ep index of the endpoint to be stalled + * @param dir direction of the endpoint + * @param stall enable/disable stalling + */ +void usbdrv_stall_endpoint(uint8_t ep, uint8_t dir, bool stall); + +/** + * Implement or rescind responding NAK globally in the chosen direction. + * + * @param dir communication direction + * @param en enable/disable NAK responses + */ +void usbdrv_set_global_NAK(uint8_t dir, bool en); + +/** + * Fetch data corresponding to a single Endpoint. + * + * @param ep index of endpoint + * @param len maximum length of data to be fetched + */ +void usbdrv_fetch_received_data(uint8_t ep, uint16_t len); + +/** + * Setup and prepare Endpoint to transfer data towards the host. + * May be called multiple times in a single transmission, since + * large messages do not have to fit into a single buffer. + * + * @param ep index of the Endpoint + * @param data pointer to the data + * @param len length of the data to be read + * @return number of bytes read from the data buffer + */ +uint32_t usbdrv_arm_IN_endpoint(uint8_t ep, const uint8_t *data, uint16_t len); + +/** + * Prepare Endpoint to expect data reception from the host. + * + * @param ep index of the Endpoint + * @param size expected reception length + * @return message length that the endpoint can support during next reception + */ +uint32_t usbdrv_arm_OUT_endpoint(uint8_t ep, uint16_t size); + +/** + * Enable or disable OUT Endpoint auto arming. + * Right after a transaction completes the USB core + * automatically prepares the endpoint for the next + * reception if this feature is enabled. + * + * @param ep index of the Endpoint + */ +void usbdrv_autoarm_OUT_endpoint(uint8_t ep); + +/** + * Mask or unmask Endpoint interrupt generation. + * + * @param ep index of the Endpoint + * @param dir direction of the Endpoint + * @param en enable/disable interrupt + */ +void usbdrv_enable_endpoint_interrupt(uint8_t ep, uint8_t dir, bool en); + + + +/** + * Set the shared FIFO size for all receptions. + * (Shared among all OUT endpoints.) + * Size of multiple of 4 are recommended. Values + * not obeying this will be rounded up. + * + * @param size FIFO size in BYTES + */ +void usbdrv_set_rx_fifo_size(uint16_t size); + +/** + * Preload Endpoint config. Do not write configuration + * to the hardware right away, just buffer them in. + * + * @param ep index of the Endpoint + * @param dir direction of the Endpint + * @param cfg pointer to Endpoint configuration + */ +void usbdrv_preload_endpoint_config(uint8_t ep, uint8_t dir, const USBDRV_EpConfig *cfg); + +/** + * Clear ALL Endpoints' preload configurations. + * Does NOT clear configuration in the hardware! + */ +void usbdrv_clear_endpoint_config(); + + +/** + * Apply preloaded Endpoint configuration, write + * Endpoint config to the hardware. + */ +void usbdrv_apply_endpoint_config(); + +/** + * Select a specific configuration from the descriptor + * dump, preload Endpoint settings and finally apply them. + * + * @param config_index configuration index + */ +void usbdrv_fetch_endpoint_configuration(uint8_t config_index); + +/** + * Construct FIFO considering configured Endpoints. + */ +void usbdrv_build_fifo(); + +/** + * Apply an initial EP0 setup. + */ +void usbdrv_initial_ep0_setup(); + +/** + * Set USB device address. + * + * @param addr device address + */ +void usbdrv_set_address(uint8_t addr); + +/** + * Get driver interface. + * + * @return pointer to the driver interface + */ +USB_DrvIntf * usbdrv_get_intf(); + +/** + * Register callback for IN transaction complete. + * + * @param ep index of the Endpoint + * @param cb pointer to the callback function + */ +void usbdrv_register_IN_complete_cb(uint8_t ep, USBDRV_IN_cb cb); #endif /* STM32_USB_DRV */ diff --git a/usb.c b/usb.c index ab65bb6..2bb88ea 100644 --- a/usb.c +++ b/usb.c @@ -2,35 +2,24 @@ #include +#include "usb_callback_event.h" #include "usb_common.h" +#include "usb_driver_common.h" // #include "utils/gen_queue.h" #include FLATUSB_DESCRIPTOR_HEADER -#include "usb_driver.h" - // --------------- #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#ifdef USBDBGMSG -#include "../cli/stdio_uart.h" -#define USBMSG(...) MSG(__VA_ARGS__) -#else -#define USBMSG(...) -#endif - // --------------- -#ifndef USB_HIGH_SPEED -#define USB_RX_BUF_SIZE (USB_MAX_FS_PCKT_SIZE_NON_ISOCHRONOUS) -#else -#define USB_RX_BUF_SIZE (USB_MAX_HS_PCKT_SIZE_NON_ISOCHRONOUS) -#endif - +#define USBDRV_ARM_IN_ZLP(ep) drv->arm_IN((ep), NULL, 0) +#define USB_RX_BUF_SIZE (512) // FIXME static uint8_t tx_assembly_buf[USB_RX_BUF_SIZE] DWORD_ALIGN; // buffer for assembling packets // --------------- @@ -67,12 +56,29 @@ typedef struct { } USB_SetupTransferState; static USB_SetupTransferState stups; +static USB_DrvIntf *drv; -void usbcore_init() { +void usbcore_reset() { // init state memset(&stups, 0, sizeof(USB_SetupTransferState)); } +static void usbcore_rst_notify() { + usbcore_reset(); +} + +static void usbcore_enum_notify(uint8_t spd) { + (void)spd; +} + +void usbcore_init(USB_DrvIntf *drvIntf) { + usbcore_reset(); // reset USB Core + + drv = drvIntf; // store USB driver interface assignments + drv->rst_notify = usbcore_rst_notify; // assign Reset Done callback + drv->enum_notify = usbcore_enum_notify; // assign Enumeration Done callback +} + void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stage) { // MSG("STUP: %u, %s\n", size, stage == UST_SETUP ? "SETUP" : "DATA"); @@ -112,7 +118,7 @@ void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stag } // expect status transaction to follow the data phase - usbdrv_arm_OUT_endpoint(0, 64); + drv->arm_OUT(0, 64); // only continue processing if the transaction has concluded if (!stups.out_complete) { @@ -136,7 +142,7 @@ void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stag DETERMINE_TRANSFER_SIZE(devDesc.bLength); SET_TRANSMISSION_POINTER(&devDesc); break; - case UD_Configuration: // CONFIGURATION DESCRIPTOR + case UD_Configuration: // CONFIGURATION DESCRIPTOR case UD_OtherSpeedConfiguration: // OTHER SPEED CONFIGURATION DESCRIPTOR DETERMINE_TRANSFER_SIZE(confDescs[desc_index]->wTotalLength); SET_TRANSMISSION_POINTER(confDescs[desc_index]); @@ -155,26 +161,26 @@ void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stag break; // Descriptors not implemented default: - usbdrv_stall_endpoint(0, USB_IN, true); // stall IN, since request in unsupported + drv->stall(0, USB_IN, true); // stall IN, since request in unsupported break; } // arm data transmission - usbdrv_arm_IN_endpoint(0, data, sz); + drv->arm_IN(0, data, sz); break; } case UREQ_SetAddress: { // SET ADDRESS uint8_t addr = stups.setup_req.wValue & 0x7F; - usbdrv_set_address(addr); - usbdrv_arm_OUT_endpoint(0, 64); // prepare for data OUT stage - USBDRV_ARM_IN_ZLP(0); // ZLP IN + drv->set_address(addr); + drv->arm_OUT(0, 64); // prepare for data OUT stage + USBDRV_ARM_IN_ZLP(0); // ZLP IN break; } case UREQ_SetConfiguration: // SET CONFIGURATION { uint8_t config_id = stups.setup_req.wValue & 0xFF; - usbdrv_fetch_endpoint_configuration(config_id - 1); + drv->set_config(config_id - 1); USBDRV_ARM_IN_ZLP(0); break; } @@ -196,7 +202,7 @@ void usbcore_process_setup_pckt(const uint8_t *data, uint16_t size, uint8_t stag usb_event_callback(&cbevt); if (cbevt.reply_valid) { - usbdrv_arm_IN_endpoint(0, cbevt.reply_data, cbevt.reply_size); + drv->arm_IN(0, cbevt.reply_data, cbevt.reply_size); } break; } @@ -241,21 +247,25 @@ void usbcore_process_nonsetup_event(USBDRV_CallbackCompound *cbcpd) { // for OUT events, check the autoarm flag if (cbevt.dir == USB_OUT) { if (cbevt.arm_out_endpoint) { - usbdrv_autoarm_OUT_endpoint(cbcpd->ep); + drv->autoarm(cbcpd->ep); } } } } void usbcore_wake_up_endpoint(uint8_t ep, uint8_t dir) { - usbdrv_enable_endpoint_interrupt(ep, dir, true); + drv->en_ep_irq(ep, dir, true); +} + +void usbcore_register_IN_callback(uint8_t ep, USBDRV_IN_cb cb) { + drv->reg_IN_cb(ep, cb); } uint32_t usbcore_schedule_transmission(uint8_t ep, const uint8_t *data, uint16_t size) { - usbdrv_enable_endpoint_interrupt(ep, USB_IN, true); - return usbdrv_arm_IN_endpoint(ep, data, size); + drv->en_ep_irq(ep, USB_IN, true); + return drv->arm_IN(ep, data, size); } uint32_t usbcore_schedule_reception(uint8_t ep, uint16_t size) { - return usbdrv_arm_OUT_endpoint(ep, size); + return drv->arm_OUT(ep, size); } \ No newline at end of file diff --git a/usb.h b/usb.h index ab439b1..0097fcb 100644 --- a/usb.h +++ b/usb.h @@ -1,11 +1,14 @@ #ifndef CORE_USB_USB #define CORE_USB_USB -#include "usb_callback_event.h" +#include -void usbcore_init(); // initialize USB core +#include "usb_driver_common.h" + +void usbcore_init(USB_DrvIntf *drvIntf); // initialize USB core uint32_t usbcore_schedule_transmission(uint8_t ep, const uint8_t *data, uint16_t size); // write data to endpoint, return with number of bytes actually written uint32_t usbcore_schedule_reception(uint8_t ep, uint16_t size); // expect data coming from the endpoint void usbcore_wake_up_endpoint(uint8_t ep, uint8_t dir); // wake up endpoint +void usbcore_register_IN_callback(uint8_t ep, USBDRV_IN_cb cb); // register IN complete callback #endif /* CORE_USB_USB */ diff --git a/usb_common.h b/usb_common.h index 08c4573..917ce7a 100644 --- a/usb_common.h +++ b/usb_common.h @@ -4,12 +4,14 @@ #include "usb_common_types.h" #include "usb_device_types.h" +#define USB_DELAY(t) for (uint64_t i = 0; i < (t) * 10000; i++) { asm("nop"); } + #define READ_FIELD(r,f) (((r) & (f##_Msk)) >> (f##_Pos)) #define WRITE_FIELD(r,f,v) ((r) = ((r) & ~(f##_Msk)) | (v << (f##_Pos))) #define WAIT_FOR_BIT(r,b) while ((r) & (b)) {} -#define WAIT_FOR_BIT_DELAY(r,b,d) while ((r) & (b)) { HAL_Delay((d)); } +#define WAIT_FOR_BIT_DELAY(r,b,d) while ((r) & (b)) { USB_DELAY((d)); } #define WAIT_FOR_nBIT(r,b) while (!((r) & (b))) {} -#define WAIT_FOR_nBIT_DELAY(r,b,d) while (!((r) & (b))) { HAL_Delay((d)); } +#define WAIT_FOR_nBIT_DELAY(r,b,d) while (!((r) & (b))) { USB_DELAY((d)); } #define DWORD_ALIGN __attribute__((aligned(4))) diff --git a/usb_driver_common.h b/usb_driver_common.h new file mode 100644 index 0000000..3cad4a4 --- /dev/null +++ b/usb_driver_common.h @@ -0,0 +1,57 @@ +#ifndef FLATUSB_USB_DRIVER_INTERFACE +#define FLATUSB_USB_DRIVER_INTERFACE + +#include +#include + +typedef enum { + USB_SPD_UKWN = 0, + USB_SPD_LOW = 1, + USB_SPD_FULL = 2, + USB_SPD_HIGH = 3 +} USBDRV_LineSpeed; + +typedef void (*USBDRV_IN_cb)(uint8_t ep); + +typedef struct { + // USB CORE -> USB DRIVER + void (*init)(); // initialize USB driver + void (*reset)(); // reset USB driver + void (*stall)(uint8_t ep, uint8_t dir, bool stall); // stall the endpoint + uint32_t (*arm_IN)(uint8_t ep, const uint8_t *data, uint16_t len); // arm IN endpoint + uint32_t (*arm_OUT)(uint8_t ep, uint16_t len); // arm OUT endpoint + void (*set_address)(uint8_t addr); // set device address + void (*set_config)(uint8_t ci); // make the chosen setting operational + void (*autoarm)(uint8_t ep); // enable OUT autoarm + void (*en_ep_irq)(uint8_t ep, uint8_t dir, bool en); // enable endpoint interrupt + void (*reg_IN_cb)(uint8_t ep, USBDRV_IN_cb cb); // register a callback for IN transaction complete + + // USB DRIVER -> USB CORE + void (*rst_notify)(); // notify USB core of a bus issued reset + void (*enum_notify)(uint8_t spd); // notify the USB core that speed negotiation has concluded +} USB_DrvIntf; + +#ifndef USBDBGMSG +#define USBMSG(...) +#endif + +// callback codes +typedef enum { + USB_CBC_OUT, // OUT done! + USB_CBC_IN_DONE, // IN done! + USB_CBC_IN_FIFOEMPTY, // could not serve IN request, since Tx FIFO was empty +} USBDRV_CallbackCode; + +// callback compound +typedef struct { + uint8_t ep : 4; // endpoint number + uint8_t dir : 1; // direction + uint8_t code : 3; // event code + uint16_t size; // data size + const uint8_t *data; // data +} USBDRV_CallbackCompound; + +typedef enum { UST_SETUP, + UST_DATA } USBDRV_ControlTfStage; + +#endif /* FLATUSB_USB_DRIVER_INTERFACE */