diff options
Diffstat (limited to 'src/portable/chipidea')
| -rw-r--r-- | src/portable/chipidea/ci_fs/ci_fs_kinetis.h | 23 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_fs/ci_fs_mcx.h | 23 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_fs/ci_fs_type.h | 77 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_fs/dcd_ci_fs.c | 73 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_fs/hcd_ci_fs.c | 635 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/ci_hs_hpm.h | 23 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/ci_hs_imxrt.h | 26 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h | 28 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/ci_hs_mcx.h | 23 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/ci_hs_rw61x.h | 23 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/ci_hs_type.h | 48 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/dcd_ci_hs.c | 277 | ||||
| -rw-r--r-- | src/portable/chipidea/ci_hs/hcd_ci_hs.c | 27 |
13 files changed, 943 insertions, 363 deletions
diff --git a/src/portable/chipidea/ci_fs/ci_fs_kinetis.h b/src/portable/chipidea/ci_fs/ci_fs_kinetis.h index 31e14a546..e9370140d 100644 --- a/src/portable/chipidea/ci_fs/ci_fs_kinetis.h +++ b/src/portable/chipidea/ci_fs/ci_fs_kinetis.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2023 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2023 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ diff --git a/src/portable/chipidea/ci_fs/ci_fs_mcx.h b/src/portable/chipidea/ci_fs/ci_fs_mcx.h index 4b93a03a7..425265cca 100644 --- a/src/portable/chipidea/ci_fs/ci_fs_mcx.h +++ b/src/portable/chipidea/ci_fs/ci_fs_mcx.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2023 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2023 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ diff --git a/src/portable/chipidea/ci_fs/ci_fs_type.h b/src/portable/chipidea/ci_fs/ci_fs_type.h index 5a5e53fb0..857a75253 100644 --- a/src/portable/chipidea/ci_fs/ci_fs_type.h +++ b/src/portable/chipidea/ci_fs/ci_fs_type.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2023 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2023 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -46,6 +27,60 @@ extern "C" { // align 4 is used to get rid of reserved fields #define _va32 volatile TU_ATTR_ALIGNED(4) +//--------------------------------------------------------------------+ +// Buffer Descriptor Table (BDT) - shared by the device (dcd) and host (hcd) drivers +// since both target the same ChipIdea-FS silicon. Keep the layout in one place so a +// fix cannot silently drift between the two drivers. +//--------------------------------------------------------------------+ + +// Token PID values reported in the BDT tok_pid field / written to the TOKEN register. +// The device driver only uses OUT/IN/SETUP; the rest are host-only. +enum { + TOK_PID_OUT = 0x1u, + TOK_PID_IN = 0x9u, + TOK_PID_SETUP = 0xDu, + TOK_PID_DATA0 = 0x3u, + TOK_PID_DATA1 = 0xbu, + TOK_PID_ACK = 0x2u, + TOK_PID_STALL = 0xeu, + TOK_PID_NAK = 0xau, + TOK_PID_BUSTO = 0x0u, + TOK_PID_ERR = 0xfu, +}; + +// Note: this header is included before the CMSIS device header, so use plain `volatile` +// rather than CMSIS `__IO`. +typedef struct TU_ATTR_PACKED +{ + union { + uint32_t head; + struct { + union { + struct { + uint16_t : 2; + volatile uint16_t tok_pid : 4; + uint16_t data : 1; + volatile uint16_t own : 1; + uint16_t : 8; + }; + struct { + uint16_t : 2; + uint16_t bdt_stall : 1; + uint16_t dts : 1; + uint16_t ninc : 1; + uint16_t keep : 1; + uint16_t : 10; + }; + }; + volatile uint16_t bc : 10; + uint16_t : 6; + }; + }; + uint8_t *addr; +}buffer_descriptor_t; + +TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); + typedef struct { _va32 uint8_t PER_ID; // [00] Peripheral ID register _va32 uint8_t ID_COMP; // [04] Peripheral ID complement register diff --git a/src/portable/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c index 62df1a6d5..295f2e578 100644 --- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c +++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c @@ -1,25 +1,7 @@ /* - * The MIT License (MIT) - * - * 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 - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2020 Koji Kitayama + * SPDX-FileCopyrightText: Copyright (c) 2020 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -42,43 +24,7 @@ //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); +// TOK_PID_* and buffer_descriptor_t are shared with the host driver in ci_fs_type.h typedef struct TU_ATTR_PACKED { @@ -193,6 +139,17 @@ static void process_tokdne(uint8_t rhport) return; } const unsigned length = ep->length; + + /* Transfer is complete. For OUT, a multi-packet transfer speculatively arms the + * sibling (even/odd) BDT to avoid NAK. When the transfer ends early - e.g. the host + * sends a short packet before filling both buffers - that sibling is left armed + * (own=1). A leftover armed BDT desyncs the even/odd ping-pong so the next OUT + * packet lands in the wrong buffer half (buffer + max_packet_size instead of + * buffer), making the stack read stale data. Disarm it here. */ + if (dir == TUSB_DIR_OUT) { + _dcd.bdt[epnum][dir][odd ^ 1].own = 0; + } + dcd_event_xfer_complete(rhport, tu_edpt_addr(epnum, dir), length - remaining, XFER_RESULT_SUCCESS, true); diff --git a/src/portable/chipidea/ci_fs/hcd_ci_fs.c b/src/portable/chipidea/ci_fs/hcd_ci_fs.c new file mode 100644 index 000000000..a6f5405b5 --- /dev/null +++ b/src/portable/chipidea/ci_fs/hcd_ci_fs.c @@ -0,0 +1,635 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2021 Koji Kitayama + * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS) + +#include "host/hcd.h" +#include "host/usbh.h" +#include "ci_fs_type.h" + +// Host is currently only available on NXP Kinetis. The ChipIdea-FS host controller +// interface is register-compatible via ci_fs_regs_t. Unlike the device driver, the host +// driver does not include the ci_fs_<family>.h header because those define the device +// dcd_int_enable()/dcd_int_disable() functions, which would collide in a dual-role build. +#if defined(TUP_USBIP_CHIPIDEA_FS_KINETIS) + #include "fsl_device_registers.h" + #define CI_FS_REG(_port) ((ci_fs_regs_t*) USB0_BASE) + #define CI_FS_IRQN USB0_IRQn +#else + #error "MCU is not supported" +#endif + +#define CI_REG CI_FS_REG(0) + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ +// TOK_PID_* and buffer_descriptor_t are shared with the device driver in ci_fs_type.h + +typedef struct TU_ATTR_PACKED +{ + union { + uint32_t state; + struct { + uint32_t pipenum:16; + uint32_t odd : 1; + uint32_t : 0; + }; + }; + uint8_t *buffer; + uint16_t length; + uint16_t remaining; +} endpoint_state_t; + +typedef struct TU_ATTR_PACKED +{ + uint8_t dev_addr; + uint8_t ep_addr; + uint16_t max_packet_size; + union { + uint8_t flags; + struct { + uint8_t data : 1; + uint8_t xfer : 2; + uint8_t : 0; + }; + }; + uint8_t *buffer; + uint16_t length; + uint16_t remaining; +} pipe_state_t; + + +typedef struct +{ + union { + /* [OUT,IN][EVEN,ODD] */ + buffer_descriptor_t bdt[2][2]; + /* bda aliases bdt for STAT-register indexing: STAT gives the byte-offset/2 of the + * completed BD, so it indexes bda[] in uint16_t units. Each buffer_descriptor_t is + * 4 uint16_t, hence 2*2*4 elements to span the whole table (must equal sizeof bdt). */ + uint16_t bda[2*2*4]; + }; + endpoint_state_t endpoint[2]; + pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2]; + uint32_t in_progress; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe is in progress */ + uint32_t pending; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe will be resume the next frame */ + bool need_reset; /* The device has not been reset after connection. */ +} hcd_data_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +// BDT(Buffer Descriptor Table) must be 256-byte aligned +CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(512) static hcd_data_t _hcd; +//CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[1024]; + +static int find_pipe(uint8_t dev_addr, uint8_t ep_addr) +{ + /* Find the target pipe */ + int num; + for (num = 0; num < CFG_TUH_ENDPOINT_MAX * 2; ++num) { + pipe_state_t *p = &_hcd.pipe[num]; + if ((p->dev_addr == dev_addr) && (p->ep_addr == ep_addr)) + return num; + } + return -1; +} + +static int prepare_packets(int pipenum) +{ + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + unsigned const dir_tx = tu_edpt_dir(pipe->ep_addr) ? 0 : 1; + endpoint_state_t *ep = &_hcd.endpoint[dir_tx]; + unsigned const odd = ep->odd; + buffer_descriptor_t *bd = _hcd.bdt[dir_tx]; + // The host shares a single BDT set across all pipes. If it is still owned by an + // in-flight transfer on another pipe, report busy so the caller can defer & retry. + if (bd[odd].own) return -1; + + // TU_LOG1(" %p dir %d odd %d data %d\r\n", &bd[odd], dir_tx, odd, pipe->data); + + ep->pipenum = pipenum; + + bd[odd ].data = pipe->data; + bd[odd ^ 1].data = pipe->data ^ 1; + bd[odd ^ 1].own = 0; + /* reset values for a next transfer */ + + int num_tokens = 0; /* The number of prepared packets */ + unsigned const mps = pipe->max_packet_size; + unsigned const rem = pipe->remaining; + if (rem > mps) { + /* When total_bytes is greater than the max packet size, + * it prepares to the next transfer to avoid NAK in advance. */ + bd[odd ^ 1].bc = rem >= 2 * mps ? mps: rem - mps; + bd[odd ^ 1].addr = pipe->buffer + mps; + bd[odd ^ 1].own = 1; + if (dir_tx) ++num_tokens; + } + bd[odd].bc = rem >= mps ? mps: rem; + bd[odd].addr = pipe->buffer; + __DSB(); + bd[odd].own = 1; /* This bit must be set last */ + ++num_tokens; + return num_tokens; +} + +static int select_next_pipenum(int pipenum) +{ + unsigned wip = _hcd.in_progress & ~_hcd.pending; + if (!wip) return -1; + unsigned msk = TU_GENMASK(31, pipenum); + int next = __builtin_ctz(wip & msk); + if (next) return next; + msk = TU_GENMASK(pipenum, 0); + next = __builtin_ctz(wip & msk); + return next; +} + +/* When transfer is completed, return true. */ +static bool continue_transfer(int pipenum, buffer_descriptor_t *bd) +{ + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + unsigned const bc = bd->bc; + unsigned const rem = pipe->remaining - bc; + + pipe->remaining = rem; + if (rem && bc == pipe->max_packet_size) { + int const next_rem = rem - pipe->max_packet_size; + if (next_rem > 0) { + /* Prepare to the after next transfer */ + bd->addr += pipe->max_packet_size * 2; + bd->bc = next_rem > pipe->max_packet_size ? pipe->max_packet_size: next_rem; + __DSB(); + bd->own = 1; /* This bit must be set last */ + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = CI_REG->TOKEN; /* Queue the same token as the last */ + } else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */ + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = CI_REG->TOKEN; + } + return true; + } + pipe->data = bd->data ^ 1; + return false; +} + +static bool resume_transfer(int pipenum) +{ + int num_tokens = prepare_packets(pipenum); + if (num_tokens < 0) { + // Shared BDT still owned by an in-flight transfer on another pipe. Defer this + // pipe and retry on the next SOF once the BDT is free (avoids dropping the + // transfer, which stalls e.g. a 2nd device enumerating behind a hub while the + // app issues concurrent control transfers). + _hcd.pending |= TU_BIT(pipenum); + CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK; + return true; + } + + const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + + unsigned flags = CI_REG->EP[0].CTL & USB_ENDPT_HOSTWOHUB_MASK; + flags |= USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; + switch (pipe->xfer) { + case TUSB_XFER_CONTROL: + flags |= USB_ENDPT_EPHSHK_MASK; + break; + case TUSB_XFER_ISOCHRONOUS: + flags |= USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; + break; + default: + flags |= USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; + break; + } + // TU_LOG1(" resume pipenum %d flags %x\r\n", pipenum, flags); + + CI_REG->EP[0].CTL = flags; + CI_REG->ADDR = (CI_REG->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr; + + unsigned const token = tu_edpt_number(pipe->ep_addr) | + ((tu_edpt_dir(pipe->ep_addr) ? TOK_PID_IN: TOK_PID_OUT) << USB_TOKEN_TOKENPID_SHIFT); + do { + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = token; + } while (--num_tokens); + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); + return true; +} + +static void suspend_transfer(int pipenum, buffer_descriptor_t *bd) +{ + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + pipe->buffer = bd->addr; + // A NAK transfers no data, so the data toggle must be preserved for the retry. + // (Do NOT flip pipe->data here: flipping it makes the retried packet use the wrong + // DATA0/DATA1, which the device silently discards - breaking any bulk/interrupt + // transfer that is NAKed, e.g. the MSC CBW/CSW when the device is momentarily busy.) + if ((TUSB_XFER_INTERRUPT == pipe->xfer) || + (TUSB_XFER_BULK == pipe->xfer)) { + _hcd.pending |= TU_BIT(pipenum); + CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK; + } +} + +// Release the speculatively-armed sibling BDT of a multi-packet transfer. +// prepare_packets arms the sibling (odd^1) BDT (own=1) so a multi-packet transfer can +// ping-pong without NAKs. When the transfer ends - completes early on a short IN packet, +// stalls/errors, or (for IN) is NAKed before the sibling's token is issued - that sibling +// is left owned by the SIE. Because the host shares ONE BDT set across all pipes, a +// leftover armed sibling blocks every other pipe forever (e.g. a 2nd device stuck +// enumerating behind a hub). Release it - but ONLY for a multi-packet transfer: a +// single-packet transfer never armed a sibling, so that BDT slot may legitimately belong +// to another pipe's in-flight transfer. +static inline void release_sibling_bd(unsigned s, const pipe_state_t *pipe) +{ + if (pipe->length > pipe->max_packet_size) { + ((buffer_descriptor_t *)&_hcd.bda[s ^ USB_STAT_ODD_MASK])->own = 0; + } +} + +static void process_tokdne(uint8_t rhport) +{ + (void)rhport; + const unsigned s = CI_REG->STAT; + CI_REG->INT_STAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */ + uint8_t const dir_in = (s & USB_STAT_TX_MASK) ? TUSB_DIR_OUT: TUSB_DIR_IN; + unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0; + + buffer_descriptor_t *bd = (buffer_descriptor_t *)&_hcd.bda[s]; + endpoint_state_t *ep = &_hcd.endpoint[s >> 3]; + + /* fetch status before discarded by the next steps */ + const unsigned pid = bd->tok_pid; + + /* reset values for a next transfer */ + bd->bdt_stall = 0; + bd->dts = 1; + bd->ninc = 0; + bd->keep = 0; + /* Update the odd variable to prepare for the next transfer */ + ep->odd = odd ^ 1; + + int pipenum = ep->pipenum; + int next_pipenum; + // TU_LOG1("TOKDNE %x PID %x pipe %d\r\n", s, pid, pipenum); + + xfer_result_t result; + switch (pid) { + default: + if (continue_transfer(pipenum, bd)) + return; + result = XFER_RESULT_SUCCESS; + break; + case TOK_PID_NAK: + // Release the speculatively-armed sibling so the deferred retry (and any other pipe + // sharing the single BDT) can claim it; otherwise it stays own=1 forever and every + // same-direction transfer wedges. IN only: an IN issues just one token so the sibling + // was never put on the wire, whereas an OUT issues both tokens and its sibling may + // still be in flight - touching it there would race the SIE write-back. + if (TUSB_DIR_IN == dir_in) release_sibling_bd(s, &_hcd.pipe[pipenum]); + suspend_transfer(pipenum, bd); + next_pipenum = select_next_pipenum(pipenum); + if (0 <= next_pipenum) + resume_transfer(next_pipenum); + return; + case TOK_PID_STALL: + result = XFER_RESULT_STALLED; + break; + case TOK_PID_ERR: /* mismatch toggle bit */ + case TOK_PID_BUSTO: + result = XFER_RESULT_FAILED; + break; + } + _hcd.in_progress &= ~TU_BIT(pipenum); + pipe_state_t *pipe = &_hcd.pipe[ep->pipenum]; + release_sibling_bd(s, pipe); + hcd_event_xfer_complete(pipe->dev_addr, + tu_edpt_addr(CI_REG->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in), + pipe->length - pipe->remaining, + result, true); + next_pipenum = select_next_pipenum(pipenum); + if (0 <= next_pipenum) + resume_transfer(next_pipenum); +} + +static void process_attach(uint8_t rhport) +{ + unsigned ctl = CI_REG->CTL; + if (!(ctl & USB_CTL_JSTATE_MASK)) { + /* The attached device is a low speed device. */ + CI_REG->ADDR = USB_ADDR_LSEN_MASK; + CI_REG->EP[0].CTL = USB_ENDPT_HOSTWOHUB_MASK; + } + hcd_event_device_attach(rhport, true); +} + +static void process_bus_reset(uint8_t rhport) +{ + CI_REG->INT_STAT = USB_ISTAT_TOKDNE_MASK; + CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; + CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK; + CI_REG->ADDR = 0; + CI_REG->EP[0].CTL = 0; + + hcd_event_device_remove(rhport, true); + + _hcd.in_progress = 0; + _hcd.pending = 0; + // Clear the ENTIRE shared BDT (both directions, both even/odd). Clearing only the IN + // pair left a stale OUT/SETUP descriptor (own=1) after a disconnect mid-OUT, which then + // blocks the first control transfer on re-enumeration. + buffer_descriptor_t *bd = &_hcd.bdt[0][0]; + for (unsigned i = 0; i < 2 * 2; ++i, ++bd) { + bd->head = 0; + } +} + +/*------------------------------------------------------------------*/ +/* Host API + *------------------------------------------------------------------*/ +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + CI_REG->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; + while (CI_REG->USBTRC0 & USB_USBTRC0_USBRESET_MASK); + + tu_memclr(&_hcd, sizeof(_hcd)); + CI_REG->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */ + CI_REG->BDT_PAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8); + CI_REG->BDT_PAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16); + CI_REG->BDT_PAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24); + + CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; + CI_REG->CTL |= USB_CTL_ODDRST_MASK; + for (unsigned i = 0; i < 16; ++i) { + CI_REG->EP[i].CTL = 0; + } + CI_REG->CTL &= ~USB_CTL_ODDRST_MASK; + + CI_REG->SOF_THLD = 74; /* for 64-byte packets */ + // CI_REG->SOF_THLD = 144; /* for low speed 8-byte packets */ + CI_REG->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK; + CI_REG->USBCTRL = USB_USBCTRL_PDE_MASK; + + NVIC_ClearPendingIRQ(CI_FS_IRQN); + CI_REG->INT_EN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK | + USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK; + CI_REG->ERR_ENB = 0xff; + + return true; +} + +void hcd_int_enable(uint8_t rhport) +{ + (void)rhport; + NVIC_EnableIRQ(CI_FS_IRQN); +} + +void hcd_int_disable(uint8_t rhport) +{ + (void)rhport; + NVIC_DisableIRQ(CI_FS_IRQN); +} + +uint32_t hcd_frame_number(uint8_t rhport) +{ + (void)rhport; + /* The device must be reset at least once after connection + * in order to start the frame counter. */ + if (_hcd.need_reset) hcd_port_reset(rhport); + uint32_t frmnum = CI_REG->FRM_NUML; + frmnum |= CI_REG->FRM_NUMH << 8u; + return frmnum; +} + +/*--------------------------------------------------------------------+ + * Port API + *--------------------------------------------------------------------+ */ +bool hcd_port_connect_status(uint8_t rhport) +{ + (void)rhport; + if (CI_REG->INT_STAT & USB_ISTAT_ATTACH_MASK) + return true; + return false; +} + +void hcd_port_reset(uint8_t rhport) +{ + (void)rhport; + CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK; + CI_REG->CTL |= USB_CTL_RESET_MASK; + unsigned cnt = SystemCoreClock / 100; + while (cnt--) __NOP(); + CI_REG->CTL &= ~USB_CTL_RESET_MASK; + CI_REG->CTL |= USB_CTL_USBENSOFEN_MASK; + _hcd.need_reset = false; +} + +void hcd_port_reset_end(uint8_t rhport) { + (void) rhport; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) +{ + (void)rhport; + tusb_speed_t speed = TUSB_SPEED_FULL; + const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + if (CI_REG->ADDR & USB_ADDR_LSEN_MASK) + speed = TUSB_SPEED_LOW; + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); + return speed; +} + +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + (void)rhport; + const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + pipe_state_t *p = &_hcd.pipe[0]; + pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; + for (;p != end; ++p) { + if (p->dev_addr == dev_addr) + tu_memclr(p, sizeof(*p)); + } + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + (void)rhport; + // TU_LOG1("SETUP %u\r\n", dev_addr); + TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(0))); + + int pipenum = find_pipe(dev_addr, 0); + if (pipenum < 0) return false; + + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + pipe[0].data = 0; + pipe[0].buffer = (uint8_t*)(uintptr_t)setup_packet; + pipe[0].length = 8; + pipe[0].remaining = 8; + pipe[1].data = 1; + + if (1 != prepare_packets(pipenum)) + return false; + + _hcd.in_progress |= TU_BIT(pipenum); + + unsigned hostwohub = CI_REG->EP[0].CTL & USB_ENDPT_HOSTWOHUB_MASK; + CI_REG->EP[0].CTL = hostwohub | + USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; + CI_REG->ADDR = (CI_REG->ADDR & USB_ADDR_LSEN_MASK) | dev_addr; + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT); + return true; +} + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) +{ + (void)rhport; + uint8_t const ep_addr = ep_desc->bEndpointAddress; + // TU_LOG1("O %u %x\r\n", dev_addr, ep_addr); + /* Find a free pipe */ + pipe_state_t *p = &_hcd.pipe[0]; + pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; + if (dev_addr || ep_addr) { + p += 2; + for (; p < end && (p->dev_addr || p->ep_addr); ++p) ; + if (p == end) return false; + } + p->dev_addr = dev_addr; + p->ep_addr = ep_addr; + p->max_packet_size = ep_desc->wMaxPacketSize; + p->xfer = ep_desc->bmAttributes.xfer; + p->data = 0; + if (!ep_addr) { + /* Open one more pipe for Control IN transfer */ + TU_ASSERT(TUSB_XFER_CONTROL == p->xfer); + pipe_state_t *q = p + 1; + TU_ASSERT(!q->dev_addr && !q->ep_addr); + q->dev_addr = dev_addr; + q->ep_addr = tu_edpt_addr(0, TUSB_DIR_IN); + q->max_packet_size = ep_desc->wMaxPacketSize; + q->xfer = ep_desc->bmAttributes.xfer; + q->data = 1; + } + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + +/* The address of buffer must be aligned to 4 byte boundary. And it must be at least 4 bytes long. + * DMA writes data in 4 byte unit */ +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) +{ + (void)rhport; + // TU_LOG1("X %u %x %x %d\r\n", dev_addr, ep_addr, (uintptr_t)buffer, buflen); + + int pipenum = find_pipe(dev_addr, ep_addr); + TU_ASSERT(0 <= pipenum); + + TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum))); + unsigned const ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + pipe->buffer = buffer; + pipe->length = buflen; + pipe->remaining = buflen; + _hcd.in_progress |= TU_BIT(pipenum); + _hcd.pending |= TU_BIT(pipenum); /* Send at the next Frame */ + CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK; + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); + return true; +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + // TODO not implemented yet + return false; +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + if (!tu_edpt_number(ep_addr)) return true; + int num = find_pipe(dev_addr, ep_addr); + if (num < 0) return false; + pipe_state_t *p = &_hcd.pipe[num]; + p->data = 0; /* Reset data toggle */ + return true; +} + +/*--------------------------------------------------------------------+ + * ISR + *--------------------------------------------------------------------+*/ +void hcd_int_handler(uint8_t rhport, bool in_isr) +{ + (void) in_isr; + uint32_t is = CI_REG->INT_STAT; + uint32_t msk = CI_REG->INT_EN; + + // TU_LOG1("S %lx\r\n", is); + + /* clear disabled interrupts */ + CI_REG->INT_STAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK; + is &= msk; + + if (is & USB_ISTAT_ERROR_MASK) { + unsigned err = CI_REG->ERR_STAT; + if (err) { + TU_LOG1(" ERR %x\r\n", err); + CI_REG->ERR_STAT = err; + } else { + CI_REG->INT_EN &= ~USB_ISTAT_ERROR_MASK; + } + } + + if (is & USB_ISTAT_USBRST_MASK) { + CI_REG->INT_EN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK; + process_bus_reset(rhport); + return; + } + if (is & USB_ISTAT_ATTACH_MASK) { + CI_REG->INT_EN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK; + _hcd.need_reset = true; + process_attach(rhport); + return; + } + if (is & USB_ISTAT_STALL_MASK) { + CI_REG->INT_STAT = USB_ISTAT_STALL_MASK; + } + if (is & USB_ISTAT_SOFTOK_MASK) { + msk &= ~USB_ISTAT_SOFTOK_MASK; + CI_REG->INT_EN = msk; + if (_hcd.pending) { + int pipenum = __builtin_ctz(_hcd.pending); + _hcd.pending = 0; + if (!(is & USB_ISTAT_TOKDNE_MASK)) + resume_transfer(pipenum); + } + } + if (is & USB_ISTAT_TOKDNE_MASK) { + process_tokdne(rhport); + } +} + +#endif diff --git a/src/portable/chipidea/ci_hs/ci_hs_hpm.h b/src/portable/chipidea/ci_hs/ci_hs_hpm.h index 68211448c..327ffb2c4 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_hpm.h +++ b/src/portable/chipidea/ci_hs/ci_hs_hpm.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2021, Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ diff --git a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h index 75d1d55b8..8f0d6083e 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h +++ b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2021, Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -55,6 +36,9 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base) +// NXP recommends AHBBRST = INCR16 (remainder as unspecified-length bursts) +#define CI_HS_SET_AHB_BURST(_p) (CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC) + //------------- DCD -------------// #define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h index c22aea887..c7dc7e69f 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h +++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2021, Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -53,4 +34,9 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) +// USB0 (high-speed) only: NXP recommends AHBBRST = INCR16 (remainder as +// unspecified-length bursts) +#define CI_HS_SET_AHB_BURST(_p) \ + do { if ((_p) == 0) { CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC; } } while (0) + #endif diff --git a/src/portable/chipidea/ci_hs/ci_hs_mcx.h b/src/portable/chipidea/ci_hs/ci_hs_mcx.h index f940f4a9d..ebf52de39 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_mcx.h +++ b/src/portable/chipidea/ci_hs/ci_hs_mcx.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2021, Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ diff --git a/src/portable/chipidea/ci_hs/ci_hs_rw61x.h b/src/portable/chipidea/ci_hs/ci_hs_rw61x.h index 114fe26c3..ddd08da6b 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_rw61x.h +++ b/src/portable/chipidea/ci_hs/ci_hs_rw61x.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2021, Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ diff --git a/src/portable/chipidea/ci_hs/ci_hs_type.h b/src/portable/chipidea/ci_hs/ci_hs_type.h index 2f3aa3694..b3ef3b6af 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_type.h +++ b/src/portable/chipidea/ci_hs/ci_hs_type.h @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2021, Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2021, Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -48,6 +29,14 @@ enum { USBCMD_INTR_THRESHOLD_MASK = 0x00FF0000u, // Interrupt Threshold bit 23:16 }; +// DEVICEADDR +#define DEVICEADDR_USBADR_POS 25 + +enum { + DEVICEADDR_USBADRA = TU_BIT(24), ///< Device Address Advance: stage USBADR until the next EP0 IN is ACKed + DEVICEADDR_USBADR_MASK = 0xFE000000u, ///< Device Address bit 31:25 +}; + // PORTSC1 #define PORTSC1_PORT_SPEED_POS 26 @@ -55,10 +44,18 @@ enum { PORTSC1_CURRENT_CONNECT_STATUS = TU_BIT(0), PORTSC1_FORCE_PORT_RESUME = TU_BIT(6), PORTSC1_SUSPEND = TU_BIT(7), + PORTSC1_PORT_RESET = TU_BIT(8), // read-only in device mode: a reset is being driven PORTSC1_FORCE_FULL_SPEED = TU_BIT(24), PORTSC1_PORT_SPEED = TU_BIT(26) | TU_BIT(27) }; +// PORTSC1 PSPD field values, once shifted down by PORTSC1_PORT_SPEED_POS. 3 is undefined. +enum { + PORTSC1_PORT_SPEED_FULL = 0, + PORTSC1_PORT_SPEED_LOW = 1, + PORTSC1_PORT_SPEED_HIGH = 2, +}; + // OTGSC enum { OTGSC_VBUS_DISCHARGE = TU_BIT(0), @@ -90,11 +87,18 @@ enum { USBMODE_VBUS_POWER_SELECT = TU_BIT(5), // Need to be enabled for LPC18XX/43XX in host mode }; +// SBUSCFG +enum { + SBUSCFG_AHBBRST_INCR16_UNSPEC = 7, // INCR16 burst, remainder as unspecified-length bursts +}; + // Device Registers typedef struct { //------------- ID + HW Parameter Registers-------------// - volatile uint32_t TU_RESERVED[64]; ///< For iMX RT10xx, but not used by LPC18XX/LPC43XX + volatile uint32_t TU_RESERVED[36]; ///< ID/HW parameter registers, not used by this driver + volatile uint32_t SBUSCFG; ///< System Bus Interface Configuration (not present on every MCU) + volatile uint32_t TU_RESERVED[27]; //------------- Capability Registers-------------// volatile uint8_t CAPLENGTH; ///< Capability Registers Length diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 9ed75ffd9..f1c333280 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -173,6 +154,14 @@ TU_VERIFY_STATIC(sizeof(dcd_qhd_t) == 64, "size is not correct"); #define QTD_NEXT_INVALID 0x01 +// Bounded spin for register waits. The longest legitimate wait is a flush held off by a packet +// already in progress: ~50 us for a full-speed 64-byte packet, a low thousands of dependent +// register reads, so healthy hardware never approaches this bound. Exceeding it means the +// controller has stopped responding, and the spin then only serves to keep an ISR (or an +// IRQ-masked caller) from hanging outright - the 3 ms reset-cleanup window of IMXRT1060RM 42.5.6.2.1 (p.2394) +// is already unreachable in that state, and the manual's remedy there is a controller reset. +#define CI_HS_BUSY_SPIN 10000u + typedef struct { // Must be at 2K alignment // Each endpoint with direction (IN/OUT) occupies a queue head @@ -183,6 +172,17 @@ typedef struct { CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(2048) static dcd_data_t _dcd_data; +// What the next Port Change Detect will be. Each one is preceded by the interrupt that causes it: +// a reset interrupt for the end of a bus reset - where the speed first becomes final - or a +// suspend interrupt for the resume that ends the suspend. A suspend itself raises no port change, +// which is why there is no such value here. Indexed by rhport, which is 0 or 1 on every ci_hs +// variant (NOT the controller count: mcx/rw61x map rhport 1 to controller 0). +enum { + PORT_CHANGE_REASON_RESET = 0, + PORT_CHANGE_REASON_RESUME = 1, +}; +static volatile uint8_t _port_change_reason[2]; + //--------------------------------------------------------------------+ // Prototypes and Helper Functions //--------------------------------------------------------------------+ @@ -191,12 +191,37 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t ci_ep_count(const ci_hs_regs_t *dcd_ return dcd_reg->DCCPARAMS & DCCPARAMS_DEN_MASK; } +static bool controller_reset(uint8_t rhport); + //--------------------------------------------------------------------+ // Controller API //--------------------------------------------------------------------+ -/// follows LPC43xx User Manual 23.10.3 -static void bus_reset(uint8_t rhport) { +// Flush endpoint buffers, following IMXRT1060RM 42.5.6.6.5 Flushing/De-priming an Endpoint +// (p.2413): write ENDPTFLUSH, wait for the controller +// to acknowledge, then confirm ENDPTSTAT went to zero. The controller refuses the flush when a +// packet is in progress, and the manual requires the procedure be repeated until it takes. +// Callers proceed regardless of the result; the bound only prevents an ISR-context hang on dead +// hardware. +static bool flush_endpoints(ci_hs_regs_t *dcd_reg, uint32_t mask) { + uint32_t guard = CI_HS_BUSY_SPIN; + do { + dcd_reg->ENDPTFLUSH = mask; + while (dcd_reg->ENDPTFLUSH & mask) { + if (!guard--) { + return false; + } + } + } while ((dcd_reg->ENDPTSTAT & mask) && guard--); + + return !(dcd_reg->ENDPTSTAT & mask); +} + +/// Everything the manual asks of the DCD when a reset is detected, in its order: clear the setup +/// and completion semaphores, cancel every prime, check the reset is still being driven, and free +/// the dTDs. All of it belongs inside the reset window (IMXRT1060RM 42.5.6.2.1, p.2394); nothing +/// is left for the port change that ends the reset, which only reports the negotiated speed. +static void bus_reset_begin(uint8_t rhport) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); // The reset value for all endpoint types is the control endpoint. If one endpoint @@ -212,17 +237,24 @@ static void bus_reset(uint8_t rhport) { //------------- Clear All Registers -------------// dcd_reg->ENDPTNAK = dcd_reg->ENDPTNAK; dcd_reg->ENDPTNAKEN = 0; - dcd_reg->USBSTS = dcd_reg->USBSTS; dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT; dcd_reg->ENDPTCOMPLETE = dcd_reg->ENDPTCOMPLETE; - while (dcd_reg->ENDPTPRIME) {} - dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; - while (dcd_reg->ENDPTFLUSH) {} + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTPRIME && guard--) {} + dcd_reg->ENDPTFLUSH = 0xFFFFFFFFUL; - // read reset bit in portsc + // All of the above must land while the reset is still being driven - it lasts at least 3 ms. + // Arriving late leaves the controller in an undefined state, and the manual's remedy is to + // hardware-reset it. That clears Run/Stop, so the device detaches and the host will drive a + // fresh reset and enumeration - which is why nothing below this point is worth doing here. + if (!(dcd_reg->PORTSC1 & PORTSC1_PORT_RESET)) { + TU_LOG1("ci_hs: reset cleanup ran past the end of the reset, resetting controller\r\n"); + controller_reset(rhport); + return; // the controller detached; the host's next reset redoes everything below + } - //------------- Queue Head & Queue TD -------------// + //------------- Free all allocated dTDs: the controller will not execute them again -------------// tu_memclr(&_dcd_data, sizeof(dcd_data_t)); //------------- Set up Control Endpoints (0 OUT, 1 IN) -------------// @@ -235,27 +267,29 @@ static void bus_reset(uint8_t rhport) { dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); } -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { - (void)rh_init; - tu_memclr(&_dcd_data, sizeof(dcd_data_t)); - +/// Reset the controller and bring it back up in device mode. Also the manual's remedy when the +/// reset cleanup misses its window: the controller reset clears Run/Stop and detaches the device, +/// so it must be re-initialised completely afterwards (IMXRT1060RM 42.5.6.2.1, p.2394). +static bool controller_reset(uint8_t rhport) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); - TU_ASSERT(ci_ep_count(dcd_reg) <= TUP_DCD_ENDPOINT_MAX); - - #if TU_CHECK_MCU(OPT_MCU_HPM) - usb_phy_init((USB_Type *)dcd_reg, false); - #endif + tu_memclr(&_dcd_data, sizeof(dcd_data_t)); // Reset controller dcd_reg->USBCMD |= USBCMD_RESET; - while (dcd_reg->USBCMD & USBCMD_RESET) {} + uint32_t guard = CI_HS_BUSY_SPIN; + while ((dcd_reg->USBCMD & USBCMD_RESET) && guard--) {} + TU_VERIFY(!(dcd_reg->USBCMD & USBCMD_RESET)); // reached from the ISR too, so never halt here // Set mode to device, must be set immediately after reset uint32_t usbmode = dcd_reg->USBMODE & ~USBMOD_CM_MASK; usbmode |= USBMODE_CM_DEVICE; dcd_reg->USBMODE = usbmode; + #ifdef CI_HS_SET_AHB_BURST + CI_HS_SET_AHB_BURST(rhport); + #endif + #ifdef CFG_TUD_CI_HS_VBUS_CHARGE dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION; #else @@ -272,9 +306,11 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; + dcd_reg->ENDPTLISTADDR = (uint32_t)_dcd_data.qhd; // Endpoint List Address has to be 2K alignment dcd_reg->USBSTS = dcd_reg->USBSTS; - dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_SUSPEND; + dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_RESET | INTR_SUSPEND; uint32_t usbcmd = dcd_reg->USBCMD; usbcmd &= ~USBCMD_INTR_THRESHOLD_MASK; // Interrupt Threshold Interval = 0 @@ -285,8 +321,22 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { return true; } +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + + TU_ASSERT(ci_ep_count(dcd_reg) <= TUP_DCD_ENDPOINT_MAX); + + #if TU_CHECK_MCU(OPT_MCU_HPM) + usb_phy_init((USB_Type *)dcd_reg, false); + #endif + + return controller_reset(rhport); +} + bool dcd_deinit(uint8_t rhport) { ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport); + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; // disable all interrupt dcd_reg->USBINTR = 0; @@ -295,9 +345,9 @@ bool dcd_deinit(uint8_t rhport) { dcd_reg->USBCMD &= ~USBCMD_RUN_STOP; // flush all endpoints - while (dcd_reg->ENDPTPRIME) {} - dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; - while (dcd_reg->ENDPTFLUSH) {} + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTPRIME && guard--) {} + flush_endpoints(dcd_reg, 0xFFFFFFFF); return true; } @@ -311,11 +361,18 @@ void dcd_int_disable(uint8_t rhport) { } void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - // Response with status first before changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + const uint32_t prev = dcd_reg->DEVICEADDR & DEVICEADDR_USBADR_MASK; - ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); - dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24); + // IMXRT1060RM 42.7.23 / UM10503 Table 478: stage the address before priming the status stage so + // hardware loads USBADR at the status ACK. Priming first races that ACK against this write. + dcd_reg->DEVICEADDR = ((uint32_t)dev_addr << DEVICEADDR_USBADR_POS) | DEVICEADDR_USBADRA; + + if (!dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false)) { + // USB 2.0 9.4.6: the address changes only after the status stage completes successfully. The + // status never went out, so drop the stage - USBADRA=0 takes effect instantly. + dcd_reg->DEVICEADDR = prev; + } } void dcd_remote_wakeup(uint8_t rhport) { @@ -412,7 +469,8 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); dcd_reg->ENDPTCTRL[epnum] |= ENDPTCTRL_STALL << (dir ? 16 : 0); - // flush to abort any primed buffer + // flush to abort any primed buffer; the aborted transfer's dQH overlay can be left + // ACTIVE with mid-transfer state - qhd_start_xfer clears it before the next prime dcd_reg->ENDPTFLUSH = TU_BIT(epnum + (dir ? 16 : 0)); } @@ -482,9 +540,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) // dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); // Flush EP - const uint32_t flush_mask = TU_BIT(epnum + (dir ? 16 : 0)); - dcd_reg->ENDPTFLUSH = flush_mask; - while (dcd_reg->ENDPTFLUSH & flush_mask) {} + flush_endpoints(dcd_reg, TU_BIT(epnum + (dir ? 16 : 0))); // disable to change max packet size ep_ctrl_clear(endptctrl, dir, ENDPTCTRL_ENABLE); @@ -510,25 +566,35 @@ void dcd_edpt_close_all(uint8_t rhport) { } } -static void qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) { +static bool qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); dcd_qhd_t *p_qhd = &_dcd_data.qhd[epnum][dir]; dcd_qtd_t *p_qtd = &_dcd_data.qtd[epnum][dir]; p_qhd->qtd_overlay.halted = false; // clear any previous error + p_qhd->qtd_overlay.active = false; // a flushed prime leaves stale ACTIVE state; clear it so the fresh qtd loads p_qhd->qtd_overlay.next = (uint32_t)p_qtd; // link qtd to qhd // flush cache dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); if (epnum == 0) { - // follows UM 24.10.8.1.1 Setup packet handling using setup lockout mechanism - // wait until ENDPTSETUPSTAT before priming data/status in response TODO add time out - while (dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) {} + // Setup lockout (IMXRT1060RM 42.5.6.4.2.1 Setup Phase, p.2403): never prime EP0 while a new + // SETUP is pending. The ISR + // normally consumes ENDPTSETUPSTAT quickly; if the guard trips, fail the transfer so usbd + // releases the endpoint (a pending SETUP supersedes this response anyway; without one, usbd + // stalls EP0 and the host recovers with a fresh control transfer). + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) { + if (!guard--) { + return false; + } + } } // start transfer dcd_reg->ENDPTPRIME = TU_BIT(epnum + (dir ? 16 : 0)); + return true; } bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { @@ -544,9 +610,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to // Start qhd transfer p_qhd->ff = NULL; - qhd_start_xfer(rhport, epnum, dir); - - return true; + return qhd_start_xfer(rhport, epnum, dir); } #if !CFG_TUD_MEM_DCACHE_ENABLE @@ -597,9 +661,7 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t // Start qhd transfer p_qhd->ff = ff; - qhd_start_xfer(rhport, epnum, dir); - - return true; + return qhd_start_xfer(rhport, epnum, dir); } #endif @@ -647,43 +709,43 @@ void dcd_int_handler(uint8_t rhport) { return; } - // Set if the port controller enters the full or high-speed operational state. - // either from Bus Reset or Suspended state - if (int_status & INTR_PORT_CHANGE) { - // TU_LOG2("PortChange %08lx\r\n", dcd_reg->PORTSC1); - - // Reset interrupt is not enabled, we manually check if Port Change is due - // to connection / disconnection - if (dcd_reg->USBSTS & INTR_RESET) { - dcd_reg->USBSTS = INTR_RESET; + const uint8_t pci_reason = _port_change_reason[rhport]; // save current pci_reason - if (dcd_reg->PORTSC1 & PORTSC1_CURRENT_CONNECT_STATUS) { - const uint32_t speed = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS; - bus_reset(rhport); - dcd_event_bus_reset(rhport, (tusb_speed_t)speed, true); - } else { - dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); - } - } else { - // Triggered by resuming from suspended state - if (!(dcd_reg->PORTSC1 & PORTSC1_SUSPEND)) { - dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); - } - } + if (int_status & INTR_SUSPEND) { + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME; // next PCI is resume + dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); } - if (int_status & INTR_SUSPEND) { - // TU_LOG2("Suspend %08lx\r\n", dcd_reg->PORTSC1); + // USB Reset Received: register cleanup runs here within the reset window (IMXRT1060RM 42.5.6.2.1, p.2394) + // and BUS_RESET_START fires now; BUS_RESET_END, with the final speed, is triggered later by PCI. + if (int_status & INTR_RESET) { + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; + bus_reset_begin(rhport); + dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET_START, true); + } - if (dcd_reg->PORTSC1 & PORTSC1_SUSPEND) { - // Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration. - // Skip suspend event if we are not addressed - if ((dcd_reg->DEVICEADDR >> 25) & 0x0f) { - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); - } + // Port entered the full/high-speed operational state: the end of a bus reset, or a resume. + if (int_status & INTR_PORT_CHANGE) { + if (pci_reason == PORT_CHANGE_REASON_RESUME) { + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + } else { + // the undefined encoding falls back to full speed + const uint32_t pspd = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS; + const tusb_speed_t speed = (pspd == PORTSC1_PORT_SPEED_LOW) ? TUSB_SPEED_LOW : + (pspd == PORTSC1_PORT_SPEED_HIGH) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; + dcd_event_bus_reset(rhport, speed, true); + // This reset is over, so the next port change is a resume. Leaving it at RESET instead would + // dispatch every later resume as another end-of-reset, clearing the queue heads mid-session. + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME; } } + // No unplug detection yet, by the manual rather than by omission: IMXRT1060RM 42.7.31 (p.2470) says a zero + // Current Connect Status means the device "did not attach successfully or was forcibly + // disconnected by the software writing a zero to the Run bit ... It does not state the device + // being disconnected or suspended", so a cable pull raises no port change at all. VBUS via + // OTGSC BSV is the manual's disconnect indicator, and it is board dependent. + if (int_status & INTR_USB) { // Make sure we read the latest version of _dcd_data. dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); @@ -691,7 +753,7 @@ void dcd_int_handler(uint8_t rhport) { const uint32_t edpt_complete = dcd_reg->ENDPTCOMPLETE; dcd_reg->ENDPTCOMPLETE = edpt_complete; // acknowledge - // 23.10.12.3 Failed QTD also get ENDPTCOMPLETE set + // 42.5.6.6.4 Transfer Completion (p.2413): a failed dTD also sets ENDPTCOMPLETE // nothing to do, we will submit xfer as error to usbd // if (int_status & INTR_ERROR) { } @@ -707,12 +769,39 @@ void dcd_int_handler(uint8_t rhport) { } // Set up Received - // 23.10.10.2 Operational model for setup transfers + // 42.5.6.4.2 Control Endpoint Operation Model (p.2403) // Must be after normal transfer complete since it is possible to have both previous control status + new setup // in the same frame and we should handle previous status first. if (dcd_reg->ENDPTSETUPSTAT) { + // 42.5.6.4.2.1 Setup Phase (p.2403) steps 1-2: duplicate the setup payload BEFORE clearing + // ENDPTSETUPSTAT - + // the clear releases the setup lockout and a back-to-back SETUP (usbtest case 10) can + // overwrite the queue-head buffer immediately after. The copy is read through the volatile + // qualifier rather than memcpy'd because C orders volatile accesses only against each + // other: a plain copy may legally be sunk past the lockout-releasing store below. + union { + tusb_control_request_t request; + uint8_t byte[8]; + } setup; + const volatile uint8_t *setup_src = (const volatile uint8_t *)&_dcd_data.qhd[0][0].setup_request; + for (uint8_t i = 0; i < sizeof(setup.request); i++) { + setup.byte[i] = setup_src[i]; + } dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT; - dcd_event_setup_received(rhport, (uint8_t *)(uintptr_t)&_dcd_data.qhd[0][0].setup_request, true); + + // Retire a status/handshake phase left primed by the previous control sequence + // (IMXRT1060RM 42.5.6.4.2.1, p.2403), which would otherwise retire the response the task is about to + // prime for this setup. Skipped when EP0 has nothing primed or priming, since the manual + // does not want the flush wait in an interrupt handler when it has nothing to do. + // One volatile read per statement: C leaves their order unspecified within a single + // expression, which IAR rejects outright (Pa082). + const uint32_t ep0_mask = TU_BIT(0) | TU_BIT(16); + const uint32_t ep0_stat = dcd_reg->ENDPTSTAT; + const uint32_t ep0_prime = dcd_reg->ENDPTPRIME; + if ((ep0_stat | ep0_prime) & ep0_mask) { + flush_endpoints(dcd_reg, ep0_mask); + } + dcd_event_setup_received(rhport, setup.byte, true); } } diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index c0d14fe57..0f24f5bb6 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -1,25 +1,6 @@ /* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ @@ -101,6 +82,10 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { hcd_reg->USBMODE = USBMODE_CM_HOST; #endif + #ifdef CI_HS_SET_AHB_BURST + CI_HS_SET_AHB_BURST(rhport); + #endif + #if !TUH_OPT_HIGH_SPEED hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; #endif |
