diff options
| author | sakumisu <[email protected]> | 2022-08-10 23:28:00 +0800 |
|---|---|---|
| committer | sakumisu <[email protected]> | 2022-08-10 23:28:00 +0800 |
| commit | 50ec3651c08073367406921e2ed7fa4ff4c1a567 (patch) | |
| tree | e70c31a8fd66d0c08b7506efa016258e0af153e4 | |
| parent | 775170217b11f65b1d50b84a0fe3a3f35aa8b587 (diff) | |
rename ehci file and dcache name
| -rw-r--r-- | port/ehci/usb_ehci.c | 2844 | ||||
| -rw-r--r-- | port/ehci/usb_hc_ehci.h (renamed from port/ehci/usb_ehci.h) | 0 |
2 files changed, 0 insertions, 2844 deletions
diff --git a/port/ehci/usb_ehci.c b/port/ehci/usb_ehci.c deleted file mode 100644 index 85c3f620..00000000 --- a/port/ehci/usb_ehci.c +++ /dev/null @@ -1,2844 +0,0 @@ -#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; /* 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 (CONFIG_USB_ALIGN_SIZE == 64) - uint8_t pad[4]; /* Padding to assure 64-byte alignment */ -#elif (CONFIG_USB_ALIGN_SIZE == 32) - uint8_t pad[8]; /* Padding to assure 32-byte alignment */ -#else -#error "ehci align size must be 32 or 64" -#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; /* 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) */ - USB_MEM_ALIGNX struct usb_ehci_qh_s qhpool[CONFIG_USB_EHCI_QH_NUM]; /* Queue Head (QH) pool */ - USB_MEM_ALIGNX struct usb_ehci_qtd_s qtdpool[CONFIG_USB_EHCI_QTD_NUM]; /* 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(32))); - -/* The head of the periodic queue */ - -static struct usb_ehci_qh_s g_intrhead __attribute__((aligned(32))); - -/* 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_qtd_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 *) - 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 *) - 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, regval); - - /* Show the HCCPARAMS register */ - regval = usb_ehci_getreg(&HCCR->hccparams); - USB_LOG_INFO("EHCI HCCPARAMS=%06x\r\n", 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; - } -#ifdef CONFIG_USB_EHCI_PORT_POWER - for (uint8_t port = 1; i <= CONFIG_USBHOST_RHPORTS; port++) { - regval = usb_ehci_getreg(&HCOR->portsc[port - 1]); - regval |= EHCI_PORTSC_PP; - usb_ehci_putreg(regval, &HCOR->portsc[port - 1]); - } -#endif - /* 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 *) - 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 *) - 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)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 *)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); - } -}
\ No newline at end of file diff --git a/port/ehci/usb_ehci.h b/port/ehci/usb_hc_ehci.h index c2c37b93..c2c37b93 100644 --- a/port/ehci/usb_ehci.h +++ b/port/ehci/usb_hc_ehci.h |
