1313 lines
44 KiB
C
1313 lines
44 KiB
C
/**************************************************************************//**
|
|
* @file ehci.c
|
|
* @version V1.10
|
|
* @brief USB Host library EHCI (USB 2.0) host controller driver.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* @copyright (C) 2017 Nuvoton Technology Corp. All rights reserved.
|
|
*****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "NuMicro.h"
|
|
|
|
#include "usb.h"
|
|
#include "hub.h"
|
|
|
|
|
|
/// @cond HIDDEN_SYMBOLS
|
|
|
|
static QH_T *_H_qh; /* head of reclamation list */
|
|
static qTD_T *_ghost_qtd; /* used as a terminator qTD */
|
|
static QH_T *qh_remove_list;
|
|
|
|
extern ISO_EP_T *iso_ep_list; /* list of activated isochronous pipes */
|
|
extern int ehci_iso_xfer(UTR_T *utr); /* EHCI isochronous transfer function */
|
|
extern int ehci_quit_iso_xfer(UTR_T *utr, EP_INFO_T *ep);
|
|
|
|
#ifdef __ICCARM__
|
|
#pragma data_alignment=4096
|
|
uint32_t _PFList[FL_SIZE]; /* Periodic frame list (IAR) */
|
|
#else
|
|
uint32_t _PFList[FL_SIZE] __attribute__((aligned(4096))); /* Periodic frame list */
|
|
#endif
|
|
|
|
QH_T *_Iqh[NUM_IQH];
|
|
|
|
|
|
#ifdef ENABLE_ERROR_MSG
|
|
void dump_ehci_regs()
|
|
{
|
|
USB_debug("Dump HSUSBH(EHCI) registers:\n");
|
|
USB_debug(" UCMDR = 0x%x\n", _ehci->UCMDR);
|
|
USB_debug(" USTSR = 0x%x\n", _ehci->USTSR);
|
|
USB_debug(" UIENR = 0x%x\n", _ehci->UIENR);
|
|
USB_debug(" UFINDR = 0x%x\n", _ehci->UFINDR);
|
|
USB_debug(" UPFLBAR = 0x%x\n", _ehci->UPFLBAR);
|
|
USB_debug(" UCALAR = 0x%x\n", _ehci->UCALAR);
|
|
USB_debug(" UASSTR = 0x%x\n", _ehci->UASSTR);
|
|
USB_debug(" UCFGR = 0x%x\n", _ehci->UCFGR);
|
|
USB_debug(" UPSCR = 0x%x\n", _ehci->UPSCR[0]);
|
|
USB_debug(" PHYCTL0 = 0x%x\n", _ehci->USBPCR0);
|
|
USB_debug(" PHYCTL1 = 0x%x\n", _ehci->USBPCR1);
|
|
}
|
|
|
|
void dump_ehci_ports()
|
|
{
|
|
USB_debug("_ehci port0=0x%x, port1=0x%x\n", _ehci->UPSCR[0], _ehci->UPSCR[1]);
|
|
}
|
|
|
|
void dump_ehci_qtd(qTD_T *qtd)
|
|
{
|
|
USB_debug(" [qTD] - 0x%08x\n", (int)qtd);
|
|
USB_debug(" 0x%08x (Next qtd Pointer)\n", qtd->Next_qTD);
|
|
USB_debug(" 0x%08x (Alternate Next qtd Pointer)\n", qtd->Alt_Next_qTD);
|
|
USB_debug(" 0x%08x (qtd Token) PID: %s, Bytes: %d, IOC: %d\n", qtd->Token, (((qtd->Token >> 8) & 0x3) == 0) ? "OUT" : ((((qtd->Token >> 8) & 0x3) == 1) ? "IN" : "SETUP"), (qtd->Token >> 16) & 0x7FFF, (qtd->Token >> 15) & 0x1);
|
|
USB_debug(" 0x%08x (Buffer Pointer (page 0))\n", qtd->Bptr[0]);
|
|
//USB_debug(" 0x%08x (Buffer Pointer (page 1))\n", qtd->Bptr[1]);
|
|
//USB_debug(" 0x%08x (Buffer Pointer (page 2))\n", qtd->Bptr[2]);
|
|
//USB_debug(" 0x%08x (Buffer Pointer (page 3))\n", qtd->Bptr[3]);
|
|
//USB_debug(" 0x%08x (Buffer Pointer (page 4))\n", qtd->Bptr[4]);
|
|
USB_debug("\n");
|
|
}
|
|
|
|
void dump_ehci_asynclist(void)
|
|
{
|
|
QH_T *qh = _H_qh;
|
|
qTD_T *qtd;
|
|
|
|
USB_debug(">>> Dump EHCI Asynchronous List <<<\n");
|
|
do
|
|
{
|
|
USB_debug("[QH] - 0x%08x\n", (int)qh);
|
|
USB_debug(" 0x%08x (Queue Head Horizontal Link Pointer, Queue Head DWord 0)\n", qh->HLink);
|
|
USB_debug(" 0x%08x (Endpoint Characteristics) DevAddr: %d, EP: 0x%x, PktSz: %d, Speed: %s\n", qh->Chrst, qh->Chrst & 0x7F, (qh->Chrst >> 8) & 0xF, (qh->Chrst >> 16) & 0x7FF, ((qh->Chrst >> 12) & 0x3 == 0) ? "Full" : (((qh->Chrst >> 12) & 0x3 == 1) ? "Low" : "High"));
|
|
USB_debug(" 0x%08x (Endpoint Capabilities: Queue Head DWord 2)\n", qh->Cap);
|
|
USB_debug(" 0x%08x (Current qtd Pointer)\n", qh->Curr_qTD);
|
|
USB_debug(" --- Overlay Area ---\n");
|
|
USB_debug(" 0x%08x (Next qtd Pointer)\n", qh->OL_Next_qTD);
|
|
USB_debug(" 0x%08x (Alternate Next qtd Pointer)\n", qh->OL_Alt_Next_qTD);
|
|
USB_debug(" 0x%08x (qtd Token)\n", qh->OL_Token);
|
|
USB_debug(" 0x%08x (Buffer Pointer (page 0))\n", qh->OL_Bptr[0]);
|
|
USB_debug("\n");
|
|
|
|
qtd = QTD_PTR(qh->Curr_qTD);
|
|
while (qtd != NULL)
|
|
{
|
|
dump_ehci_qtd(qtd);
|
|
qtd = QTD_PTR(qtd->Next_qTD);
|
|
}
|
|
qh = QH_PTR(qh->HLink);
|
|
}
|
|
while (qh != _H_qh);
|
|
}
|
|
|
|
void dump_ehci_asynclist_simple(void)
|
|
{
|
|
QH_T *qh = _H_qh;
|
|
|
|
USB_debug(">>> EHCI Asynchronous List <<<\n");
|
|
USB_debug("[QH] => ");
|
|
do
|
|
{
|
|
USB_debug("0x%08x ", (int)qh);
|
|
qh = QH_PTR(qh->HLink);
|
|
}
|
|
while (qh != _H_qh);
|
|
USB_debug("\n");
|
|
}
|
|
|
|
void dump_ehci_period_frame_list_simple(void)
|
|
{
|
|
QH_T *qh = _Iqh[NUM_IQH - 1];
|
|
|
|
USB_debug(">>> EHCI period frame list simple <<<\n");
|
|
USB_debug("[FList] => ");
|
|
do
|
|
{
|
|
USB_debug("0x%08x ", (int)qh);
|
|
qh = QH_PTR(qh->HLink);
|
|
}
|
|
while (qh != NULL);
|
|
USB_debug("\n");
|
|
}
|
|
|
|
void dump_ehci_period_frame_list()
|
|
{
|
|
int i;
|
|
QH_T *qh;
|
|
|
|
for (i = 0; i < FL_SIZE; i++)
|
|
{
|
|
USB_debug("!%02d: ", i);
|
|
qh = QH_PTR(_PFList[i]);;
|
|
while (qh != NULL)
|
|
{
|
|
// USB_debug("0x%x (0x%x) => ", (int)qh, qh->HLink);
|
|
USB_debug("0x%x => ", (int)qh);
|
|
qh = QH_PTR(qh->HLink);
|
|
}
|
|
USB_debug("0\n");
|
|
}
|
|
}
|
|
|
|
#endif /* ENABLE_ERROR_MSG */
|
|
|
|
static void init_periodic_frame_list()
|
|
{
|
|
QH_T *qh_p;
|
|
int i, idx, interval;
|
|
|
|
memset(_PFList, 0, sizeof(_PFList));
|
|
|
|
iso_ep_list = NULL;
|
|
|
|
for (i = NUM_IQH - 1; i >= 0; i--) /* interval = i^2 */
|
|
{
|
|
_Iqh[i] = alloc_ehci_QH();
|
|
|
|
_Iqh[i]->HLink = QH_HLNK_END;
|
|
_Iqh[i]->Curr_qTD = (uint32_t)_ghost_qtd;
|
|
_Iqh[i]->OL_Next_qTD = QTD_LIST_END;
|
|
_Iqh[i]->OL_Alt_Next_qTD = (uint32_t)_ghost_qtd;
|
|
_Iqh[i]->OL_Token = QTD_STS_HALT;
|
|
|
|
interval = 0x1 << i;
|
|
|
|
for (idx = interval - 1; idx < FL_SIZE; idx += interval)
|
|
{
|
|
if (_PFList[idx] == 0) /* is empty list, insert directly */
|
|
{
|
|
_PFList[idx] = QH_HLNK_QH(_Iqh[i]);
|
|
}
|
|
else
|
|
{
|
|
qh_p = QH_PTR(_PFList[idx]);
|
|
|
|
while (1)
|
|
{
|
|
if (qh_p == _Iqh[i])
|
|
break; /* already chained by previous visit */
|
|
|
|
if (qh_p->HLink == QH_HLNK_END) /* reach end of list? */
|
|
{
|
|
qh_p->HLink = QH_HLNK_QH(_Iqh[i]);
|
|
break;
|
|
}
|
|
qh_p = QH_PTR(qh_p->HLink);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static QH_T *get_int_tree_head_node(int interval)
|
|
{
|
|
int i;
|
|
|
|
interval /= 8; /* each frame list entry for 8 micro-frame */
|
|
|
|
for (i = 0; i < NUM_IQH - 1; i++)
|
|
{
|
|
interval >>= 1;
|
|
if (interval == 0)
|
|
return _Iqh[i];
|
|
}
|
|
return _Iqh[NUM_IQH - 1];
|
|
}
|
|
|
|
static int make_int_s_mask(int bInterval)
|
|
{
|
|
int order, interval;
|
|
|
|
interval = 1;
|
|
while (bInterval > 1)
|
|
{
|
|
interval *= 2;
|
|
bInterval--;
|
|
}
|
|
|
|
if (interval < 2)
|
|
return 0xFF; /* interval 1 */
|
|
if (interval < 4)
|
|
return 0x55; /* interval 2 */
|
|
if (interval < 8)
|
|
return 0x22; /* interval 4 */
|
|
for (order = 0; (interval > 1); order++)
|
|
{
|
|
interval >>= 1;
|
|
}
|
|
return (0x1 << (order % 8));
|
|
}
|
|
|
|
static int ehci_init(void)
|
|
{
|
|
int timeout = 250 * 1000; /* EHCI reset time-out 250 ms */
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Reset EHCI host controller */
|
|
/*------------------------------------------------------------------------------------*/
|
|
_ehci->UCMDR = HSUSBH_UCMDR_HCRST_Msk;
|
|
while ((_ehci->UCMDR & HSUSBH_UCMDR_HCRST_Msk) && (timeout > 0))
|
|
{
|
|
usbh_delay_ms(1);
|
|
timeout -= 1000;
|
|
}
|
|
if (_ehci->UCMDR & HSUSBH_UCMDR_HCRST_Msk)
|
|
return USBH_ERR_EHCI_INIT;
|
|
|
|
_ehci->UCMDR = UCMDR_INT_THR_CTRL | HSUSBH_UCMDR_RUN_Msk;
|
|
|
|
_ghost_qtd = alloc_ehci_qTD(NULL);
|
|
_ghost_qtd->Token = 0x11197B3F; //QTD_STS_HALT; visit_qtd() will not remove a qTD with this mark. It represents a qhost qTD.
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Initialize asynchronous list */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qh_remove_list = NULL;
|
|
|
|
/* Create the QH list head with H-bit 1 */
|
|
_H_qh = alloc_ehci_QH();
|
|
_H_qh->HLink = QH_HLNK_QH(_H_qh); /* circular link to itself, the only one QH */
|
|
_H_qh->Chrst = QH_RCLM_LIST_HEAD; /* it's the head of reclamation list */
|
|
_H_qh->Curr_qTD = (uint32_t)_ghost_qtd;
|
|
_H_qh->OL_Next_qTD = QTD_LIST_END;
|
|
_H_qh->OL_Alt_Next_qTD = (uint32_t)_ghost_qtd;
|
|
_H_qh->OL_Token = QTD_STS_HALT;
|
|
_ehci->UCALAR = (uint32_t)_H_qh;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Initialize periodic list */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (FL_SIZE == 256)
|
|
_ehci->UCMDR |= (0x2 << HSUSBH_UCMDR_FLSZ_Pos);
|
|
else if (FL_SIZE == 512)
|
|
_ehci->UCMDR |= (0x1 << HSUSBH_UCMDR_FLSZ_Pos);
|
|
else if (FL_SIZE == 1024)
|
|
_ehci->UCMDR |= (0x0 << HSUSBH_UCMDR_FLSZ_Pos);
|
|
else
|
|
return USBH_ERR_EHCI_INIT; /* Invalid FL_SIZE setting! */
|
|
|
|
_ehci->UPFLBAR = (uint32_t)_PFList;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* start run */
|
|
/*------------------------------------------------------------------------------------*/
|
|
|
|
_ehci->UCFGR = 0x1; /* enable port routing to EHCI */
|
|
_ehci->UIENR = HSUSBH_UIENR_USBIEN_Msk | HSUSBH_UIENR_UERRIEN_Msk | HSUSBH_UIENR_HSERREN_Msk | HSUSBH_UIENR_IAAEN_Msk;
|
|
|
|
usbh_delay_ms(1); /* delay 1 ms */
|
|
|
|
_ehci->UPSCR[0] = HSUSBH_UPSCR_PP_Msk; /* enable port 1 port power */
|
|
_ehci->UPSCR[1] = HSUSBH_UPSCR_PP_Msk | HSUSBH_UPSCR_PO_Msk; /* set port 2 owner to OHCI */
|
|
|
|
init_periodic_frame_list();
|
|
|
|
usbh_delay_ms(10); /* delay 10 ms */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ehci_suspend(void)
|
|
{
|
|
if (_ehci->UPSCR[0] & 0x1)
|
|
_ehci->UPSCR[0] |= HSUSBH_UPSCR_SUSPEND_Msk;
|
|
}
|
|
|
|
static void ehci_resume(void)
|
|
{
|
|
if (_ehci->UPSCR[0] & 0x1)
|
|
_ehci->UPSCR[0] = (HSUSBH->UPSCR[0] & ~HSUSBH_UPSCR_SUSPEND_Msk) | HSUSBH_UPSCR_FPR_Msk;
|
|
}
|
|
|
|
static void ehci_shutdown(void)
|
|
{
|
|
ehci_suspend();
|
|
}
|
|
|
|
static void move_qh_to_remove_list(QH_T *qh)
|
|
{
|
|
QH_T *q;
|
|
|
|
// USB_debug("move_qh_to_remove_list - 0x%x (0x%x)\n", (int)qh, qh->Chrst);
|
|
|
|
/* check if this ED found in ed_remove_list */
|
|
q = qh_remove_list;
|
|
while (q)
|
|
{
|
|
if (q == qh) /* This QH found in qh_remove_list. */
|
|
{
|
|
return; /* Do nothing, return... */
|
|
}
|
|
q = q->next;
|
|
}
|
|
|
|
DISABLE_EHCI_IRQ();
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Search asynchronous frame list and remove qh if found in list. */
|
|
/*------------------------------------------------------------------------------------*/
|
|
q = _H_qh; /* find and remove it from asynchronous list */
|
|
while (QH_PTR(q->HLink) != _H_qh)
|
|
{
|
|
if (QH_PTR(q->HLink) == qh)
|
|
{
|
|
/* q's next QH is qh, found... */
|
|
q->HLink = qh->HLink; /* remove qh from list */
|
|
|
|
qh->next = qh_remove_list; /* add qh to qh_remove_list */
|
|
qh_remove_list = qh;
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_IAAD_Msk; /* trigger IAA interrupt */
|
|
ENABLE_EHCI_IRQ();
|
|
return; /* done */
|
|
}
|
|
q = QH_PTR(q->HLink); /* advance to next QH in asynchronous list */
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Search periodic frame list and remove qh if found in list. */
|
|
/*------------------------------------------------------------------------------------*/
|
|
q = _Iqh[NUM_IQH - 1];
|
|
while (q->HLink != QH_HLNK_END)
|
|
{
|
|
if (QH_PTR(q->HLink) == qh)
|
|
{
|
|
/* q's next QH is qh, found... */
|
|
q->HLink = qh->HLink; /* remove qh from list */
|
|
|
|
qh->next = qh_remove_list; /* add qh to qh_remove_list */
|
|
qh_remove_list = qh;
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_IAAD_Msk; /* trigger IAA interrupt */
|
|
ENABLE_EHCI_IRQ();
|
|
return; /* done */
|
|
}
|
|
q = QH_PTR(q->HLink); /* advance to next QH in asynchronous list */
|
|
}
|
|
ENABLE_EHCI_IRQ();
|
|
}
|
|
|
|
static void append_to_qtd_list_of_QH(QH_T *qh, qTD_T *qtd)
|
|
{
|
|
qTD_T *q;
|
|
|
|
if (qh->qtd_list == NULL)
|
|
{
|
|
qh->qtd_list = qtd;
|
|
}
|
|
else
|
|
{
|
|
q = qh->qtd_list;
|
|
while (q->next != NULL)
|
|
{
|
|
q = q->next;
|
|
}
|
|
q->next = qtd;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If ep==NULL, it's a control endpoint QH.
|
|
*/
|
|
static void write_qh(UDEV_T *udev, EP_INFO_T *ep, QH_T *qh)
|
|
{
|
|
uint32_t chrst, cap;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Write QH DWord 1 - Endpoint Characteristics */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (ep == NULL) /* is control endpoint? */
|
|
{
|
|
if (udev->descriptor.bMaxPacketSize0 == 0)
|
|
{
|
|
if (udev->speed == SPEED_LOW) /* give a default maximum packet size */
|
|
udev->descriptor.bMaxPacketSize0 = 8;
|
|
else
|
|
udev->descriptor.bMaxPacketSize0 = 64;
|
|
}
|
|
chrst = QH_DTC | QH_NAK_RL | (udev->descriptor.bMaxPacketSize0 << 16);
|
|
if (udev->speed != SPEED_HIGH)
|
|
chrst |= QH_CTRL_EP_FLAG; /* non-high-speed control endpoint */
|
|
}
|
|
else /* not a control endpoint */
|
|
{
|
|
chrst = QH_NAK_RL | (ep->wMaxPacketSize << 16);
|
|
chrst |= ((ep->bEndpointAddress & 0xf) << 8); /* Endpoint Address */
|
|
}
|
|
|
|
if (udev->speed == SPEED_LOW)
|
|
chrst |= QH_EPS_LOW;
|
|
else if (udev->speed == SPEED_FULL)
|
|
chrst |= QH_EPS_FULL;
|
|
else
|
|
chrst |= QH_EPS_HIGH;
|
|
|
|
chrst |= udev->dev_num;
|
|
|
|
qh->Chrst = chrst;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Write QH DWord 2 - Endpoint Capabilities */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (udev->speed == SPEED_HIGH)
|
|
{
|
|
cap = 0;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Backtrace device tree until the USB 2.0 hub found
|
|
*/
|
|
HUB_DEV_T *hub;
|
|
int port_num;
|
|
|
|
port_num = udev->port_num;
|
|
hub = udev->parent;
|
|
|
|
while ((hub != NULL) && (hub->iface->udev->speed != SPEED_HIGH))
|
|
{
|
|
port_num = hub->iface->udev->port_num;
|
|
hub = hub->iface->udev->parent;
|
|
}
|
|
|
|
cap = (port_num << QH_HUB_PORT_Pos) |
|
|
(hub->iface->udev->dev_num << QH_HUB_ADDR_Pos);
|
|
}
|
|
|
|
qh->Cap = cap;
|
|
}
|
|
|
|
static void write_qtd_bptr(qTD_T *qtd, uint32_t buff_addr, int xfer_len)
|
|
{
|
|
int i;
|
|
|
|
qtd->xfer_len = xfer_len;
|
|
qtd->Bptr[0] = buff_addr;
|
|
|
|
buff_addr = (buff_addr + 0x1000) & ~0xFFF;
|
|
|
|
for (i = 1; i < 5; i++)
|
|
{
|
|
qtd->Bptr[i] = buff_addr;
|
|
buff_addr += 0x1000;
|
|
}
|
|
}
|
|
|
|
static int ehci_ctrl_xfer(UTR_T *utr)
|
|
{
|
|
UDEV_T *udev;
|
|
QH_T *qh;
|
|
qTD_T *qtd_setup, *qtd_data, *qtd_status;
|
|
uint32_t token;
|
|
int is_new_qh = 0;
|
|
|
|
udev = utr->udev;
|
|
|
|
if (utr->data_len > 0)
|
|
{
|
|
if (((uint32_t)utr->buff + utr->data_len) > (((uint32_t)utr->buff & ~0xFFF) + 0x5000))
|
|
return USBH_ERR_BUFF_OVERRUN;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Allocate and link QH */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (udev->ep0.hw_pipe != NULL)
|
|
{
|
|
qh = (QH_T *)udev->ep0.hw_pipe;
|
|
if (qh->qtd_list)
|
|
return USBH_ERR_EHCI_QH_BUSY;
|
|
}
|
|
else
|
|
{
|
|
qh = alloc_ehci_QH();
|
|
if (qh == NULL)
|
|
return USBH_ERR_MEMORY_OUT;
|
|
|
|
udev->ep0.hw_pipe = (void *)qh; /* driver can find QH from EP */
|
|
is_new_qh = 1;
|
|
}
|
|
write_qh(udev, NULL, qh);
|
|
utr->ep = &udev->ep0; /* driver can find EP from UTR */
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Allocate qTDs */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qtd_setup = alloc_ehci_qTD(utr); /* allocate qTD for SETUP */
|
|
|
|
if (utr->data_len > 0)
|
|
qtd_data = alloc_ehci_qTD(utr); /* allocate qTD for DATA */
|
|
else
|
|
qtd_data = NULL;
|
|
|
|
qtd_status = alloc_ehci_qTD(utr); /* allocate qTD for USTSR */
|
|
|
|
if (qtd_status == NULL) /* out of memory? */
|
|
{
|
|
if (qtd_setup)
|
|
free_ehci_qTD(qtd_setup); /* free memory */
|
|
if (qtd_data)
|
|
free_ehci_qTD(qtd_data); /* free memory */
|
|
return USBH_ERR_MEMORY_OUT; /* out of memory */
|
|
}
|
|
|
|
// USB_debug("qh=0x%x, qtd_setup=0x%x, qtd_data=0x%x, qtd_status=0x%x\n", (int)qh, (int)qtd_setup, (int)qtd_data, (int)qtd_status);
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* prepare SETUP stage qTD */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qtd_setup->qh = qh;
|
|
//qtd_setup->utr = utr;
|
|
write_qtd_bptr(qtd_setup, (uint32_t)&utr->setup, 8);
|
|
append_to_qtd_list_of_QH(qh, qtd_setup);
|
|
qtd_setup->Token = (8 << 16) | QTD_ERR_COUNTER | QTD_PID_SETUP | QTD_STS_ACTIVE;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* prepare DATA stage qTD */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (utr->data_len > 0)
|
|
{
|
|
qtd_setup->Next_qTD = (uint32_t)qtd_data;
|
|
qtd_data->Next_qTD = (uint32_t)qtd_status;
|
|
|
|
if ((utr->setup.bmRequestType & 0x80) == REQ_TYPE_OUT)
|
|
token = QTD_ERR_COUNTER | QTD_PID_OUT | QTD_STS_ACTIVE;
|
|
else
|
|
token = QTD_ERR_COUNTER | QTD_PID_IN | QTD_STS_ACTIVE;
|
|
|
|
qtd_data->qh = qh;
|
|
//qtd_data->utr = utr;
|
|
write_qtd_bptr(qtd_data, (uint32_t)utr->buff, utr->data_len);
|
|
append_to_qtd_list_of_QH(qh, qtd_data);
|
|
qtd_data->Token = QTD_DT | (utr->data_len << 16) | token;
|
|
}
|
|
else
|
|
{
|
|
qtd_setup->Next_qTD = (uint32_t)qtd_status;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* prepare USTSR stage qTD */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qtd_status->Next_qTD = (uint32_t)_ghost_qtd;
|
|
qtd_status->Alt_Next_qTD = QTD_LIST_END;
|
|
|
|
if ((utr->setup.bmRequestType & 0x80) == REQ_TYPE_OUT)
|
|
token = QTD_ERR_COUNTER | QTD_PID_IN | QTD_STS_ACTIVE;
|
|
else
|
|
token = QTD_ERR_COUNTER | QTD_PID_OUT | QTD_STS_ACTIVE;
|
|
|
|
qtd_status->qh = qh;
|
|
//qtd_status->utr = utr;
|
|
append_to_qtd_list_of_QH(qh, qtd_status);
|
|
qtd_status->Token = QTD_DT | QTD_IOC | token;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Update QH overlay */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qh->Curr_qTD = 0;
|
|
qh->OL_Next_qTD = (uint32_t)qtd_setup;
|
|
qh->OL_Alt_Next_qTD = QTD_LIST_END;
|
|
qh->OL_Token = 0;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Link QH and start asynchronous transfer */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (is_new_qh)
|
|
{
|
|
qh->HLink = _H_qh->HLink;
|
|
_H_qh->HLink = QH_HLNK_QH(qh);
|
|
}
|
|
|
|
/* Start transfer */
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_ASEN_Msk; /* start asynchronous transfer */
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_bulk_xfer(UTR_T *utr)
|
|
{
|
|
UDEV_T *udev;
|
|
EP_INFO_T *ep = utr->ep;
|
|
QH_T *qh;
|
|
qTD_T *qtd, *qtd_pre;
|
|
uint32_t data_len, xfer_len;
|
|
uint8_t *buff;
|
|
uint32_t token;
|
|
int is_new_qh = 0;
|
|
|
|
//USB_debug("Bulk XFER =>\n");
|
|
// dump_ehci_asynclist_simple();
|
|
|
|
udev = utr->udev;
|
|
|
|
if (ep->hw_pipe != NULL)
|
|
{
|
|
qh = (QH_T *)ep->hw_pipe ;
|
|
if (qh->qtd_list)
|
|
{
|
|
return USBH_ERR_EHCI_QH_BUSY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qh = alloc_ehci_QH();
|
|
if (qh == NULL)
|
|
return USBH_ERR_MEMORY_OUT;
|
|
is_new_qh = 1;
|
|
write_qh(udev, ep, qh);
|
|
ep->hw_pipe = (void *)qh; /* associate QH with endpoint */
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Prepare qTDs */
|
|
/*------------------------------------------------------------------------------------*/
|
|
data_len = utr->data_len;
|
|
buff = utr->buff;
|
|
qtd_pre = NULL;
|
|
|
|
while (data_len > 0)
|
|
{
|
|
qtd = alloc_ehci_qTD(utr);
|
|
if (qtd == NULL) /* failed to allocate a qTD */
|
|
{
|
|
qtd = qh->qtd_list;
|
|
while (qtd != NULL)
|
|
{
|
|
qtd_pre = qtd;
|
|
qtd = qtd->next;
|
|
free_ehci_qTD(qtd_pre);
|
|
}
|
|
if (is_new_qh)
|
|
{
|
|
free_ehci_QH(qh);
|
|
ep->hw_pipe = NULL;
|
|
}
|
|
return USBH_ERR_MEMORY_OUT;
|
|
}
|
|
|
|
if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_OUT)
|
|
token = QTD_ERR_COUNTER | QTD_PID_OUT | QTD_STS_ACTIVE;
|
|
else
|
|
token = QTD_ERR_COUNTER | QTD_PID_IN | QTD_STS_ACTIVE;
|
|
|
|
if (data_len > 0x4000) /* force maximum x'fer length 16K per qTD */
|
|
xfer_len = 0x4000;
|
|
else
|
|
xfer_len = data_len; /* remaining data length < 4K */
|
|
|
|
qtd->qh = qh;
|
|
qtd->Next_qTD = (uint32_t)_ghost_qtd;
|
|
qtd->Alt_Next_qTD = QTD_LIST_END; //(uint32_t)_ghost_qtd;
|
|
write_qtd_bptr(qtd, (uint32_t)buff, xfer_len);
|
|
append_to_qtd_list_of_QH(qh, qtd);
|
|
qtd->Token = (xfer_len << 16) | token;
|
|
|
|
buff += xfer_len; /* advanced buffer pointer */
|
|
data_len -= xfer_len;
|
|
|
|
if (data_len == 0) /* is this the latest qTD? */
|
|
{
|
|
qtd->Token |= QTD_IOC; /* ask to raise an interrupt on the last qTD */
|
|
qtd->Next_qTD = (uint32_t)_ghost_qtd; /* qTD list end */
|
|
}
|
|
|
|
if (qtd_pre != NULL)
|
|
qtd_pre->Next_qTD = (uint32_t)qtd;
|
|
qtd_pre = qtd;
|
|
}
|
|
|
|
//USB_debug("utr=0x%x, qh=0x%x, qtd=0x%x\n", (int)utr, (int)qh, (int)qh->qtd_list);
|
|
|
|
qtd = qh->qtd_list;
|
|
|
|
// qh->Curr_qTD = 0; //(uint32_t)qtd;
|
|
qh->OL_Next_qTD = (uint32_t)qtd;
|
|
// qh->OL_Alt_Next_qTD = QTD_LIST_END;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Link QH and start asynchronous transfer */
|
|
/*------------------------------------------------------------------------------------*/
|
|
if (is_new_qh)
|
|
{
|
|
memcpy(&(qh->OL_Bptr[0]), &(qtd->Bptr[0]), 20);
|
|
qh->Curr_qTD = (uint32_t)qtd;
|
|
|
|
qh->OL_Token = 0; //qtd->Token;
|
|
|
|
if (utr->ep->bToggle)
|
|
qh->OL_Token |= QTD_DT;
|
|
|
|
qh->HLink = _H_qh->HLink;
|
|
_H_qh->HLink = QH_HLNK_QH(qh);
|
|
}
|
|
|
|
/* Start transfer */
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_ASEN_Msk; /* start asynchronous transfer */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ehci_int_xfer(UTR_T *utr)
|
|
{
|
|
UDEV_T *udev = utr->udev;
|
|
EP_INFO_T *ep = utr->ep;
|
|
QH_T *qh, *iqh;
|
|
qTD_T *qtd, *dummy_qtd;
|
|
uint32_t token;
|
|
|
|
dummy_qtd = alloc_ehci_qTD(NULL); /* allocate a new dummy qTD */
|
|
if (dummy_qtd == NULL)
|
|
return USBH_ERR_MEMORY_OUT;
|
|
dummy_qtd->Token &= ~(QTD_STS_ACTIVE | QTD_STS_HALT);
|
|
|
|
if (ep->hw_pipe != NULL)
|
|
{
|
|
qh = (QH_T *)ep->hw_pipe ;
|
|
}
|
|
else
|
|
{
|
|
qh = alloc_ehci_QH();
|
|
if (qh == NULL)
|
|
{
|
|
free_ehci_qTD(dummy_qtd);
|
|
return USBH_ERR_MEMORY_OUT;
|
|
}
|
|
write_qh(udev, ep, qh);
|
|
qh->Chrst &= ~0xF0000000;
|
|
|
|
if (udev->speed == SPEED_HIGH)
|
|
{
|
|
qh->Cap = (0x1 << QH_MULT_Pos) | (qh->Cap & 0xff) | make_int_s_mask(ep->bInterval);
|
|
}
|
|
else
|
|
{
|
|
qh->Cap = (0x1 << QH_MULT_Pos) | (qh->Cap & ~(QH_C_MASK_Msk | QH_S_MASK_Msk)) | 0x7802;
|
|
}
|
|
ep->hw_pipe = (void *)qh; /* associate QH with endpoint */
|
|
|
|
/*
|
|
* Allocate another dummy qTD
|
|
*/
|
|
qtd = alloc_ehci_qTD(NULL); /* allocate a new dummy qTD */
|
|
if (qtd == NULL)
|
|
{
|
|
free_ehci_qTD(dummy_qtd);
|
|
free_ehci_QH(qh);
|
|
return USBH_ERR_MEMORY_OUT;
|
|
}
|
|
qtd->Token &= ~(QTD_STS_ACTIVE | QTD_STS_HALT);
|
|
|
|
qh->dummy = dummy_qtd;
|
|
qh->OL_Next_qTD = (uint32_t)dummy_qtd;
|
|
qh->OL_Token = 0; /* !Active & !Halted */
|
|
|
|
/*
|
|
* link QH
|
|
*/
|
|
if (udev->speed == SPEED_HIGH) /* get head node of this interval */
|
|
iqh = get_int_tree_head_node(ep->bInterval);
|
|
else
|
|
iqh = get_int_tree_head_node(ep->bInterval * 8);
|
|
qh->HLink = iqh->HLink; /* Add to list of the same interval */
|
|
iqh->HLink = QH_HLNK_QH(qh);
|
|
|
|
dummy_qtd = qtd;
|
|
}
|
|
|
|
qtd = qh->dummy; /* use the current dummy qTD */
|
|
qtd->Next_qTD = (uint32_t)dummy_qtd;
|
|
qtd->utr = utr;
|
|
qh->dummy = dummy_qtd; /* give the new dummy qTD */
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Prepare qTD */
|
|
/*------------------------------------------------------------------------------------*/
|
|
|
|
if ((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_OUT)
|
|
token = QTD_ERR_COUNTER | QTD_PID_OUT;
|
|
else
|
|
token = QTD_ERR_COUNTER | QTD_PID_IN;
|
|
|
|
qtd->qh = qh;
|
|
qtd->Alt_Next_qTD = QTD_LIST_END;
|
|
write_qtd_bptr(qtd, (uint32_t)utr->buff, utr->data_len);
|
|
append_to_qtd_list_of_QH(qh, qtd);
|
|
qtd->Token = QTD_IOC | (utr->data_len << 16) | token | QTD_STS_ACTIVE;
|
|
|
|
// printf("ehci_int_xfer - qh: 0x%x, 0x%x, 0x%x\n", (int)qh, (int)qh->Chrst, (int)qh->Cap);
|
|
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_PSEN_Msk; /* periodic list enable */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Quit current trasnfer via UTR or hardware EP.
|
|
*/
|
|
static int ehci_quit_xfer(UTR_T *utr, EP_INFO_T *ep)
|
|
{
|
|
QH_T *qh;
|
|
|
|
// USB_debug("ehci_quit_xfer - utr: 0x%x, ep: 0x%x\n", (int)utr, (int)ep);
|
|
|
|
DISABLE_EHCI_IRQ();
|
|
if (ehci_quit_iso_xfer(utr, ep) == 0)
|
|
{
|
|
ENABLE_EHCI_IRQ();
|
|
return 0;
|
|
}
|
|
ENABLE_EHCI_IRQ();
|
|
|
|
if (utr != NULL)
|
|
{
|
|
if (utr->ep == NULL)
|
|
return USBH_ERR_NOT_FOUND;
|
|
|
|
qh = (QH_T *)(utr->ep->hw_pipe);
|
|
|
|
if (!qh)
|
|
return USBH_ERR_NOT_FOUND;
|
|
|
|
/* add the QH to remove list, it will be removed on the next IAAD interrupt */
|
|
move_qh_to_remove_list(qh);
|
|
utr->ep->hw_pipe = NULL;
|
|
}
|
|
|
|
if ((ep != NULL) && (ep->hw_pipe != NULL))
|
|
{
|
|
qh = (QH_T *)(ep->hw_pipe);
|
|
/* add the QH to remove list, it will be removed on the next IAAD interrupt */
|
|
move_qh_to_remove_list(qh);
|
|
ep->hw_pipe = NULL;
|
|
}
|
|
usbh_delay_ms(2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int visit_qtd(qTD_T *qtd)
|
|
{
|
|
if ((qtd->Token == 0x11197B3F) || (qtd->Token == 0x1197B3F))
|
|
return 0; /* A Dummy qTD or qTD on writing, don't touch it. */
|
|
|
|
// USB_debug("Visit qtd 0x%x - 0x%x\n", (int)qtd, qtd->Token);
|
|
|
|
if ((qtd->Token & QTD_STS_ACTIVE) == 0)
|
|
{
|
|
if (qtd->Token & (QTD_STS_HALT | QTD_STS_DATA_BUFF_ERR | QTD_STS_BABBLE | QTD_STS_XactErr | QTD_STS_MISS_MF))
|
|
{
|
|
USB_error("qTD error token=0x%x! 0x%x\n", qtd->Token, qtd->Bptr[0]);
|
|
if (qtd->utr->status == 0)
|
|
qtd->utr->status = USBH_ERR_TRANSACTION;
|
|
}
|
|
else
|
|
{
|
|
if ((qtd->Token & QTD_PID_Msk) != QTD_PID_SETUP)
|
|
{
|
|
qtd->utr->xfer_len += qtd->xfer_len - QTD_TODO_LEN(qtd->Token);
|
|
// USB_debug("0x%x utr->xfer_len += %d\n", qtd->Token, qtd->xfer_len - QTD_TODO_LEN(qtd->Token));
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void scan_asynchronous_list()
|
|
{
|
|
QH_T *qh, *qh_tmp;
|
|
qTD_T *q_pre = NULL, *qtd, *qtd_tmp;
|
|
UTR_T *utr;
|
|
|
|
qh = QH_PTR(_H_qh->HLink);
|
|
while (qh != _H_qh)
|
|
{
|
|
// USB_debug("Scan qh=0x%x, 0x%x\n", (int)qh, qh->OL_Token);
|
|
|
|
utr = NULL;
|
|
qtd = qh->qtd_list;
|
|
while (qtd != NULL)
|
|
{
|
|
if (visit_qtd(qtd)) /* if TRUE, reclaim this qtd */
|
|
{
|
|
/* qTD is completed, will remove it */
|
|
utr = qtd->utr;
|
|
if (qtd == qh->qtd_list)
|
|
qh->qtd_list = qtd->next; /* unlink the qTD from qtd_list */
|
|
else
|
|
q_pre->next = qtd->next; /* unlink the qTD from qtd_list */
|
|
|
|
qtd_tmp = qtd; /* remember this qTD for freeing later */
|
|
qtd = qtd->next; /* advance to the next qTD */
|
|
|
|
qtd_tmp->next = qh->done_list; /* push this qTD to QH's done list */
|
|
qh->done_list = qtd_tmp;
|
|
}
|
|
else
|
|
{
|
|
q_pre = qtd; /* remember this qTD as a preceder */
|
|
qtd = qtd->next; /* advance to next qTD */
|
|
}
|
|
}
|
|
|
|
qh_tmp = qh;
|
|
qh = QH_PTR(qh->HLink); /* advance to the next QH */
|
|
|
|
/* If all TDs are done, call-back to requester and then remove this QH. */
|
|
if ((qh_tmp->qtd_list == NULL) && utr)
|
|
{
|
|
// printf("T %d [%d]\n", (qh_tmp->Chrst>>8)&0xf, (qh_tmp->OL_Token&QTD_DT) ? 1 : 0);
|
|
if (qh_tmp->OL_Token & QTD_DT)
|
|
utr->ep->bToggle = 1;
|
|
else
|
|
utr->ep->bToggle = 0;
|
|
|
|
utr->bIsTransferDone = 1;
|
|
if (utr->func)
|
|
utr->func(utr);
|
|
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_IAAD_Msk; /* trigger IAA to reclaim done_list */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void scan_periodic_frame_list()
|
|
{
|
|
QH_T *qh;
|
|
qTD_T *qtd, *qNext;
|
|
UTR_T *utr;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Scan interrupt frame list */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qh = _Iqh[NUM_IQH - 1];
|
|
while (qh != NULL)
|
|
{
|
|
qtd = qh->qtd_list;
|
|
|
|
if (qtd == NULL)
|
|
{
|
|
/* empty QH */
|
|
qh = QH_PTR(qh->HLink); /* advance to the next QH */
|
|
continue;
|
|
}
|
|
|
|
while (qtd != NULL)
|
|
{
|
|
qNext = qtd->next;
|
|
|
|
if (visit_qtd(qtd)) /* if TRUE, reclaim this qtd */
|
|
{
|
|
qh->qtd_list = qtd->next; /* proceed to next qTD or NULL */
|
|
qtd->next = qh->done_list; /* push qTD into the done list */
|
|
qh->done_list = qtd; /* move qTD to done list */
|
|
}
|
|
qtd = qNext;
|
|
}
|
|
|
|
qtd = qh->done_list;
|
|
|
|
while (qtd != NULL)
|
|
{
|
|
utr = qtd->utr;
|
|
|
|
if (qh->OL_Token & QTD_DT)
|
|
utr->ep->bToggle = 1;
|
|
else
|
|
utr->ep->bToggle = 0;
|
|
|
|
utr->bIsTransferDone = 1;
|
|
if (utr->func)
|
|
utr->func(utr);
|
|
|
|
_ehci->UCMDR |= HSUSBH_UCMDR_IAAD_Msk; /* trigger IAA to reclaim done_list */
|
|
|
|
qtd = qtd->next;
|
|
}
|
|
|
|
qh = QH_PTR(qh->HLink); /* advance to the next QH */
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Scan isochronous frame list */
|
|
/*------------------------------------------------------------------------------------*/
|
|
|
|
scan_isochronous_list();
|
|
}
|
|
|
|
void iaad_remove_qh()
|
|
{
|
|
QH_T *qh;
|
|
qTD_T *qtd;
|
|
UTR_T *utr;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Remove all QHs in qh_remove_list... */
|
|
/*------------------------------------------------------------------------------------*/
|
|
while (qh_remove_list != NULL)
|
|
{
|
|
qh = qh_remove_list;
|
|
qh_remove_list = qh->next;
|
|
|
|
// USB_debug("iaad_remove_qh - remove QH 0x%x\n", (int)qh);
|
|
|
|
while (qh->done_list) /* we can free the qTDs now */
|
|
{
|
|
qtd = qh->done_list;
|
|
qh->done_list = qtd->next;
|
|
free_ehci_qTD(qtd);
|
|
}
|
|
|
|
if (qh->qtd_list != NULL) /* still have incomplete qTDs? */
|
|
{
|
|
utr = qh->qtd_list->utr;
|
|
while (qh->qtd_list)
|
|
{
|
|
qtd = qh->qtd_list;
|
|
qh->qtd_list = qtd->next;
|
|
free_ehci_qTD(qtd);
|
|
}
|
|
utr->status = USBH_ERR_ABORT;
|
|
utr->bIsTransferDone = 1;
|
|
if (utr->func)
|
|
utr->func(utr); /* call back */
|
|
}
|
|
free_ehci_QH(qh); /* free the QH */
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Free all qTD in done_list of each asynchronous QH */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qh = QH_PTR(_H_qh->HLink);
|
|
while (qh != _H_qh)
|
|
{
|
|
while (qh->done_list) /* we can free the qTDs now */
|
|
{
|
|
qtd = qh->done_list;
|
|
qh->done_list = qtd->next;
|
|
free_ehci_qTD(qtd);
|
|
}
|
|
qh = QH_PTR(qh->HLink); /* advance to the next QH */
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* Free all qTD in done_list of each QH of periodic frame list */
|
|
/*------------------------------------------------------------------------------------*/
|
|
qh = _Iqh[NUM_IQH - 1];
|
|
while (qh != NULL)
|
|
{
|
|
while (qh->done_list) /* we can free the qTDs now */
|
|
{
|
|
qtd = qh->done_list;
|
|
qh->done_list = qtd->next;
|
|
free_ehci_qTD(qtd);
|
|
}
|
|
qh = QH_PTR(qh->HLink); /* advance to the next QH */
|
|
}
|
|
}
|
|
|
|
//static irqreturn_t ehci_irq (struct usb_hcd *hcd)
|
|
void EHCI_IRQHandler(void)
|
|
{
|
|
uint32_t intsts;
|
|
|
|
/* enter interrupt */
|
|
rt_interrupt_enter();
|
|
|
|
intsts = _ehci->USTSR;
|
|
_ehci->USTSR = intsts; /* clear interrupt status */
|
|
|
|
// USB_debug("Eirq USTSR=0x%x\n", intsts);
|
|
|
|
if (intsts & HSUSBH_USTSR_UERRINT_Msk)
|
|
{
|
|
// USB_error("Transfer error!\n");
|
|
}
|
|
|
|
if (intsts & HSUSBH_USTSR_USBINT_Msk)
|
|
{
|
|
/* some transfers completed, travel asynchronous */
|
|
/* and periodic lists to find and reclaim them. */
|
|
scan_asynchronous_list();
|
|
|
|
scan_periodic_frame_list();
|
|
}
|
|
|
|
if (intsts & HSUSBH_USTSR_IAA_Msk)
|
|
{
|
|
iaad_remove_qh();
|
|
}
|
|
|
|
/* leave interrupt */
|
|
rt_interrupt_leave();
|
|
}
|
|
|
|
static UDEV_T *ehci_find_device_by_port(int port)
|
|
{
|
|
UDEV_T *udev;
|
|
|
|
udev = g_udev_list;
|
|
while (udev != NULL)
|
|
{
|
|
if ((udev->parent == NULL) && (udev->port_num == port) && (udev->speed == SPEED_HIGH))
|
|
return udev;
|
|
udev = udev->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int ehci_rh_port_reset(int port)
|
|
{
|
|
int retry;
|
|
int reset_time;
|
|
uint32_t t0;
|
|
|
|
reset_time = usbh_tick_from_millisecond(PORT_RESET_TIME_MS);
|
|
|
|
for (retry = 0; retry < PORT_RESET_RETRY; retry++)
|
|
{
|
|
_ehci->UPSCR[port] = (_ehci->UPSCR[port] | HSUSBH_UPSCR_PRST_Msk) & ~HSUSBH_UPSCR_PE_Msk;
|
|
|
|
t0 = usbh_get_ticks();
|
|
while (usbh_get_ticks() - t0 < (reset_time) + 1) ; /* wait at least 50 ms */
|
|
|
|
_ehci->UPSCR[port] &= ~HSUSBH_UPSCR_PRST_Msk;
|
|
|
|
t0 = usbh_get_ticks();
|
|
while (usbh_get_ticks() - t0 < (reset_time) + 1)
|
|
{
|
|
if (!(_ehci->UPSCR[port] & HSUSBH_UPSCR_CCS_Msk) ||
|
|
((_ehci->UPSCR[port] & (HSUSBH_UPSCR_CCS_Msk | HSUSBH_UPSCR_PE_Msk)) == (HSUSBH_UPSCR_CCS_Msk | HSUSBH_UPSCR_PE_Msk)))
|
|
goto port_reset_done;
|
|
}
|
|
reset_time += PORT_RESET_RETRY_INC_MS;
|
|
}
|
|
|
|
USB_debug("EHCI port %d - port reset failed!\n", port + 1);
|
|
return USBH_ERR_PORT_RESET;
|
|
|
|
port_reset_done:
|
|
if ((_ehci->UPSCR[port] & HSUSBH_UPSCR_CCS_Msk) == 0) /* check again if device disconnected */
|
|
{
|
|
_ehci->UPSCR[port] |= HSUSBH_UPSCR_CSC_Msk; /* clear CSC */
|
|
return USBH_ERR_DISCONNECTED;
|
|
}
|
|
_ehci->UPSCR[port] |= HSUSBH_UPSCR_PEC_Msk; /* clear port enable change status */
|
|
return USBH_OK; /* port reset success */
|
|
}
|
|
|
|
static int ehci_rh_polling(void)
|
|
{
|
|
UDEV_T *udev;
|
|
int ret;
|
|
int connect_status, t0, debounce_tick;
|
|
|
|
if (!(_ehci->UPSCR[0] & HSUSBH_UPSCR_CSC_Msk))
|
|
return 0;
|
|
|
|
/*------------------------------------------------------------------------------------*/
|
|
/* connect status change */
|
|
/*------------------------------------------------------------------------------------*/
|
|
|
|
USB_debug("EHCI port1 status change: 0x%x\n", _ehci->UPSCR[0]);
|
|
|
|
/*--------------------------------------------------------------------------------*/
|
|
/* Disconnect the devices attached to this port. */
|
|
/*--------------------------------------------------------------------------------*/
|
|
while (1)
|
|
{
|
|
udev = ehci_find_device_by_port(1);
|
|
if (udev == NULL)
|
|
break;
|
|
usbh_disconnect_device(udev);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------------*/
|
|
/* Port de-bounce */
|
|
/*--------------------------------------------------------------------------------*/
|
|
t0 = usbh_get_ticks();
|
|
debounce_tick = usbh_tick_from_millisecond(HUB_DEBOUNCE_TIME);
|
|
connect_status = _ehci->UPSCR[0] & HSUSBH_UPSCR_CCS_Msk;
|
|
while (usbh_get_ticks() - t0 < debounce_tick)
|
|
{
|
|
if (connect_status != (_ehci->UPSCR[0] & HSUSBH_UPSCR_CCS_Msk))
|
|
{
|
|
/* reset stable time counting */
|
|
t0 = usbh_get_ticks();
|
|
connect_status = _ehci->UPSCR[0] & HSUSBH_UPSCR_CCS_Msk;
|
|
}
|
|
}
|
|
|
|
_ehci->UPSCR[0] |= HSUSBH_UPSCR_CSC_Msk; /* clear connect status change bit */
|
|
|
|
if (connect_status == HSUSBH_UPSCR_CCS_Msk)
|
|
{
|
|
/*--------------------------------------------------------------------------------*/
|
|
/* A new device connected. */
|
|
/*--------------------------------------------------------------------------------*/
|
|
if (ehci_rh_port_reset(0) != USBH_OK)
|
|
{
|
|
/* port reset failed, maybe an USB 1.1 device */
|
|
_ehci->UPSCR[0] |= HSUSBH_UPSCR_PO_Msk; /* change port owner to OHCI */
|
|
_ehci->UPSCR[0] |= HSUSBH_UPSCR_CSC_Msk; /* clear all status change bits */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Port reset success. Start to enumerate this new device.
|
|
*/
|
|
udev = alloc_device();
|
|
if (udev == NULL)
|
|
return 0; /* out-of-memory, do nothing... */
|
|
|
|
udev->parent = NULL;
|
|
udev->port_num = 1;
|
|
udev->speed = SPEED_HIGH;
|
|
udev->hc_driver = &ehci_driver;
|
|
|
|
ret = usbh_connect_device(udev);
|
|
if (ret < 0)
|
|
{
|
|
USB_error("connect_device error! [%d]\n", ret);
|
|
free_device(udev);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Device disconnected
|
|
*/
|
|
while (1)
|
|
{
|
|
udev = ehci_find_device_by_port(1);
|
|
if (udev == NULL)
|
|
break;
|
|
usbh_disconnect_device(udev);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
HC_DRV_T ehci_driver =
|
|
{
|
|
ehci_init, /* init */
|
|
ehci_shutdown, /* shutdown */
|
|
ehci_suspend, /* suspend */
|
|
ehci_resume, /* resume */
|
|
ehci_ctrl_xfer, /* ctrl_xfer */
|
|
ehci_bulk_xfer, /* bulk_xfer */
|
|
ehci_int_xfer, /* int_xfer */
|
|
ehci_iso_xfer, /* iso_xfer */
|
|
ehci_quit_xfer, /* quit_xfer */
|
|
ehci_rh_port_reset, /* rthub_port_reset */
|
|
ehci_rh_polling /* rthub_polling */
|
|
};
|
|
|
|
|
|
/// @endcond HIDDEN_SYMBOLS
|
|
|
|
/*** (C) COPYRIGHT 2017 Nuvoton Technology Corp. ***/
|