2020-01-15 05:30:39 +01:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* This file is part of the TinyUSB stack.
|
|
|
|
*/
|
|
|
|
|
2022-02-25 16:45:05 +01:00
|
|
|
#include "tusb_option.h"
|
2020-01-15 05:30:39 +01:00
|
|
|
|
2022-02-26 11:03:54 +01:00
|
|
|
#if CFG_TUH_ENABLED && defined(TUP_USBIP_EHCI)
|
2021-02-22 12:02:52 +01:00
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
// INCLUDE
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
#include "osal/osal.h"
|
|
|
|
|
2021-02-23 17:09:48 +01:00
|
|
|
#include "host/hcd.h"
|
2021-08-23 07:37:54 +02:00
|
|
|
#include "ehci_api.h"
|
2020-01-15 05:30:39 +01:00
|
|
|
#include "ehci.h"
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
// MACRO CONSTANT TYPEDEF
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
2021-08-23 07:37:54 +02:00
|
|
|
// Debug level of EHCI
|
|
|
|
#define EHCI_DBG 2
|
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
// Framelist size as small as possible to save SRAM
|
2022-02-26 09:18:51 +01:00
|
|
|
#ifdef TUP_USBIP_CHIPIDEA_HS
|
2021-08-23 10:40:57 +02:00
|
|
|
// NXP Transdimension: 8 elements
|
|
|
|
#define FRAMELIST_SIZE_BIT_VALUE 7u
|
|
|
|
#define FRAMELIST_SIZE_USBCMD_VALUE (((FRAMELIST_SIZE_BIT_VALUE & 3) << EHCI_USBCMD_POS_FRAMELIST_SIZE) | \
|
|
|
|
((FRAMELIST_SIZE_BIT_VALUE >> 2) << EHCI_USBCMD_POS_NXP_FRAMELIST_SIZE_MSB))
|
|
|
|
#else
|
|
|
|
// STD EHCI: 256 elements
|
|
|
|
#define FRAMELIST_SIZE_BIT_VALUE 2u
|
|
|
|
#define FRAMELIST_SIZE_USBCMD_VALUE ((FRAMELIST_SIZE_BIT_VALUE & 3) << EHCI_USBCMD_POS_FRAMELIST_SIZE)
|
|
|
|
#endif
|
2021-08-23 07:37:54 +02:00
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
#define FRAMELIST_SIZE (1024 >> FRAMELIST_SIZE_BIT_VALUE)
|
2021-08-23 07:37:54 +02:00
|
|
|
|
2022-03-04 15:04:52 +01:00
|
|
|
#define QHD_MAX (CFG_TUH_DEVICE_MAX*CFG_TUH_ENDPOINT_MAX)
|
|
|
|
#define QTD_MAX QHD_MAX
|
2022-03-04 11:07:04 +01:00
|
|
|
|
2021-05-31 11:05:55 +02:00
|
|
|
typedef struct
|
|
|
|
{
|
2021-08-23 10:40:57 +02:00
|
|
|
ehci_link_t period_framelist[FRAMELIST_SIZE];
|
2021-05-31 11:05:55 +02:00
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
// TODO only implement 1 ms & 2 ms & 4 ms, 8 ms (framelist)
|
2021-05-31 11:05:55 +02:00
|
|
|
// [0] : 1ms, [1] : 2ms, [2] : 4ms, [3] : 8 ms
|
2021-08-23 10:40:57 +02:00
|
|
|
// TODO better implementation without dummy head to save SRAM
|
2021-05-31 11:05:55 +02:00
|
|
|
ehci_qhd_t period_head_arr[4];
|
|
|
|
|
|
|
|
// Note control qhd of dev0 is used as head of async list
|
|
|
|
struct {
|
|
|
|
ehci_qhd_t qhd;
|
|
|
|
ehci_qtd_t qtd;
|
2021-08-23 14:56:53 +02:00
|
|
|
}control[CFG_TUH_DEVICE_MAX+CFG_TUH_HUB+1];
|
2021-05-31 11:05:55 +02:00
|
|
|
|
2022-03-04 16:31:48 +01:00
|
|
|
ehci_qhd_t qhd_pool[QHD_MAX];
|
2022-03-04 15:04:52 +01:00
|
|
|
ehci_qtd_t qtd_pool[QTD_MAX] TU_ATTR_ALIGNED(32);
|
2021-05-31 11:05:55 +02:00
|
|
|
|
|
|
|
ehci_registers_t* regs;
|
|
|
|
|
|
|
|
volatile uint32_t uframe_number;
|
|
|
|
}ehci_data_t;
|
2021-08-23 07:37:54 +02:00
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
// Periodic frame list must be 4K alignment
|
|
|
|
CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4096) static ehci_data_t ehci_data;
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
// PROTOTYPE
|
|
|
|
//--------------------------------------------------------------------+
|
2021-08-23 10:40:57 +02:00
|
|
|
static inline ehci_link_t* get_period_head(uint8_t rhport, uint32_t interval_ms)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
(void) rhport;
|
2021-08-23 10:40:57 +02:00
|
|
|
return (ehci_link_t*) &ehci_data.period_head_arr[ tu_log2( tu_min32(FRAMELIST_SIZE, interval_ms) ) ];
|
2020-01-15 05:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_control(uint8_t dev_addr)
|
|
|
|
{
|
|
|
|
return &ehci_data.control[dev_addr].qhd;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_async_head(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
2021-05-31 11:05:55 +02:00
|
|
|
// control qhd of dev0 is used as async head
|
|
|
|
return qhd_control(0);
|
2020-01-15 05:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_qtd_t* qtd_control(uint8_t dev_addr)
|
|
|
|
{
|
|
|
|
return &ehci_data.control[dev_addr].qtd;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_next (ehci_qhd_t const * p_qhd);
|
|
|
|
static inline ehci_qhd_t* qhd_find_free (void);
|
|
|
|
static inline ehci_qhd_t* qhd_get_from_addr (uint8_t dev_addr, uint8_t ep_addr);
|
|
|
|
|
|
|
|
// determine if a queue head has bus-related error
|
|
|
|
static inline bool qhd_has_xact_error (ehci_qhd_t * p_qhd)
|
|
|
|
{
|
|
|
|
return (p_qhd->qtd_overlay.buffer_err || p_qhd->qtd_overlay.babble_err || p_qhd->qtd_overlay.xact_err);
|
|
|
|
//p_qhd->qtd_overlay.non_hs_period_missed_uframe || p_qhd->qtd_overlay.pingstate_err TODO split transaction error
|
|
|
|
}
|
|
|
|
|
2021-08-23 07:37:54 +02:00
|
|
|
static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc);
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
static inline ehci_qtd_t* qtd_find_free (void);
|
|
|
|
static inline ehci_qtd_t* qtd_next (ehci_qtd_t const * p_qtd);
|
|
|
|
static inline void qtd_insert_to_qhd (ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new);
|
|
|
|
static inline void qtd_remove_1st_from_qhd (ehci_qhd_t *p_qhd);
|
2021-10-17 11:26:27 +02:00
|
|
|
static void qtd_init (ehci_qtd_t* p_qtd, void const* buffer, uint16_t total_bytes);
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
static inline void list_insert (ehci_link_t *current, ehci_link_t *new, uint8_t new_type);
|
|
|
|
static inline ehci_link_t* list_next (ehci_link_t *p_link_pointer);
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
// HCD API
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
2021-05-31 13:15:47 +02:00
|
|
|
uint32_t hcd_frame_number(uint8_t rhport)
|
2020-05-04 09:11:58 +02:00
|
|
|
{
|
|
|
|
(void) rhport;
|
2021-05-31 13:15:47 +02:00
|
|
|
return (ehci_data.uframe_number + ehci_data.regs->frame_index) >> 3;
|
2020-05-04 09:11:58 +02:00
|
|
|
}
|
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
void hcd_port_reset(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
ehci_registers_t* regs = ehci_data.regs;
|
|
|
|
|
2020-05-18 19:55:43 +02:00
|
|
|
// regs->portsc_bm.port_enabled = 0; // disable port before reset
|
|
|
|
// regs->portsc_bm.port_reset = 1;
|
|
|
|
|
|
|
|
uint32_t portsc = regs->portsc;
|
|
|
|
|
|
|
|
portsc &= ~(EHCI_PORTSC_MASK_PORT_EANBLED);
|
|
|
|
portsc |= EHCI_PORTSC_MASK_PORT_RESET;
|
|
|
|
|
|
|
|
regs->portsc = portsc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void hcd_port_reset_end(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
2022-03-02 06:33:47 +01:00
|
|
|
#if 0
|
2020-05-18 19:55:43 +02:00
|
|
|
ehci_registers_t* regs = ehci_data.regs;
|
|
|
|
regs->portsc_bm.port_reset = 0;
|
|
|
|
#endif
|
2022-03-02 06:33:47 +01:00
|
|
|
}
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
bool hcd_port_connect_status(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
return ehci_data.regs->portsc_bm.current_connect_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
tusb_speed_t hcd_port_speed_get(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
return (tusb_speed_t) ehci_data.regs->portsc_bm.nxp_port_speed; // NXP specific port speed
|
|
|
|
}
|
|
|
|
|
|
|
|
static void list_remove_qhd_by_addr(ehci_link_t* list_head, uint8_t dev_addr)
|
|
|
|
{
|
|
|
|
for(ehci_link_t* prev = list_head;
|
|
|
|
!prev->terminate && (tu_align32(prev->address) != (uint32_t) list_head);
|
|
|
|
prev = list_next(prev) )
|
|
|
|
{
|
|
|
|
// TODO check type for ISO iTD and siTD
|
2022-03-04 11:07:04 +01:00
|
|
|
// TODO Suppress cast-align warning
|
2022-03-04 01:53:22 +01:00
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wcast-align"
|
2020-01-15 05:30:39 +01:00
|
|
|
ehci_qhd_t* qhd = (ehci_qhd_t*) list_next(prev);
|
2022-03-04 01:53:22 +01:00
|
|
|
#pragma GCC diagnostic pop
|
2020-01-15 05:30:39 +01:00
|
|
|
if ( qhd->dev_addr == dev_addr )
|
|
|
|
{
|
2022-12-04 07:58:47 +01:00
|
|
|
// TODO deactivate all TD, wait for QHD to inactive before removal
|
2020-01-15 05:30:39 +01:00
|
|
|
prev->address = qhd->next.address;
|
|
|
|
|
|
|
|
// EHCI 4.8.2 link the removed qhd to async head (which always reachable by Host Controller)
|
|
|
|
qhd->next.address = ((uint32_t) list_head) | (EHCI_QTYPE_QHD << 1);
|
|
|
|
|
|
|
|
if ( qhd->int_smask )
|
|
|
|
{
|
|
|
|
// period list queue element is guarantee to be free in the next frame (1 ms)
|
|
|
|
qhd->used = 0;
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
// async list use async advance handshake
|
|
|
|
// mark as removing, will completely re-usable when async advance isr occurs
|
|
|
|
qhd->removing = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close all opened endpoint belong to this device
|
|
|
|
void hcd_device_close(uint8_t rhport, uint8_t dev_addr)
|
|
|
|
{
|
|
|
|
// skip dev0
|
|
|
|
if (dev_addr == 0) return;
|
|
|
|
|
|
|
|
// Remove from async list
|
|
|
|
list_remove_qhd_by_addr( (ehci_link_t*) qhd_async_head(rhport), dev_addr );
|
|
|
|
|
|
|
|
// Remove from all interval period list
|
|
|
|
for(uint8_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++)
|
|
|
|
{
|
|
|
|
list_remove_qhd_by_addr( (ehci_link_t*) &ehci_data.period_head_arr[i], dev_addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Async doorbell (EHCI 4.8.2 for operational details)
|
|
|
|
ehci_data.regs->command_bm.async_adv_doorbell = 1;
|
|
|
|
}
|
|
|
|
|
2021-08-23 07:37:54 +02:00
|
|
|
bool ehci_init(uint8_t rhport, uint32_t capability_reg, uint32_t operatial_reg)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
2021-08-23 07:37:54 +02:00
|
|
|
(void) capability_reg; // not used yet
|
|
|
|
|
2021-02-22 16:40:51 +01:00
|
|
|
tu_memclr(&ehci_data, sizeof(ehci_data_t));
|
|
|
|
|
2021-08-23 07:37:54 +02:00
|
|
|
ehci_data.regs = (ehci_registers_t* ) operatial_reg;
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
ehci_registers_t* regs = ehci_data.regs;
|
|
|
|
|
|
|
|
//------------- CTRLDSSEGMENT Register (skip) -------------//
|
|
|
|
//------------- USB INT Register -------------//
|
|
|
|
regs->inten = 0; // 1. disable all the interrupt
|
|
|
|
regs->status = EHCI_INT_MASK_ALL; // 2. clear all status
|
|
|
|
|
|
|
|
regs->inten = EHCI_INT_MASK_ERROR | EHCI_INT_MASK_PORT_CHANGE | EHCI_INT_MASK_ASYNC_ADVANCE |
|
2020-05-04 09:11:58 +02:00
|
|
|
EHCI_INT_MASK_NXP_PERIODIC | EHCI_INT_MASK_NXP_ASYNC | EHCI_INT_MASK_FRAMELIST_ROLLOVER;
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
//------------- Asynchronous List -------------//
|
|
|
|
ehci_qhd_t * const async_head = qhd_async_head(rhport);
|
|
|
|
tu_memclr(async_head, sizeof(ehci_qhd_t));
|
|
|
|
|
|
|
|
async_head->next.address = (uint32_t) async_head; // circular list, next is itself
|
|
|
|
async_head->next.type = EHCI_QTYPE_QHD;
|
|
|
|
async_head->head_list_flag = 1;
|
|
|
|
async_head->qtd_overlay.halted = 1; // inactive most of time
|
|
|
|
async_head->qtd_overlay.next.terminate = 1; // TODO removed if verified
|
|
|
|
|
|
|
|
regs->async_list_addr = (uint32_t) async_head;
|
|
|
|
|
|
|
|
//------------- Periodic List -------------//
|
|
|
|
// Build the polling interval tree with 1 ms, 2 ms, 4 ms and 8 ms (framesize) only
|
2021-08-23 10:40:57 +02:00
|
|
|
for ( uint32_t i = 0; i < TU_ARRAY_SIZE(ehci_data.period_head_arr); i++ )
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
2021-08-23 10:40:57 +02:00
|
|
|
ehci_data.period_head_arr[i].int_smask = 1; // queue head in period list must have smask non-zero
|
2020-01-15 05:30:39 +01:00
|
|
|
ehci_data.period_head_arr[i].qtd_overlay.halted = 1; // dummy node, always inactive
|
|
|
|
}
|
|
|
|
|
|
|
|
ehci_link_t * const framelist = ehci_data.period_framelist;
|
2021-08-23 10:40:57 +02:00
|
|
|
ehci_link_t * const period_1ms = get_period_head(rhport, 1u);
|
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
// all links --> period_head_arr[0] (1ms)
|
|
|
|
// 0, 2, 4, 6 etc --> period_head_arr[1] (2ms)
|
|
|
|
// 1, 5 --> period_head_arr[2] (4ms)
|
|
|
|
// 3 --> period_head_arr[3] (8ms)
|
|
|
|
|
|
|
|
// TODO EHCI_FRAMELIST_SIZE with other size than 8
|
2021-08-23 10:40:57 +02:00
|
|
|
for(uint32_t i=0; i<FRAMELIST_SIZE; i++)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
framelist[i].address = (uint32_t) period_1ms;
|
|
|
|
framelist[i].type = EHCI_QTYPE_QHD;
|
|
|
|
}
|
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
for(uint32_t i=0; i<FRAMELIST_SIZE; i+=2)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
2021-08-23 10:40:57 +02:00
|
|
|
list_insert(framelist + i, get_period_head(rhport, 2u), EHCI_QTYPE_QHD);
|
2020-01-15 05:30:39 +01:00
|
|
|
}
|
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
for(uint32_t i=1; i<FRAMELIST_SIZE; i+=4)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
2021-08-23 10:40:57 +02:00
|
|
|
list_insert(framelist + i, get_period_head(rhport, 4u), EHCI_QTYPE_QHD);
|
2020-01-15 05:30:39 +01:00
|
|
|
}
|
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
list_insert(framelist+3, get_period_head(rhport, 8u), EHCI_QTYPE_QHD);
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
period_1ms->terminate = 1;
|
|
|
|
|
|
|
|
regs->periodic_list_base = (uint32_t) framelist;
|
|
|
|
|
|
|
|
//------------- TT Control (NXP only) -------------//
|
|
|
|
regs->nxp_tt_control = 0;
|
|
|
|
|
|
|
|
//------------- USB CMD Register -------------//
|
2021-08-23 10:40:57 +02:00
|
|
|
regs->command |= TU_BIT(EHCI_USBCMD_POS_RUN_STOP) | TU_BIT(EHCI_USBCMD_POS_ASYNC_ENABLE) |
|
|
|
|
TU_BIT(EHCI_USBCMD_POS_PERIOD_ENABLE) | // TODO enable period list only there is int/iso endpoint
|
|
|
|
FRAMELIST_SIZE_USBCMD_VALUE;
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
//------------- ConfigFlag Register (skip) -------------//
|
|
|
|
regs->portsc_bm.port_power = 1; // enable port power
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
static void ehci_stop(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
ehci_registers_t* regs = ehci_data.regs;
|
|
|
|
|
|
|
|
regs->command_bm.run_stop = 0;
|
|
|
|
|
|
|
|
// USB Spec: controller has to stop within 16 uframe = 2 frames
|
|
|
|
while( regs->status_bm.hc_halted == 0 ) {}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
2021-05-31 07:08:37 +02:00
|
|
|
// Endpoint API
|
2020-01-15 05:30:39 +01:00
|
|
|
//--------------------------------------------------------------------+
|
2021-05-31 07:08:37 +02:00
|
|
|
|
2021-05-31 11:05:55 +02:00
|
|
|
bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
// TODO not support ISO yet
|
|
|
|
TU_ASSERT (ep_desc->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS);
|
|
|
|
|
|
|
|
//------------- Prepare Queue Head -------------//
|
|
|
|
ehci_qhd_t * p_qhd;
|
|
|
|
|
|
|
|
if ( ep_desc->bEndpointAddress == 0 )
|
|
|
|
{
|
|
|
|
p_qhd = qhd_control(dev_addr);
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
p_qhd = qhd_find_free();
|
|
|
|
}
|
|
|
|
TU_ASSERT(p_qhd);
|
|
|
|
|
|
|
|
qhd_init(p_qhd, dev_addr, ep_desc);
|
|
|
|
|
|
|
|
// control of dev0 is always present as async head
|
|
|
|
if ( dev_addr == 0 ) return true;
|
|
|
|
|
|
|
|
// Insert to list
|
|
|
|
ehci_link_t * list_head = NULL;
|
|
|
|
|
|
|
|
switch (ep_desc->bmAttributes.xfer)
|
|
|
|
{
|
|
|
|
case TUSB_XFER_CONTROL:
|
|
|
|
case TUSB_XFER_BULK:
|
|
|
|
list_head = (ehci_link_t*) qhd_async_head(rhport);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TUSB_XFER_INTERRUPT:
|
|
|
|
list_head = get_period_head(rhport, p_qhd->interval_ms);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TUSB_XFER_ISOCHRONOUS:
|
|
|
|
// TODO iso is not supported
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
TU_ASSERT(list_head);
|
|
|
|
|
|
|
|
// TODO might need to disable async/period list
|
|
|
|
list_insert(list_head, (ehci_link_t*) p_qhd, EHCI_QTYPE_QHD);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-31 07:08:37 +02:00
|
|
|
bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8])
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
ehci_qhd_t* qhd = &ehci_data.control[dev_addr].qhd;
|
|
|
|
ehci_qtd_t* td = &ehci_data.control[dev_addr].qtd;
|
|
|
|
|
2021-10-17 11:26:27 +02:00
|
|
|
qtd_init(td, setup_packet, 8);
|
2021-05-31 07:08:37 +02:00
|
|
|
td->pid = EHCI_PID_SETUP;
|
|
|
|
td->int_on_complete = 1;
|
|
|
|
td->next.terminate = 1;
|
|
|
|
|
|
|
|
// sw region
|
|
|
|
qhd->p_qtd_list_head = td;
|
|
|
|
qhd->p_qtd_list_tail = td;
|
|
|
|
|
|
|
|
// attach TD
|
|
|
|
qhd->qtd_overlay.next.address = (uint32_t) td;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
uint8_t const epnum = tu_edpt_number(ep_addr);
|
|
|
|
uint8_t const dir = tu_edpt_dir(ep_addr);
|
|
|
|
|
|
|
|
if ( epnum == 0 )
|
|
|
|
{
|
|
|
|
ehci_qhd_t* qhd = qhd_control(dev_addr);
|
|
|
|
ehci_qtd_t* qtd = qtd_control(dev_addr);
|
|
|
|
|
|
|
|
qtd_init(qtd, buffer, buflen);
|
|
|
|
|
|
|
|
// first first data toggle is always 1 (data & setup stage)
|
|
|
|
qtd->data_toggle = 1;
|
|
|
|
qtd->pid = dir ? EHCI_PID_IN : EHCI_PID_OUT;
|
|
|
|
qtd->int_on_complete = 1;
|
|
|
|
qtd->next.terminate = 1;
|
|
|
|
|
|
|
|
// sw region
|
|
|
|
qhd->p_qtd_list_head = qtd;
|
|
|
|
qhd->p_qtd_list_tail = qtd;
|
|
|
|
|
|
|
|
// attach TD
|
|
|
|
qhd->qtd_overlay.next.address = (uint32_t) qtd;
|
2020-09-05 15:34:45 +02:00
|
|
|
}else
|
|
|
|
{
|
2020-09-06 06:49:00 +02:00
|
|
|
ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
|
|
|
|
ehci_qtd_t *p_qtd = qtd_find_free();
|
|
|
|
TU_ASSERT(p_qtd);
|
|
|
|
|
|
|
|
qtd_init(p_qtd, buffer, buflen);
|
|
|
|
p_qtd->pid = p_qhd->pid;
|
|
|
|
|
|
|
|
// Insert TD to QH
|
|
|
|
qtd_insert_to_qhd(p_qhd, p_qtd);
|
|
|
|
|
|
|
|
p_qhd->p_qtd_list_tail->int_on_complete = 1;
|
|
|
|
|
|
|
|
// attach head QTD to QHD start transferring
|
|
|
|
p_qhd->qtd_overlay.next.address = (uint32_t) p_qhd->p_qtd_list_head;
|
2020-01-15 05:30:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr)
|
|
|
|
{
|
|
|
|
ehci_qhd_t *p_qhd = qhd_get_from_addr(dev_addr, ep_addr);
|
|
|
|
p_qhd->qtd_overlay.halted = 0;
|
|
|
|
// TODO reset data toggle ?
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
// EHCI Interrupt Handler
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
|
|
|
// async_advance is handshake between usb stack & ehci controller.
|
|
|
|
// This isr mean it is safe to modify previously removed queue head from async list.
|
|
|
|
// In tinyusb, queue head is only removed when device is unplugged.
|
|
|
|
static void async_advance_isr(uint8_t rhport)
|
|
|
|
{
|
|
|
|
(void) rhport;
|
|
|
|
|
|
|
|
ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
|
2022-03-04 16:31:48 +01:00
|
|
|
for(uint32_t i = 0; i < QHD_MAX; i++)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
if ( qhd_pool[i].removing )
|
|
|
|
{
|
|
|
|
qhd_pool[i].removing = 0;
|
|
|
|
qhd_pool[i].used = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-23 07:37:54 +02:00
|
|
|
static void port_connect_status_change_isr(uint8_t rhport)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
// NOTE There is an sequence plug->unplug->…..-> plug if device is powering with pre-plugged device
|
|
|
|
if (ehci_data.regs->portsc_bm.current_connect_status)
|
|
|
|
{
|
2021-08-23 07:37:54 +02:00
|
|
|
hcd_port_reset(rhport);
|
|
|
|
hcd_event_device_attach(rhport, true);
|
2020-01-15 05:30:39 +01:00
|
|
|
}else // device unplugged
|
|
|
|
{
|
2021-08-23 07:37:54 +02:00
|
|
|
hcd_event_device_remove(rhport, true);
|
2020-01-15 05:30:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qhd_xfer_complete_isr(ehci_qhd_t * p_qhd)
|
|
|
|
{
|
|
|
|
// free all TDs from the head td to the first active TD
|
|
|
|
while(p_qhd->p_qtd_list_head != NULL && !p_qhd->p_qtd_list_head->active)
|
|
|
|
{
|
2021-05-11 13:11:46 +02:00
|
|
|
ehci_qtd_t * volatile qtd = (ehci_qtd_t * volatile) p_qhd->p_qtd_list_head;
|
|
|
|
bool const is_ioc = (qtd->int_on_complete != 0);
|
|
|
|
uint8_t const ep_addr = tu_edpt_addr(p_qhd->ep_number, qtd->pid == EHCI_PID_IN ? 1 : 0);
|
|
|
|
|
|
|
|
p_qhd->total_xferred_bytes += qtd->expected_bytes - qtd->total_bytes;
|
2020-01-15 05:30:39 +01:00
|
|
|
|
2021-05-11 13:11:46 +02:00
|
|
|
// TD need to be freed and removed from qhd, before invoking callback
|
|
|
|
qtd->used = 0; // free QTD
|
2020-01-15 05:30:39 +01:00
|
|
|
qtd_remove_1st_from_qhd(p_qhd);
|
|
|
|
|
|
|
|
if (is_ioc)
|
|
|
|
{
|
2021-05-11 13:11:46 +02:00
|
|
|
hcd_event_xfer_complete(p_qhd->dev_addr, ep_addr, p_qhd->total_xferred_bytes, XFER_RESULT_SUCCESS, true);
|
2020-01-15 05:30:39 +01:00
|
|
|
p_qhd->total_xferred_bytes = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void async_list_xfer_complete_isr(ehci_qhd_t * const async_head)
|
|
|
|
{
|
|
|
|
ehci_qhd_t *p_qhd = async_head;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
if ( !p_qhd->qtd_overlay.halted ) // halted or error is processed in error isr
|
|
|
|
{
|
|
|
|
qhd_xfer_complete_isr(p_qhd);
|
|
|
|
}
|
|
|
|
p_qhd = qhd_next(p_qhd);
|
|
|
|
}while(p_qhd != async_head); // async list traversal, stop if loop around
|
|
|
|
}
|
|
|
|
|
2021-08-23 10:40:57 +02:00
|
|
|
static void period_list_xfer_complete_isr(uint8_t hostid, uint32_t interval_ms)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
2020-09-05 15:20:45 +02:00
|
|
|
uint16_t max_loop = 0;
|
2021-08-23 10:40:57 +02:00
|
|
|
uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1u);
|
2020-01-15 05:30:39 +01:00
|
|
|
ehci_link_t next_item = * get_period_head(hostid, interval_ms);
|
|
|
|
|
|
|
|
// TODO abstract max loop guard for period
|
|
|
|
while( !next_item.terminate &&
|
|
|
|
!(interval_ms > 1 && period_1ms_addr == tu_align32(next_item.address)) &&
|
2022-03-04 16:31:48 +01:00
|
|
|
max_loop < (QHD_MAX + EHCI_MAX_ITD + EHCI_MAX_SITD)*CFG_TUH_DEVICE_MAX)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
switch ( next_item.type )
|
|
|
|
{
|
|
|
|
case EHCI_QTYPE_QHD:
|
|
|
|
{
|
|
|
|
ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
|
|
|
|
if ( !p_qhd_int->qtd_overlay.halted )
|
|
|
|
{
|
|
|
|
qhd_xfer_complete_isr(p_qhd_int);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EHCI_QTYPE_ITD: // TODO support hs/fs ISO
|
|
|
|
case EHCI_QTYPE_SITD:
|
|
|
|
case EHCI_QTYPE_FSTN:
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_item = *list_next(&next_item);
|
|
|
|
max_loop++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qhd_xfer_error_isr(ehci_qhd_t * p_qhd)
|
|
|
|
{
|
|
|
|
if ( (p_qhd->dev_addr != 0 && p_qhd->qtd_overlay.halted) || // addr0 cannot be protocol STALL
|
|
|
|
qhd_has_xact_error(p_qhd) )
|
|
|
|
{
|
|
|
|
// current qhd has error in transaction
|
|
|
|
xfer_result_t error_event;
|
|
|
|
|
|
|
|
// no error bits are set, endpoint is halted due to STALL
|
|
|
|
error_event = qhd_has_xact_error(p_qhd) ? XFER_RESULT_FAILED : XFER_RESULT_STALLED;
|
|
|
|
|
|
|
|
p_qhd->total_xferred_bytes += p_qhd->p_qtd_list_head->expected_bytes - p_qhd->p_qtd_list_head->total_bytes;
|
|
|
|
|
|
|
|
// if ( XFER_RESULT_FAILED == error_event ) TU_BREAKPOINT(); // TODO skip unplugged device
|
|
|
|
|
|
|
|
p_qhd->p_qtd_list_head->used = 0; // free QTD
|
|
|
|
qtd_remove_1st_from_qhd(p_qhd);
|
|
|
|
|
|
|
|
if ( 0 == p_qhd->ep_number )
|
|
|
|
{
|
|
|
|
// control cannot be halted --> clear all qtd list
|
|
|
|
p_qhd->p_qtd_list_head = NULL;
|
|
|
|
p_qhd->p_qtd_list_tail = NULL;
|
|
|
|
|
|
|
|
p_qhd->qtd_overlay.next.terminate = 1;
|
|
|
|
p_qhd->qtd_overlay.alternate.terminate = 1;
|
|
|
|
p_qhd->qtd_overlay.halted = 0;
|
|
|
|
|
|
|
|
ehci_qtd_t *p_setup = qtd_control(p_qhd->dev_addr);
|
|
|
|
p_setup->used = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// call USBH callback
|
2020-09-05 10:45:03 +02:00
|
|
|
hcd_event_xfer_complete(p_qhd->dev_addr, tu_edpt_addr(p_qhd->ep_number, p_qhd->pid == EHCI_PID_IN ? 1 : 0), p_qhd->total_xferred_bytes, error_event, true);
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
p_qhd->total_xferred_bytes = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xfer_error_isr(uint8_t hostid)
|
|
|
|
{
|
|
|
|
//------------- async list -------------//
|
|
|
|
ehci_qhd_t * const async_head = qhd_async_head(hostid);
|
|
|
|
ehci_qhd_t *p_qhd = async_head;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
qhd_xfer_error_isr( p_qhd );
|
|
|
|
p_qhd = qhd_next(p_qhd);
|
|
|
|
}while(p_qhd != async_head); // async list traversal, stop if loop around
|
|
|
|
|
|
|
|
//------------- TODO refractor period list -------------//
|
2021-08-23 10:40:57 +02:00
|
|
|
uint32_t const period_1ms_addr = (uint32_t) get_period_head(hostid, 1u);
|
|
|
|
for (uint32_t interval_ms=1; interval_ms <= FRAMELIST_SIZE; interval_ms *= 2)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
ehci_link_t next_item = * get_period_head(hostid, interval_ms);
|
|
|
|
|
|
|
|
// TODO abstract max loop guard for period
|
|
|
|
while( !next_item.terminate &&
|
|
|
|
!(interval_ms > 1 && period_1ms_addr == tu_align32(next_item.address)) )
|
|
|
|
{
|
|
|
|
switch ( next_item.type )
|
|
|
|
{
|
|
|
|
case EHCI_QTYPE_QHD:
|
|
|
|
{
|
|
|
|
ehci_qhd_t *p_qhd_int = (ehci_qhd_t *) tu_align32(next_item.address);
|
|
|
|
qhd_xfer_error_isr(p_qhd_int);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// TODO support hs/fs ISO
|
|
|
|
case EHCI_QTYPE_ITD:
|
|
|
|
case EHCI_QTYPE_SITD:
|
|
|
|
case EHCI_QTYPE_FSTN:
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
next_item = *list_next(&next_item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 18:43:31 +01:00
|
|
|
#if CFG_TUSB_DEBUG >= EHCI_DBG
|
|
|
|
|
|
|
|
static inline void print_portsc(ehci_registers_t* regs)
|
|
|
|
{
|
|
|
|
TU_LOG_HEX(EHCI_DBG, regs->portsc);
|
|
|
|
TU_LOG(EHCI_DBG, " Current Connect Status: %u\r\n", regs->portsc_bm.current_connect_status);
|
|
|
|
TU_LOG(EHCI_DBG, " Connect Status Change : %u\r\n", regs->portsc_bm.connect_status_change);
|
|
|
|
TU_LOG(EHCI_DBG, " Port Enabled : %u\r\n", regs->portsc_bm.port_enabled);
|
|
|
|
TU_LOG(EHCI_DBG, " Port Enabled Change : %u\r\n", regs->portsc_bm.port_enable_change);
|
|
|
|
|
|
|
|
TU_LOG(EHCI_DBG, " Port Reset : %u\r\n", regs->portsc_bm.port_reset);
|
|
|
|
TU_LOG(EHCI_DBG, " Port Power : %u\r\n", regs->portsc_bm.port_power);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
#define print_portsc(_reg)
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
//------------- Host Controller Driver's Interrupt Handler -------------//
|
2020-09-03 08:12:09 +02:00
|
|
|
void hcd_int_handler(uint8_t rhport)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
ehci_registers_t* regs = ehci_data.regs;
|
|
|
|
|
|
|
|
uint32_t int_status = regs->status;
|
|
|
|
int_status &= regs->inten;
|
|
|
|
|
Updated the clearing of the status register bits to use a straight '=', rather than an '|='. Use of the latter caused an extra, unwanted read of the status register before the write-to-clear operation, which, in some cases, allowed new status bits to assert (relative to the initial read of the status register two statements earlier), and then be cleared blindly and unconditionally during the write-back. This had the potential (and, in my case, observed) effect of dropping the handling of an enabled interrupt. Ultimately, the system would lock up in a busy state, with no hope of clearing the condition. See Issue #1339 for more information.
2022-02-25 19:39:09 +01:00
|
|
|
regs->status = int_status; // Acknowledge handled interrupt
|
2020-01-15 05:30:39 +01:00
|
|
|
|
|
|
|
if (int_status == 0) return;
|
|
|
|
|
2020-05-04 09:11:58 +02:00
|
|
|
if (int_status & EHCI_INT_MASK_FRAMELIST_ROLLOVER)
|
|
|
|
{
|
2021-08-23 10:40:57 +02:00
|
|
|
ehci_data.uframe_number += (FRAMELIST_SIZE << 3);
|
2020-05-04 09:11:58 +02:00
|
|
|
}
|
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
if (int_status & EHCI_INT_MASK_PORT_CHANGE)
|
|
|
|
{
|
2022-03-18 18:43:31 +01:00
|
|
|
uint32_t const port_status = regs->portsc & EHCI_PORTSC_MASK_ALL;
|
|
|
|
print_portsc(regs);
|
2021-08-23 07:37:54 +02:00
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
if (regs->portsc_bm.connect_status_change)
|
|
|
|
{
|
|
|
|
port_connect_status_change_isr(rhport);
|
|
|
|
}
|
|
|
|
|
|
|
|
regs->portsc |= port_status; // Acknowledge change bits in portsc
|
|
|
|
}
|
|
|
|
|
|
|
|
if (int_status & EHCI_INT_MASK_ERROR)
|
|
|
|
{
|
|
|
|
xfer_error_isr(rhport);
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------- some QTD/SITD/ITD with IOC set is completed -------------//
|
|
|
|
if (int_status & EHCI_INT_MASK_NXP_ASYNC)
|
|
|
|
{
|
|
|
|
async_list_xfer_complete_isr( qhd_async_head(rhport) );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (int_status & EHCI_INT_MASK_NXP_PERIODIC)
|
|
|
|
{
|
2021-08-23 10:40:57 +02:00
|
|
|
for (uint32_t i=1; i <= FRAMELIST_SIZE; i *= 2)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
period_list_xfer_complete_isr( rhport, i );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------- There is some removed async previously -------------//
|
|
|
|
if (int_status & EHCI_INT_MASK_ASYNC_ADVANCE) // need to place after EHCI_INT_MASK_NXP_ASYNC
|
|
|
|
{
|
|
|
|
async_advance_isr(rhport);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
// HELPER
|
|
|
|
//--------------------------------------------------------------------+
|
|
|
|
|
|
|
|
|
|
|
|
//------------- queue head helper -------------//
|
|
|
|
static inline ehci_qhd_t* qhd_find_free (void)
|
|
|
|
{
|
2022-03-04 16:31:48 +01:00
|
|
|
for (uint32_t i=0; i<QHD_MAX; i++)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
if ( !ehci_data.qhd_pool[i].used ) return &ehci_data.qhd_pool[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_next(ehci_qhd_t const * p_qhd)
|
|
|
|
{
|
|
|
|
return (ehci_qhd_t*) tu_align32(p_qhd->next.address);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_qhd_t* qhd_get_from_addr(uint8_t dev_addr, uint8_t ep_addr)
|
|
|
|
{
|
|
|
|
ehci_qhd_t* qhd_pool = ehci_data.qhd_pool;
|
|
|
|
|
2022-03-04 16:31:48 +01:00
|
|
|
for(uint32_t i=0; i<QHD_MAX; i++)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
if ( (qhd_pool[i].dev_addr == dev_addr) &&
|
|
|
|
ep_addr == tu_edpt_addr(qhd_pool[i].ep_number, qhd_pool[i].pid) )
|
|
|
|
{
|
|
|
|
return &qhd_pool[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------- TD helper -------------//
|
|
|
|
static inline ehci_qtd_t* qtd_find_free(void)
|
|
|
|
{
|
2022-03-04 15:04:52 +01:00
|
|
|
for (uint32_t i=0; i<QTD_MAX; i++)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
if ( !ehci_data.qtd_pool[i].used ) return &ehci_data.qtd_pool[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_qtd_t* qtd_next(ehci_qtd_t const * p_qtd )
|
|
|
|
{
|
|
|
|
return (ehci_qtd_t*) tu_align32(p_qtd->next.address);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void qtd_remove_1st_from_qhd(ehci_qhd_t *p_qhd)
|
|
|
|
{
|
|
|
|
if (p_qhd->p_qtd_list_head == p_qhd->p_qtd_list_tail) // last TD --> make it NULL
|
|
|
|
{
|
|
|
|
p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = NULL;
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
p_qhd->p_qtd_list_head = qtd_next( p_qhd->p_qtd_list_head );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void qtd_insert_to_qhd(ehci_qhd_t *p_qhd, ehci_qtd_t *p_qtd_new)
|
|
|
|
{
|
|
|
|
if (p_qhd->p_qtd_list_head == NULL) // empty list
|
|
|
|
{
|
|
|
|
p_qhd->p_qtd_list_head = p_qhd->p_qtd_list_tail = p_qtd_new;
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
p_qhd->p_qtd_list_tail->next.address = (uint32_t) p_qtd_new;
|
|
|
|
p_qhd->p_qtd_list_tail = p_qtd_new;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qhd_init(ehci_qhd_t *p_qhd, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc)
|
|
|
|
{
|
|
|
|
// address 0 is used as async head, which always on the list --> cannot be cleared (ehci halted otherwise)
|
|
|
|
if (dev_addr != 0)
|
|
|
|
{
|
|
|
|
tu_memclr(p_qhd, sizeof(ehci_qhd_t));
|
|
|
|
}
|
|
|
|
|
2021-08-23 12:00:41 +02:00
|
|
|
hcd_devtree_info_t devtree_info;
|
|
|
|
hcd_devtree_get_info(dev_addr, &devtree_info);
|
|
|
|
|
2020-01-15 05:30:39 +01:00
|
|
|
uint8_t const xfer_type = ep_desc->bmAttributes.xfer;
|
|
|
|
uint8_t const interval = ep_desc->bInterval;
|
|
|
|
|
|
|
|
p_qhd->dev_addr = dev_addr;
|
|
|
|
p_qhd->fl_inactive_next_xact = 0;
|
|
|
|
p_qhd->ep_number = tu_edpt_number(ep_desc->bEndpointAddress);
|
2021-08-23 12:00:41 +02:00
|
|
|
p_qhd->ep_speed = devtree_info.speed;
|
2020-01-15 05:30:39 +01:00
|
|
|
p_qhd->data_toggle_control= (xfer_type == TUSB_XFER_CONTROL) ? 1 : 0;
|
|
|
|
p_qhd->head_list_flag = (dev_addr == 0) ? 1 : 0; // addr0's endpoint is the static asyn list head
|
2021-10-24 08:11:21 +02:00
|
|
|
p_qhd->max_packet_size = tu_edpt_packet_size(ep_desc);
|
2020-01-15 05:30:39 +01:00
|
|
|
p_qhd->fl_ctrl_ep_flag = ((xfer_type == TUSB_XFER_CONTROL) && (p_qhd->ep_speed != TUSB_SPEED_HIGH)) ? 1 : 0;
|
|
|
|
p_qhd->nak_reload = 0;
|
|
|
|
|
|
|
|
// Bulk/Control -> smask = cmask = 0
|
|
|
|
// TODO Isochronous
|
|
|
|
if (TUSB_XFER_INTERRUPT == xfer_type)
|
|
|
|
{
|
|
|
|
if (TUSB_SPEED_HIGH == p_qhd->ep_speed)
|
|
|
|
{
|
|
|
|
TU_ASSERT( interval <= 16, );
|
2022-12-04 07:58:47 +01:00
|
|
|
if ( interval < 4) // sub millisecond interval
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
p_qhd->interval_ms = 0;
|
|
|
|
p_qhd->int_smask = (interval == 1) ? TU_BIN8(11111111) :
|
|
|
|
(interval == 2) ? TU_BIN8(10101010) : TU_BIN8(01000100);
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
p_qhd->interval_ms = (uint8_t) tu_min16( 1 << (interval-4), 255 );
|
|
|
|
p_qhd->int_smask = TU_BIT(interval % 8);
|
|
|
|
}
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
TU_ASSERT( 0 != interval, );
|
|
|
|
// Full/Low: 4.12.2.1 (EHCI) case 1 schedule start split at 1 us & complete split at 2,3,4 uframes
|
|
|
|
p_qhd->int_smask = 0x01;
|
|
|
|
p_qhd->fl_int_cmask = TU_BIN8(11100);
|
|
|
|
p_qhd->interval_ms = interval;
|
|
|
|
}
|
|
|
|
}else
|
|
|
|
{
|
|
|
|
p_qhd->int_smask = p_qhd->fl_int_cmask = 0;
|
|
|
|
}
|
|
|
|
|
2021-08-23 12:00:41 +02:00
|
|
|
p_qhd->fl_hub_addr = devtree_info.hub_addr;
|
|
|
|
p_qhd->fl_hub_port = devtree_info.hub_port;
|
2020-01-15 05:30:39 +01:00
|
|
|
p_qhd->mult = 1; // TODO not use high bandwidth/park mode yet
|
|
|
|
|
|
|
|
//------------- HCD Management Data -------------//
|
|
|
|
p_qhd->used = 1;
|
|
|
|
p_qhd->removing = 0;
|
|
|
|
p_qhd->p_qtd_list_head = NULL;
|
|
|
|
p_qhd->p_qtd_list_tail = NULL;
|
|
|
|
p_qhd->pid = tu_edpt_dir(ep_desc->bEndpointAddress) ? EHCI_PID_IN : EHCI_PID_OUT; // PID for TD under this endpoint
|
|
|
|
|
|
|
|
//------------- active, but no TD list -------------//
|
|
|
|
p_qhd->qtd_overlay.halted = 0;
|
|
|
|
p_qhd->qtd_overlay.next.terminate = 1;
|
|
|
|
p_qhd->qtd_overlay.alternate.terminate = 1;
|
|
|
|
if (TUSB_XFER_BULK == xfer_type && p_qhd->ep_speed == TUSB_SPEED_HIGH && p_qhd->pid == EHCI_PID_OUT)
|
|
|
|
{
|
|
|
|
p_qhd->qtd_overlay.ping_err = 1; // do PING for Highspeed Bulk OUT, EHCI section 4.11
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 11:26:27 +02:00
|
|
|
static void qtd_init(ehci_qtd_t* p_qtd, void const* buffer, uint16_t total_bytes)
|
2020-01-15 05:30:39 +01:00
|
|
|
{
|
|
|
|
tu_memclr(p_qtd, sizeof(ehci_qtd_t));
|
|
|
|
|
|
|
|
p_qtd->used = 1;
|
|
|
|
|
|
|
|
p_qtd->next.terminate = 1; // init to null
|
|
|
|
p_qtd->alternate.terminate = 1; // not used, always set to terminated
|
|
|
|
p_qtd->active = 1;
|
|
|
|
p_qtd->err_count = 3; // TODO 3 consecutive errors tolerance
|
|
|
|
p_qtd->data_toggle = 0;
|
|
|
|
p_qtd->total_bytes = total_bytes;
|
|
|
|
p_qtd->expected_bytes = total_bytes;
|
|
|
|
|
|
|
|
p_qtd->buffer[0] = (uint32_t) buffer;
|
|
|
|
for(uint8_t i=1; i<5; i++)
|
|
|
|
{
|
|
|
|
p_qtd->buffer[i] |= tu_align4k( p_qtd->buffer[i-1] ) + 4096;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//------------- List Managing Helper -------------//
|
|
|
|
static inline void list_insert(ehci_link_t *current, ehci_link_t *new, uint8_t new_type)
|
|
|
|
{
|
|
|
|
new->address = current->address;
|
|
|
|
current->address = ((uint32_t) new) | (new_type << 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline ehci_link_t* list_next(ehci_link_t *p_link_pointer)
|
|
|
|
{
|
|
|
|
return (ehci_link_t*) tu_align32(p_link_pointer->address);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|