From 28a103f1f234eb2c1beff4af7038ba50ec2dacd8 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sat, 24 Aug 2013 11:34:24 -0600 Subject: [PATCH] SAMA5: EHCI now handles low- and full-speed connections by giving them to OHCI; OHCI now uses the work queue to defer interrupt processing; If both OHCI and EHCI are enabled, EHCI is the master of the UHPHS interrupt --- arch/arm/src/sama5/Kconfig | 71 ++------ arch/arm/src/sama5/chip/sam_sfr.h | 7 +- arch/arm/src/sama5/sam_ehci.c | 148 +++++++++++++--- arch/arm/src/sama5/sam_ohci.c | 285 ++++++++++++++++++++---------- arch/arm/src/sama5/sam_usbhost.h | 14 ++ 5 files changed, 345 insertions(+), 180 deletions(-) diff --git a/arch/arm/src/sama5/Kconfig b/arch/arm/src/sama5/Kconfig index ce16d974a6..a492fd8068 100644 --- a/arch/arm/src/sama5/Kconfig +++ b/arch/arm/src/sama5/Kconfig @@ -339,7 +339,7 @@ if SAMA5_UHPHS menu "USB High Speed Host device driver options" config SAMA5_OHCI - bool "Full speed OHCI support" + bool "Full/low speed OHCI support" default n ---help--- Build support for the SAMA5 USB full speed Open Host Controller @@ -377,7 +377,8 @@ config SAMA5_EHCI default n ---help--- Build support for the SAMA5 USB high speed Enhanced Host Controller - Interface (EHCI). + Interface (EHCI). If low/full speed is needed too, then you must + also enable the OHCI controller. if SAMA5_EHCI @@ -410,72 +411,22 @@ config SAMA5_EHCI_REGDEBUG endif # SAMA5_EHCI -if SAMA5_OHCI && SAMA5_EHCI +if SAMA5_OHCI || SAMA5_EHCI -config SAMA5_OHCI_RHPORT1 - bool "Use Port A for OHCI" - default n - depends on !SAMA5_UDPHS - -config SAMA5_OHCI_RHPORT2 - bool "Use Port B for OHCI" - default n - -config SAMA5_OHCI_RHPORT3 - bool "Use Port C for OHCI" - default y - -config SAMA5_EHCI_RHPORT1 - bool - default y if !SAMA5_OHCI_RHPORT1 - default n if SAMA5_OHCI_RHPORT1 - depends on !SAMA5_UDPHS - -config SAMA5_EHCI_RHPORT2 - bool - default y if !SAMA5_OHCI_RHPORT2 - default n if SAMA5_OHCI_RHPORT2 - -config SAMA5_EHCI_RHPORT3 - bool - default y if !SAMA5_OHCI_RHPORT3 - default n if SAMA5_OHCI_RHPORT3 - -endif # SAMA5_OHCI && SAMA5_EHCI - -if SAMA5_OHCI && !SAMA5_EHCI - -config SAMA5_OHCI_RHPORT1 - bool +config SAMA5_UHPHS_RHPORT1 + bool "Use Port A" default y depends on !SAMA5_UDPHS -config SAMA5_OHCI_RHPORT2 - bool +config SAMA5_UHPHS_RHPORT2 + bool "Use Port B" default y -config SAMA5_OHCI_RHPORT3 - bool +config SAMA5_UHPHS_RHPORT3 + bool "Use Port C" default y -endif # SAMA5_OHCI && !SAMA5_EHCI - -if !SAMA5_OHCI && SAMA5_EHCI - -config SAMA5_EHCI_RHPORT1 - bool - default y - depends on !SAMA5_UDPHS - -config SAMA5_EHCI_RHPORT2 - bool - default y - -config SAMA5_EHCI_RHPORT3 - bool - default y - -endif # !SAMA5_OHCI && SAMA5_EHCI +endif # SAMA5_OHCI || SAMA5_EHCI endmenu # USB High Speed Host driver option endif # SAMA5_UHPHS diff --git a/arch/arm/src/sama5/chip/sam_sfr.h b/arch/arm/src/sama5/chip/sam_sfr.h index 9ea3600a54..ca6558e38c 100644 --- a/arch/arm/src/sama5/chip/sam_sfr.h +++ b/arch/arm/src/sama5/chip/sam_sfr.h @@ -77,9 +77,10 @@ /* OHCI Interrupt Configuration Register */ -#define SFR_OHCIICR_RES0 (1 << 0) /* Bit 0: USB port 0 reset */ -#define SFR_OHCIICR_RES1 (1 << 1) /* Bit 1: USB port 1 reset */ -#define SFR_OHCIICR_RES2 (1 << 2) /* Bit 2: USB port 2 reset */ +#define SFR_OHCIICR_RES(n) (1 << (n)) /* Bit 0: USB port n reset, n=0..2 */ +# define SFR_OHCIICR_RES0 (1 << 0) /* Bit 0: USB port 0 reset */ +# define SFR_OHCIICR_RES1 (1 << 1) /* Bit 1: USB port 1 reset */ +# define SFR_OHCIICR_RES2 (1 << 2) /* Bit 2: USB port 2 reset */ #define SFR_OHCIICR_ARIE (1 << 4) /* Bit 4: OHCI asynchronous resume interrupt enable */ #define SFR_OHCIICR_APPSTART (0) /* Bit 5: Reserved, must write 0 */ #define SFR_OHCIICR_UDPPUDIS (1 << 23) /* Bit 23: USB device pull-up disable */ diff --git a/arch/arm/src/sama5/sam_ehci.c b/arch/arm/src/sama5/sam_ehci.c index f34d11d996..e42d2c7c6a 100755 --- a/arch/arm/src/sama5/sam_ehci.c +++ b/arch/arm/src/sama5/sam_ehci.c @@ -114,14 +114,14 @@ /* If UDPHS is enabled, then don't use port A */ #ifdef CONFIG_SAMA5_UDPHS -# undef CONFIG_SAMA5_EHCI_RHPORT1 +# undef CONFIG_SAMA5_UHPHS_RHPORT1 #endif /* For now, suppress use of PORTA in any event. I use that for SAM-BA and * would prefer that the board not try to drive VBUS on that port! */ -#undef CONFIG_SAMA5_EHCI_RHPORT1 +#undef CONFIG_SAMA5_UHPHS_RHPORT1 /* Driver-private Definitions **************************************************/ @@ -2547,6 +2547,32 @@ static int sam_ehci_tophalf(int irq, FAR void *context) return OK; } +/******************************************************************************* + * Name: sam_uhphs_interrupt + * + * Description: + * Common UHPHS interrupt handler. When both OHCI and EHCI are enabled, EHCI + * owns the interrupt and provides the interrupting event to both the OHCI and + * EHCI controllers. + * + *******************************************************************************/ + +#ifdef CONFIG_SAMA5_OHCI +static int sam_uhphs_interrupt(int irq, FAR void *context) +{ + int ohci; + int ehci; + + /* Provide the interrupting event to both the EHCI and OHCI top half */ + ohci = sam_ohci_tophalf(irq, context); + ehci = sam_ehci_tophalf(irq, context); + + /* Return OK only if both handlers returned OK */ + + return ohci == OK ? ehci : ohci; +} +#endif + /******************************************************************************* * USB Host Controller Operations *******************************************************************************/ @@ -2696,23 +2722,49 @@ static int sam_enumerate(FAR struct usbhost_connection_s *conn, int rhpndx) { /* Paragraph 2.3.9: * - * "Port Owner ... This bit unconditionally goes to a 0b when the - * Configured bit in the CONFIGFLAG register makes a 0b to 1b - * transition. This bit unconditionally goes to 1b whenever the - * Configured bit is zero. + * "Port Owner ... This bit unconditionally goes to a 0b when the + * Configured bit in the CONFIGFLAG register makes a 0b to 1b + * transition. This bit unconditionally goes to 1b whenever the + * Configured bit is zero. * - * "System software uses this field to release ownership of the - * port to a selected host controller (in the event that the - * attached device is not a high-speed device). Software writes - * a one to this bit when the attached device is not a high-speed - * device. A one in this bit means that a companion host - * controller owns and controls the port. .... + * "System software uses this field to release ownership of the + * port to a selected host controller (in the event that the + * attached device is not a high-speed device). Software writes + * a one to this bit when the attached device is not a high-speed + * device. A one in this bit means that a companion host + * controller owns and controls the port. .... + * + * Paragraph 4.2: + * + * "When a port is routed to a companion HC, it remains under the + * control of the companion HC until the device is disconnected + * from the root por ... When a disconnect occurs, the disconnect + * event is detected by both the companion HC port control and the + * EHCI port ownership control. On the event, the port ownership + * is returned immediately to the EHCI controller. The companion + * HC stack detects the disconnect and acknowledges as it would + * in an ordinary standalone implementation. Subsequent connects + * will be detected by the EHCI port register and the process will + * repeat." */ -#warning REVISIT rhport->ep0.speed = EHCI_LOW_SPEED; - regval &= EHCI_PORTSC_OWNER; + regval |= EHCI_PORTSC_OWNER; sam_putreg(regval, &HCOR->portsc[rhpndx]); + +#ifndef CONFIG_SAMA5_OHCI + /* Give the port to the OHCI controller. Zero is the reset value for + * all ports; one makes the corresponding port available to OHCI. + */ + + regval = getreg32(SAM_SFR_OHCIICR); + regval |= SFR_OHCIICR_RES(rhpndx); + putreg32(regval, SAM_SFR_OHCIICR); +#endif + + /* And return a failure */ + + return -EPERM; } else { @@ -2802,10 +2854,38 @@ static int sam_enumerate(FAR struct usbhost_connection_s *conn, int rhpndx) } else { - /* Low- or Full- speed device */ + /* Low- or Full- speed device. Set the port ownership bit. + * + * Paragraph 4.2: + * + * "When a port is routed to a companion HC, it remains under the + * control of the companion HC until the device is disconnected + * from the root por ... When a disconnect occurs, the disconnect + * event is detected by both the companion HC port control and the + * EHCI port ownership control. On the event, the port ownership + * is returned immediately to the EHCI controller. The companion + * HC stack detects the disconnect and acknowledges as it would + * in an ordinary standalone implementation. Subsequent connects + * will be detected by the EHCI port register and the process will + * repeat." + */ - regval &= EHCI_PORTSC_OWNER; + regval |= EHCI_PORTSC_OWNER; sam_putreg(regval, &HCOR->portsc[rhpndx]); + +#ifndef CONFIG_SAMA5_OHCI + /* Give the port to the OHCI controller. Zero is the reset value for + * all ports; one makes the corresponding port available to OHCI. + */ + + regval = getreg32(SAM_SFR_OHCIICR); + regval |= SFR_OHCIICR_RES(rhpndx); + putreg32(regval, SAM_SFR_OHCIICR); +#endif + + /* And return a failure */ + + return -EPERM; } /* Let the common usbhost_enumerate do all of the real work. Note that the @@ -3521,13 +3601,13 @@ FAR struct usbhost_connection_s *sam_ehci_initialize(int controller) */ regval = getreg32(SAM_SFR_OHCIICR); -#ifdef CONFIG_SAMA5_EHCI_RHPORT1 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT1 + regval &= ~SFR_OHCIICR_RES0; +#endif +#ifdef CONFIG_SAMA5_UHPHS_RHPORT2 regval &= ~SFR_OHCIICR_RES1; #endif -#ifdef CONFIG_SAMA5_EHCI_RHPORT2 - regval &= ~SFR_OHCIICR_RES1; -#endif -#ifdef CONFIG_SAMA5_EHCI_RHPORT3 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT3 regval &= ~SFR_OHCIICR_RES2; #endif putreg32(regval, SAM_SFR_OHCIICR); @@ -3767,9 +3847,16 @@ FAR struct usbhost_connection_s *sam_ehci_initialize(int controller) } /* Interrupt Configuration ***************************************************/ - /* Attach USB host controller interrupt handler */ + /* Attach USB host controller interrupt handler. If OHCI is also enabled, + * then we have to use a common UHPHS interrupt handler. + */ - if (irq_attach(SAM_IRQ_UHPHS, sam_ehci_tophalf) != 0) +#ifdef CONFIG_SAMA5_OHCI + ret = irq_attach(SAM_IRQ_UHPHS, sam_uhphs_interrupt); +#else + ret = irq_attach(SAM_IRQ_UHPHS, sam_ehci_tophalf); +#endif + if (ret != 0) { udbg("ERROR: Failed to attach IRQ\n"); return NULL; @@ -3781,17 +3868,22 @@ FAR struct usbhost_connection_s *sam_ehci_initialize(int controller) sam_putreg(EHCI_HANDLED_INTS, &HCOR->usbintr); - /* Drive Vbus +5V (the smoke test). Should be done elsewhere in OTG - * mode. + /* Drive Vbus +5V (the smoke test) + * + * REVISIT: + * - Should be done elsewhere in OTG mode. + * - Can we postpone enabling VBUS to save power? + * - Some EHCI implementations require setting the power bit in the + * PORTSC register to enable power. */ -#ifndef CONFIG_SAMA5_EHCI_RHPORT1 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT1 sam_usbhost_vbusdrive(SAM_RHPORT1, true); #endif -#ifndef CONFIG_SAMA5_EHCI_RHPORT2 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT2 sam_usbhost_vbusdrive(SAM_RHPORT2, true); #endif -#ifndef CONFIG_SAMA5_EHCI_RHPORT3 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT3 sam_usbhost_vbusdrive(SAM_RHPORT3, true); #endif up_mdelay(50); diff --git a/arch/arm/src/sama5/sam_ohci.c b/arch/arm/src/sama5/sam_ohci.c index 25c29302af..55088e728b 100644 --- a/arch/arm/src/sama5/sam_ohci.c +++ b/arch/arm/src/sama5/sam_ohci.c @@ -50,6 +50,7 @@ #include #include +#include #include #include #include @@ -70,10 +71,17 @@ #include "chip/sam_sfr.h" #include "chip/sam_ohci.h" +#ifdef CONFIG_SAMA5_OHCI + /******************************************************************************* * Definitions *******************************************************************************/ /* Configuration ***************************************************************/ +/* Pre-requisites */ + +#ifndef CONFIG_SCHED_WORKQUEUE +# error Work queue support is required (CONFIG_SCHED_WORKQUEUE) +#endif /* Configurable number of user endpoint descriptors (EDs). This number excludes * the control endpoint that is always allocated. @@ -120,14 +128,14 @@ /* If UDPHS is enabled, then don't use port A */ #ifdef CONFIG_SAMA5_UDPHS -# undef CONFIG_SAMA5_OHCI_RHPORT1 +# undef CONFIG_SAMA5_UHPHS_RHPORT1 #endif /* For now, suppress use of PORTA in any event. I use that for SAM-BA and * would prefer that the board not try to drive VBUS on that port! */ -#undef CONFIG_SAMA5_OHCI_RHPORT1 +#undef CONFIG_SAMA5_UHPHS_RHPORT1 /* Debug */ @@ -235,6 +243,7 @@ struct sam_ohci_s #endif sem_t exclsem; /* Support mutually exclusive access */ sem_t rhssem; /* Semaphore to wait Writeback Done Head event */ + struct work_s work; /* Supports interrupt bottom half */ /* Root hub ports */ @@ -362,9 +371,9 @@ static int sam_ctrltd(struct sam_rhport_s *rhport, uint32_t dirpid, /* Interrupt handling **********************************************************/ -static void sam_rhsc_interrupt(void); -static void sam_wdh_interrupt(void); -static int sam_ohci_interrupt(int irq, FAR void *context); +static void sam_rhsc_bottomhalf(void); +static void sam_wdh_bottomhalf(void); +static void sam_ohci_bottomhalf(void *arg); /* USB host controller operations **********************************************/ @@ -1681,14 +1690,14 @@ static int sam_ctrltd(struct sam_rhport_s *rhport, uint32_t dirpid, } /******************************************************************************* - * Name: sam_rhsc_interrupt + * Name: sam_rhsc_bottomhalf * * Description: * OHCI root hub status change interrupt handler * *******************************************************************************/ -static void sam_rhsc_interrupt(void) +static void sam_rhsc_bottomhalf(void) { struct sam_rhport_s *rhport; uint32_t regaddr; @@ -1813,14 +1822,14 @@ static void sam_rhsc_interrupt(void) } /******************************************************************************* - * Name: sam_wdh_interrupt + * Name: sam_wdh_bottomhalf * * Description: * OHCI write done head interrupt handler * *******************************************************************************/ -static void sam_wdh_interrupt(void) +static void sam_wdh_bottomhalf(void) { struct sam_eplist_s *eplist; struct sam_gtd_s *td; @@ -1913,82 +1922,79 @@ static void sam_wdh_interrupt(void) } /******************************************************************************* - * Name: sam_ohci_interrupt + * Name: sam_ohci_bottomhalf * * Description: - * OHCI interrupt handler + * OHCI interrupt bottom half. This function runs on the high priority worker + * thread and was xcheduled when the last interrupt occurred. The set of + * pending interrupts is provided as the argument. OHCI interrupts were + * disabled when this function is scheduled so no further interrupts can + * occur until this work re-enables OHCI interrupts * *******************************************************************************/ -static int sam_ohci_interrupt(int irq, FAR void *context) +static void sam_ohci_bottomhalf(void *arg) { - uint32_t intst; - uint32_t pending; - uint32_t regval; + uint32_t pending = (uint32_t)arg; - /* Read Interrupt Status and mask out interrupts that are not enabled. */ + /* 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). + */ - intst = sam_getreg(SAM_USBHOST_INTST); - regval = sam_getreg(SAM_USBHOST_INTEN); - ullvdbg("INST: %08x INTEN: %08x\n", intst, regval); + sam_takesem(&g_ohci.exclsem); - pending = intst & regval; - if (pending != 0) + /* Root hub status change interrupt */ + + if ((pending & OHCI_INT_RHSC) != 0) { - /* Root hub status change interrupt */ + /* Handle root hub status change on each root port */ - if ((pending & OHCI_INT_RHSC) != 0) - { - /* Handle root hub status change on each root port */ - - ullvdbg("Root Hub Status Change\n"); - sam_rhsc_interrupt(); - } - - /* Writeback Done Head interrupt */ - - if ((pending & OHCI_INT_WDH) != 0) - { - /* The host controller just wrote the list of finished TDs into the HCCA - * done head. This may include multiple packets that were transferred - * in the preceding frame. - */ - - ullvdbg("Writeback Done Head interrupt\n"); - sam_wdh_interrupt(); - } - -#ifdef CONFIG_DEBUG_USB - if ((pending & SAM_DEBUG_INTS) != 0) - { - if ((pending & OHCI_INT_UE) != 0) - { - /* An unrecoverable error occurred. Unrecoverable errors - * are usually the consequence of bad descriptor contents - * or DMA errors. - * - * Treat this like a normal write done head interrupt. We - * just want to see if there is any status information writen - * to the descriptors (and the normal write done head - * interrupt will not be occurring). - */ - - ulldbg("ERROR: Unrecoverable error. INTST: %08x\n", intst); - sam_wdh_interrupt(); - } - else - { - ulldbg("ERROR: Unhandled interrupts INTST: %08x\n", intst); - } - } -#endif - - /* Clear interrupt status register */ - - sam_putreg(intst, SAM_USBHOST_INTST); + ullvdbg("Root Hub Status Change\n"); + sam_rhsc_bottomhalf(); } - return OK; + /* Writeback Done Head interrupt */ + + if ((pending & OHCI_INT_WDH) != 0) + { + /* The host controller just wrote the list of finished TDs into the HCCA + * done head. This may include multiple packets that were transferred + * in the preceding frame. + */ + + ullvdbg("Writeback Done Head interrupt\n"); + sam_wdh_bottomhalf(); + } + +#ifdef CONFIG_DEBUG_USB + if ((pending & SAM_DEBUG_INTS) != 0) + { + if ((pending & OHCI_INT_UE) != 0) + { + /* An unrecoverable error occurred. Unrecoverable errors + * are usually the consequence of bad descriptor contents + * or DMA errors. + * + * Treat this like a normal write done head interrupt. We + * just want to see if there is any status information writen + * to the descriptors (and the normal write done head + * interrupt will not be occurring). + */ + + ulldbg("ERROR: Unrecoverable error. pending: %08x\n", pending); + sam_wdh_bottomhalf(); + } + else + { + ulldbg("ERROR: Unhandled interrupts pending: %08x\n", pending); + } + } +#endif + + /* Now re-enable interrupts */ + + sam_putreg(OHCI_INT_MIE, SAM_USBHOST_INTEN); } /******************************************************************************* @@ -2040,11 +2046,25 @@ static int sam_wait(FAR struct usbhost_connection_s *conn, for (rhpndx = 0; rhpndx < SAM_OHCI_NRHPORT; rhpndx++) { +#ifndef CONFIG_SAMA5_EHCI + /* If a device is no longer connected, return the port to the EHCI + * controller. Zero is the reset value for all ports; one makes + * the corresponding port available to OHCI. + */ + + if (!g_ohci.rhport[rhpndx].connected) + { + regval = getreg32(SAM_SFR_OHCIICR); + regval &= ~SFR_OHCIICR_RES(rhpndx); + putreg32(regval, SAM_SFR_OHCIICR); + } +#endif + /* Has the connection state changed on the RH port? */ if (g_ohci.rhport[rhpndx].connected != connected[rhpndx]) { - /* Yes.. Return the RH port number */ + /* Yes.. Return the RH port number ;to inform the call which */ irqrestore(flags); @@ -2864,6 +2884,18 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, sam_putreg(regval, SAM_USBHOST_CMDST); } + /* Release the OHCI 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 OHCI exclsem + * or we will deadlock while waiting (because the working thread that + * wakes this thread up needs the exclsem). + */ +#warning REVISIT + sam_givesem(&g_ohci.exclsem); + /* Wait for the Writeback Done Head interrupt Loop to handle any false * alarm semaphore counts. */ @@ -2873,6 +2905,12 @@ static int sam_transfer(FAR struct usbhost_driver_s *drvr, usbhost_ep_t ep, sam_takesem(&eplist->wdhsem); } + /* Re-aquire the ECHI semaphore. The caller expects to be holding + * this upon return. + */ + + sam_takesem(&g_ohci.exclsem); + /* Invalidate the D cache to force the ED to be reloaded from RAM */ cp15_invalidate_dcache((uintptr_t)ed, @@ -3029,23 +3067,27 @@ FAR struct usbhost_connection_s *sam_ohci_initialize(int controller) /* "One transceiver is shared with the USB High Speed Device (port A). The * selection between Host Port A and USB Device is controlled by the UDPHS * enable bit (EN_UDPHS) located in the UDPHS_CTRL control register." - * - * Make all three ports usable for OHCI unless the high speed device is + */ + +#ifndef CONFIG_SAMA5_EHCI + /* Make all three ports usable for OHCI unless the high speed device is * enabled; then let the device manage port zero. Zero is the reset * value for all ports; one makes the corresponding port available to OHCI. */ regval = getreg32(SAM_SFR_OHCIICR); -#ifdef CONFIG_SAMA5_OHCI_RHPORT1 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT1 + regval |= SFR_OHCIICR_RES0; +#endif +#ifdef CONFIG_SAMA5_UHPHS_RHPORT2 regval |= SFR_OHCIICR_RES1; #endif -#ifdef CONFIG_SAMA5_OHCI_RHPORT2 - regval |= SFR_OHCIICR_RES1; -#endif -#ifdef CONFIG_SAMA5_OHCI_RHPORT3 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT3 regval |= SFR_OHCIICR_RES2; #endif putreg32(regval, SAM_SFR_OHCIICR); +#endif + irqrestore(flags); /* Note that no pin configuration is required. All USB HS pins have @@ -3148,27 +3190,32 @@ FAR struct usbhost_connection_s *sam_ohci_initialize(int controller) /* Enable OHCI interrupts */ - sam_putreg((SAM_ALL_INTS|OHCI_INT_MIE), SAM_USBHOST_INTEN); + sam_putreg((SAM_ALL_INTS | OHCI_INT_MIE), SAM_USBHOST_INTEN); - /* Attach USB host controller interrupt handler */ +#ifndef CONFIG_SAMA5_EHCI + /* Attach USB host controller interrupt handler. If ECHI is enabled, + * then it will manage the shared interrupt. */ - if (irq_attach(SAM_IRQ_UHPHS, sam_ohci_interrupt) != 0) + if (irq_attach(SAM_IRQ_UHPHS, sam_ohci_tophalf) != 0) { udbg("Failed to attach IRQ\n"); return NULL; } - /* Drive Vbus +5V (the smoke test). Should be done elsewhere in OTG - * mode. + /* Drive Vbus +5V (the smoke test). + * + * REVISIT: + * - Should be done elsewhere in OTG mode. + * - Can we postpone enabling VBUS to save power? */ -#ifndef CONFIG_SAMA5_OHCI_RHPORT1 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT1 sam_usbhost_vbusdrive(SAM_RHPORT1, true); #endif -#ifndef CONFIG_SAMA5_OHCI_RHPORT2 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT2 sam_usbhost_vbusdrive(SAM_RHPORT2, true); #endif -#ifndef CONFIG_SAMA5_OHCI_RHPORT3 +#ifdef CONFIG_SAMA5_UHPHS_RHPORT3 sam_usbhost_vbusdrive(SAM_RHPORT3, true); #endif up_mdelay(50); @@ -3187,9 +3234,14 @@ FAR struct usbhost_connection_s *sam_ohci_initialize(int controller) i+1, g_ohci.rhport[i].connected ? "YES" : "NO"); } - /* Enable interrupts at the interrupt controller */ + /* Enable interrupts at the interrupt controller. If ECHI is enabled, + * then it will manage the shared interrupt. + */ up_enable_irq(SAM_IRQ_UHPHS); /* enable USB interrupt */ + +#endif /* CONFIG_SAMA5_EHCI */ + uvdbg("USB OHCI Initialized\n"); /* Initialize and return the connection interface */ @@ -3198,3 +3250,58 @@ FAR struct usbhost_connection_s *sam_ohci_initialize(int controller) g_ohciconn.enumerate = sam_enumerate; return &g_ohciconn; } + +/******************************************************************************* + * Name: sam_ohci_tophalf + * + * Description: + * OHCI "Top Half" interrupt handler. If both EHCI and OHCI are enabled, then + * EHCI will manage the common UHPHS interrupt and will forward the interrupt + * event to this function. + * + *******************************************************************************/ + +int sam_ohci_tophalf(int irq, FAR void *context) +{ + uint32_t intst; + uint32_t regval; + uint32_t pending; + + /* Read Interrupt Status and mask out interrupts that are not enabled. */ + + intst = sam_getreg(SAM_USBHOST_INTST); + regval = sam_getreg(SAM_USBHOST_INTEN); + ullvdbg("INST: %08x INTEN: %08x\n", intst, regval); + + pending = intst & 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 OHCI interrupts. + */ + + DEBUGASSERT(work_available(&g_ohci.work)); + DEBUGVERIFY(work_queue(HPWORK, &g_ohci.work, sam_ohci_bottomhalf, + (FAR void *)pending, 0)); + + /* Disable further OHCI interrupts so that we do not overrun the work + * queue. + */ + + sam_putreg(OHCI_INT_MIE, SAM_USBHOST_INTDIS); + + /* Clear all pending status bits by writing the value of the pending + * interrupt bits back to the status register. + */ + + sam_putreg(intst, SAM_USBHOST_INTST); + } + + return OK; +} + +#endif /* CONFIG_SAMA5_OHCI */ diff --git a/arch/arm/src/sama5/sam_usbhost.h b/arch/arm/src/sama5/sam_usbhost.h index 0a4494a455..7ab5cca85a 100644 --- a/arch/arm/src/sama5/sam_usbhost.h +++ b/arch/arm/src/sama5/sam_usbhost.h @@ -119,6 +119,20 @@ struct usbhost_connection_s; FAR struct usbhost_connection_s *sam_ohci_initialize(int controller); #endif +/******************************************************************************* + * Name: sam_ohci_tophalf + * + * Description: + * OHCI "Top Half" interrupt handler. If both EHCI and OHCI are enabled, then + * EHCI will manage the common UHPHS interrupt and will forward the interrupt + * event to this function. + * + *******************************************************************************/ + +#ifdef CONFIG_SAMA5_OHCI +int sam_ohci_tophalf(int irq, FAR void *context); +#endif + /******************************************************************************* * Name: sam_ehci_initialize *