diff --git a/ChangeLog b/ChangeLog index c980ecc481b94fa8bccbc26d669bf76cad96a5e8..e5592576d650603f059ab7d4c654708bae1511d8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5424,4 +5424,7 @@ * nuttx/arch/arm/src/sama5/sam_ehci.c and other files: Create a skeleton environment for development of an EHCI driver. Not much in place yet (2013-8-20). + * nuttx/arch/arm/src/sama5/sam_ehci.c: Now code complete for all + asynchronous endpoints (control and bulk); nothing yet in place + for periodic endponts (interrupt and isochronous) (2013-8-22). diff --git a/arch/arm/src/sama5/sam_ehci.c b/arch/arm/src/sama5/sam_ehci.c index 2a510f3f3d53dc16443b71ae3a21e7872c29e9bd..ae539d5b15b39bd2d854cbda720619867cb8420c 100755 --- a/arch/arm/src/sama5/sam_ehci.c +++ b/arch/arm/src/sama5/sam_ehci.c @@ -108,7 +108,7 @@ #define CONFIG_USBHOST_INT_DISABLE 1 #undef CONFIG_USBHOST_ISOC_DISABLE -#define CONFIG_USBHOST_INT_DISABLE 1 +#define CONFIG_USBHOST_ISOC_DISABLE 1 /* Driver-private Definitions **************************************************/ @@ -178,11 +178,14 @@ struct sam_epinfo_s uint8_t dirin:1; /* 1:IN endpoint 0:OUT endpoint */ uint8_t devaddr:7; /* Device address */ uint8_t toggle:1; /* Next data toggle */ - uint8_t xfrtype:2; /* See USB_EP_ATTR_XFER_* definitions in usb.h */ - uint8_t speed:2; /* See USB_*_SPEED definitions in ehci.h */ +#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; /* Maximum packet size */ + 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 */ sem_t iocsem; /* Semaphore used to wait for transfer completion */ }; @@ -236,8 +239,10 @@ struct sam_ehci_s static uint16_t sam_read16(const uint8_t *addr); static uint32_t sam_read32(const uint8_t *addr); +#if 0 /* Not used */ static void sam_write16(uint16_t memval, uint8_t *addr); static void sam_write32(uint32_t memval, uint8_t *addr); +#endif #ifdef CONFIG_ENDIAN_BIG static uint16_t sam_swap16(uint16_t value); @@ -285,8 +290,10 @@ static int sam_qh_discard(struct sam_qh_s *qh); /* Cache Operations ************************************************************/ +#if 0 /* Not used */ static int sam_qtd_invalidate(struct sam_qtd_s *qtd, uint32_t **bp, void *arg); static int sam_qh_invalidate(struct sam_qh_s *qh); +#endif static int sam_qtd_flush(struct sam_qtd_s *qtd, uint32_t **bp, void *arg); static int sam_qh_flush(struct sam_qh_s *qh); @@ -311,7 +318,6 @@ static int sam_async_transfer(struct sam_rhport_s *rhport, static int sam_qtd_ioccheck(struct sam_qtd_s *qtd, uint32_t **bp, void *arg); static int sam_qh_ioccheck(struct sam_qh_s *qh, uint32_t **bp, void *arg); static inline void sam_ioc_bottomhalf(void); -static inline void sam_err_bottomhalf(void); static inline void sam_portsc_bottomhalf(void); static inline void sam_syserr_bottomhalf(void); static inline void sam_async_advance_bottomhalf(void); @@ -439,6 +445,7 @@ static inline uint32_t sam_read32(const uint8_t *addr) * *******************************************************************************/ +#if 0 /* Not used */ static void sam_write16(uint16_t memval, uint8_t *addr) { #ifdef CONFIG_ENDIAN_BIG @@ -449,6 +456,7 @@ static void sam_write16(uint16_t memval, uint8_t *addr) addr[1] = memval & 0xff; #endif } +#endif /******************************************************************************* * Name: sam_write32 @@ -458,6 +466,7 @@ static void sam_write16(uint16_t memval, uint8_t *addr) * *******************************************************************************/ +#if 0 /* Not used */ static void sam_write32(uint32_t memval, uint8_t *addr) { #ifdef CONFIG_ENDIAN_BIG @@ -468,6 +477,7 @@ static void sam_write32(uint32_t memval, uint8_t *addr) sam_write16(memval >> 16, &addr[2]); #endif } +#endif /******************************************************************************* * Name: sam_swap16 @@ -1053,6 +1063,7 @@ static int sam_qh_discard(struct sam_qh_s *qh) * *******************************************************************************/ +#if 0 /* Not used */ static int sam_qtd_invalidate(struct sam_qtd_s *qtd, uint32_t **bp, void *arg) { /* Invalidate the D-Cache, i.e., force reloading of the D-Cache from memory @@ -1063,6 +1074,7 @@ static int sam_qtd_invalidate(struct sam_qtd_s *qtd, uint32_t **bp, void *arg) (uintptr_t)&qtd->hw + sizeof(struct ehci_qtd_s)); return OK; } +#endif /******************************************************************************* * Name: sam_qh_invalidate @@ -1072,6 +1084,7 @@ static int sam_qtd_invalidate(struct sam_qtd_s *qtd, uint32_t **bp, void *arg) * *******************************************************************************/ +#if 0 /* Not used */ static int sam_qh_invalidate(struct sam_qh_s *qh) { /* Invalidate the QH first so that we reload the qTD list head */ @@ -1081,8 +1094,9 @@ static int sam_qh_invalidate(struct sam_qh_s *qh) /* Then invalidate all of the qTD entries in the queue */ - return sam_qtd_foreach(qh, NULL, NULL); + return sam_qtd_foreach(qh, sam_qtd_invalidate, NULL); } +#endif /******************************************************************************* * Name: sam_qtd_flush @@ -1121,7 +1135,7 @@ static int sam_qh_flush(struct sam_qh_s *qh) /* Then flush all of the qTD entries in the queue */ - return sam_qtd_foreach(qh, NULL, NULL); + return sam_qtd_foreach(qh, sam_qtd_flush, NULL); } /******************************************************************************* @@ -1922,8 +1936,6 @@ static inline void sam_ioc_bottomhalf(void) { int ret; - uvdbg("USB Interrupt (USBINT) Interrupt\n"); - /* Make sure that the head of the asynchronous queue is invalidated */ cp15_invalidate_dcache((uintptr_t)&g_asynchead.hw, @@ -1938,27 +1950,6 @@ static inline void sam_ioc_bottomhalf(void) } } -/******************************************************************************* - * Name: sam_err_bottomhalf - * - * Description: - * EHCI USB Error Interrupt (USBERRINT) "Bottom Half" interrupt handler - * - * "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. ..." - * - *******************************************************************************/ - -static inline void sam_err_bottomhalf(void) -{ - udbg("USB Error Interrupt (USBERRINT) Interrupt\n"); - - /* Remove all queued transfers */ -#warning Missing logic -} - /******************************************************************************* * Name: sam_portsc_bottomhalf * @@ -2114,8 +2105,7 @@ static inline void sam_async_advance_bottomhalf(void) { udbg("Async Advance Interrupt\n"); - /* Remove all tagged QH entries */ -#warning Missing logic + /* REVISIT: Could remove all tagged QH entries here */ } /******************************************************************************* @@ -2147,24 +2137,30 @@ static void sam_ehci_bottomhalf(FAR void *arg) * "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)." - */ - - if ((pending & EHCI_INT_USBINT) != 0) - { - sam_ioc_bottomhalf(); - } - - /* USB Error Interrupt (USBERRINT) + * + * 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 asynchonous queue + * and remove all of the transfers that are no longer active. */ - if ((pending & EHCI_INT_USBERRINT) != 0) + if ((pending & (EHCI_INT_USBINT | EHCI_INT_USBERRINT)) != 0) { - sam_err_bottomhalf(); + if ((pending & EHCI_INT_USBERRINT) != 0) + { + udbg("USB Error Interrupt (USBERRINT) Interrupt\n"); + } + else + { + uvdbg("USB Interrupt (USBINT) Interrupt\n"); + } + + sam_ioc_bottomhalf(); } /* Port Change Detect @@ -2403,6 +2399,8 @@ static int sam_wait(FAR struct usbhost_connection_s *conn, static int sam_enumerate(FAR struct usbhost_connection_s *conn, int rhpndx) { struct sam_rhport_s *rhport; + volatile uint32_t *regaddr; + uint32_t regval; DEBUGASSERT(rhpndx >= 0 && rhpndx < SAM_EHCI_NRHPORT); rhport = &g_ehci.rhport[rhpndx]; @@ -2419,26 +2417,74 @@ static int sam_enumerate(FAR struct usbhost_connection_s *conn, int rhpndx) return -ENODEV; } - /* Add EP0 to the control list */ -#warning Missing logic + /* USB 2.0 spec says at least 50ms delay before port reset. + * REVISIT: I think this is wrong. It needs to hold the port in + * reset for 50Msec, not wait 50Msec before resetting. + */ + + usleep(100*1000); + + /* Put the root hub port in reset. + * + * Paragraph 2.3.9: + * + * "The HCHalted bit in the USBSTS register should be a zero before + * software attempts to use [the Port Reset] bit. The host controller + * may hold Port Reset asserted to a one when the HCHalted bit is a one. + */ + + DEBUGASSERT((sam_getreg(&HCOR->usbsts) & EHCI_USBSTS_HALTED) == 0); - /* USB 2.0 spec says at least 50ms delay before port reset */ + /* paragraph 2.3.9: + * + * "When software writes a one to [the Port Reset] bit (from a zero), the + * bus reset sequence as defined in the USB Specification Revision 2.0 is + * started. Software writes a zero to this bit to terminate the bus reset + * sequence. Software must keep this bit at a one long enough to ensure + * the reset sequence, as specified in the USB Specification Revision 2.0, + * completes. Note: when software writes this bit to a one, it must also + * write a zero to the Port Enable bit." + */ - up_mdelay(100); + regaddr = &HCOR->portsc[rhport->rhpndx]; + regval = sam_getreg(regaddr); + regval &= ~EHCI_PORTSC_PE; + regval &= EHCI_PORTSC_RESET; + sam_putreg(regval, regaddr); - /* Put the root hub port in reset (the SAMA5 supports three downstream ports) */ -#warning Missing logic + /* USB 2.0 "Root hubs must provide an aggregate reset period of at least + * 50 ms." + */ - /* Wait for the port reset to complete */ -#warning Missing logic + usleep(50*1000); - /* Release RH port 1 from reset and wait a bit */ -#warning Missing logic + regval = sam_getreg(regaddr); + regval &= ~EHCI_PORTSC_RESET; + sam_putreg(regval, regaddr); - up_mdelay(200); + /* Wait for the port reset to complete + * + * Paragraph 2.3.9: + * + * "Note that when software writes a zero to this bit there may be a + * delay before the bit status changes to a zero. The bit status will + * not read as a zero until after the reset has completed. If the port + * is in high-speed mode after reset is complete, the host controller + * will automatically enable this port (e.g. set the Port Enable bit + * to a one). A host controller must terminate the reset and stabilize + * the state of the port within 2 milliseconds of software transitioning + * this bit from a one to a zero ..." + */ + + while ((sam_getreg(regaddr) & EHCI_PORTSC_RESET) != 0); + usleep(200*1000); /* Let the common usbhost_enumerate do all of the real work. Note that the - * FunctionAddress (USB address) is set to the root hub port number for now. + * FunctionAddress (USB address) is set to the root hub port number + 1 + * for now. + * + * REVISIT: Hub support will require better device address assignment. + * See include/nuttx/usb/usbhost_devaddr.h. */ uvdbg("Enumerate the device\n"); @@ -2475,19 +2521,25 @@ static int sam_ep0configure(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr, uint16_t maxpacketsize) { struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; + struct sam_epinfo_s *epinfo; DEBUGASSERT(rhport && funcaddr >= 0 && funcaddr <= SAM_EHCI_NRHPORT && maxpacketsize < 2048); + epinfo = &rhport->ep0; + /* We must have exclusive access to the EHCI data structures. */ sam_takesem(&g_ehci.exclsem); -#warning Missing logic + /* Remember the new device address and max packet size */ + + epinfo->devaddr = funcaddr; + epinfo->maxpacket = maxpacketsize; sam_givesem(&g_ehci.exclsem); - return -ENOSYS; + return OK; } /************************************************************************************ @@ -2515,46 +2567,48 @@ static int sam_ep0configure(FAR struct usbhost_driver_s *drvr, uint8_t funcaddr, static int sam_epalloc(FAR struct usbhost_driver_s *drvr, const FAR struct usbhost_epdesc_s *epdesc, usbhost_ep_t *ep) { - struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; struct sam_epinfo_s *epinfo; - int ret = -ENOMEM; /* Sanity check. NOTE that this method should only be called if a device is * connected (because we need a valid low speed indication). */ - DEBUGASSERT(rhport && epdesc && ep && rhport->connected); + DEBUGASSERT(drvr && epdesc && ep); + uvdbg("EP%d DIR=%s FA=%08x TYPE=%d Interval=%d Max Packet Size=%d\n", + epdesc->addr, epdesc->in ? "IN" : "OUT", epdesc->funcaddr, + epdesc->xfrtype, epdesc->interval, epdesc->mxpacketsize); - /* Allocate a container for the endpoint data */ + /* Allocate a endpoint information structure */ epinfo = (struct sam_epinfo_s *)kzalloc(sizeof(struct sam_epinfo_s)); if (!epinfo) { udbg("ERROR: Failed to allocate EP info structure\n"); - goto errout; + return -ENOMEM; } - /* Initialize the endpoint container */ + /* Initialize the endpoint container (which is really just another form of + * 'struct usbhost_epdesc_s', packed differently and with additional + * information. A cleaner design might just embed struct usbhost_epdesc_s + * inside of struct sam_epinfo_s and just memcpy here. + */ + epinfo->epno = epdesc->addr; + epinfo->dirin = epdesc->in; + epinfo->devaddr = epdesc->funcaddr; + epinfo->xfrtype = epdesc->xfrtype; +#ifndef CONFIG_USBHOST_INT_DISABLE + epinfo->interval = epdesc->interval; +#endif + epinfo->maxpacket = epdesc->mxpacketsize; sem_init(&epinfo->iocsem, 0, 0); - /* We must have exclusive access to the EHCI data structures. */ - - sam_takesem(&g_ehci.exclsem); - -#warning Missing logic - - /* Success.. return an opaque reference to the endpoint list container */ + /* Success.. return an opaque reference to the endpoint information structure + * instance + */ *ep = (usbhost_ep_t)epinfo; - sam_givesem(&g_ehci.exclsem); return OK; - -errout_with_semaphore: - sam_givesem(&g_ehci.exclsem); - kfree(epinfo); -errout: - return ret; } /************************************************************************************ @@ -2579,28 +2633,16 @@ errout: static int sam_epfree(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep) { - struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; struct sam_epinfo_s *epinfo = (struct sam_epinfo_s *)ep; - int ret; - - DEBUGASSERT(rhport && epinfo); /* There should not be any pending, transfers */ -#warning Missing logic - - /* We must have exclusive access to the EHCI data structures. */ - sam_takesem(&g_ehci.exclsem); - -#warning Missing logic - ret = -ENOSYS; + DEBUGASSERT(drvr && epinfo && epinfo->iocwait == 0); - /* And free the container */ + /* Free the container */ - sem_destroy(&epinfo->iocsem); kfree(epinfo); - sam_givesem(&g_ehci.exclsem); - return ret; + return OK; } /******************************************************************************* @@ -2877,7 +2919,6 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, int ret; DEBUGASSERT(rhport && epinfo && buffer && buflen > 0); -#warning Missing logic /* We must have exclusive access to the EHCI hardware and data structures. */ @@ -2891,10 +2932,16 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, ret = sam_async_transfer(rhport, epinfo, NULL, buffer, buflen); break; - default: - case USB_EP_ATTR_XFER_CONTROL: - case USB_EP_ATTR_XFER_ISOC: +#ifndef CONFIG_USBHOST_INT_DISABLE case USB_EP_ATTR_XFER_INT: +# warning "Interrupt endpoint support not emplemented" +#endif +#ifndef CONFIG_USBHOST_ISOC_DISABLE + case USB_EP_ATTR_XFER_ISOC: +# warning "Isochronous endpoint support not emplemented" +#endif + case USB_EP_ATTR_XFER_CONTROL: + default: udbg("ERROR: Support for transfer type %d not implemented\n"); ret = -ENOSYS; break; @@ -2932,10 +2979,8 @@ static void sam_disconnect(FAR struct usbhost_driver_s *drvr) struct sam_rhport_s *rhport = (struct sam_rhport_s *)drvr; DEBUGASSERT(rhport); - /* Remove the disconnected port */ -#warning Missing logic - /* Unbind the class */ + /* REVISIT: Is there more that needs to be done? */ rhport->class = NULL; } @@ -3397,7 +3442,8 @@ FAR struct usbhost_connection_s *sam_ehci_initialize(int controller) for (i = 0; i < SAM_EHCI_NRHPORT; i++) { -#warning Missing logic + g_ehci.rhport[i].connected = + ((sam_getreg(&HCOR->portsc[i]) & EHCI_PORTSC_CCS) != 0); } /* Enable interrupts at the interrupt controller */