diff options
| author | HiFiPhile <[email protected]> | 2025-03-24 21:09:17 +0100 |
|---|---|---|
| committer | HiFiPhile <[email protected]> | 2025-03-24 21:09:17 +0100 |
| commit | 3caff80c8ec5ba06be799d00dd1ff8ac9b58a5bc (patch) | |
| tree | be64d4af522196ee43b80510131e0b7ac9c0723a /src/portable | |
| parent | abfbcf5ccca88017dfb50e237a4981e47057b3a8 (diff) | |
| parent | cf76af1056e9a3019d9b08503b783a86602374f0 (diff) | |
Merge remote-tracking branch 'upstream/master' into h7rs
Signed-off-by: HiFiPhile <[email protected]>
Diffstat (limited to 'src/portable')
| -rw-r--r-- | src/portable/microchip/pic/README.md | 51 | ||||
| -rw-r--r-- | src/portable/microchip/pic/dcd_pic.c | 117 | ||||
| -rw-r--r-- | src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c | 5 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/dcd_rp2040.c | 57 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/hcd_rp2040.c | 46 | ||||
| -rw-r--r-- | src/portable/raspberrypi/rp2040/rp2040_usb.c | 2 | ||||
| -rw-r--r-- | src/portable/synopsys/dwc2/hcd_dwc2.c | 2 | ||||
| -rw-r--r-- | src/portable/wch/dcd_ch32_usbfs.c | 3 |
8 files changed, 218 insertions, 65 deletions
diff --git a/src/portable/microchip/pic/README.md b/src/portable/microchip/pic/README.md new file mode 100644 index 000000000..7ba6a4a96 --- /dev/null +++ b/src/portable/microchip/pic/README.md @@ -0,0 +1,51 @@ +# Microchip PIC Chipidea FS Driver + +This driver adds support for Microchip PIC microcontrollers with full-speed Chipidea USB peripheral to the TinyUSB stack. It supports the following families: + +- PIC32MX (untested) +- PIC32MM +- PIC32MK (untested) +- PIC24FJ +- PIC24EP (untested) +- dsPIC33EP (untested) + +Currently only the device mode is supported. + + +## Important Notes + +### Handling of shared VBUS & GPIO pin + +Some PICs have the USB VBUS pin bonded with a GPIO pin in the chip package. This driver does **NOT** handle the potential conflict between the VBUS and GPIO functionalities. + +Developers must ensure that the GPIO pin is tristated when the VBUS pin is managed by the USB peripheral in order to prevent damaging the chip. + +This design choice allows developers the flexibility to use the GPIO functionality for controlling VBUS in device mode if desired. + + +## TODO + +### Handle USB remote wakeup timing correctly + +The Chipidea FS IP doesn't handle the RESUME signal automatically and it must be managed in software. It needs to be asserted for exactly 10ms, and this is impossible to do without per-device support due to BSP differences. For now, a simple for-based loop is used. + +### 8-bit PIC support + +The 8-bit PICs also uses the Chipidea FS IP. Technically it's possible to support them as well. + +Possible difficulties: +- Memory size constraints (1KB/8KB ballpark) +- A third BDT layout (now we have two) +- Different compiler-specific directives +- Compiler bugs if you use SDCC + + +## Author +[ReimuNotMoe](https://github.com/ReimuNotMoe) at SudoMaker, Ltd. + + +## Credits + +This driver is based on: +- Microchip's USB driver (usb_device.c) +- TinyUSB's NXP KHCI driver (dcd_khci.c) diff --git a/src/portable/microchip/pic/dcd_pic.c b/src/portable/microchip/pic/dcd_pic.c index d40c4794a..b4a698199 100644 --- a/src/portable/microchip/pic/dcd_pic.c +++ b/src/portable/microchip/pic/dcd_pic.c @@ -1,8 +1,11 @@ /* * The MIT License (MIT) * - * Copyright (c) 2020 Koji Kitayama - * Copyright (c) 2022 Reimu NotMoe <[email protected]> + * Copyright (c) 2022-2024 SudoMaker, Ltd. + * Author: Mike Yang (Reimu NotMoe) <[email protected]> + * + * Based on usb_device.c - Copyright (c) 2015 Microchip Technology Inc. + * Based on dcd_khci.c - Copyright (c) 2020 Koji Kitayama * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -313,12 +316,23 @@ static void prepare_next_setup_packet(uint8_t rhport) { const unsigned out_odd = _dcd.endpoint[0][0].odd; const unsigned in_odd = _dcd.endpoint[0][1].odd; - TU_ASSERT(0 == _dcd.bdt[0][0][out_odd].own, ); + + // Abandon any previous control transfers that might have been using EP0. + // Ordinarily, nothing actually needs abandoning, since the previous control + // transfer would have completed successfully prior to the host sending the + // next SETUP packet. However, in a timeout error case, or after an EP0 + // STALL event, one or more UOWN bits might still be set. If so, we should + // clear the UOWN bits, so the EP0 IN/OUT endpoints are in a known inactive + // state, ready for re-arming by the `dcd_edpt_xfer' function that will be + // called next. _dcd.bdt[0][0][out_odd].data = 0; + _dcd.bdt[0][0][out_odd].own = 0; _dcd.bdt[0][0][out_odd ^ 1].data = 1; _dcd.bdt[0][1][in_odd].data = 1; + _dcd.bdt[0][1][in_odd].own = 0; _dcd.bdt[0][1][in_odd ^ 1].data = 0; + _dcd.bdt[0][1][in_odd ^ 1].own = 0; dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_OUT), _dcd.setup_packet, sizeof(_dcd.setup_packet)); } @@ -475,13 +489,12 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { intr_disable(rhport); intr_clear(rhport); -#if CFG_TUSB_MCU == OPT_MCU_PIC32MM - TRISBbits.TRISB6 = 1; -#endif - tu_memclr(&_dcd, sizeof(_dcd)); #if TU_PIC_INT_SIZE == 4 + // The USBBUSY bit is present on PIC32s and we're required to check it + // prior to powering on the USB peripheral (see DS61126F page 27) + while (U1PWRCbits.USBBUSY); U1PWRCSET = _U1PWRC_USBPWR_MASK; #else U1PWRCbits.USBPWR = 1; @@ -505,6 +518,19 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { return true; } +bool dcd_deinit(uint8_t rhport) +{ + U1CON = 0; + U1IE = 0; + U1OTGIE = 0; +#if TU_PIC_INT_SIZE == 4 + U1PWRCCLR = _U1PWRC_USUSPEND_MASK | _U1PWRC_USBPWR_MASK; +#else + U1PWRC &= ~(_U1PWRC_USUSPEND_MASK | _U1PWRC_USBPWR_MASK); +#endif + return true; +} + void dcd_int_enable(uint8_t rhport) { intr_enable(rhport); @@ -529,8 +555,25 @@ void dcd_remote_wakeup(uint8_t rhport) #else U1CONbits.RESUME = 1; #endif - unsigned cnt = 25000000 / 1000; + + // FIXME: Assert RESUME signal correctly, requires device-specific handling + // For now we use a hardcoded cycle-based delay which attempts to delay 10ms + // at the most common CPU frequencies. On PIC32s we assume the loop body + // takes 3 cycles. On 16-bit PICs we assume the XC16 compiler is in use and + // use its `__delay_ms' function. + +#if CFG_TUSB_MCU == OPT_MCU_PIC32MM + uint32_t cnt = 24000000 / 1000 / 3; + while (cnt--) asm volatile("nop"); +#elif CFG_TUSB_MCU == OPT_MCU_PIC32MX + uint32_t cnt = 40000000 / 1000 / 3; + while (cnt--) asm volatile("nop"); +#elif CFG_TUSB_MCU == OPT_MCU_PIC32MK + uint32_t cnt = 120000000 / 1000 / 3; while (cnt--) asm volatile("nop"); +#else + __delay_ms(10); +#endif #if TU_PIC_INT_SIZE == 4 U1CONCLR = _U1CON_RESUME_MASK; @@ -738,8 +781,11 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) //--------------------------------------------------------------------+ void dcd_int_handler(uint8_t rhport) { - uint32_t is = U1IR; - uint32_t msk = U1IE; + uint32_t is, msk; + + // Part 1 - "USB interrupts" + is = U1IR; + msk = U1IE; U1IR = is & ~msk; is &= msk; @@ -747,7 +793,7 @@ void dcd_int_handler(uint8_t rhport) if (is & _U1IR_UERRIF_MASK) { uint32_t es = U1EIR; U1EIR = es; - U1IR = is; /* discard any pending events */ + U1IR = is; /* discard any pending events */ } if (is & _U1IR_URSTIF_MASK) { @@ -758,29 +804,66 @@ void dcd_int_handler(uint8_t rhport) if (is & _U1IR_IDLEIF_MASK) { // Note Host usually has extra delay after bus reset (without SOF), which could falsely // detected as Sleep event. Though usbd has debouncing logic so we are good + + /* + * NOTE: Do not clear U1OTGIRbits.ACTVIF here! + * Reason: + * ACTVIF is only generated once an IDLEIF has been generated. + * This is a 1:1 ratio interrupt generation. + * For every IDLEIF, there will be only one ACTVIF regardless of + * the number of subsequent bus transitions. + * + * If the ACTIF is cleared here, a problem could occur when: + * [ IDLE ][bus activity -> + * <--- 3 ms -----> ^ + * ^ ACTVIF=1 + * IDLEIF=1 + * # # # # (#=Program polling flags) + * ^ + * This polling loop will see both + * IDLEIF=1 and ACTVIF=1. + * However, the program services IDLEIF first + * because ACTIVIE=0. + * If this routine clears the only ACTIVIF, + * then it can never get out of the suspend + * mode. + */ + U1OTGIESET = _U1OTGIE_ACTVIE_MASK; U1IR = _U1IR_IDLEIF_MASK; process_bus_sleep(rhport); } - if (is & _U1IR_RESUMEIF_MASK) { - U1IR = _U1IR_RESUMEIF_MASK; - process_bus_resume(rhport); - } - if (is & _U1IR_SOFIF_MASK) { U1IR = _U1IR_SOFIF_MASK; dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); } if (is & _U1IR_STALLIF_MASK) { - U1IR = _U1IR_STALLIF_MASK; process_stall(rhport); + U1IR = _U1IR_STALLIF_MASK; } if (is & _U1IR_TRNIF_MASK) { process_tokdne(rhport); } + // Part 2 - "USB OTG interrupts" + is = U1OTGIR; + msk = U1OTGIE; + + U1OTGIR = is & ~msk; + is &= msk; + + if (is & _U1OTGIR_ACTVIF_MASK) { +#if TU_PIC_INT_SIZE == 4 + U1OTGIECLR = _U1OTGIE_ACTVIE_MASK; +#else + U1OTGIE &= ~_U1OTGIE_ACTVIE_MASK; +#endif + U1OTGIR = _U1OTGIR_ACTVIF_MASK; + process_bus_resume(rhport); + } + intr_clear(rhport); } diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index 7d5cfb5b0..7c637ce0c 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -297,7 +297,7 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { dcd_reg->EPLISTSTART = (uint32_t) _dcd.ep; dcd_reg->DATABUFSTART = tu_align((uint32_t) &_dcd, TU_BIT(22)); // 22-bit alignment - dcd_reg->INTSTAT |= dcd_reg->INTSTAT; // clear all pending interrupt + dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt dcd_reg->INTEN = INT_DEVICE_STATUS_MASK; dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK | DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK; @@ -563,7 +563,8 @@ void dcd_int_handler(uint8_t rhport) uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT; - uint32_t int_status = dcd_reg->INTSTAT & dcd_reg->INTEN; + uint32_t int_status = dcd_reg->INTSTAT; + int_status &= dcd_reg->INTEN; dcd_reg->INTSTAT = int_status; // Acknowledge handled interrupt if (int_status == 0) return; diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index af08b549d..358ce84bc 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -148,6 +148,32 @@ static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t* buffer, uint16_t total_by hw_endpoint_xfer_start(ep, buffer, total_bytes); } +static void hw_endpoint_abort_xfer(struct hw_endpoint* ep) { + // Abort any pending transfer + // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). + // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. + const uint8_t dir = tu_edpt_dir(ep->ep_addr); + const uint8_t epnum = tu_edpt_number(ep->ep_addr); + const uint32_t abort_mask = TU_BIT((epnum << 1) | (dir ? 0 : 1)); + if (rp2040_chip_version() >= 2) { + usb_hw_set->abort = abort_mask; + while ((usb_hw->abort_done & abort_mask) != abort_mask) {} + } + + uint32_t buf_ctrl = USB_BUF_CTRL_SEL; // reset to buffer 0 + if (ep->next_pid) { + buf_ctrl |= USB_BUF_CTRL_DATA1_PID; + } + + _hw_endpoint_buffer_control_set_value32(ep, buf_ctrl); + hw_endpoint_reset_transfer(ep); + + if (rp2040_chip_version() >= 2) { + usb_hw_clear->abort_done = abort_mask; + usb_hw_clear->abort = abort_mask; + } +} + static void __tusb_irq_path_func(hw_handle_buff_status)(void) { uint32_t remaining_buffers = usb_hw->buf_status; pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers); @@ -178,25 +204,10 @@ TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) { // setup transfer. Also clear a stall in case for (uint8_t dir = 0; dir < 2; dir++) { struct hw_endpoint* ep = hw_endpoint_get_by_num(0, dir); + ep->next_pid = 1u; if (ep->active) { - // Abort any pending transfer from a prior control transfer per USB specs - // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). - // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. - uint32_t const abort_mask = (dir ? USB_EP_ABORT_EP0_IN_BITS : USB_EP_ABORT_EP0_OUT_BITS); - if (rp2040_chip_version() >= 2) { - usb_hw_set->abort = abort_mask; - while ((usb_hw->abort_done & abort_mask) != abort_mask) {} - } - - _hw_endpoint_buffer_control_set_value32(ep, USB_BUF_CTRL_DATA1_PID | USB_BUF_CTRL_SEL); - hw_endpoint_reset_transfer(ep); - - if (rp2040_chip_version() >= 2) { - usb_hw_clear->abort_done = abort_mask; - usb_hw_clear->abort = abort_mask; - } + hw_endpoint_abort_xfer(ep); // Abort any pending transfer per USB specs } - ep->next_pid = 1u; } } @@ -495,12 +506,14 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet // New API: Configure and enable an ISO endpoint according to descriptor bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { (void) rhport; - const uint8_t ep_addr = ep_desc->bEndpointAddress; - // Fill in endpoint control register with buffer offset - struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); - TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and buffer allocated - ep->wMaxPacketSize = ep_desc->wMaxPacketSize; + struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_desc->bEndpointAddress); + TU_ASSERT(ep->hw_data_buf != NULL); // must be inited and allocated previously + + if (ep->active) { + hw_endpoint_abort_xfer(ep); // abort any pending transfer + } + ep->wMaxPacketSize = ep_desc->wMaxPacketSize; hw_endpoint_enable(ep); return true; } diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 2c0a3fd49..478c6e789 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -459,28 +459,33 @@ tusb_speed_t hcd_port_speed_get(uint8_t rhport) } // Close all opened endpoint belong to this device -void hcd_device_close(uint8_t rhport, uint8_t dev_addr) -{ +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) { pico_trace("hcd_device_close %d\n", dev_addr); (void) rhport; - if (dev_addr == 0) return; + // reset epx if it is currently active with unplugged device + if (epx.configured && epx.active && epx.dev_addr == dev_addr) { + epx.configured = false; + *epx.endpoint_control = 0; + *epx.buffer_control = 0; + hw_endpoint_reset_transfer(&epx); + } - for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) - { - hw_endpoint_t* ep = &ep_pool[i]; + // dev0 only has ep0 + if (dev_addr != 0) { + for (size_t i = 1; i < TU_ARRAY_SIZE(ep_pool); i++) { + hw_endpoint_t *ep = &ep_pool[i]; + if (ep->dev_addr == dev_addr && ep->configured) { + // in case it is an interrupt endpoint, disable it + usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1)); + usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0; - if (ep->dev_addr == dev_addr && ep->configured) - { - // in case it is an interrupt endpoint, disable it - usb_hw_clear->int_ep_ctrl = (1 << (ep->interrupt_num + 1)); - usb_hw->int_ep_addr_ctrl[ep->interrupt_num] = 0; - - // unconfigure the endpoint - ep->configured = false; - *ep->endpoint_control = 0; - *ep->buffer_control = 0; - hw_endpoint_reset_transfer(ep); + // unconfigure the endpoint + ep->configured = false; + *ep->endpoint_control = 0; + *ep->buffer_control = 0; + hw_endpoint_reset_transfer(ep); + } } } } @@ -557,7 +562,7 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * } // If a normal transfer (non-interrupt) then initiate using - // sie ctrl registers. Otherwise interrupt ep registers should + // sie ctrl registers. Otherwise, interrupt ep registers should // already be configured if ( ep == &epx ) { @@ -597,13 +602,12 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet (void) rhport; // Copy data into setup packet buffer - for ( uint8_t i = 0; i < 8; i++ ) - { + for (uint8_t i = 0; i < 8; i++) { usbh_dpram->setup_packet[i] = setup_packet[i]; } // Configure EP0 struct with setup info for the trans complete - struct hw_endpoint * ep = _hw_endpoint_allocate(0); + struct hw_endpoint * ep = _hw_endpoint_allocate( (uint8_t) TUSB_XFER_CONTROL); TU_ASSERT(ep); // EPX should be inactive diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 43f48da39..9d0bd762d 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -110,7 +110,7 @@ void __tusb_irq_path_func(_hw_endpoint_buffer_control_update32)(struct hw_endpoi *ep->buffer_control = value & ~USB_BUF_CTRL_AVAIL; // 4.1.2.5.1 Con-current access: 12 cycles (should be good for 48*12Mhz = 576Mhz) after write to buffer control // Don't need delay in host mode as host is in charge - if ( !is_host_mode()) { + if (!is_host_mode()) { busy_wait_at_least_cycles(12); } } diff --git a/src/portable/synopsys/dwc2/hcd_dwc2.c b/src/portable/synopsys/dwc2/hcd_dwc2.c index ebbb6200a..b13479b02 100644 --- a/src/portable/synopsys/dwc2/hcd_dwc2.c +++ b/src/portable/synopsys/dwc2/hcd_dwc2.c @@ -26,7 +26,7 @@ #include "tusb_option.h" -#if CFG_TUH_ENABLED && defined(TUP_USBIP_DWC2) +#if CFG_TUH_ENABLED && defined(TUP_USBIP_DWC2) && !CFG_TUH_MAX3421 #if !(CFG_TUH_DWC2_SLAVE_ENABLE || CFG_TUH_DWC2_DMA_ENABLE) #error DWC2 require either CFG_TUH_DWC2_SLAVE_ENABLE or CFG_TUH_DWC2_DMA_ENABLE to be enabled diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index 4e8e25a00..c248ba14e 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -192,7 +192,8 @@ void dcd_int_handler(uint8_t rhport) { data.xfer[0][TUSB_DIR_OUT].max_size = 64; data.xfer[0][TUSB_DIR_IN].max_size = 64; - dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET, true); + //dcd_event_bus_reset(rhport, (USBOTG_FS->BASE_CTRL & USBFS_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); + dcd_event_bus_reset(rhport, (USBOTG_FS->UDEV_CTRL & USBFS_UDEV_CTRL_LOW_SPEED) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL, true); USBOTG_FS->DEV_ADDR = 0x00; EP_RX_CTRL(0) = USBFS_EP_R_RES_ACK; |
