|
|
@@ -0,0 +1,2833 @@
|
|
|
+#include "usbh_core.h"
|
|
|
+#include "usb_ehci.h"
|
|
|
+
|
|
|
+#define DEBUGASSERT(f)
|
|
|
+
|
|
|
+#define BL_USBOTG_HCCR_BASE (0x20072000)
|
|
|
+#define BL_USBOTG_HCOR_BASE (0x20072000 + 0x10)
|
|
|
+
|
|
|
+/* 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 *)BL_USBOTG_HCCR_BASE)
|
|
|
+
|
|
|
+/* Host Controller Operational Registers */
|
|
|
+
|
|
|
+#define HCOR ((volatile struct ehci_hcor_s *)BL_USBOTG_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)
|
|
|
+
|
|
|
+/* Port numbers */
|
|
|
+// #define RHPNDX(rh) (rh)
|
|
|
+// #define RHPORT(rh) (RHPNDX(rh) + 1)
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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) */
|
|
|
+ uint8_t pad[8]; /* Padding to assure 32-byte alignment */
|
|
|
+};
|
|
|
+
|
|
|
+/* 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
|
|
|
+ uint8_t status; /* Retained token status bits (for debug purposes) */
|
|
|
+ volatile bool iocwait; /* TRUE: Thread is waiting for transfer completion */
|
|
|
+ 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 */
|
|
|
+
|
|
|
+ usb_osal_sem_t iocsem; /* Semaphore used to wait for transfer completion */
|
|
|
+#ifdef CONFIG_USBHOST_ASYNCH
|
|
|
+ usbh_asynch_callback_t callback; /* Transfer complete callback */
|
|
|
+ void *arg; /* Argument that accompanies the callback */
|
|
|
+#endif
|
|
|
+ struct usbh_hubport *hport;
|
|
|
+};
|
|
|
+
|
|
|
+/* This structure retains the overall state of the USB host controller */
|
|
|
+
|
|
|
+struct usb_ehci_s {
|
|
|
+ volatile bool connected; /* Connected to device */
|
|
|
+ usb_osal_mutex_t exclsem; /* Support mutually exclusive access */
|
|
|
+ struct usb_work work;
|
|
|
+
|
|
|
+ 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_epinfo_s ep0[CONFIG_USBHOST_RHPORTS]; /* EP0 endpoint info */
|
|
|
+};
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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 usb_ehci_s g_ehci;
|
|
|
+
|
|
|
+/* The head of the asynchronous queue */
|
|
|
+
|
|
|
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_asynchead __attribute__((aligned(32)));
|
|
|
+
|
|
|
+/* The head of the periodic queue */
|
|
|
+
|
|
|
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_intrhead __attribute__((aligned(32)));
|
|
|
+
|
|
|
+/* Queue Head (QH) pool */
|
|
|
+
|
|
|
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qh_s g_qhpool[CONFIG_USB_EHCI_QH_NUM] __attribute__((aligned(32)));
|
|
|
+
|
|
|
+/* Queue Element Transfer Descriptor (qTD) pool */
|
|
|
+
|
|
|
+USB_NOCACHE_RAM_SECTION struct usb_ehci_qtd_s g_qtdpool[CONFIG_USB_EHCI_QTD_NUM] __attribute__((aligned(32)));
|
|
|
+
|
|
|
+/* The frame list */
|
|
|
+USB_NOCACHE_RAM_SECTION 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;
|
|
|
+}
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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.qhfree;
|
|
|
+ if (qh) {
|
|
|
+ g_ehci.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.qhfree;
|
|
|
+ g_ehci.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.qtdfree;
|
|
|
+ if (qtd) {
|
|
|
+ g_ehci.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.qtdfree;
|
|
|
+ g_ehci.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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Then free the QH itself */
|
|
|
+
|
|
|
+ usb_ehci_qh_free(qh);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QHALLOC_FAILED, 0);
|
|
|
+ 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) {
|
|
|
+ /* Here, the S-Mask field in the queue head is set to 1, indicating
|
|
|
+ * that the transaction for the endpoint should be executed on the bus
|
|
|
+ * during micro-frame 0 of the frame.
|
|
|
+ *
|
|
|
+ * REVISIT: The polling interval should be controlled by the which
|
|
|
+ * entry is the framelist holds the QH pointer for a given micro-frame
|
|
|
+ * and the QH pointer should be replicated for different polling rates.
|
|
|
+ * This implementation currently just sets all frame_list entry to
|
|
|
+ * all the same interrupt queue. That should work but will not give
|
|
|
+ * any control over polling rates.
|
|
|
+ */
|
|
|
+ // #warning REVISIT
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_BUFTOOBIG, buflen);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_DATAQTDALLOC_FAILED, 0);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_ADDBPL_FAILED, -ret);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_REQQTDALLOC_FAILED, 0);
|
|
|
+ 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;
|
|
|
+ //usb_ehci_qh_dump(qh, NULL, NULL);
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ /* 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);
|
|
|
+}
|
|
|
+
|
|
|
+static int usb_ehci_control_setup(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;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Create and initialize a Queue Head (QH) structure for this transfer */
|
|
|
+ qh = usb_ehci_qh_create(epinfo);
|
|
|
+ if (qh == NULL) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+ ret = -EIO;
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDSETUP_FAILED, 0);
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto errout_with_qh;
|
|
|
+ }
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0);
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto errout_with_qh;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDSTATUS_FAILED, 0);
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto errout_with_qh;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+errout_with_qh:
|
|
|
+ /* Clean-up after an error */
|
|
|
+ usb_ehci_qh_discard(qh);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int usb_ehci_bulk_setup(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;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Create and initialize a Queue Head (QH) structure for this transfer */
|
|
|
+ qh = usb_ehci_qh_create(epinfo);
|
|
|
+ if (qh == NULL) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Initialize the QH link and get the next data toggle */
|
|
|
+ tokenbits = (uint32_t)epinfo->toggle << QTD_TOKEN_TOGGLE_SHIFT;
|
|
|
+ ret = -EIO;
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0);
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto errout_with_qh;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+errout_with_qh:
|
|
|
+ usb_ehci_qh_discard(qh);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int usb_ehci_intr_setup(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;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Create and initialize a Queue Head (QH) structure for this transfer */
|
|
|
+ qh = usb_ehci_qh_create(epinfo);
|
|
|
+ if (qh == NULL) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QHCREATE_FAILED, 0);
|
|
|
+ return -ENOMEM;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDDATA_FAILED, 0);
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto errout_with_qh;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+errout_with_qh:
|
|
|
+ usb_ehci_qh_discard(qh);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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 (g_ehci.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 (g_ehci.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_ioc_wait
|
|
|
+ *
|
|
|
+ * Description:
|
|
|
+ * Wait for the IOC event.
|
|
|
+ *
|
|
|
+ * Assumption: The caller does *NOT* hold the EHCI exclsem. That would
|
|
|
+ * cause a deadlock when the bottom-half, worker thread needs to take the
|
|
|
+ * semaphore.
|
|
|
+ *
|
|
|
+ ****************************************************************************/
|
|
|
+
|
|
|
+static int usb_ehci_ioc_wait(struct usb_ehci_epinfo_s *epinfo)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /* Wait for the IOC event. Loop to handle any false alarm semaphore
|
|
|
+ * counts. Return an error if the task is canceled.
|
|
|
+ */
|
|
|
+
|
|
|
+ while (epinfo->iocwait) {
|
|
|
+ ret = usb_osal_sem_take(epinfo->iocsem);
|
|
|
+ if (ret < 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret < 0 ? ret : epinfo->result;
|
|
|
+}
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ int ret2;
|
|
|
+
|
|
|
+ /* Release the EHCI semaphore while we wait. Other threads need the
|
|
|
+ * opportunity to access the EHCI resources while we wait.
|
|
|
+ *
|
|
|
+ * REVISIT: Is this safe? NO. This is a bug and needs rethinking.
|
|
|
+ * We need to lock all of the port-resources (not EHCI common) until
|
|
|
+ * the transfer is complete. But we can't use the common EHCI exclsem
|
|
|
+ * or we will deadlock while waiting (because the working thread that
|
|
|
+ * wakes this thread up needs the exclsem).
|
|
|
+ */
|
|
|
+
|
|
|
+ /* REVISIT */
|
|
|
+
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+
|
|
|
+ /* Wait for the IOC completion event */
|
|
|
+
|
|
|
+ ret = usb_ehci_ioc_wait(epinfo);
|
|
|
+
|
|
|
+ /* Re-acquire the EHCI semaphore. The caller expects to be holding
|
|
|
+ * this upon return.
|
|
|
+ */
|
|
|
+
|
|
|
+ ret2 = usb_osal_mutex_take(g_ehci.exclsem);
|
|
|
+ if (ret2 < 0) {
|
|
|
+ ret = ret2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Did usb_ehci_ioc_wait() or usb_osal_mutex_take()report an
|
|
|
+ * error?
|
|
|
+ */
|
|
|
+
|
|
|
+ if (ret < 0) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_TRANSFER_FAILED, -ret);
|
|
|
+ epinfo->iocwait = false;
|
|
|
+ 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_qtd_print(qtd);
|
|
|
+
|
|
|
+ /* 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_qh_print(qh);
|
|
|
+
|
|
|
+ /* 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);
|
|
|
+ //usbhost_vtrace2(EHCI_VTRACE2_IOCCHECK, epinfo->epno, 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
|
|
|
+ }
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ /* 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.
|
|
|
+ */
|
|
|
+
|
|
|
+ //usbhost_trace2(EHCI_TRACE2_EPSTALLED, epinfo->epno, token);
|
|
|
+ epinfo->result = -EPERM;
|
|
|
+ epinfo->toggle = 0;
|
|
|
+ } else {
|
|
|
+ /* Otherwise, it is some kind of data transfer error */
|
|
|
+
|
|
|
+ //usbhost_trace2(EHCI_TRACE2_EPIOERROR, epinfo->epno, token);
|
|
|
+ epinfo->result = -EIO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Is there a thread waiting for this transfer to complete? */
|
|
|
+ if (epinfo->iocwait) {
|
|
|
+ /* Yes... wake it up */
|
|
|
+ epinfo->iocwait = false;
|
|
|
+ /* TODO */
|
|
|
+ // clang-format off
|
|
|
+ for (size_t i = 0; i < 3200; i++) {
|
|
|
+ __asm volatile("nop" ::: "memory");
|
|
|
+ }
|
|
|
+ // clang-format on
|
|
|
+ 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_qtd_print(qtd);
|
|
|
+
|
|
|
+ /* 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_qh_print(qh);
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ /* 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 */
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QHFOREACH_FAILED, -ret);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
|
|
|
+
|
|
|
+/****************************************************************************
|
|
|
+ * 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]);
|
|
|
+
|
|
|
+ //usbhost_vtrace2(EHCI_VTRACE2_PORTSC, rhpndx + 1, portsc);
|
|
|
+
|
|
|
+ /* Handle port connection status change (CSC) events */
|
|
|
+ if ((portsc & EHCI_PORTSC_CSC) != 0) {
|
|
|
+ //usbhost_vtrace1(EHCI_VTRACE1_PORTSC_CSC, portsc);
|
|
|
+
|
|
|
+ /* Check current connect status */
|
|
|
+ if ((portsc & EHCI_PORTSC_CCS) != 0) {
|
|
|
+ /* Connected ... Did we just become connected? */
|
|
|
+ g_ehci.connected = 1;
|
|
|
+ usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1);
|
|
|
+ } else {
|
|
|
+ g_ehci.connected = 0;
|
|
|
+ usbh_event_notify_handler(USBH_EVENT_REMOVED, 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_HCHALTED_TIMEOUT, regval);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_SYSTEMERROR, regval);
|
|
|
+ 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_ehci_pre_hw_init(void)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+__WEAK void usb_ehci_last_hw_init(void)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+int usb_hc_init(void)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ uint32_t regval;
|
|
|
+ uintptr_t physaddr1;
|
|
|
+ uintptr_t physaddr2;
|
|
|
+
|
|
|
+ /* 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_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_qtdpool[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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);
|
|
|
+
|
|
|
+ g_ehci.exclsem = usb_osal_mutex_create();
|
|
|
+
|
|
|
+ /* 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_pre_hw_init();
|
|
|
+ /* Host Controller Initialization. Paragraph 4.1 */
|
|
|
+
|
|
|
+ /* Reset the EHCI hardware */
|
|
|
+ ret = usb_ehci_reset();
|
|
|
+ if (ret < 0) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_RESET_FAILED, -ret);
|
|
|
+ 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);
|
|
|
+ //usbhost_vtrace2(EHCI_VTRACE2_HCIVERSION, 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;
|
|
|
+
|
|
|
+ //usbhost_vtrace2(EHCI_VTRACE2_HCSPARAMS, nports, regval);
|
|
|
+ //DEBUGASSERT(nports == LPC43_EHCI_NRHPORT);
|
|
|
+
|
|
|
+ /* Show the HCCPARAMS register */
|
|
|
+ regval = usb_ehci_getreg(&HCCR->hccparams);
|
|
|
+ //usbhost_vtrace1(EHCI_VTRACE1_HCCPARAMS, 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 == 512
|
|
|
+ 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. */
|
|
|
+ // regval = usb_ehci_getreg(&HCOR->configflag);
|
|
|
+ // regval |= EHCI_CONFIGFLAG;
|
|
|
+ // usb_ehci_putreg(regval, &HCOR->configflag);
|
|
|
+
|
|
|
+ /* 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_RUN_FAILED, usb_ehci_getreg(&HCOR->usbsts));
|
|
|
+ return -2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Enable EHCI interrupts. Interrupts are still disabled at the level of
|
|
|
+ * the interrupt controller.
|
|
|
+ */
|
|
|
+ usb_ehci_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr);
|
|
|
+
|
|
|
+ usb_ehci_last_hw_init();
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+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(g_ehci.exclsem);
|
|
|
+ if (ret < 0) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ epinfo->devaddr = dev_addr;
|
|
|
+ epinfo->speed = speed;
|
|
|
+ epinfo->maxpacket = ep_mps;
|
|
|
+
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg)
|
|
|
+{
|
|
|
+ // int ret;
|
|
|
+ struct usb_ehci_epinfo_s *epinfo;
|
|
|
+ struct usbh_hubport *hport;
|
|
|
+
|
|
|
+ DEBUGASSERT(ep_cfg != NULL && ep_cfg->hport != NULL);
|
|
|
+
|
|
|
+ // ret = usb_osal_mutex_take(g_ehci.exclsem);
|
|
|
+ // if (ret < 0) {
|
|
|
+ // return ret;
|
|
|
+ // }
|
|
|
+
|
|
|
+ hport = ep_cfg->hport;
|
|
|
+
|
|
|
+ /* new roothub ep info */
|
|
|
+ if (((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_CONTROL) && (hport->parent == NULL)) {
|
|
|
+ epinfo = &g_ehci.ep0[hport->port - 1];
|
|
|
+ } else {
|
|
|
+ /* new exteranl hub ep info */
|
|
|
+ epinfo = usb_malloc(sizeof(struct usb_ehci_epinfo_s));
|
|
|
+ memset(epinfo, 0, sizeof(struct usb_ehci_epinfo_s));
|
|
|
+ if (epinfo == NULL) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ epinfo->iocsem = usb_osal_sem_create(0);
|
|
|
+
|
|
|
+ *ep = epinfo;
|
|
|
+
|
|
|
+ //usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int usbh_ep_free(usbh_epinfo_t ep)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
|
|
|
+
|
|
|
+ ret = usb_osal_mutex_take(g_ehci.exclsem);
|
|
|
+ if (ret < 0) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ usb_osal_sem_delete(epinfo->iocsem);
|
|
|
+ usb_free(epinfo);
|
|
|
+
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
|
|
|
+{
|
|
|
+ int nbytes;
|
|
|
+ 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(g_ehci.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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret);
|
|
|
+ goto errout_with_sem;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = usb_ehci_control_setup(epinfo, setup, buffer, setup->wLength);
|
|
|
+ if (ret < 0) {
|
|
|
+ goto errout_with_iocwait;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* And wait for the transfer to complete */
|
|
|
+ nbytes = usb_ehci_transfer_wait(epinfo);
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return nbytes >= 0 ? 0 : (int)nbytes;
|
|
|
+
|
|
|
+errout_with_iocwait:
|
|
|
+ epinfo->iocwait = false;
|
|
|
+errout_with_sem:
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
|
|
|
+{
|
|
|
+ int nbytes;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
|
|
|
+
|
|
|
+ DEBUGASSERT(epinfo && buffer && buflen > 0);
|
|
|
+
|
|
|
+ ret = usb_osal_mutex_take(g_ehci.exclsem);
|
|
|
+ if (ret < 0) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = usb_ehci_ioc_setup(epinfo);
|
|
|
+ if (ret != 0) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret);
|
|
|
+ goto errout_with_sem;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = usb_ehci_bulk_setup(epinfo, buffer, buflen);
|
|
|
+ if (ret < 0) {
|
|
|
+ goto errout_with_iocwait;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* And wait for the transfer to complete */
|
|
|
+ nbytes = usb_ehci_transfer_wait(epinfo);
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return nbytes;
|
|
|
+
|
|
|
+errout_with_iocwait:
|
|
|
+ epinfo->iocwait = false;
|
|
|
+errout_with_sem:
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
|
|
|
+{
|
|
|
+ int nbytes;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ struct usb_ehci_epinfo_s *epinfo = (struct usb_ehci_epinfo_s *)ep;
|
|
|
+
|
|
|
+ DEBUGASSERT(epinfo && buffer && buflen > 0);
|
|
|
+
|
|
|
+ ret = usb_osal_mutex_take(g_ehci.exclsem);
|
|
|
+ if (ret < 0) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = usb_ehci_ioc_setup(epinfo);
|
|
|
+
|
|
|
+ if (ret != 0) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_DEVDISCONNECTED, -ret);
|
|
|
+ goto errout_with_sem;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = usb_ehci_intr_setup(epinfo, buffer, buflen);
|
|
|
+ if (ret < 0) {
|
|
|
+ goto errout_with_iocwait;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* And wait for the transfer to complete */
|
|
|
+ nbytes = usb_ehci_transfer_wait(epinfo);
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return nbytes;
|
|
|
+
|
|
|
+errout_with_iocwait:
|
|
|
+ epinfo->iocwait = false;
|
|
|
+errout_with_sem:
|
|
|
+ usb_osal_mutex_give(g_ehci.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(g_ehci.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_sem;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check for errors in the setup of the transfer */
|
|
|
+ ret = usb_ehci_bulk_setup(epinfo, buffer, buflen);
|
|
|
+ if (ret < 0) {
|
|
|
+ goto errout_with_callback;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The transfer is in progress */
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+errout_with_callback:
|
|
|
+ epinfo->callback = NULL;
|
|
|
+ epinfo->arg = NULL;
|
|
|
+errout_with_sem:
|
|
|
+ usb_osal_mutex_give(&g_ehci.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(g_ehci.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_sem;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check for errors in the setup of the transfer */
|
|
|
+ ret = usb_ehci_intr_setup(epinfo, buffer, buflen);
|
|
|
+ if (ret < 0) {
|
|
|
+ goto errout_with_callback;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* The transfer is in progress */
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+errout_with_callback:
|
|
|
+ epinfo->callback = NULL;
|
|
|
+ epinfo->arg = NULL;
|
|
|
+errout_with_sem:
|
|
|
+ usb_osal_mutex_give(g_ehci.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_sem_take(g_ehci.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
|
|
|
+
|
|
|
+#ifndef CONFIG_USBHOST_ISOC_DISABLE
|
|
|
+ case USB_ENDPOINT_TYPE_ISOCHRONOUS:
|
|
|
+#warning "Isochronous endpoint support not emplemented"
|
|
|
+#endif
|
|
|
+ default:
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_BADXFRTYPE, epinfo->xfrtype);
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_QTDFOREACH_FAILED, -ret);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 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(g_ehci.exclsem);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void usb_ehci_bottomhalf(void *arg)
|
|
|
+{
|
|
|
+ uint32_t pending = (uint32_t)arg;
|
|
|
+ /* We need to have exclusive access to the EHCI data structures. Waiting
|
|
|
+ * here is not a good thing to do on the worker thread, but there is no
|
|
|
+ * real option (other than to reschedule and delay).
|
|
|
+ */
|
|
|
+
|
|
|
+ usb_osal_mutex_take(g_ehci.exclsem);
|
|
|
+
|
|
|
+ /* Handle all unmasked interrupt sources */
|
|
|
+ /* 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) {
|
|
|
+ if ((pending & EHCI_INT_USBERRINT) != 0) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_USBERR_INTR, pending);
|
|
|
+ } else {
|
|
|
+ //usbhost_vtrace1(EHCI_VTRACE1_USBINTR, pending);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ //usbhost_trace1(EHCI_TRACE1_SYSERR_INTR, 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) {
|
|
|
+ //usbhost_vtrace1(EHCI_VTRACE1_AAINTR, 0);
|
|
|
+ }
|
|
|
+ /* 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);
|
|
|
+
|
|
|
+ /* We are done with the EHCI structures */
|
|
|
+ usb_osal_mutex_give(g_ehci.exclsem);
|
|
|
+}
|
|
|
+
|
|
|
+void usb_ehci_interrupt(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) {
|
|
|
+
|
|
|
+ /* Schedule interrupt handling work for the high priority worker
|
|
|
+ * thread so that we are not pressed for time and so that we can
|
|
|
+ * interrupt with other USB threads gracefully.
|
|
|
+ *
|
|
|
+ * The worker should be available now because we implement a handshake
|
|
|
+ * by controlling the EHCI interrupts.
|
|
|
+ */
|
|
|
+
|
|
|
+ usb_workqueue_submit(&g_hpworkq, &g_ehci.work, usb_ehci_bottomhalf, (void *)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);
|
|
|
+ }
|
|
|
+}
|