Skip to content

Commit 457c2b5

Browse files
authored
Merge pull request #2914 from ReimuNotMoe/master
Various fixes for the Microchip Chipidea FS driver
2 parents 24b2abb + 655092d commit 457c2b5

File tree

2 files changed

+151
-17
lines changed

2 files changed

+151
-17
lines changed

src/portable/microchip/pic/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Microchip PIC Chipidea FS Driver
2+
3+
This driver adds support for Microchip PIC microcontrollers with full-speed Chipidea USB peripheral to the TinyUSB stack. It supports the following families:
4+
5+
- PIC32MX (untested)
6+
- PIC32MM
7+
- PIC32MK (untested)
8+
- PIC24FJ
9+
- PIC24EP (untested)
10+
- dsPIC33EP (untested)
11+
12+
Currently only the device mode is supported.
13+
14+
15+
## Important Notes
16+
17+
### Handling of shared VBUS & GPIO pin
18+
19+
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.
20+
21+
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.
22+
23+
This design choice allows developers the flexibility to use the GPIO functionality for controlling VBUS in device mode if desired.
24+
25+
26+
## TODO
27+
28+
### Handle USB remote wakeup timing correctly
29+
30+
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.
31+
32+
### 8-bit PIC support
33+
34+
The 8-bit PICs also uses the Chipidea FS IP. Technically it's possible to support them as well.
35+
36+
Possible difficulties:
37+
- Memory size constraints (1KB/8KB ballpark)
38+
- A third BDT layout (now we have two)
39+
- Different compiler-specific directives
40+
- Compiler bugs if you use SDCC
41+
42+
43+
## Author
44+
[ReimuNotMoe](https://github.com/ReimuNotMoe) at SudoMaker, Ltd.
45+
46+
47+
## Credits
48+
49+
This driver is based on:
50+
- Microchip's USB driver (usb_device.c)
51+
- TinyUSB's NXP KHCI driver (dcd_khci.c)

src/portable/microchip/pic/dcd_pic.c

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
/*
22
* The MIT License (MIT)
33
*
4-
* Copyright (c) 2020 Koji Kitayama
5-
* Copyright (c) 2022 Reimu NotMoe <[email protected]>
4+
* Copyright (c) 2022-2024 SudoMaker, Ltd.
5+
* Author: Mike Yang (Reimu NotMoe) <[email protected]>
6+
*
7+
* Based on usb_device.c - Copyright (c) 2015 Microchip Technology Inc.
8+
* Based on dcd_khci.c - Copyright (c) 2020 Koji Kitayama
69
*
710
* Permission is hereby granted, free of charge, to any person obtaining a copy
811
* of this software and associated documentation files (the "Software"), to deal
@@ -313,12 +316,23 @@ static void prepare_next_setup_packet(uint8_t rhport)
313316
{
314317
const unsigned out_odd = _dcd.endpoint[0][0].odd;
315318
const unsigned in_odd = _dcd.endpoint[0][1].odd;
316-
TU_ASSERT(0 == _dcd.bdt[0][0][out_odd].own, );
319+
320+
// Abandon any previous control transfers that might have been using EP0.
321+
// Ordinarily, nothing actually needs abandoning, since the previous control
322+
// transfer would have completed successfully prior to the host sending the
323+
// next SETUP packet. However, in a timeout error case, or after an EP0
324+
// STALL event, one or more UOWN bits might still be set. If so, we should
325+
// clear the UOWN bits, so the EP0 IN/OUT endpoints are in a known inactive
326+
// state, ready for re-arming by the `dcd_edpt_xfer' function that will be
327+
// called next.
317328

318329
_dcd.bdt[0][0][out_odd].data = 0;
330+
_dcd.bdt[0][0][out_odd].own = 0;
319331
_dcd.bdt[0][0][out_odd ^ 1].data = 1;
320332
_dcd.bdt[0][1][in_odd].data = 1;
333+
_dcd.bdt[0][1][in_odd].own = 0;
321334
_dcd.bdt[0][1][in_odd ^ 1].data = 0;
335+
_dcd.bdt[0][1][in_odd ^ 1].own = 0;
322336
dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_OUT),
323337
_dcd.setup_packet, sizeof(_dcd.setup_packet));
324338
}
@@ -475,13 +489,12 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
475489
intr_disable(rhport);
476490
intr_clear(rhport);
477491

478-
#if CFG_TUSB_MCU == OPT_MCU_PIC32MM
479-
TRISBbits.TRISB6 = 1;
480-
#endif
481-
482492
tu_memclr(&_dcd, sizeof(_dcd));
483493

484494
#if TU_PIC_INT_SIZE == 4
495+
// The USBBUSY bit is present on PIC32s and we're required to check it
496+
// prior to powering on the USB peripheral (see DS61126F page 27)
497+
while (U1PWRCbits.USBBUSY);
485498
U1PWRCSET = _U1PWRC_USBPWR_MASK;
486499
#else
487500
U1PWRCbits.USBPWR = 1;
@@ -505,6 +518,19 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
505518
return true;
506519
}
507520

521+
bool dcd_deinit(uint8_t rhport)
522+
{
523+
U1CON = 0;
524+
U1IE = 0;
525+
U1OTGIE = 0;
526+
#if TU_PIC_INT_SIZE == 4
527+
U1PWRCCLR = _U1PWRC_USUSPEND_MASK | _U1PWRC_USBPWR_MASK;
528+
#else
529+
U1PWRC &= ~(_U1PWRC_USUSPEND_MASK | _U1PWRC_USBPWR_MASK);
530+
#endif
531+
return true;
532+
}
533+
508534
void dcd_int_enable(uint8_t rhport)
509535
{
510536
intr_enable(rhport);
@@ -529,8 +555,25 @@ void dcd_remote_wakeup(uint8_t rhport)
529555
#else
530556
U1CONbits.RESUME = 1;
531557
#endif
532-
unsigned cnt = 25000000 / 1000;
558+
559+
// FIXME: Assert RESUME signal correctly, requires device-specific handling
560+
// For now we use a hardcoded cycle-based delay which attempts to delay 10ms
561+
// at the most common CPU frequencies. On PIC32s we assume the loop body
562+
// takes 3 cycles. On 16-bit PICs we assume the XC16 compiler is in use and
563+
// use its `__delay_ms' function.
564+
565+
#if CFG_TUSB_MCU == OPT_MCU_PIC32MM
566+
uint32_t cnt = 24000000 / 1000 / 3;
567+
while (cnt--) asm volatile("nop");
568+
#elif CFG_TUSB_MCU == OPT_MCU_PIC32MX
569+
uint32_t cnt = 40000000 / 1000 / 3;
570+
while (cnt--) asm volatile("nop");
571+
#elif CFG_TUSB_MCU == OPT_MCU_PIC32MK
572+
uint32_t cnt = 120000000 / 1000 / 3;
533573
while (cnt--) asm volatile("nop");
574+
#else
575+
__delay_ms(10);
576+
#endif
534577

535578
#if TU_PIC_INT_SIZE == 4
536579
U1CONCLR = _U1CON_RESUME_MASK;
@@ -738,16 +781,19 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr)
738781
//--------------------------------------------------------------------+
739782
void dcd_int_handler(uint8_t rhport)
740783
{
741-
uint32_t is = U1IR;
742-
uint32_t msk = U1IE;
784+
uint32_t is, msk;
785+
786+
// Part 1 - "USB interrupts"
787+
is = U1IR;
788+
msk = U1IE;
743789

744790
U1IR = is & ~msk;
745791
is &= msk;
746792

747793
if (is & _U1IR_UERRIF_MASK) {
748794
uint32_t es = U1EIR;
749795
U1EIR = es;
750-
U1IR = is; /* discard any pending events */
796+
U1IR = is; /* discard any pending events */
751797
}
752798

