|
|
@@ -0,0 +1,626 @@
|
|
|
+/*
|
|
|
+ * The MIT License (MIT)
|
|
|
+ *
|
|
|
+ * Copyright (c) 2021 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.
|
|
|
+ *
|
|
|
+ * This file is part of the TinyUSB stack.
|
|
|
+ */
|
|
|
+
|
|
|
+#include "tusb_option.h"
|
|
|
+
|
|
|
+#if TUSB_OPT_HOST_ENABLED && ( \
|
|
|
+ ( CFG_TUSB_MCU == OPT_MCU_MKL25ZXX ) || ( CFG_TUSB_MCU == OPT_MCU_K32L2BXX ) \
|
|
|
+ )
|
|
|
+
|
|
|
+#include "fsl_device_registers.h"
|
|
|
+#define KHCI USB0
|
|
|
+
|
|
|
+#include "host/hcd.h"
|
|
|
+
|
|
|
+//--------------------------------------------------------------------+
|
|
|
+// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
|
|
+//--------------------------------------------------------------------+
|
|
|
+
|
|
|
+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,
|
|
|
+};
|
|
|
+
|
|
|
+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" );
|
|
|
+
|
|
|
+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];
|
|
|
+ uint16_t bda[2*2];
|
|
|
+ };
|
|
|
+ endpoint_state_t endpoint[2];
|
|
|
+ pipe_state_t pipe[HCD_MAX_XFER * 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_TUSB_MEM_SECTION TU_ATTR_ALIGNED(512) static hcd_data_t _hcd;
|
|
|
+//CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[1024];
|
|
|
+
|
|
|
+int find_pipe(uint8_t dev_addr, uint8_t ep_addr)
|
|
|
+{
|
|
|
+ /* Find the target pipe */
|
|
|
+ int num;
|
|
|
+ for (num = 0; num < HCD_MAX_XFER * 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];
|
|
|
+ TU_ASSERT(0 == bd[odd].own, -1);
|
|
|
+
|
|
|
+ // TU_LOG1(" %p dir %d odd %d data %d\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 (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
|
|
|
+ KHCI->TOKEN = KHCI->TOKEN; /* Queue the same token as the last */
|
|
|
+ } else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */
|
|
|
+ while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
|
|
|
+ KHCI->TOKEN = KHCI->TOKEN;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ pipe->data = bd->data ^ 1;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static bool resume_transfer(int pipenum)
|
|
|
+{
|
|
|
+ int num_tokens = prepare_packets(pipenum);
|
|
|
+ TU_ASSERT(0 <= num_tokens);
|
|
|
+
|
|
|
+ const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
|
|
|
+ NVIC_DisableIRQ(USB0_IRQn);
|
|
|
+ pipe_state_t *pipe = &_hcd.pipe[pipenum];
|
|
|
+
|
|
|
+ unsigned flags = KHCI->ENDPOINT[0].ENDPT & 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\n", pipenum, flags);
|
|
|
+
|
|
|
+ KHCI->ENDPOINT[0].ENDPT = flags;
|
|
|
+ KHCI->ADDR = (KHCI->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 (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
|
|
|
+ KHCI->TOKEN = token;
|
|
|
+ } while (--num_tokens);
|
|
|
+ if (ie) NVIC_EnableIRQ(USB0_IRQn);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static void suspend_transfer(int pipenum, buffer_descriptor_t *bd)
|
|
|
+{
|
|
|
+ pipe_state_t *pipe = &_hcd.pipe[pipenum];
|
|
|
+ pipe->buffer = bd->addr;
|
|
|
+ pipe->data = bd->data ^ 1;
|
|
|
+ if ((TUSB_XFER_INTERRUPT == pipe->xfer) ||
|
|
|
+ (TUSB_XFER_BULK == pipe->xfer)) {
|
|
|
+ _hcd.pending |= TU_BIT(pipenum);
|
|
|
+ KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void process_tokdne(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ const unsigned s = KHCI->STAT;
|
|
|
+ KHCI->ISTAT = 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\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:
|
|
|
+ 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];
|
|
|
+ hcd_event_xfer_complete(pipe->dev_addr,
|
|
|
+ tu_edpt_addr(KHCI->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 = KHCI->CTL;
|
|
|
+ if (!(ctl & USB_CTL_JSTATE_MASK)) {
|
|
|
+ /* The attached device is a low speed device. */
|
|
|
+ KHCI->ADDR = USB_ADDR_LSEN_MASK;
|
|
|
+ KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_HOSTWOHUB_MASK;
|
|
|
+ }
|
|
|
+ hcd_event_device_attach(rhport, true);
|
|
|
+}
|
|
|
+
|
|
|
+static void process_bus_reset(uint8_t rhport)
|
|
|
+{
|
|
|
+ KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK;
|
|
|
+ KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
|
|
|
+ KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
|
|
|
+ KHCI->ADDR = 0;
|
|
|
+ KHCI->ENDPOINT[0].ENDPT = 0;
|
|
|
+
|
|
|
+ hcd_event_device_remove(rhport, true);
|
|
|
+
|
|
|
+ _hcd.in_progress = 0;
|
|
|
+ _hcd.pending = 0;
|
|
|
+ buffer_descriptor_t *bd = &_hcd.bdt[0][0];
|
|
|
+ for (unsigned i = 0; i < 2; ++i, ++bd) {
|
|
|
+ bd->head = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*------------------------------------------------------------------*/
|
|
|
+/* Host API
|
|
|
+ *------------------------------------------------------------------*/
|
|
|
+bool hcd_init(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+
|
|
|
+ KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
|
|
|
+ while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK);
|
|
|
+
|
|
|
+ tu_memclr(&_hcd, sizeof(_hcd));
|
|
|
+ KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */
|
|
|
+ KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8);
|
|
|
+ KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16);
|
|
|
+ KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24);
|
|
|
+
|
|
|
+ KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
|
|
|
+ KHCI->CTL |= USB_CTL_ODDRST_MASK;
|
|
|
+ for (unsigned i = 0; i < 16; ++i) {
|
|
|
+ KHCI->ENDPOINT[i].ENDPT = 0;
|
|
|
+ }
|
|
|
+ KHCI->CTL &= ~USB_CTL_ODDRST_MASK;
|
|
|
+
|
|
|
+ KHCI->SOFTHLD = 74; /* for 64-byte packets */
|
|
|
+ // KHCI->SOFTHLD = 144; /* for low speed 8-byte packets */
|
|
|
+ KHCI->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK;
|
|
|
+ KHCI->USBCTRL = USB_USBCTRL_PDE_MASK;
|
|
|
+
|
|
|
+ NVIC_ClearPendingIRQ(USB0_IRQn);
|
|
|
+ KHCI->INTEN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK |
|
|
|
+ USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK;
|
|
|
+ KHCI->ERREN = 0xff;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void hcd_int_enable(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ NVIC_EnableIRQ(USB0_IRQn);
|
|
|
+}
|
|
|
+
|
|
|
+void hcd_int_disable(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ NVIC_DisableIRQ(USB0_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 = KHCI->FRMNUML;
|
|
|
+ frmnum |= KHCI->FRMNUMH << 8u;
|
|
|
+ return frmnum;
|
|
|
+}
|
|
|
+
|
|
|
+/*--------------------------------------------------------------------+
|
|
|
+ * Port API
|
|
|
+ *--------------------------------------------------------------------+ */
|
|
|
+bool hcd_port_connect_status(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ if (KHCI->ISTAT & USB_ISTAT_ATTACH_MASK)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+void hcd_port_reset(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
|
|
|
+ KHCI->CTL |= USB_CTL_RESET_MASK;
|
|
|
+ unsigned cnt = SystemCoreClock / 100;
|
|
|
+ while (cnt--) __NOP();
|
|
|
+ KHCI->CTL &= ~USB_CTL_RESET_MASK;
|
|
|
+ KHCI->CTL |= USB_CTL_USBENSOFEN_MASK;
|
|
|
+ _hcd.need_reset = false;
|
|
|
+}
|
|
|
+
|
|
|
+tusb_speed_t hcd_port_speed_get(uint8_t rhport)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ tusb_speed_t speed = TUSB_SPEED_FULL;
|
|
|
+ const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
|
|
|
+ NVIC_DisableIRQ(USB0_IRQn);
|
|
|
+ if (KHCI->ADDR & USB_ADDR_LSEN_MASK)
|
|
|
+ speed = TUSB_SPEED_LOW;
|
|
|
+ if (ie) NVIC_EnableIRQ(USB0_IRQn);
|
|
|
+ return speed;
|
|
|
+}
|
|
|
+
|
|
|
+void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
|
|
|
+{
|
|
|
+ (void)rhport;
|
|
|
+ const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn);
|
|
|
+ NVIC_DisableIRQ(USB0_IRQn);
|
|
|
+ pipe_state_t *p = &_hcd.pipe[0];
|
|
|
+ pipe_state_t *end = &_hcd.pipe[HCD_MAX_XFER * 2];
|
|
|
+ for (;p != end; ++p) {
|
|
|
+ if (p->dev_addr == dev_addr)
|
|
|
+ tu_memclr(p, sizeof(*p));
|
|
|
+ }
|
|
|
+ if (ie) NVIC_EnableIRQ(USB0_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\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 = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
|
|
|
+ KHCI->ENDPOINT[0].ENDPT = hostwohub |
|
|
|
+ USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK;
|
|
|
+ KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | dev_addr;
|
|
|
+ while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
|
|
|
+ KHCI->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\n", dev_addr, ep_addr);
|
|
|
+ /* Find a free pipe */
|
|
|
+ pipe_state_t *p = &_hcd.pipe[0];
|
|
|
+ pipe_state_t *end = &_hcd.pipe[HCD_MAX_XFER * 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;
|
|
|
+}
|
|
|
+
|
|
|
+/* 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\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(USB0_IRQn);
|
|
|
+ NVIC_DisableIRQ(USB0_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 */
|
|
|
+ KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
|
|
|
+ if (ie) NVIC_EnableIRQ(USB0_IRQn);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
|
|
|
+{
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ uint32_t is = KHCI->ISTAT;
|
|
|
+ uint32_t msk = KHCI->INTEN;
|
|
|
+
|
|
|
+ // TU_LOG1("S %lx\n", is);
|
|
|
+
|
|
|
+ /* clear disabled interrupts */
|
|
|
+ KHCI->ISTAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK;
|
|
|
+ is &= msk;
|
|
|
+
|
|
|
+ if (is & USB_ISTAT_ERROR_MASK) {
|
|
|
+ unsigned err = KHCI->ERRSTAT;
|
|
|
+ if (err) {
|
|
|
+ TU_LOG1(" ERR %x\n", err);
|
|
|
+ KHCI->ERRSTAT = err;
|
|
|
+ } else {
|
|
|
+ KHCI->INTEN &= ~USB_ISTAT_ERROR_MASK;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (is & USB_ISTAT_USBRST_MASK) {
|
|
|
+ KHCI->INTEN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK;
|
|
|
+ process_bus_reset(rhport);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (is & USB_ISTAT_ATTACH_MASK) {
|
|
|
+ KHCI->INTEN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK;
|
|
|
+ _hcd.need_reset = true;
|
|
|
+ process_attach(rhport);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (is & USB_ISTAT_STALL_MASK) {
|
|
|
+ KHCI->ISTAT = USB_ISTAT_STALL_MASK;
|
|
|
+ }
|
|
|
+ if (is & USB_ISTAT_SOFTOK_MASK) {
|
|
|
+ msk &= ~USB_ISTAT_SOFTOK_MASK;
|
|
|
+ KHCI->INTEN = 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
|