Files
luban-lite/packages/third-party/cherryusb/port/ehci/usb_ehci.c
刘可亮 564e22b32f v0.7.5
2023-08-28 09:48:01 +08:00

2840 lines
88 KiB
C

#include "usbh_core.h"
#include "usb_ehci.h"
#ifndef USBH_IRQHandler
#define USBH_IRQHandler USBH_IRQHandler
#endif
#define DEBUGASSERT(f)
/* Configurable number of Queue Head (QH) structures. The default is one per
* Root hub port plus one for EP0.
*/
#ifndef CONFIG_USB_EHCI_QH_NUM
#define CONFIG_USB_EHCI_QH_NUM (CONFIG_USBHOST_RHPORTS + 1)
#endif
/* Configurable number of Queue Element Transfer Descriptor (qTDs). The
* default is one per root hub plus three from EP0.
*/
#ifndef CONFIG_USB_EHCI_QTD_NUM
#define CONFIG_USB_EHCI_QTD_NUM (CONFIG_USBHOST_RHPORTS + 3)
#endif
/* Registers ****************************************************************/
/* Traditionally, NuttX specifies register locations using individual
* register offsets from a base address. That tradition is broken here and,
* instead, register blocks are represented as structures. This is done here
* because, in principle, EHCI operational register address may not be known
* at compile time; the operational registers lie at an offset specified in
* the 'caplength' byte of the Host Controller Capability Registers.
*
* However, for the case of the LPC43 EHCI, we know apriori that locations
* of these register blocks.
*/
/* Host Controller Capability Registers */
#define HCCR ((struct ehci_hccr_s *)CONFIG_USB_EHCI_HCCR_BASE)
/* Host Controller Operational Registers */
#define HCOR ((volatile struct ehci_hcor_s *)CONFIG_USB_EHCI_HCOR_BASE)
/* Interrupts ***************************************************************/
/* This is the set of interrupts handled by this driver */
#define EHCI_HANDLED_INTS (EHCI_INT_USBINT | EHCI_INT_USBERRINT | \
EHCI_INT_PORTSC | EHCI_INT_SYSERROR | \
EHCI_INT_AAINT)
/* The periodic frame list is a 4K-page aligned array of Frame List Link
* pointers. The length of the frame list may be programmable. The
* programmability of the periodic frame list is exported to system software
* via the HCCPARAMS register. If non-programmable, the length is 1024
* elements. If programmable, the length can be selected by system software
* as one of 256, 512, or 1024 elements.
*/
#define FRAME_LIST_SIZE 1024
/* DMA **********************************************************************/
/* For now, we are assuming an identity mapping between physical and virtual
* address spaces.
*/
#define usb_ehci_physramaddr(a) (a)
#define usb_ehci_virtramaddr(a) (a)
/****************************************************************************
* Private Types
****************************************************************************/
/* Internal representation of the EHCI Queue Head (QH) */
struct usb_ehci_epinfo_s;
struct usb_ehci_qh_s {
/* Fields visible to hardware */
struct ehci_qh_s hw __attribute__((aligned(CACHE_LINE_SIZE))); /* Hardware representation of the queue head */
/* Internal fields used by the EHCI driver */
struct usb_ehci_epinfo_s *epinfo; /* Endpoint used for the transfer */
uint32_t fqp; /* First qTD in the list (physical address) */
#if (__LONG_WIDTH__ == 32)
uint8_t pad[8]; /* Padding to assure 32-byte alignment */
#endif
};
/* Internal representation of the EHCI Queue Element Transfer Descriptor
* (qTD)
*/
struct usb_ehci_qtd_s {
/* Fields visible to hardware */
struct ehci_qtd_s hw __attribute__((aligned(CACHE_LINE_SIZE))); /* Hardware representation of the queue head */
/* Internal fields used by the EHCI driver */
};
/* The following is used to manage lists of free QHs and qTDs */
struct usb_ehci_list_s {
struct usb_ehci_list_s *flink; /* Link to next entry in the list */
/* Variable length entry data follows */
};
/* List traversal callout functions */
typedef int (*foreach_qh_t)(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg);
typedef int (*foreach_qtd_t)(struct usb_ehci_qtd_s *qtd, uint32_t **bp, void *arg);
/* This structure describes one endpoint. */
struct usb_ehci_epinfo_s {
uint8_t epno : 7; /* Endpoint number */
uint8_t dirin : 1; /* 1:IN endpoint 0:OUT endpoint */
uint8_t devaddr : 7; /* Device address */
uint8_t toggle : 1; /* Next data toggle */
#ifndef CONFIG_USBHOST_INT_DISABLE
uint8_t interval; /* Polling interval */
#endif
bool inuse;
uint8_t status; /* Retained token status bits (for debug purposes) */
uint16_t maxpacket : 11; /* Maximum packet size */
uint16_t xfrtype : 2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */
uint16_t speed : 2; /* See USB_*_SPEED definitions in ehci.h */
int result; /* The result of the transfer */
uint32_t xfrd; /* On completion, will hold the number of bytes transferred */
volatile bool iocwait; /* TRUE: Thread is waiting for transfer completion */
usb_osal_sem_t iocsem; /* Semaphore used to wait for transfer completion */
usb_osal_mutex_t exclsem; /* Support mutually exclusive access */
#ifdef CONFIG_USBHOST_ASYNCH
usbh_asynch_callback_t callback; /* Transfer complete callback */
void *arg; /* Argument that accompanies the callback */
#endif
struct usbh_hubport *hport;
struct usb_ehci_qh_s *qh;
};
/* This structure retains the overall state of the USB host controller */
struct ehci_hcd {
struct usb_ehci_list_s *qhfree; /* List of free Queue Head (QH) structures */
struct usb_ehci_list_s *qtdfree; /* List of free Queue Element Transfer Descriptor (qTD) */
struct usb_ehci_qh_s qhpool[CONFIG_USB_EHCI_QH_NUM] __attribute__((aligned(CACHE_LINE_SIZE))); /* Queue Head (QH) pool */
struct usb_ehci_qtd_s qtdpool[CONFIG_USB_EHCI_QTD_NUM] __attribute__((aligned(CACHE_LINE_SIZE))); /* Queue Element Transfer Descriptor (qTD) pool */
struct usb_ehci_epinfo_s chan[CONFIG_USBHOST_PIPE_NUM];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Register operations ******************************************************/
static uint16_t usb_ehci_read16(const uint8_t *addr);
static uint32_t usb_ehci_read32(const uint8_t *addr);
#if 0 /* Not used */
static void usb_ehci_write16(uint16_t memval, uint8_t *addr);
static void usb_ehci_write32(uint32_t memval, uint8_t *addr);
#endif
#ifdef CONFIG_ENDIAN_BIG
static uint16_t usb_ehci_swap16(uint16_t value);
static uint32_t usb_ehci_swap32(uint32_t value);
#else
#define usb_ehci_swap16(value) (value)
#define usb_ehci_swap32(value) (value)
#endif
#ifdef CONFIG_USB_DCACHE_ENABLE
void usb_ehci_dcache_clean(uintptr_t addr, uint32_t len);
void usb_ehci_dcache_invalidate(uintptr_t addr, uint32_t len);
void usb_ehci_dcache_clean_invalidate(uintptr_t addr, uint32_t len);
#else
#define usb_ehci_dcache_clean(addr, len)
#define usb_ehci_dcache_invalidate(addr, len)
#define usb_ehci_dcache_clean_invalidate(addr, len)
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* In this driver implementation, support is provided for only a single
* USB device. All status information can be simply retained in a single
* global instance.
*/
/* Maps USB chapter 9 speed to EHCI speed */
static const uint8_t g_ehci_speed[4] = {
0, EHCI_LOW_SPEED, EHCI_FULL_SPEED, EHCI_HIGH_SPEED
};
/* In this driver implementation, support is provided for only a single
* USB device. All status information can be simply retained in a single
* global instance.
*/
static struct ehci_hcd g_ehci_hcd;
/* The head of the asynchronous queue */
static struct usb_ehci_qh_s g_asynchead __attribute__((aligned(CACHE_LINE_SIZE)));
/* The head of the periodic queue */
static struct usb_ehci_qh_s g_intrhead __attribute__((aligned(CACHE_LINE_SIZE)));
/* The frame list */
static uint32_t g_framelist[FRAME_LIST_SIZE] __attribute__((aligned(4096)));
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: usb_ehci_read16
*
* Description:
* Read 16-bit little endian data
*
****************************************************************************/
static uint16_t usb_ehci_read16(const uint8_t *addr)
{
#ifdef CONFIG_ENDIAN_BIG
return (uint16_t)addr[0] << 8 | (uint16_t)addr[1];
#else
return (uint16_t)addr[1] << 8 | (uint16_t)addr[0];
#endif
}
/****************************************************************************
* Name: usb_ehci_read32
*
* Description:
* Read 32-bit little endian data
*
****************************************************************************/
static inline uint32_t usb_ehci_read32(const uint8_t *addr)
{
#ifdef CONFIG_ENDIAN_BIG
return (uint32_t)usb_ehci_read16(&addr[0]) << 16 |
(uint32_t)usb_ehci_read16(&addr[2]);
#else
return (uint32_t)usb_ehci_read16(&addr[2]) << 16 |
(uint32_t)usb_ehci_read16(&addr[0]);
#endif
}
/****************************************************************************
* Name: usb_ehci_swap16
*
* Description:
* Swap bytes on a 16-bit value
*
****************************************************************************/
#ifdef CONFIG_ENDIAN_BIG
static uint16_t usb_ehci_swap16(uint16_t value)
{
return ((value >> 8) & 0xff) | ((value & 0xff) << 8);
}
#endif
/****************************************************************************
* Name: usb_ehci_swap32
*
* Description:
* Swap bytes on a 32-bit value
*
****************************************************************************/
#ifdef CONFIG_ENDIAN_BIG
static uint32_t usb_ehci_swap32(uint32_t value)
{
return (uint32_t)usb_ehci_swap16((uint16_t)((value >> 16) & 0xffff)) |
(uint32_t)usb_ehci_swap16((uint16_t)(value & 0xffff)) << 16;
}
#endif
static inline void usb_ehci_putreg(uint32_t regval, volatile uint32_t *regaddr)
{
*regaddr = regval;
}
static inline uint32_t usb_ehci_getreg(volatile uint32_t *regaddr)
{
return *regaddr;
}
static int usb_ehci_chan_alloc(void)
{
int chidx;
/* Search the table of channels */
for (chidx = 0; chidx < CONFIG_USBHOST_PIPE_NUM; chidx++) {
/* Is this channel available? */
if (!g_ehci_hcd.chan[chidx].inuse) {
/* Yes... make it "in use" and return the index */
g_ehci_hcd.chan[chidx].inuse = true;
return chidx;
}
}
/* All of the channels are "in-use" */
return -EBUSY;
}
static void usb_ehci_chan_free(struct usb_ehci_epinfo_s *chan)
{
/* Mark the channel available */
chan->inuse = false;
}
/****************************************************************************
* Name: usb_ehci_qh_alloc
*
* Description:
* Allocate a Queue Head (QH) structure by removing it from the free list
*
* Assumption: Caller holds the exclsem
*
****************************************************************************/
static struct usb_ehci_qh_s *usb_ehci_qh_alloc(void)
{
struct usb_ehci_qh_s *qh;
/* Remove the QH structure from the freelist */
qh = (struct usb_ehci_qh_s *)g_ehci_hcd.qhfree;
if (qh) {
g_ehci_hcd.qhfree = ((struct usb_ehci_list_s *)qh)->flink;
memset(qh, 0, sizeof(struct usb_ehci_qh_s));
}
return qh;
}
/****************************************************************************
* Name: usb_ehci_qh_free
*
* Description:
* Free a Queue Head (QH) structure by returning it to the free list
*
* Assumption: Caller holds the exclsem
*
****************************************************************************/
static void usb_ehci_qh_free(struct usb_ehci_qh_s *qh)
{
struct usb_ehci_list_s *entry = (struct usb_ehci_list_s *)qh;
/* Put the QH structure back into the free list */
entry->flink = g_ehci_hcd.qhfree;
g_ehci_hcd.qhfree = entry;
}
/****************************************************************************
* Name: usb_ehci_qtd_alloc
*
* Description:
* Allocate a Queue Element Transfer Descriptor (qTD) by removing it from
* the free list
*
* Assumption: Caller holds the exclsem
*
****************************************************************************/
static struct usb_ehci_qtd_s *usb_ehci_qtd_alloc(void)
{
struct usb_ehci_qtd_s *qtd;
/* Remove the qTD from the freelist */
qtd = (struct usb_ehci_qtd_s *)g_ehci_hcd.qtdfree;
if (qtd) {
g_ehci_hcd.qtdfree = ((struct usb_ehci_list_s *)qtd)->flink;
memset(qtd, 0, sizeof(struct usb_ehci_qtd_s));
}
return qtd;
}
/****************************************************************************
* Name: usb_ehci_qtd_free
*
* Description:
* Free a Queue Element Transfer Descriptor (qTD) by returning it to the
* free list
*
* Assumption: Caller holds the exclsem
*
****************************************************************************/
static void usb_ehci_qtd_free(struct usb_ehci_qtd_s *qtd)
{
struct usb_ehci_list_s *entry = (struct usb_ehci_list_s *)qtd;
/* Put the qTD back into the free list */
entry->flink = g_ehci_hcd.qtdfree;
g_ehci_hcd.qtdfree = entry;
}
/****************************************************************************
* Name: usb_ehci_qh_foreach
*
* Description:
* Give the first entry in a list of Queue Head (QH) structures, call the
* handler for each QH structure in the list (including the one at the head
* of the list).
*
****************************************************************************/
static int usb_ehci_qh_foreach(struct usb_ehci_qh_s *qh, uint32_t **bp,
foreach_qh_t handler, void *arg)
{
struct usb_ehci_qh_s *next;
uintptr_t physaddr;
int ret;
DEBUGASSERT(qh && handler);
while (qh) {
/* Is this the end of the list? Check the horizontal link pointer
* (HLP) terminate (T) bit. If T==1, then the HLP address is not
* valid.
*/
physaddr = usb_ehci_swap32(qh->hw.hlp);
if ((physaddr & QH_HLP_T) != 0) {
/* Set the next pointer to NULL. This will terminate the loop. */
next = NULL;
}
/* Is the next QH the asynchronous list head which will always be at
* the end of the asynchronous queue?
*/
else if (usb_ehci_virtramaddr(physaddr & QH_HLP_MASK) ==
(uintptr_t)&g_asynchead) {
/* That will also terminate the loop */
next = NULL;
}
/* Otherwise, there is a QH structure after this one that describes
* another transaction.
*/
else {
physaddr = usb_ehci_swap32(qh->hw.hlp) & QH_HLP_MASK;
next = (struct usb_ehci_qh_s *)usb_ehci_virtramaddr(physaddr);
}
/* Perform the user action on this entry. The action might result in
* unlinking the entry! But that is okay because we already have the
* next QH pointer.
*
* Notice that we do not manage the back pointer (bp). If the callout
* uses it, it must update it as necessary.
*/
ret = handler(qh, bp, arg);
/* If the handler returns any non-zero value, then terminate the
* traversal early.
*/
if (ret != 0) {
return ret;
}
/* Set up to visit the next entry */
qh = next;
}
return 0;
}
/****************************************************************************
* Name: usb_ehci_qtd_foreach
*
* Description:
* Give a Queue Head (QH) instance, call the handler for each qTD structure
* in the queue.
*
****************************************************************************/
static int usb_ehci_qtd_foreach(struct usb_ehci_qh_s *qh, foreach_qtd_t handler,
void *arg)
{
struct usb_ehci_qtd_s *qtd;
struct usb_ehci_qtd_s *next;
uintptr_t physaddr;
uint32_t *bp;
int ret;
DEBUGASSERT(qh && handler);
/* Handle the special case where the queue is empty */
bp = &qh->fqp; /* Start of qTDs in original list */
physaddr = usb_ehci_swap32(*bp); /* Physical address of first qTD in CPU order */
if ((physaddr & QTD_NQP_T) != 0) {
return 0;
}
/* Start with the first qTD in the list */
qtd = (struct usb_ehci_qtd_s *)usb_ehci_virtramaddr(physaddr);
next = NULL;
/* And loop until we encounter the end of the qTD list */
while (qtd) {
/* Is this the end of the list? Check the next qTD pointer (NQP)
* terminate (T) bit. If T==1, then the NQP address is not valid.
*/
if ((usb_ehci_swap32(qtd->hw.nqp) & QTD_NQP_T) != 0) {
/* Set the next pointer to NULL. This will terminate the loop. */
next = NULL;
} else {
physaddr = usb_ehci_swap32(qtd->hw.nqp) & QTD_NQP_NTEP_MASK;
next = (struct usb_ehci_qtd_s *)usb_ehci_virtramaddr(physaddr);
}
/* Perform the user action on this entry. The action might result in
* unlinking the entry! But that is okay because we already have the
* next qTD pointer.
*
* Notice that we do not manage the back pointer (bp). If the call-
* out uses it, it must update it as necessary.
*/
ret = handler(qtd, &bp, arg);
/* If the handler returns any non-zero value, then terminate the
* traversal early.
*/
if (ret != 0) {
return ret;
}
/* Set up to visit the next entry */
qtd = next;
}
return 0;
}
/****************************************************************************
* Name: usb_ehci_qtd_discard
*
* Description:
* This is a usb_ehci_qtd_foreach callback. It simply unlinks the QTD,
* updates the back pointer, and frees the QTD structure.
*
****************************************************************************/
static int usb_ehci_qtd_discard(struct usb_ehci_qtd_s *qtd, uint32_t **bp,
void *arg)
{
DEBUGASSERT(qtd && bp && *bp);
/* Remove the qTD from the list by updating the forward pointer to skip
* around this qTD. We do not change that pointer because are repeatedly
* removing the aTD at the head of the QH list.
*/
**bp = qtd->hw.nqp;
/* Then free the qTD */
usb_ehci_qtd_free(qtd);
return 0;
}
/****************************************************************************
* Name: usb_ehci_qh_discard
*
* Description:
* Free the Queue Head (QH) and all qTD's attached to the QH.
*
* Assumptions:
* The QH structure itself has already been unlinked from whatever list it
* may have been in.
*
****************************************************************************/
static int usb_ehci_qh_discard(struct usb_ehci_qh_s *qh)
{
int ret;
DEBUGASSERT(qh);
/* Free all of the qTD's attached to the QH */
ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_discard, NULL);
if (ret < 0) {
}
/* Then free the QH itself */
usb_ehci_qh_free(qh);
return ret;
}
#ifdef CONFIG_USB_DCACHE_ENABLE
/****************************************************************************
* Name: usb_ehci_qtd_flush
*
* Description:
* This is a callback from usb_ehci_qtd_foreach. It simply flushes D-cache
* for address range of the qTD entry.
*
****************************************************************************/
static int usb_ehci_qtd_flush(struct usb_ehci_qtd_s *qtd, uint32_t **bp, void *arg)
{
/* Flush the D-Cache, i.e., make the contents of the memory match the
* contents of the D-Cache in the specified address range and invalidate
* the D-Cache to force re-loading of the data from memory when next
* accessed.
*/
usb_ehci_dcache_clean_invalidate((uintptr_t)&qtd->hw, sizeof(struct usb_ehci_qtd_s));
return 0;
}
/****************************************************************************
* Name: usb_ehci_qh_flush
*
* Description:
* Invalidate the Queue Head and all qTD entries in the queue.
*
****************************************************************************/
static int usb_ehci_qh_flush(struct usb_ehci_qh_s *qh)
{
/* Flush the QH first. This will write the contents of the D-cache to RAM
* and invalidate the contents of the D-cache so that the next access will
* be reloaded from D-Cache.
*/
usb_ehci_dcache_clean_invalidate((uintptr_t)&qh->hw, sizeof(struct usb_ehci_qh_s));
/* Then flush all of the qTD entries in the queue */
return usb_ehci_qtd_foreach(qh, usb_ehci_qtd_flush, NULL);
}
#else
#define usb_ehci_qtd_flush(qtd, bp, arg)
#define usb_ehci_qh_flush(qh)
#endif
/****************************************************************************
* Name: usb_ehci_speed
*
* Description:
* Map a speed enumeration value per Chapter 9 of the USB specification to
* the speed enumeration required in the EHCI queue head.
*
****************************************************************************/
static inline uint8_t usb_ehci_speed(uint8_t usbspeed)
{
DEBUGASSERT(usbspeed >= USB_SPEED_LOW && usbspeed <= USB_SPEED_HIGH);
return g_ehci_speed[usbspeed];
}
static struct usb_ehci_qh_s *usb_ehci_qh_create(struct usb_ehci_epinfo_s *epinfo)
{
struct usb_ehci_qh_s *qh;
uint32_t regval;
uint8_t hubaddr;
uint8_t hubport;
struct usb_ehci_epinfo_s *ep0info;
struct usbh_hubport *rhport;
rhport = epinfo->hport;
while (rhport->parent != NULL) {
rhport = rhport->parent->parent;
}
ep0info = (struct usb_ehci_epinfo_s *)rhport->ep0;
/* Allocate a new queue head structure */
qh = usb_ehci_qh_alloc();
if (qh == NULL) {
return NULL;
}
/* Save the endpoint information with the QH itself */
qh->epinfo = epinfo;
/* Write QH endpoint characteristics:
*
* FIELD DESCRIPTION VALUE/SOURCE
* -------- ------------------------------- --------------------
* DEVADDR Device address Endpoint structure
* I Inactivate on Next Transaction 0
* ENDPT Endpoint number Endpoint structure
* EPS Endpoint speed Endpoint structure
* DTC Data toggle control 1
* MAXPKT Max packet size Endpoint structure
* C Control endpoint Calculated
* RL NAK count reloaded 0
*/
regval = ((uint32_t)epinfo->devaddr << QH_EPCHAR_DEVADDR_SHIFT) |
((uint32_t)epinfo->epno << QH_EPCHAR_ENDPT_SHIFT) |
((uint32_t)usb_ehci_speed(epinfo->speed) << QH_EPCHAR_EPS_SHIFT) |
QH_EPCHAR_DTC |
((uint32_t)epinfo->maxpacket << QH_EPCHAR_MAXPKT_SHIFT) |
((uint32_t)0 << QH_EPCHAR_RL_SHIFT);
/* Paragraph 3.6.3: "Control Endpoint Flag (C). If the QH.EPS field
* indicates the endpoint is not a high-speed device, and the endpoint
* is an control endpoint, then software must set this bit to a one.
* Otherwise it should always set this bit to a zero."
*/
if (epinfo->speed != USB_SPEED_HIGH &&
epinfo->xfrtype == USB_ENDPOINT_TYPE_CONTROL) {
regval |= QH_EPCHAR_C;
}
/* Save the endpoint characteristics word with the correct byte order */
qh->hw.epchar = usb_ehci_swap32(regval);
/* Write QH endpoint capabilities
*
* FIELD DESCRIPTION VALUE/SOURCE
* -------- ------------------------------- --------------------
* SSMASK Interrupt Schedule Mask Depends on epinfo->xfrtype
* SCMASK Split Completion Mask 0
* HUBADDR Hub Address roothub port devaddr
* PORT Port number RH port index
* MULT High band width multiplier 1
*/
hubaddr = ep0info->devaddr;
hubport = rhport->port;
regval = ((uint32_t)hubaddr << QH_EPCAPS_HUBADDR_SHIFT) |
((uint32_t)hubport << QH_EPCAPS_PORT_SHIFT) |
((uint32_t)1 << QH_EPCAPS_MULT_SHIFT);
#ifndef CONFIG_USBHOST_INT_DISABLE
if (epinfo->xfrtype == USB_ENDPOINT_TYPE_INTERRUPT) {
regval |= ((uint32_t)1 << QH_EPCAPS_SSMASK_SHIFT);
}
#endif
qh->hw.epcaps = usb_ehci_swap32(regval);
/* Mark this as the end of this list. This will be overwritten if/when the
* next qTD is added to the queue.
*/
qh->hw.hlp = usb_ehci_swap32(QH_HLP_T);
qh->hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T);
qh->hw.overlay.alt = usb_ehci_swap32(QH_AQP_T);
return qh;
}
static int usb_ehci_qtd_addbpl(struct usb_ehci_qtd_s *qtd, const void *buffer, size_t buflen)
{
uint32_t physaddr;
uint32_t nbytes;
uint32_t next;
int ndx;
usb_ehci_dcache_clean_invalidate((uintptr_t)buffer, buflen);
/* Loop, adding the aligned physical addresses of the buffer to the buffer
* page list. Only the first entry need not be aligned (because only the
* first entry has the offset field). The subsequent entries must begin on
* 4KB address boundaries.
*/
physaddr = (uint32_t)usb_ehci_physramaddr((uintptr_t)buffer);
for (ndx = 0; ndx < 5; ndx++) {
/* Write the physical address of the buffer into the qTD buffer
* pointer list.
*/
qtd->hw.bpl[ndx] = usb_ehci_swap32(physaddr);
/* Get the next buffer pointer (in the case where we will have to
* transfer more then one chunk). This buffer must be aligned to a
* 4KB address boundary.
*/
next = (physaddr + 4096) & ~4095;
/* How many bytes were included in the last buffer? Was it the whole
* thing?
*/
nbytes = next - physaddr;
if (nbytes >= buflen) {
/* Yes... it was the whole thing. Break out of the loop early. */
break;
}
/* Adjust the buffer length and physical address for the next time
* through the loop.
*/
buflen -= nbytes;
physaddr = next;
}
/* Handle the case of a huge buffer > 4*4KB = 16KB */
if (ndx >= 5) {
return -EFBIG;
}
return 0;
}
static struct usb_ehci_qtd_s *usb_ehci_qtd_setupphase(struct usb_ehci_epinfo_s *epinfo, struct usb_setup_packet *setup)
{
struct usb_ehci_qtd_s *qtd;
uint32_t regval;
int ret;
/* Allocate a new Queue Element Transfer Descriptor (qTD) */
qtd = usb_ehci_qtd_alloc();
if (qtd == NULL) {
return NULL;
}
/* Mark this as the end of the list (this will be overwritten if another
* qTD is added after this one).
*/
qtd->hw.nqp = usb_ehci_swap32(QTD_NQP_T);
qtd->hw.alt = usb_ehci_swap32(QTD_AQP_T);
/* Write qTD token:
*
* FIELD DESCRIPTION VALUE/SOURCE
* -------- ------------------------------- --------------------
* STATUS Status QTD_TOKEN_ACTIVE
* PID PID Code QTD_TOKEN_PID_SETUP
* CERR Error Counter 3
* CPAGE Current Page 0
* IOC Interrupt on complete 0
* NBYTES Total Bytes to Transfer 8
* TOGGLE Data Toggle 0
*/
regval = QTD_TOKEN_ACTIVE | QTD_TOKEN_PID_SETUP |
((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
((uint32_t)8 << QTD_TOKEN_NBYTES_SHIFT);
qtd->hw.token = usb_ehci_swap32(regval);
/* Add the buffer data */
ret = usb_ehci_qtd_addbpl(qtd, (uint8_t *)setup, 8);
if (ret < 0) {
usb_ehci_qtd_free(qtd);
return NULL;
}
/* Add the data transfer size to the count in the epinfo structure */
epinfo->xfrd += 8;
return qtd;
}
static struct usb_ehci_qtd_s *usb_ehci_qtd_dataphase(struct usb_ehci_epinfo_s *epinfo, void *buffer, int buflen, uint32_t tokenbits)
{
struct usb_ehci_qtd_s *qtd;
uint32_t regval;
int ret;
/* Allocate a new Queue Element Transfer Descriptor (qTD) */
qtd = usb_ehci_qtd_alloc();
if (qtd == NULL) {
return NULL;
}
/* Mark this as the end of the list (this will be overwritten if another
* qTD is added after this one).
*/
qtd->hw.nqp = usb_ehci_swap32(QTD_NQP_T);
qtd->hw.alt = usb_ehci_swap32(QTD_AQP_T);
/* Write qTD token:
*
* FIELD DESCRIPTION VALUE/SOURCE
* -------- ------------------------------- --------------------
* STATUS Status QTD_TOKEN_ACTIVE
* PID PID Code Contained in tokenbits
* CERR Error Counter 3
* CPAGE Current Page 0
* IOC Interrupt on complete Contained in tokenbits
* NBYTES Total Bytes to Transfer buflen
* TOGGLE Data Toggle Contained in tokenbits
*/
regval = tokenbits | QTD_TOKEN_ACTIVE |
((uint32_t)3 << QTD_TOKEN_CERR_SHIFT) |
((uint32_t)buflen << QTD_TOKEN_NBYTES_SHIFT);
qtd->hw.token = usb_ehci_swap32(regval);
ret = usb_ehci_qtd_addbpl(qtd, buffer, buflen);
if (ret < 0) {
usb_ehci_qtd_free(qtd);
return NULL;
}
/* Add the data transfer size to the count in the epinfo structure */
epinfo->xfrd += buflen;
return qtd;
}
static struct usb_ehci_qtd_s *usb_ehci_qtd_statusphase(uint32_t tokenbits)
{
struct usb_ehci_qtd_s *qtd;
uint32_t regval;
/* Allocate a new Queue Element Transfer Descriptor (qTD) */
qtd = usb_ehci_qtd_alloc();
if (qtd == NULL) {
return NULL;
}
/* Mark this as the end of the list (this will be overwritten if another
* qTD is added after this one).
*/
qtd->hw.nqp = usb_ehci_swap32(QTD_NQP_T);
qtd->hw.alt = usb_ehci_swap32(QTD_AQP_T);
/* Write qTD token:
*
* FIELD DESCRIPTION VALUE/SOURCE
* -------- ------------------------------- --------------------
* STATUS Status QTD_TOKEN_ACTIVE
* PID PID Code Contained in tokenbits
* CERR Error Counter 3
* CPAGE Current Page 0
* IOC Interrupt on complete QTD_TOKEN_IOC
* NBYTES Total Bytes to Transfer 0
* TOGGLE Data Toggle Contained in tokenbits
*/
regval = tokenbits | QTD_TOKEN_ACTIVE | QTD_TOKEN_IOC |
((uint32_t)3 << QTD_TOKEN_CERR_SHIFT);
qtd->hw.token = usb_ehci_swap32(regval);
return qtd;
}
static void usb_ehci_qh_enqueue(struct usb_ehci_qh_s *qhead, struct usb_ehci_qh_s *qh)
{
uintptr_t physaddr;
/* Set the internal fqp field. When we transverse the QH list later,
* we need to know the correct place to start because the overlay may no
* longer point to the first qTD entry.
*/
qh->fqp = qh->hw.overlay.nqp;
/* Add the new QH to the head of the asynchronous queue list.
*
* First, attach the old head as the new QH HLP and flush the new QH and
* its attached qTDs to RAM.
*/
qh->hw.hlp = qhead->hw.hlp;
usb_ehci_qh_flush(qh);
/* Then set the new QH as the first QH in the asynchronous queue */
physaddr = (uintptr_t)usb_ehci_physramaddr((uintptr_t)qh);
qhead->hw.hlp = usb_ehci_swap32(physaddr | QH_HLP_TYP_QH);
usb_ehci_dcache_clean((uintptr_t)&qhead->hw, sizeof(struct usb_ehci_qh_s));
}
static int usb_ehci_control_init(struct usb_ehci_epinfo_s *epinfo, struct usb_setup_packet *setup, uint8_t *buffer, uint32_t buflen)
{
struct usb_ehci_qh_s *qh;
struct usb_ehci_qtd_s *qtd;
uint32_t tokenbits;
uintptr_t physaddr;
uint32_t *flink;
uint32_t *alt;
uint32_t toggle;
bool dirin = false;
/* Create and initialize a Queue Head (QH) structure for this transfer */
qh = usb_ehci_qh_create(epinfo);
if (qh == NULL) {
return -ENOMEM;
}
epinfo->qh = qh;
/* Initialize the QH link and get the next data toggle (not used for SETUP
* transfers)
*/
flink = &qh->hw.overlay.nqp;
toggle = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
/* Is there an EP0 SETUP request? If so, we will queue two or three qTDs:
*
* 1) One for the SETUP phase,
* 2) One for the DATA phase (if there is data), and
* 3) One for the STATUS phase.
*/
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the SETUP
* phase of the request sequence.
*/
{
qtd = usb_ehci_qtd_setupphase(epinfo, setup);
if (qtd == NULL) {
return -ENOMEM;
}
/* Link the new qTD to the QH head. */
physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
*flink = usb_ehci_swap32(physaddr);
/* Get the new forward link pointer and data toggle */
flink = &qtd->hw.nqp;
toggle = QTD_TOKEN_TOGGLE;
}
/* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer
* will always be present for normal endpoint data transfers.
*/
alt = NULL;
if (buffer != NULL && buflen > 0) {
/* Extra TOKEN bits include the data toggle, the data PID, and if
* there is no request, an indication to interrupt at the end of this
* transfer.
*/
tokenbits = toggle;
/* Get the data token direction.
*
* If this is a SETUP request, use the direction contained in the
* request. The IOC bit is not set.
*/
if ((setup->bmRequestType & 0x80) == 0x80) {
tokenbits |= QTD_TOKEN_PID_IN;
dirin = true;
} else {
tokenbits |= QTD_TOKEN_PID_OUT;
dirin = false;
}
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
* buffer.
*/
qtd = usb_ehci_qtd_dataphase(epinfo, buffer, buflen, tokenbits);
if (qtd == NULL) {
return -ENOMEM;
}
/* Link the new qTD to either QH head of the SETUP qTD. */
physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
*flink = usb_ehci_swap32(physaddr);
/* Set the forward link pointer to this new qTD */
flink = &qtd->hw.nqp;
/* If this was an IN transfer, then setup a pointer alternate link.
* The EHCI hardware will use this link if a short packet is received.
*/
if (dirin) {
alt = &qtd->hw.alt;
}
}
{
/* Extra TOKEN bits include the data toggle and the correct data PID. */
tokenbits = toggle;
/* The status phase direction is the opposite of the data phase. If
* this is an IN request, then we received the buffer and we will send
* the zero length packet handshake.
*/
if ((setup->bmRequestType & 0x80) == 0x80) {
tokenbits |= QTD_TOKEN_PID_OUT;
} else {
/* Otherwise, this in an OUT request. We send the buffer and we expect
* to receive the NULL packet handshake.
*/
tokenbits |= QTD_TOKEN_PID_IN;
}
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the
* status
*/
qtd = usb_ehci_qtd_statusphase(tokenbits);
if (qtd == NULL) {
return -ENOMEM;
}
/* Link the new qTD to either the SETUP or data qTD. */
physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
*flink = usb_ehci_swap32(physaddr);
/* In an IN data qTD was also enqueued, then linked the data qTD's
* alternate pointer to this STATUS phase qTD in order to handle short
* transfers.
*/
if (alt) {
*alt = usb_ehci_swap32(physaddr);
}
}
/* Add the new QH to the head of the asynchronous queue list */
usb_ehci_qh_enqueue(&g_asynchead, qh);
return 0;
}
static int usb_ehci_bulk_init(struct usb_ehci_epinfo_s *epinfo, uint8_t *buffer, uint32_t buflen)
{
struct usb_ehci_qh_s *qh;
struct usb_ehci_qtd_s *qtd;
uint32_t tokenbits;
uintptr_t physaddr;
/* Create and initialize a Queue Head (QH) structure for this transfer */
qh = usb_ehci_qh_create(epinfo);
if (qh == NULL) {
return -ENOMEM;
}
epinfo->qh = qh;
/* Initialize the QH link and get the next data toggle */
tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
if (buffer != NULL && buflen > 0) {
/* Get the direction from the epinfo structure. Since this is not an EP0 SETUP request,
* nothing follows the data and we want the IOC interrupt when the data transfer completes.
*/
if (epinfo->dirin) {
tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC);
} else {
tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC);
}
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
* buffer.
*/
qtd = usb_ehci_qtd_dataphase(epinfo, buffer, buflen, tokenbits);
if (qtd == NULL) {
return -ENOMEM;
}
/* Link the new qTD to the QH. */
physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
qh->hw.overlay.nqp = usb_ehci_swap32(physaddr);
}
/* Add the new QH to the head of the asynchronous queue list */
usb_ehci_qh_enqueue(&g_asynchead, qh);
return 0;
}
static int usb_ehci_intr_init(struct usb_ehci_epinfo_s *epinfo, uint8_t *buffer, uint32_t buflen)
{
struct usb_ehci_qh_s *qh;
struct usb_ehci_qtd_s *qtd;
uint32_t tokenbits;
uintptr_t physaddr;
uint32_t regval;
/* Create and initialize a Queue Head (QH) structure for this transfer */
qh = usb_ehci_qh_create(epinfo);
if (qh == NULL) {
return -ENOMEM;
}
epinfo->qh = qh;
/* Initialize the QH link and get the next data toggle */
tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
/* Get the direction from the epinfo structure. Since this is not an EP0 SETUP request,
* nothing follows the data and we want the IOC interrupt when the data transfer completes.
*/
if (epinfo->dirin) {
tokenbits |= (QTD_TOKEN_PID_IN | QTD_TOKEN_IOC);
} else {
tokenbits |= (QTD_TOKEN_PID_OUT | QTD_TOKEN_IOC);
}
/* Allocate a new Queue Element Transfer Descriptor (qTD) for the data
* buffer.
*/
qtd = usb_ehci_qtd_dataphase(epinfo, buffer, buflen, tokenbits);
if (qtd == NULL) {
return -ENOMEM;
}
/* Link the new qTD to the QH. */
physaddr = usb_ehci_physramaddr((uintptr_t)qtd);
qh->hw.overlay.nqp = usb_ehci_swap32(physaddr);
/* Disable the periodic schedule */
regval = usb_ehci_getreg(&HCOR->usbcmd);
regval &= ~EHCI_USBCMD_PSEN;
usb_ehci_putreg(regval, &HCOR->usbcmd);
/* Add the new QH to the head of the interrupt transfer list */
usb_ehci_qh_enqueue(&g_intrhead, qh);
/* Re-enable the periodic schedule */
regval |= EHCI_USBCMD_PSEN;
usb_ehci_putreg(regval, &HCOR->usbcmd);
return 0;
}
/****************************************************************************
* Name: usb_ehci_ioc_setup
*
* Description:
* Set the request for the IOC event well BEFORE enabling the transfer (as
* soon as we are absolutely committed to the to avoid transfer). We do
* this to minimize race conditions. This logic would have to be expanded
* if we want to have more than one packet in flight at a time!
*
* Assumption: The caller holds tex EHCI exclsem
*
****************************************************************************/
static int usb_ehci_ioc_setup(struct usb_ehci_epinfo_s *epinfo)
{
uint32_t flags;
int ret = -ENODEV;
DEBUGASSERT(rhport && epinfo && !epinfo->iocwait);
#ifdef CONFIG_USBHOST_ASYNCH
DEBUGASSERT(epinfo->callback == NULL);
#endif
/* Is the device still connected? */
flags = usb_osal_enter_critical_section();
if (epinfo->hport->connected) {
/* Then set iocwait to indicate that we expect to be informed when
* either (1) the device is disconnected, or (2) the transfer
* completed.
*/
epinfo->iocwait = true; /* We want to be awakened by IOC interrupt */
epinfo->status = 0; /* No status yet */
epinfo->xfrd = 0; /* Nothing transferred yet */
epinfo->result = -EBUSY; /* Transfer in progress */
#ifdef CONFIG_USBHOST_ASYNCH
epinfo->callback = NULL; /* No asynchronous callback */
epinfo->arg = NULL;
#endif
ret = 0; /* We are good to go */
}
usb_osal_leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: usb_ehci_ioc_async_setup
*
* Description:
* Setup to receive an asynchronous notification when a transfer completes.
*
* Input Parameters:
* epinfo - The IN or OUT endpoint descriptor for the device endpoint on
* which the transfer will be performed.
* callback - The function to be called when the completes
* arg - An arbitrary argument that will be provided with the callback.
*
* Returned Value:
* None
*
* Assumptions:
* - Called from the interrupt level
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static int usb_ehci_ioc_async_setup(struct usb_ehci_epinfo_s *epinfo, usbh_asynch_callback_t callback, void *arg)
{
uint32_t flags;
int ret = -ENODEV;
DEBUGASSERT(rhport && epinfo && !epinfo->iocwait);
#ifdef CONFIG_USBHOST_ASYNCH
DEBUGASSERT(epinfo->callback == NULL);
#endif
/* Is the device still connected? */
flags = usb_osal_enter_critical_section();
if (epinfo->hport->connected) {
/* Then set iocwait to indicate that we expect to be informed when
* either (1) the device is disconnected, or (2) the transfer
* completed.
*/
epinfo->iocwait = false; /* We want to be awakened by IOC interrupt */
epinfo->status = 0; /* No status yet */
epinfo->xfrd = 0; /* Nothing transferred yet */
epinfo->result = -EBUSY; /* Transfer in progress */
#ifdef CONFIG_USBHOST_ASYNCH
epinfo->callback = callback; /* No asynchronous callback */
epinfo->arg = arg;
#endif
ret = 0; /* We are good to go */
}
usb_osal_leave_critical_section(flags);
return ret;
}
#endif
/****************************************************************************
* Name: usb_ehci_asynch_completion
*
* Description:
* This function is called at the interrupt level when an asynchronous
* transfer completes. It performs the pending callback.
*
* Input Parameters:
* epinfo - The IN or OUT endpoint descriptor for the device endpoint on
* which the transfer was performed.
*
* Returned Value:
* None
*
* Assumptions:
* - Called from the interrupt level
*
****************************************************************************/
#ifdef CONFIG_USBHOST_ASYNCH
static void usb_ehci_asynch_completion(struct usb_ehci_epinfo_s *epinfo)
{
usbh_asynch_callback_t callback;
int nbytes;
void *arg;
int result;
DEBUGASSERT(epinfo != NULL && epinfo->iocwait == false &&
epinfo->callback != NULL);
/* Extract and reset the callback info */
callback = epinfo->callback;
arg = epinfo->arg;
result = epinfo->result;
nbytes = epinfo->xfrd;
epinfo->callback = NULL;
epinfo->arg = NULL;
epinfo->result = 0;
epinfo->iocwait = false;
/* Then perform the callback. Provide the number of bytes successfully
* transferred or the negated errno value in the event of a failure.
*/
if (result < 0) {
nbytes = (int)result;
}
callback(arg, nbytes);
}
#endif
/****************************************************************************
* Name: usb_ehci_transfer_wait
*
* Description:
* Wait for an IN or OUT transfer to complete.
*
* Assumption: The caller holds the EHCI exclsem. The caller must be aware
* that the EHCI exclsem will released while waiting for the transfer to
* complete, but will be re-acquired when before returning. The state of
* EHCI resources could be very different upon return.
*
* Returned Value:
* On success, this function returns the number of bytes actually
* transferred. For control transfers, this size includes the size of the
* control request plus the size of the data (which could be short); for
* bulk transfers, this will be the number of data bytes transfers (which
* could be short).
*
****************************************************************************/
static int usb_ehci_transfer_wait(struct usb_ehci_epinfo_s *epinfo, uint32_t timeout)
{
int ret = 0;
/* Wait for the IOC completion event */
if (epinfo->iocwait) {
ret = usb_osal_sem_take(epinfo->iocsem, timeout);
if (ret < 0) {
return ret;
}
}
ret = epinfo->result;
if (ret < 0) {
return ret;
}
/* Transfer completed successfully. Return the number of bytes transferred.*/
return epinfo->xfrd;
}
/****************************************************************************
* Name: usb_ehci_qtd_ioccheck
*
* Description:
* This function is a usb_ehci_qtd_foreach() callback function. It services
* one qTD in the asynchronous queue. It removes all of the qTD
* structures that are no longer active.
*
****************************************************************************/
static int usb_ehci_qtd_ioccheck(struct usb_ehci_qtd_s *qtd, uint32_t **bp,
void *arg)
{
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)arg;
DEBUGASSERT(qtd && epinfo);
usb_ehci_dcache_invalidate((uintptr_t)&qtd->hw, sizeof(struct usb_ehci_qtd_s));
/* Remove the qTD from the list
*
* NOTE that we don't check if the qTD is active nor do we check if there
* are any errors reported in the qTD. If the transfer halted due to
* an error, then qTDs in the list after the error qTD will still appear
* to be active.
*/
**bp = qtd->hw.nqp;
/* Subtract the number of bytes left untransferred. The epinfo->xfrd
* field is initialized to the total number of bytes to be transferred
* (all qTDs in the list). We subtract out the number of untransferred
* bytes on each transfer and the final result will be the number of bytes
* actually transferred.
*/
epinfo->xfrd -= (usb_ehci_swap32(qtd->hw.token) & QTD_TOKEN_NBYTES_MASK) >>
QTD_TOKEN_NBYTES_SHIFT;
/* Release this QH by returning it to the free list */
usb_ehci_qtd_free(qtd);
return 0;
}
/****************************************************************************
* Name: usb_ehci_qh_ioccheck
*
* Description:
* This function is a usb_ehci_qh_foreach() callback function. It services
* one QH in the asynchronous queue. It check all attached qTD structures
* and remove all of the structures that are no longer active. if all of
* the qTD structures are removed, then QH itself will also be removed.
*
****************************************************************************/
static int usb_ehci_qh_ioccheck(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg)
{
struct usb_ehci_epinfo_s *epinfo;
uint32_t token;
int ret;
DEBUGASSERT(qh && bp);
usb_ehci_dcache_invalidate((uintptr_t)&qh->hw, sizeof(struct ehci_qh_s));
/* Get the endpoint info pointer from the extended QH data. Only the
* g_asynchead QH can have a NULL epinfo field.
*/
epinfo = qh->epinfo;
DEBUGASSERT(epinfo);
/* Paragraph 3.6.3: "The nine DWords in [the Transfer Overlay] area
* represent a transaction working space for the host controller. The
* general operational model is that the host controller can detect
* whether the overlay area contains a description of an active transfer.
* If it does not contain an active transfer, then it follows the Queue
* Head Horizontal Link Pointer to the next queue head. The host
* controller will never follow the Next Transfer Queue Element or
* Alternate Queue Element pointers unless it is actively attempting to
* advance the queue ..."
*/
/* Is the qTD still active? */
token = usb_ehci_swap32(qh->hw.overlay.token);
if ((token & QH_TOKEN_ACTIVE) != 0) {
/* Yes... we cannot process the QH while it is still active. Return
* zero to visit the next QH in the list.
*/
*bp = &qh->hw.hlp;
return 0;
}
/* Remove all active, attached qTD structures from the inactive QH */
ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_ioccheck, (void *)qh->epinfo);
if (ret < 0) {
}
/* If there is no longer anything attached to the QH, then remove it from
* the asynchronous queue.
*/
if ((usb_ehci_swap32(qh->fqp) & QTD_NQP_T) != 0) {
/* Set the forward link of the previous QH to point to the next
* QH in the list.
*/
**bp = qh->hw.hlp;
usb_ehci_dcache_clean((uintptr_t)*bp, sizeof(uint32_t));
/* Check for errors, update the data toggle */
if ((token & QH_TOKEN_ERRORS) == 0) {
/* No errors.. Save the last data toggle value */
epinfo->toggle = (token >> QTD_TOKEN_TOGGLE_SHIFT) & 1;
/* Report success */
epinfo->status = 0;
epinfo->result = 0;
} else {
/* An error occurred */
epinfo->status = (token & QH_TOKEN_STATUS_MASK) >>
QH_TOKEN_STATUS_SHIFT;
/* The HALT condition is set on a variety of conditions: babble,
* error counter countdown to zero, or a STALL. If we can rule
* out babble (babble bit not set) and if the error counter is
* non-zero, then we can assume a STALL. In this case, we return
* -PERM to inform the class driver of the stall condition.
*/
if ((token & (QH_TOKEN_BABBLE | QH_TOKEN_HALTED)) ==
QH_TOKEN_HALTED &&
(token & QH_TOKEN_CERR_MASK) != 0) {
/* It is a stall, Note that the data toggle is reset
* after the stall.
*/
epinfo->result = -EPERM;
epinfo->toggle = 0;
} else {
/* Otherwise, it is some kind of data transfer error */
epinfo->result = -EIO;
}
}
/* Is there a thread waiting for this transfer to complete? */
if (epinfo->iocwait) {
/* Yes... wake it up */
epinfo->iocwait = false;
usb_osal_sem_give(epinfo->iocsem);
}
#ifdef CONFIG_USBHOST_ASYNCH
/* No.. Is there a pending asynchronous transfer? */
else if (epinfo->callback != NULL) {
/* Yes.. perform the callback */
usb_ehci_asynch_completion(epinfo);
}
#endif
/* Then release this QH by returning it to the free list */
usb_ehci_qh_free(qh);
} else {
/* Otherwise, the horizontal link pointer of this QH will become the
* next back pointer.
*/
*bp = &qh->hw.hlp;
}
return 0;
}
/****************************************************************************
* Name: usb_ehci_qtd_cancel
*
* Description:
* This function is a usb_ehci_qtd_foreach() callback function. It removes
* each qTD attached to a QH.
*
****************************************************************************/
static int usb_ehci_qtd_cancel(struct usb_ehci_qtd_s *qtd, uint32_t **bp,
void *arg)
{
DEBUGASSERT(qtd != NULL && bp != NULL);
usb_ehci_dcache_invalidate((uintptr_t)&qtd->hw, sizeof(struct usb_ehci_qtd_s));
/* Remove the qTD from the list
*
* NOTE that we don't check if the qTD is active nor do we check if there
* are any errors reported in the qTD. If the transfer halted due to
* an error, then qTDs in the list after the error qTD will still appear
* to be active.
*
* REVISIT: There is a race condition here that needs to be resolved.
*/
**bp = qtd->hw.nqp;
/* Release this QH by returning it to the free list */
usb_ehci_qtd_free(qtd);
return 0;
}
/****************************************************************************
* Name: usb_ehci_qh_cancel
*
* Description:
* This function is a imxrt_qh_foreach() callback function. It cancels
* one QH in the asynchronous queue. It will remove all attached qTD
* structures and remove all of the structures that are no longer active.
* Then QH itself will also be removed.
*
****************************************************************************/
static int usb_ehci_qh_cancel(struct usb_ehci_qh_s *qh, uint32_t **bp, void *arg)
{
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)arg;
uint32_t regval;
int ret;
DEBUGASSERT(qh != NULL && bp != NULL && epinfo != NULL);
usb_ehci_dcache_invalidate((uintptr_t)&qh->hw, sizeof(struct usb_ehci_qh_s));
/* Check if this is the QH that we are looking for */
if (qh->epinfo == epinfo) {
/* No... keep looking */
return 0;
}
/* Disable both the asynchronous and period schedules */
regval = usb_ehci_getreg(&HCOR->usbcmd);
usb_ehci_putreg(regval & ~(EHCI_USBCMD_ASEN | EHCI_USBCMD_PSEN),
&HCOR->usbcmd);
/* Remove the QH from the list
*
* NOTE that we don't check if the qTD is active nor do we check if there
* are any errors reported in the qTD. If the transfer halted due to
* an error, then qTDs in the list after the error qTD will still appear
* to be active.
*
* REVISIT: There is a race condition here that needs to be resolved.
*/
**bp = qh->hw.hlp;
usb_ehci_dcache_clean((uintptr_t)*bp, sizeof(uint32_t));
/* Re-enable the schedules (if they were enabled before. */
usb_ehci_putreg(regval, &HCOR->usbcmd);
/* Remove all active, attached qTD structures from the removed QH */
ret = usb_ehci_qtd_foreach(qh, usb_ehci_qtd_cancel, NULL);
if (ret < 0) {
}
/* Then release this QH by returning it to the free list. Return 1
* to stop the traverse without an error.
*/
usb_ehci_qh_free(qh);
return 1;
}
static inline void usb_ehci_ioc_bottomhalf(void)
{
struct usb_ehci_qh_s *qh;
uint32_t *bp;
//int ret;
usb_ehci_dcache_invalidate((uintptr_t)&g_asynchead.hw, sizeof(struct usb_ehci_qh_s));
/* Set the back pointer to the forward qTD pointer of the asynchronous
* queue head.
*/
bp = (uint32_t *)&g_asynchead.hw.hlp;
qh = (struct usb_ehci_qh_s *)(unsigned long)
usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
/* If the asynchronous queue is empty, then the forward point in the
* asynchronous queue head will point back to the queue head.
*/
if (qh && qh != &g_asynchead) {
/* Then traverse and operate on every QH and qTD in the asynchronous
* queue
*/
usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_ioccheck, NULL);
}
#ifndef CONFIG_USBHOST_INT_DISABLE
/* Check the Interrupt Queue */
usb_ehci_dcache_invalidate((uintptr_t)&g_intrhead.hw, sizeof(struct usb_ehci_qh_s));
/* Set the back pointer to the forward qTD pointer of the asynchronous
* queue head.
*/
bp = (uint32_t *)&g_intrhead.hw.hlp;
qh = (struct usb_ehci_qh_s *)(unsigned long)
usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
if (qh) {
/* Then traverse and operate on every QH and qTD in the asynchronous
* queue.
*/
ret = usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_ioccheck, NULL);
if (ret < 0) {
}
}
#endif
}
/****************************************************************************
* Name: usb_ehci_portsc_bottomhalf
*
* Description:
* EHCI Port Change Detect "Bottom Half" interrupt handler
*
* "The Host Controller sets this bit to a one when any port for which the
* Port Owner bit is set to zero ... has a change bit transition from a
* zero to a one or a Force Port Resume bit transition from a zero to a
* one as a result of a J-K transition detected on a suspended port.
* This bit will also be set as a result of the Connect Status Change
* being set to a one after system software has relinquished ownership of
* a connected port by writing a one to a port's Port Owner bit...
*
* "This bit is allowed to be maintained in the Auxiliary power well.
* Alternatively, it is also acceptable that on a D3 to D0 transition of
* the EHCI HC device, this bit is loaded with the OR of all of the PORTSC
* change bits (including: Force port resume, over-current change,
* enable/disable change and connect status change)."
*
****************************************************************************/
static inline void usb_ehci_portsc_bottomhalf(void)
{
uint32_t portsc;
int rhpndx;
/* Handle root hub status change on each root port */
for (rhpndx = 0; rhpndx < CONFIG_USBHOST_RHPORTS; rhpndx++) {
portsc = usb_ehci_getreg(&HCOR->portsc[rhpndx]);
/* Handle port connection status change (CSC) events */
if ((portsc & EHCI_PORTSC_CSC) != 0) {
if ((portsc & EHCI_PORTSC_CCS) == EHCI_PORTSC_CCS) {
/* Connected ... Did we just become connected? */
usbh_event_notify_handler(USBH_EVENT_CONNECTED, 1);
} else {
for (uint8_t chidx = 0; chidx < CONFIG_USBHOST_PIPE_NUM; chidx++) {
struct usb_ehci_epinfo_s *epinfo = &g_ehci_hcd.chan[chidx];
if (epinfo->iocwait) {
epinfo->iocwait = false;
usb_osal_sem_give(epinfo->iocsem);
}
}
usbh_event_notify_handler(USBH_EVENT_DISCONNECTED, 1);
}
}
/* Clear all pending port interrupt sources by writing a '1' to the
* corresponding bit in the PORTSC register. In addition, we need
* to preserve the values of all R/W bits (RO bits don't matter)
*/
usb_ehci_putreg(portsc, &HCOR->portsc[rhpndx]);
}
}
/****************************************************************************
* Name: usb_ehci_reset
*
* Description:
* Set the HCRESET bit in the USBCMD register to reset the EHCI hardware.
*
* Table 2-9. USBCMD - USB Command Register Bit Definitions
*
* "Host Controller Reset (HCRESET) ... This control bit is used by
* software to reset the host controller. The effects of this on Root
* Hub registers are similar to a Chip Hardware Reset.
*
* "When software writes a one to this bit, the Host Controller resets its
* internal pipelines, timers, counters, state machines, etc. to their
* initial value. Any transaction currently in progress on USB is
* immediately terminated. A USB reset is not driven on downstream
* ports.
*
* "PCI Configuration registers are not affected by this reset. All
* operational registers, including port registers and port state
* machines are set to their initial values. Port ownership reverts
* to the companion host controller(s)... Software must reinitialize
* the host controller ... in order to return the host controller to
* an operational state.
*
* "This bit is set to zero by the Host Controller when the reset process
* is complete. Software cannot terminate the reset process early by
* writing a zero to this register. Software should not set this bit to
* a one when the HCHalted bit in the USBSTS register is a zero.
* Attempting to reset an actively running host controller will result
* in undefined behavior."
*
* Input Parameters:
* None.
*
* Returned Value:
* Zero (OK) is returned on success; A negated errno value is returned
* on failure.
*
* Assumptions:
* - Called during the initialization of the EHCI.
*
****************************************************************************/
static int usb_ehci_reset(void)
{
uint32_t regval;
unsigned int timeout;
/* Make sure that the EHCI is halted: "When [the Run/Stop] bit is set to
* 0, the Host Controller completes the current transaction on the USB and
* then halts. The HC Halted bit in the status register indicates when the
* Host Controller has finished the transaction and has entered the
* stopped state..."
*/
usb_ehci_putreg(0, &HCOR->usbcmd);
/* "... Software should not set [HCRESET] to a one when the HCHalted bit in
* the USBSTS register is a zero. Attempting to reset an actively running
* host controller will result in undefined behavior."
*/
timeout = 0;
do {
/* Wait one microsecond and update the timeout counter */
usb_osal_msleep(1);
timeout++;
/* Get the current value of the USBSTS register. This loop will
* terminate when either the timeout exceeds one millisecond or when
* the HCHalted bit is no longer set in the USBSTS register.
*/
regval = usb_ehci_getreg(&HCOR->usbsts);
} while (((regval & EHCI_USBSTS_HALTED) == 0) && (timeout < 1000));
/* Is the EHCI still running? Did we timeout? */
if ((regval & EHCI_USBSTS_HALTED) == 0) {
return -ETIMEDOUT;
}
/* Now we can set the HCReset bit in the USBCMD register to
* initiate the reset
*/
regval = usb_ehci_getreg(&HCOR->usbcmd);
regval |= EHCI_USBCMD_HCRESET;
usb_ehci_putreg(regval, &HCOR->usbcmd);
/* Wait for the HCReset bit to become clear */
do {
/* Wait five microsecondw and update the timeout counter */
usb_osal_msleep(5);
timeout += 5;
/* Get the current value of the USBCMD register. This loop will
* terminate when either the timeout exceeds one second or when the
* HCReset bit is no longer set in the USBSTS register.
*/
regval = usb_ehci_getreg(&HCOR->usbcmd);
} while (((regval & EHCI_USBCMD_HCRESET) != 0) && (timeout < 1000000));
/* Return either success or a timeout */
return (regval & EHCI_USBCMD_HCRESET) != 0 ? -ETIMEDOUT : 0;
}
/****************************************************************************
* Name: usb_ehci_wait_usbsts
*
* Description:
* Wait for either (1) a field in the USBSTS register to take a specific
* value, (2) for a timeout to occur, or (3) a error to occur. Return
* a value to indicate which terminated the wait.
*
****************************************************************************/
static int usb_ehci_wait_usbsts(uint32_t maskbits, uint32_t donebits, unsigned int delay)
{
uint32_t regval;
unsigned int timeout;
timeout = 0;
do {
/* Wait 5usec before trying again */
usb_osal_msleep(5);
timeout += 5;
/* Read the USBSTS register and check for a system error */
regval = usb_ehci_getreg(&HCOR->usbsts);
if ((regval & EHCI_INT_SYSERROR) != 0) {
return -EIO;
}
/* Mask out the bits of interest */
regval &= maskbits;
/* Loop until the masked bits take the specified value or until a
* timeout occurs.
*/
} while (regval != donebits && timeout < delay);
/* We got here because either the waited for condition or a timeout
* occurred. Return a value to indicate which.
*/
return (regval == donebits) ? 0 : -ETIMEDOUT;
}
__WEAK void usb_hc_low_level_init(void)
{
}
int usb_hc_sw_init(void)
{
memset(&g_ehci_hcd, 0, sizeof(struct ehci_hcd));
/* Initialize the list of free Queue Head (QH) structures */
for (uint8_t i = 0; i < CONFIG_USB_EHCI_QH_NUM; i++) {
/* Put the QH structure in a free list */
usb_ehci_qh_free(&g_ehci_hcd.qhpool[i]);
}
/* Initialize the list of free Queue Head (QH) structures */
for (uint8_t i = 0; i < CONFIG_USB_EHCI_QTD_NUM; i++) {
/* Put the QH structure in a free list */
usb_ehci_qtd_free(&g_ehci_hcd.qtdpool[i]);
}
for (uint8_t chidx = 0; chidx < CONFIG_USB_EHCI_QH_NUM; chidx++) {
struct usb_ehci_epinfo_s *epinfo;
epinfo = &g_ehci_hcd.chan[chidx];
epinfo->iocsem = usb_osal_sem_create(0);
epinfo->exclsem = usb_osal_mutex_create();
}
return 0;
}
int usb_hc_hw_init(void)
{
int ret;
uint32_t regval;
uintptr_t physaddr1;
uintptr_t physaddr2;
/* Initialize the head of the asynchronous queue/reclamation list.
*
* "In order to communicate with devices via the asynchronous schedule,
* system software must write the ASYNDLISTADDR register with the address
* of a control or bulk queue head. Software must then enable the
* asynchronous schedule by writing a one to the Asynchronous Schedule
* Enable bit in the USBCMD register. In order to communicate with devices
* via the periodic schedule, system software must enable the periodic
* schedule by writing a one to the Periodic Schedule Enable bit in the
* USBCMD register. Note that the schedules can be turned on before the
* first port is reset (and enabled)."
*/
memset(&g_asynchead, 0, sizeof(struct usb_ehci_qh_s));
physaddr1 = usb_ehci_physramaddr((uintptr_t)&g_asynchead);
g_asynchead.hw.hlp = usb_ehci_swap32(physaddr1 | QH_HLP_TYP_QH);
g_asynchead.hw.epchar = usb_ehci_swap32(QH_EPCHAR_H |
QH_EPCHAR_EPS_FULL);
g_asynchead.hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T);
g_asynchead.hw.overlay.alt = usb_ehci_swap32(QH_NQP_T);
g_asynchead.hw.overlay.token = usb_ehci_swap32(QH_TOKEN_HALTED);
g_asynchead.fqp = usb_ehci_swap32(QTD_NQP_T);
usb_ehci_dcache_clean((uintptr_t)&g_asynchead.hw, sizeof(struct usb_ehci_qh_s));
/* Initialize the head of the periodic list. Since Isochronous
* endpoints are not not yet supported, each element of the
* frame list is initialized to point to the Interrupt Queue
* Head (g_intrhead).
*/
memset(&g_intrhead, 0, sizeof(struct usb_ehci_qh_s));
g_intrhead.hw.hlp = usb_ehci_swap32(QH_HLP_T);
g_intrhead.hw.overlay.nqp = usb_ehci_swap32(QH_NQP_T);
g_intrhead.hw.overlay.alt = usb_ehci_swap32(QH_NQP_T);
g_intrhead.hw.overlay.token = usb_ehci_swap32(QH_TOKEN_HALTED);
g_intrhead.hw.epcaps = usb_ehci_swap32(QH_EPCAPS_SSMASK(1));
/* Attach the periodic QH to Period Frame List */
physaddr2 = usb_ehci_physramaddr((uintptr_t)&g_intrhead);
for (uint32_t i = 0; i < FRAME_LIST_SIZE; i++) {
g_framelist[i] = usb_ehci_swap32(physaddr2) | PFL_TYP_QH;
}
/* Set the Periodic Frame List Base Address. */
physaddr2 = usb_ehci_physramaddr((uintptr_t)g_framelist);
usb_ehci_dcache_clean((uintptr_t)&g_intrhead.hw, sizeof(struct usb_ehci_qh_s));
usb_ehci_dcache_clean((uintptr_t)g_framelist, FRAME_LIST_SIZE * sizeof(uint32_t));
usb_hc_low_level_init();
/* Host Controller Initialization. Paragraph 4.1 */
/* Reset the EHCI hardware */
ret = usb_ehci_reset();
if (ret < 0) {
return -1;
}
/* Disable all interrupts */
usb_ehci_putreg(0, &HCOR->usbintr);
/* Clear pending interrupts. Bits in the USBSTS register are cleared by
* writing a '1' to the corresponding bit.
*/
usb_ehci_putreg(EHCI_INT_ALLINTS, &HCOR->usbsts);
#if defined(CONFIG_USB_EHCI_INFO_ENABLE)
/* Show the EHCI version */
uint16_t regval16 = usb_ehci_swap16(HCCR->hciversion);
USB_LOG_INFO("EHCI HCIVERSION %x.%02x\r\n", regval16 >> 8, regval16 & 0xff);
/* Verify that the correct number of ports is reported */
regval = usb_ehci_getreg(&HCCR->hcsparams);
uint8_t nports = (regval & EHCI_HCSPARAMS_NPORTS_MASK) >> EHCI_HCSPARAMS_NPORTS_SHIFT;
USB_LOG_INFO("EHCI nports=%d, HCSPARAMS=%04x\r\n", nports, (unsigned int)regval);
/* Show the HCCPARAMS register */
regval = usb_ehci_getreg(&HCCR->hccparams);
USB_LOG_INFO("EHCI HCCPARAMS=%06x\r\n", (unsigned int)regval);
#endif
/* Set the Current Asynchronous List Address. */
usb_ehci_putreg(usb_ehci_swap32(physaddr1), &HCOR->asynclistaddr);
/* Set the Periodic Frame List Base Address. */
usb_ehci_putreg(usb_ehci_swap32(physaddr2), &HCOR->periodiclistbase);
/* Enable the asynchronous schedule and, possibly enable the periodic
* schedule and set the frame list size.
*/
regval = usb_ehci_getreg(&HCOR->usbcmd);
regval &= ~(EHCI_USBCMD_HCRESET | EHCI_USBCMD_FLSIZE_MASK |
EHCI_USBCMD_PSEN | EHCI_USBCMD_ASEN | EHCI_USBCMD_IAADB);
regval |= EHCI_USBCMD_ASEN;
#ifndef CONFIG_USBHOST_INT_DISABLE
regval |= EHCI_USBCMD_PSEN;
#if FRAME_LIST_SIZE == 1024
regval |= EHCI_USBCMD_FLSIZE_1024;
#elif FRAME_LIST_SIZE == 512
regval |= EHCI_USBCMD_FLSIZE_512;
#elif FRAME_LIST_SIZE == 256
regval |= EHCI_USBCMD_FLSIZE_256;
#else
#error Unsupported frame size list size
#endif
#endif
usb_ehci_putreg(regval, &HCOR->usbcmd);
/* Start the host controller by setting the RUN bit in the
* USBCMD register.
*/
regval = usb_ehci_getreg(&HCOR->usbcmd);
regval |= EHCI_USBCMD_RUN;
usb_ehci_putreg(regval, &HCOR->usbcmd);
/* Route all ports to this host controller by setting the CONFIG flag. */
#ifdef CONFIG_USB_EHCI_CONFIGFLAG
regval = usb_ehci_getreg(&HCOR->configflag);
regval |= EHCI_CONFIGFLAG;
usb_ehci_putreg(regval, &HCOR->configflag);
#endif
/* Wait for the EHCI to run (i.e., no longer report halted) */
ret = usb_ehci_wait_usbsts(EHCI_USBSTS_HALTED, 0, 100 * 1000);
if (ret < 0) {
return -2;
}
/* Enable port power */
regval = usb_ehci_getreg(&HCOR->portsc[0]);
regval |= EHCI_PORTSC_PP;
usb_ehci_putreg(regval, &HCOR->portsc[0]);
/* Enable EHCI interrupts. Interrupts are still disabled at the level of
* the interrupt controller.
*/
usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr);
return ret;
}
bool usbh_get_port_connect_status(const uint8_t port)
{
uint32_t portsc;
portsc = usb_ehci_getreg(&HCOR->portsc[port - 1]);
if ((portsc & EHCI_PORTSC_CCS) == EHCI_PORTSC_CCS) {
return true;
} else {
return false;
}
}
int usbh_reset_port(const uint8_t port)
{
uint32_t timeout = 0;
uint32_t regval;
regval = usb_ehci_getreg(&HCOR->portsc[port - 1]);
regval &= ~EHCI_PORTSC_PE;
regval |= EHCI_PORTSC_RESET;
usb_ehci_putreg(regval, &HCOR->portsc[port - 1]);
usb_osal_msleep(55);
regval = usb_ehci_getreg(&HCOR->portsc[port - 1]);
regval &= ~EHCI_PORTSC_RESET;
usb_ehci_putreg(regval, &HCOR->portsc[port - 1]);
/* Wait for the port reset to complete
*
* Paragraph 2.3.9:
*
* "Note that when software writes a zero to this bit there may be a
* delay before the bit status changes to a zero. The bit status will
* not read as a zero until after the reset has completed. If the port
* is in high-speed mode after reset is complete, the host controller
* will automatically enable this port (e.g. set the Port Enable bit
* to a one). A host controller must terminate the reset and stabilize
* the state of the port within 2 milliseconds of software transitioning
* this bit from a one to a zero ..."
*/
while ((usb_ehci_getreg(&HCOR->portsc[port - 1]) & EHCI_PORTSC_RESET) != 0) {
usb_osal_msleep(1);
timeout++;
if (timeout > 100) {
return -ETIMEDOUT;
}
}
return 0;
}
__WEAK uint8_t usbh_get_port_speed(const uint8_t port)
{
/* Defined by individual manufacturers */
return 0;
}
int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed)
{
struct usb_ehci_epinfo_s *epinfo;
int ret;
epinfo = (struct usb_ehci_epinfo_s *)ep;
DEBUGASSERT(epinfo != NULL && ep_mps < 2048);
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
epinfo->devaddr = dev_addr;
epinfo->speed = speed;
epinfo->maxpacket = ep_mps;
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg)
{
struct usb_ehci_epinfo_s *epinfo;
struct usbh_hubport *hport;
usb_osal_sem_t iocsem;
usb_osal_mutex_t exclsem;
int chidx;
DEBUGASSERT(ep_cfg != NULL && ep_cfg->hport != NULL);
hport = ep_cfg->hport;
chidx = usb_ehci_chan_alloc();
epinfo = &g_ehci_hcd.chan[chidx];
iocsem = epinfo->iocsem;
exclsem = epinfo->exclsem;
memset(epinfo, 0, sizeof(struct usb_ehci_epinfo_s));
epinfo->epno = ep_cfg->ep_addr & 0x7f;
epinfo->dirin = (ep_cfg->ep_addr & 0x80) ? 1 : 0;
epinfo->devaddr = hport->dev_addr;
#ifndef CONFIG_USBHOST_INT_DISABLE
epinfo->interval = ep_cfg->ep_interval;
#endif
epinfo->maxpacket = ep_cfg->ep_mps;
epinfo->xfrtype = ep_cfg->ep_type;
epinfo->speed = hport->speed;
epinfo->hport = hport;
/* restore variable */
epinfo->inuse = true;
epinfo->iocsem = iocsem;
epinfo->exclsem = exclsem;
*ep = (usbh_epinfo_t)epinfo;
return 0;
}
int usbh_ep_free(usbh_epinfo_t ep)
{
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
usb_ehci_chan_free(epinfo);
return 0;
}
int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
{
int ret;
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
DEBUGASSERT(epinfo);
/* A buffer may or may be supplied with an EP0 SETUP transfer. A buffer
* will always be present for normal endpoint data transfers.
*/
DEBUGASSERT(setup || buffer);
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
/* Set the request for the IOC event well BEFORE initiating the transfer. */
ret = usb_ehci_ioc_setup(epinfo);
if (ret != 0) {
goto errout_with_setup;
}
ret = usb_ehci_control_init(epinfo, setup, buffer, setup->wLength);
if (ret < 0) {
goto errout_with_iocwait;
}
/* And wait for the transfer to complete */
ret = usb_ehci_transfer_wait(epinfo, CONFIG_USBHOST_CONTROL_TRANSFER_TIMEOUT);
if (ret < 0) {
goto errout_with_iocwait;
}
usb_osal_mutex_give(epinfo->exclsem);
return ret;
errout_with_iocwait:
epinfo->iocwait = false;
/* Clean-up after an error */
if (epinfo->qh) {
usb_ehci_qh_discard(epinfo->qh);
epinfo->qh = NULL;
}
errout_with_setup:
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
DEBUGASSERT(epinfo && buffer && buflen > 0);
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
ret = usb_ehci_ioc_setup(epinfo);
if (ret < 0) {
goto errout_with_setup;
}
ret = usb_ehci_bulk_init(epinfo, buffer, buflen);
if (ret < 0) {
goto errout_with_iocwait;
}
/* And wait for the transfer to complete */
ret = usb_ehci_transfer_wait(epinfo, timeout);
if (ret < 0) {
goto errout_with_iocwait;
}
usb_osal_mutex_give(epinfo->exclsem);
return ret;
errout_with_iocwait:
epinfo->iocwait = false;
/* Clean-up after an error */
if (epinfo->qh) {
usb_ehci_qh_discard(epinfo->qh);
epinfo->qh = NULL;
}
errout_with_setup:
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
DEBUGASSERT(epinfo && buffer && buflen > 0);
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
ret = usb_ehci_ioc_setup(epinfo);
if (ret < 0) {
goto errout_with_setup;
}
ret = usb_ehci_intr_init(epinfo, buffer, buflen);
if (ret < 0) {
goto errout_with_iocwait;
}
/* And wait for the transfer to complete */
ret = usb_ehci_transfer_wait(epinfo, timeout);
if (ret < 0) {
goto errout_with_iocwait;
}
usb_osal_mutex_give(epinfo->exclsem);
return ret;
errout_with_iocwait:
epinfo->iocwait = false;
/* Clean-up after an error */
if (epinfo->qh) {
usb_ehci_qh_discard(epinfo->qh);
epinfo->qh = NULL;
}
errout_with_setup:
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
#ifdef CONFIG_USBHOST_ASYNCH
int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
{
int ret;
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
DEBUGASSERT(epinfo && buffer && buflen > 0);
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
/* Set the request for the callback well BEFORE initiating the transfer. */
ret = usb_ehci_ioc_async_setup(epinfo, callback, arg);
if (ret != 0) {
goto errout_with_setup;
}
/* Check for errors in the setup of the transfer */
ret = usb_ehci_bulk_init(epinfo, buffer, buflen);
if (ret < 0) {
goto errout_with_qh;
}
/* The transfer is in progress */
usb_osal_mutex_give(epinfo->exclsem);
return 0;
errout_with_qh:
/* Clean-up after an error */
if (epinfo->qh) {
usb_ehci_qh_discard(epinfo->qh);
epinfo->qh = NULL;
}
errout_with_setup:
epinfo->callback = NULL;
epinfo->arg = NULL;
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
{
int ret;
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
DEBUGASSERT(epinfo && buffer && buflen > 0);
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
/* Set the request for the callback well BEFORE initiating the transfer. */
ret = usb_ehci_ioc_async_setup(epinfo, callback, arg);
if (ret != 0) {
goto errout_with_setup;
}
/* Check for errors in the setup of the transfer */
ret = usb_ehci_intr_init(epinfo, buffer, buflen);
if (ret < 0) {
goto errout_with_qh;
}
/* The transfer is in progress */
usb_osal_mutex_give(epinfo->exclsem);
return 0;
errout_with_qh:
/* Clean-up after an error */
if (epinfo->qh) {
usb_ehci_qh_discard(epinfo->qh);
epinfo->qh = NULL;
}
errout_with_setup:
epinfo->callback = NULL;
epinfo->arg = NULL;
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
#endif
/****************************************************************************
* Name: usb_ehci_cancel
*
* Description:
* Cancel a pending transfer on an endpoint. Canceled synchronous or
* asynchronous transfer will complete normally with the error -ESHUTDOWN.
*
* Input Parameters:
* drvr - The USB host driver instance obtained as a parameter from the
* call to the class create() method.
* ep - The IN or OUT endpoint descriptor for the device endpoint on
* which an asynchronous transfer should be transferred.
*
* Returned Value:
* On success, zero (OK) is returned. On a failure, a negated errno value
* is returned indicating the nature of the failure
*
****************************************************************************/
int usb_ep_cancel(usbh_epinfo_t ep)
{
struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
struct usb_ehci_qh_s *qh;
#ifdef CONFIG_USBHOST_ASYNCH
usbh_asynch_callback_t callback;
void *arg;
#endif
uint32_t *bp;
uint32_t flags;
bool iocwait;
int ret;
DEBUGASSERT(epinfo);
/* We must have exclusive access to the EHCI hardware and data structures.
* This will prevent servicing any transfer completion events while we
* perform the cancellation, but will not prevent DMA-related race
* conditions.
*
* REVISIT: This won't work. This function must be callable from the
* interrupt level.
*/
ret = usb_osal_mutex_take(epinfo->exclsem);
if (ret < 0) {
return ret;
}
/* Sample and reset all transfer termination information. This will
* prevent any callbacks from occurring while are performing the
* cancellation. The transfer may still be in progress, however, so this
* does not eliminate other DMA-related race conditions.
*/
flags = usb_osal_enter_critical_section();
#ifdef CONFIG_USBHOST_ASYNCH
callback = epinfo->callback;
arg = epinfo->arg;
#endif
iocwait = epinfo->iocwait;
#ifdef CONFIG_USBHOST_ASYNCH
epinfo->callback = NULL;
epinfo->arg = NULL;
#endif
epinfo->iocwait = false;
/* This will prevent any callbacks from occurring while are performing
* the cancellation. The transfer may still be in progress, however, so
* this does not eliminate other DMA-related race conditions.
*/
epinfo->callback = NULL;
epinfo->arg = NULL;
usb_osal_leave_critical_section(flags);
/* Bail if there is no transfer in progress for this endpoint */
#ifdef CONFIG_USBHOST_ASYNCH
if (callback == NULL && !iocwait)
#else
if (!iocwait)
#endif
{
ret = 0;
goto errout_with_sem;
}
/* Handle the cancellation according to the type of the transfer */
switch (epinfo->xfrtype) {
case USB_ENDPOINT_TYPE_CONTROL:
case USB_ENDPOINT_TYPE_BULK: {
/* Get the horizontal pointer from the head of the asynchronous
* queue.
*/
bp = (uint32_t *)&g_asynchead.hw.hlp;
qh = (struct usb_ehci_qh_s *)(unsigned long)
usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
/* If the asynchronous queue is empty, then the forward point in
* the asynchronous queue head will point back to the queue
* head.
*/
if (qh && qh != &g_asynchead) {
/* Claim that we successfully cancelled the transfer */
ret = 0;
goto exit_terminate;
}
} break;
#ifndef CONFIG_USBHOST_INT_DISABLE
case USB_ENDPOINT_TYPE_INTERRUPT: {
/* Get the horizontal pointer from the head of the interrupt
* queue.
*/
bp = (uint32_t *)&g_intrhead.hw.hlp;
qh = (struct usb_ehci_qh_s *)(unsigned long)
usb_ehci_virtramaddr(usb_ehci_swap32(*bp) & QH_HLP_MASK);
if (qh) {
/* if the queue is empty, then just claim that we successfully
* cancelled the transfer.
*/
ret = 0;
goto exit_terminate;
}
} break;
#endif
case USB_ENDPOINT_TYPE_ISOCHRONOUS:
default:
ret = -ENOSYS;
goto errout_with_sem;
}
/* Find and remove the QH. There are four possibilities:
*
* 1) The transfer has already completed and the QH is no longer in the
* list. In this case, sam_hq_foreach will return zero
* 2a) The transfer is not active and still pending. It was removed from
* the list and sam_hq_foreach will return one.
* 2b) The is active but not yet complete. This is currently handled the
* same as 2a). REVISIT: This needs to be fixed.
* 3) Some bad happened and sam_hq_foreach returned an error code < 0.
*/
ret = usb_ehci_qh_foreach(qh, &bp, usb_ehci_qh_cancel, epinfo);
if (ret < 0) {
}
/* Was there a pending synchronous transfer? */
exit_terminate:
epinfo->result = -ESHUTDOWN;
#ifdef CONFIG_USBHOST_ASYNCH
if (iocwait) {
/* Yes... wake it up */
DEBUGASSERT(callback == NULL);
usb_osal_sem_give(epinfo->iocsem);
}
/* No.. Is there a pending asynchronous transfer? */
else /* if (callback != NULL) */
{
/* Yes.. perform the callback */
callback(arg, -ESHUTDOWN);
}
#else
/* Wake up the waiting thread */
usb_osal_sem_give(epinfo->iocsem);
#endif
errout_with_sem:
usb_osal_mutex_give(epinfo->exclsem);
return ret;
}
static void usb_ehci_bottomhalf(void *arg)
{
uint32_t pending = (uint32_t)(unsigned long)arg;
/* USB Interrupt (USBINT)
*
* "The Host Controller sets this bit to 1 on the completion of a USB
* transaction, which results in the retirement of a Transfer Descriptor
* that had its IOC bit set.
*
* "The Host Controller also sets this bit to 1 when a short packet is
* detected (actual number of bytes received was less than the expected
* number of bytes)."
*
* USB Error Interrupt (USBERRINT)
*
* "The Host Controller sets this bit to 1 when completion of a USB
* transaction results in an error condition (e.g., error counter
* underflow). If the TD on which the error interrupt occurred also
* had its IOC bit set, both this bit and USBINT bit are set. ..."
*
* We do the same thing in either case: Traverse the asynchronous queue
* and remove all of the transfers that are no longer active.
*/
if ((pending & (EHCI_INT_USBINT | EHCI_INT_USBERRINT)) != 0) {
usb_ehci_ioc_bottomhalf();
}
/* Port Change Detect
*
* "The Host Controller sets this bit to a one when any port for which
* the Port Owner bit is set to zero ... has a change bit transition
* from a zero to a one or a Force Port Resume bit transition from a zero
* to a one as a result of a J-K transition detected on a suspended port.
* This bit will also be set as a result of the Connect Status Change
* being set to a one after system software has relinquished ownership
* of a connected port by writing a one to a port's Port Owner bit...
*
* "This bit is allowed to be maintained in the Auxiliary power well.
* Alternatively, it is also acceptable that on a D3 to D0 transition
* of the EHCI HC device, this bit is loaded with the OR of all of the
* PORTSC change bits (including: Force port resume, over-current change,
* enable/disable change and connect status change)."
*/
if ((pending & EHCI_INT_PORTSC) != 0) {
usb_ehci_portsc_bottomhalf();
}
/* Frame List Rollover
*
* "The Host Controller sets this bit to a one when the Frame List Index
* ... rolls over from its maximum value to zero. The exact value at
* which the rollover occurs depends on the frame list size. For example,
* if the frame list size (as programmed in the Frame List Size field of
* the USBCMD register) is 1024, the Frame Index Register rolls over
* every time FRINDEX[13] toggles. Similarly, if the size is 512, the
* Host Controller sets this bit to a one every time FRINDEX[12]
* toggles."
*/
#if 0 /* Not used */
if ((pending & EHCI_INT_FLROLL) != 0)
{
}
#endif
/* Host System Error
*
* "The Host Controller sets this bit to 1 when a serious error occurs
* during a host system access involving the Host Controller module. ...
* When this error occurs, the Host Controller clears the Run/Stop bit
* in the Command register to prevent further execution of the scheduled
* TDs."
*/
if ((pending & EHCI_INT_SYSERROR) != 0) {
}
/* Interrupt on Async Advance
*
* "System software can force the host controller to issue an interrupt
* the next time the host controller advances the asynchronous schedule
* by writing a one to the Interrupt on Async Advance Doorbell bit in
* the USBCMD register. This status bit indicates the assertion of that
* interrupt source."
*/
if ((pending & EHCI_INT_AAINT) != 0) {
}
}
void USBH_IRQHandler(void)
{
uint32_t usbsts;
uint32_t pending;
uint32_t regval;
/* Read Interrupt Status and mask out interrupts that are not enabled. */
usbsts = usb_ehci_getreg(&HCOR->usbsts);
regval = usb_ehci_getreg(&HCOR->usbintr);
/* Handle all unmasked interrupt sources */
pending = usbsts & regval;
if (pending != 0) {
/* Disable further EHCI interrupts so that we do not overrun the work queue.*/
usb_ehci_putreg(0, &HCOR->usbintr);
/* Clear all pending status bits by writing the value of the pending
* interrupt bits back to the status register.
*/
usb_ehci_putreg(usbsts & EHCI_INT_ALLINTS, &HCOR->usbsts);
usb_ehci_bottomhalf((void *)(unsigned long)pending);
/* Re-enable relevant EHCI interrupts. Interrupts should still be enabled
* at the level of the interrupt controller.
*/
usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr);
}
}