753799
if (is & _U1IR_URSTIF_MASK) {
@@ -758,29 +804,66 @@ void dcd_int_handler(uint8_t rhport)
758804
if (is & _U1IR_IDLEIF_MASK) {
759805
// Note Host usually has extra delay after bus reset (without SOF), which could falsely
760806
// detected as Sleep event. Though usbd has debouncing logic so we are good
807+
808+
/*
809+
* NOTE: Do not clear U1OTGIRbits.ACTVIF here!
810+
* Reason:
811+
* ACTVIF is only generated once an IDLEIF has been generated.
812+
* This is a 1:1 ratio interrupt generation.
813+
* For every IDLEIF, there will be only one ACTVIF regardless of
814+
* the number of subsequent bus transitions.
815+
*
816+
* If the ACTIF is cleared here, a problem could occur when:
817+
* [ IDLE ][bus activity ->
818+
* <--- 3 ms -----> ^
819+
* ^ ACTVIF=1
820+
* IDLEIF=1
821+
* # # # # (#=Program polling flags)
822+
* ^
823+
* This polling loop will see both
824+
* IDLEIF=1 and ACTVIF=1.
825+
* However, the program services IDLEIF first
826+
* because ACTIVIE=0.
827+
* If this routine clears the only ACTIVIF,
828+
* then it can never get out of the suspend
829+
* mode.
830+
*/
831+
U1OTGIESET = _U1OTGIE_ACTVIE_MASK;
761832
U1IR = _U1IR_IDLEIF_MASK;
762833
process_bus_sleep(rhport);
763834
}
764835

765-
if (is & _U1IR_RESUMEIF_MASK) {
766-
U1IR = _U1IR_RESUMEIF_MASK;
767-
process_bus_resume(rhport);
768-
}
769-
770836
if (is & _U1IR_SOFIF_MASK) {
771837
U1IR = _U1IR_SOFIF_MASK;
772838
dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true);
773839
}
774840

775841
if (is & _U1IR_STALLIF_MASK) {
776-
U1IR = _U1IR_STALLIF_MASK;
777842
process_stall(rhport);
843+
U1IR = _U1IR_STALLIF_MASK;
778844
}
779845

780846
if (is & _U1IR_TRNIF_MASK) {
781847
process_tokdne(rhport);
782848
}
783849

850+
// Part 2 - "USB OTG interrupts"
851+
is = U1OTGIR;
852+
msk = U1OTGIE;
853+
854+
U1OTGIR = is & ~msk;
855+
is &= msk;
856+
857+
if (is & _U1OTGIR_ACTVIF_MASK) {
858+
#if TU_PIC_INT_SIZE == 4
859+
U1OTGIECLR = _U1OTGIE_ACTVIE_MASK;
860+
#else
861+
U1OTGIE &= ~_U1OTGIE_ACTVIE_MASK;
862+
#endif
863+
U1OTGIR = _U1OTGIR_ACTVIF_MASK;
864+
process_bus_resume(rhport);
865+
}
866+
784867
intr_clear(rhport);
785868
}
786869

0 commit comments

Comments
 (0)