From 5128234c3c1c86a2587b5f0baf3bbd34445f5632 Mon Sep 17 00:00:00 2001 From: popcornmix Date: Wed, 3 Jul 2013 00:46:42 +0100 Subject: [PATCH 28/54] Add FIQ patch to dwc_otg driver. Enable with dwc_otg.fiq_fix_enable=1. Should give about 10% more ARM performance. Thanks to Gordon and Costas Avoid dynamic memory allocation for channel lock in USB driver. Thanks ddv2005. Add NAK holdoff scheme. Enabled by default, disable with dwc_otg.nak_holdoff_enable=0. Thanks gsh Make sure we wait for the reset to finish dwc_otg: fix bug in dwc_otg_hcd.c resulting in silent kernel memory corruption, escalating to OOPS under high USB load. dwc_otg: Fix unsafe access of QTD during URB enqueue In dwc_otg_hcd_urb_enqueue during qtd creation, it was possible that the transaction could complete almost immediately after the qtd was assigned to a host channel during URB enqueue, which meant the qtd pointer was no longer valid having been completed and removed. Usually, this resulted in an OOPS during URB submission. By predetermining whether transactions need to be queued or not, this unsafe pointer access is avoided. This bug was only evident on the Pi model A where a device was attached that had no periodic endpoints (e.g. USB pendrive or some wlan devices). dwc_otg: Fix incorrect URB allocation error handling If the memory allocation for a dwc_otg_urb failed, the kernel would OOPS because for some reason a member of the *unallocated* struct was set to zero. Error handling changed to fail correctly. dwc_otg: fix potential use-after-free case in interrupt handler If a transaction had previously aborted, certain interrupts are enabled to track error counts and reset where necessary. On IN endpoints the host generates an ACK interrupt near-simultaneously with completion of transfer. In the case where this transfer had previously had an error, this results in a use-after-free on the QTD memory space with a 1-byte length being overwritten to 0x00. dwc_otg: add handling of SPLIT transaction data toggle errors Previously a data toggle error on packets from a USB1.1 device behind a TT would result in the Pi locking up as the driver never handled the associated interrupt. Patch adds basic retry mechanism and interrupt acknowledgement to cater for either a chance toggle error or for devices that have a broken initial toggle state (FT8U232/FT232BM). dwc_otg: implement tasklet for returning URBs to usbcore hcd layer The dwc_otg driver interrupt handler for transfer completion will spend a very long time with interrupts disabled when a URB is completed - this is because usb_hcd_giveback_urb is called from within the handler which for a USB device driver with complicated processing (e.g. webcam) will take an exorbitant amount of time to complete. This results in missed completion interrupts for other USB packets which lead to them being dropped due to microframe overruns. This patch splits returning the URB to the usb hcd layer into a high-priority tasklet. This will have most benefit for isochronous IN transfers but will also have incidental benefit where multiple periodic devices are active at once. dwc_otg: fix NAK holdoff and allow on split transactions only This corrects a bug where if a single active non-periodic endpoint had at least one transaction in its qh, on frnum == MAX_FRNUM the qh would get skipped and never get queued again. This would result in a silent device until error detection (automatic or otherwise) would either reset the device or flush and requeue the URBs. Additionally the NAK holdoff was enabled for all transactions - this would potentially stall a HS endpoint for 1ms if a previous error state enabled this interrupt and the next response was a NAK. Fix so that only split transactions get held off. dwc_otg: Call usb_hcd_unlink_urb_from_ep with lock held in completion handler usb_hcd_unlink_urb_from_ep must be called with the HCD lock held. Calling it asynchronously in the tasklet was not safe (regression in c4564d4a1a0a9b10d4419e48239f5d99e88d2667). This change unlinks it from the endpoint prior to queueing it for handling in the tasklet, and also adds a check to ensure the urb is OK to be unlinked before doing so. NULL pointer dereference kernel oopses had been observed in usb_hcd_giveback_urb when a USB device was unplugged/replugged during data transfer. This effect was reproduced using automated USB port power control, hundreds of replug events were performed during active transfers to confirm that the problem was eliminated. USB fix using a FIQ to implement split transactions This commit adds a FIQ implementaion that schedules the split transactions using a FIQ so we don't get held off by the interrupt latency of Linux dwc_otg: fix device attributes and avoid kernel warnings on boot dcw_otg: avoid logging function that can cause panics See: https://github.com/raspberrypi/firmware/issues/21 Thanks to cleverca22 for fix dwc_otg: mask correct interrupts after transaction error recovery The dwc_otg driver will unmask certain interrupts on a transaction that previously halted in the error state in order to reset the QTD error count. The various fine-grained interrupt handlers do not consider that other interrupts besides themselves were unmasked. By disabling the two other interrupts only ever enabled in DMA mode for this purpose, we can avoid unnecessary function calls in the IRQ handler. This will also prevent an unneccesary FIQ interrupt from being generated if the FIQ is enabled. dwc_otg: fiq: prevent FIQ thrash and incorrect state passing to IRQ In the case of a transaction to a device that had previously aborted due to an error, several interrupts are enabled to reset the error count when a device responds. This has the side-effect of making the FIQ thrash because the hardware will generate multiple instances of a NAK on an IN bulk/interrupt endpoint and multiple instances of ACK on an OUT bulk/interrupt endpoint. Make the FIQ mask and clear the associated interrupts. Additionally, on non-split transactions make sure that only unmasked interrupts are cleared. This caused a hard-to-trigger but serious race condition when you had the combination of an endpoint awaiting error recovery and a transaction completed on an endpoint - due to the sequencing and timing of interrupts generated by the dwc_otg core, it was possible to confuse the IRQ handler. Fix function tracing dwc_otg: whitespace cleanup in dwc_otg_urb_enqueue dwc_otg: prevent OOPSes during device disconnects The dwc_otg_urb_enqueue function is thread-unsafe. In particular the access of urb->hcpriv, usb_hcd_link_urb_to_ep, dwc_otg_urb->qtd and friends does not occur within a critical section and so if a device was unplugged during activity there was a high chance that the usbcore hub_thread would try to disable the endpoint with partially- formed entries in the URB queue. This would result in BUG() or null pointer dereferences. Fix so that access of urb->hcpriv, enqueuing to the hardware and adding to usbcore endpoint URB lists is contained within a single critical section. dwc_otg: prevent BUG() in TT allocation if hub address is > 16 A fixed-size array is used to track TT allocation. This was previously set to 16 which caused a crash because dwc_otg_hcd_allocate_port would read past the end of the array. This was hit if a hub was plugged in which enumerated as addr > 16, due to previous device resets or unplugs. Also add #ifdef FIQ_DEBUG around hcd->hub_port_alloc[], which grows to a large size if 128 hub addresses are supported. This field is for debug only for tracking which frame an allocate happened in. dwc_otg: make channel halts with unknown state less damaging If the IRQ received a channel halt interrupt through the FIQ with no other bits set, the IRQ would not release the host channel and never complete the URB. Add catchall handling to treat as a transaction error and retry. dwc_otg: fiq_split: use TTs with more granularity This fixes certain issues with split transaction scheduling. - Isochronous multi-packet OUT transactions now hog the TT until they are completed - this prevents hubs aborting transactions if they get a periodic start-split out-of-order - Don't perform TT allocation on non-periodic endpoints - this allows simultaneous use of the TT's bulk/control and periodic transaction buffers This commit will mainly affect USB audio playback. dwc_otg: fix potential sleep while atomic during urb enqueue Fixes a regression introduced with eb1b482a. Kmalloc called from dwc_otg_hcd_qtd_add / dwc_otg_hcd_qtd_create did not always have the GPF_ATOMIC flag set. Force this flag when inside the larger critical section. dwc_otg: make fiq_split_enable imply fiq_fix_enable Failing to set up the FIQ correctly would result in "IRQ 32: nobody cared" errors in dmesg. dwc_otg: prevent crashes on host port disconnects Fix several issues resulting in crashes or inconsistent state if a Model A root port was disconnected. - Clean up queue heads properly in kill_urbs_in_qh_list by removing the empty QHs from the schedule lists - Set the halt status properly to prevent IRQ handlers from using freed memory - Add fiq_split related cleanup for saved registers - Make microframe scheduling reclaim host channels if active during a disconnect - Abort URBs with -ESHUTDOWN status response, informing device drivers so they respond in a more correct fashion and don't try to resubmit URBs - Prevent IRQ handlers from attempting to handle channel interrupts if the associated URB was dequeued (and the driver state was cleared) dwc_otg: prevent leaking URBs during enqueue A dwc_otg_urb would get leaked if the HCD enqueue function failed for any reason. Free the URB at the appropriate points. dwc_otg: Enable NAK holdoff for control split transactions Certain low-speed devices take a very long time to complete a data or status stage of a control transaction, producing NAK responses until they complete internal processing - the USB2.0 spec limit is up to 500mS. This causes the same type of interrupt storm as seen with USB-serial dongles prior to c8edb238. In certain circumstances, usually while booting, this interrupt storm could cause SD card timeouts. dwc_otg: Fix for occasional lockup on boot when doing a USB reset dwc_otg: Don't issue traffic to LS devices in FS mode Issuing low-speed packets when the root port is in full-speed mode causes the root port to stop responding. Explicitly fail when enqueuing URBs to a LS endpoint on a FS bus. Fix ARM architecture issue with local_irq_restore() If local_fiq_enable() is called before a local_irq_restore(flags) where the flags variable has the F bit set, the FIQ will be erroneously disabled. Fixup arch_local_irq_restore to avoid trampling the F bit in CPSR. Also fix some of the hacks previously implemented for previous dwc_otg incarnations. --- arch/arm/Kconfig | 1 + arch/arm/include/asm/irqflags.h | 16 +- arch/arm/kernel/fiqasm.S | 4 + arch/arm/mach-bcm2708/armctrl.c | 19 +- arch/arm/mach-bcm2708/bcm2708.c | 29 +- arch/arm/mach-bcm2708/include/mach/irqs.h | 155 ++--- arch/arm/mach-bcm2708/include/mach/platform.h | 2 + .../usb/host/dwc_common_port/dwc_common_linux.c | 11 + drivers/usb/host/dwc_common_port/dwc_list.h | 14 +- drivers/usb/host/dwc_common_port/dwc_os.h | 2 + drivers/usb/host/dwc_otg/Makefile | 1 + drivers/usb/host/dwc_otg/dwc_otg_attr.c | 14 +- drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c | 47 +- drivers/usb/host/dwc_otg/dwc_otg_dbg.h | 1 + drivers/usb/host/dwc_otg/dwc_otg_driver.c | 52 +- drivers/usb/host/dwc_otg/dwc_otg_hcd.c | 304 +++++++-- drivers/usb/host/dwc_otg/dwc_otg_hcd.h | 37 +- drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c | 3 +- drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h | 5 + drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c | 705 ++++++++++++++++++++- drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c | 159 +++-- drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c | 53 +- drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.c | 113 ++++ drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.h | 48 ++ drivers/usb/host/dwc_otg/dwc_otg_os_dep.h | 3 + drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c | 2 +- 26 files changed, 1548 insertions(+), 252 deletions(-) create mode 100755 drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.c create mode 100755 drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.h diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index be8a752..7cdab14 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -394,6 +394,7 @@ config ARCH_BCM2708 select ARM_ERRATA_411920 select MACH_BCM2708 select VC4 + select FIQ help This enables support for Broadcom BCM2708 boards. diff --git a/arch/arm/include/asm/irqflags.h b/arch/arm/include/asm/irqflags.h index 3b763d6..5770408 100644 --- a/arch/arm/include/asm/irqflags.h +++ b/arch/arm/include/asm/irqflags.h @@ -145,12 +145,22 @@ static inline unsigned long arch_local_save_flags(void) } /* - * restore saved IRQ & FIQ state + * restore saved IRQ state */ static inline void arch_local_irq_restore(unsigned long flags) { - asm volatile( - " msr " IRQMASK_REG_NAME_W ", %0 @ local_irq_restore" + unsigned long temp = 0; + flags &= ~(1 << 6); + asm volatile ( + " mrs %0, cpsr" + : "=r" (temp) + : + : "memory", "cc"); + /* Preserve FIQ bit */ + temp &= (1 << 6); + flags = flags | temp; + asm volatile ( + " msr cpsr_c, %0 @ local_irq_restore" : : "r" (flags) : "memory", "cc"); diff --git a/arch/arm/kernel/fiqasm.S b/arch/arm/kernel/fiqasm.S index 207f9d6..5233d54 100644 --- a/arch/arm/kernel/fiqasm.S +++ b/arch/arm/kernel/fiqasm.S @@ -47,3 +47,7 @@ ENTRY(__get_fiq_regs) mov r0, r0 @ avoid hazard prior to ARMv4 mov pc, lr ENDPROC(__get_fiq_regs) + +ENTRY(__FIQ_Branch) + mov pc, r8 +ENDPROC(__FIQ_Branch) diff --git a/arch/arm/mach-bcm2708/armctrl.c b/arch/arm/mach-bcm2708/armctrl.c index da18725..274aa30 100644 --- a/arch/arm/mach-bcm2708/armctrl.c +++ b/arch/arm/mach-bcm2708/armctrl.c @@ -52,8 +52,12 @@ static void armctrl_mask_irq(struct irq_data *d) 0 }; - unsigned int data = (unsigned int)irq_get_chip_data(d->irq); - writel(1 << (data & 0x1f), __io_address(disables[(data >> 5) & 0x3])); + if (d->irq >= FIQ_START) { + writel(0, __io_address(ARM_IRQ_FAST)); + } else { + unsigned int data = (unsigned int)irq_get_chip_data(d->irq); + writel(1 << (data & 0x1f), __io_address(disables[(data >> 5) & 0x3])); + } } static void armctrl_unmask_irq(struct irq_data *d) @@ -65,8 +69,14 @@ static void armctrl_unmask_irq(struct irq_data *d) 0 }; - unsigned int data = (unsigned int)irq_get_chip_data(d->irq); - writel(1 << (data & 0x1f), __io_address(enables[(data >> 5) & 0x3])); + if (d->irq >= FIQ_START) { + unsigned int data = + (unsigned int)irq_get_chip_data(d->irq) - FIQ_START; + writel(0x80 | data, __io_address(ARM_IRQ_FAST)); + } else { + unsigned int data = (unsigned int)irq_get_chip_data(d->irq); + writel(1 << (data & 0x1f), __io_address(enables[(data >> 5) & 0x3])); + } } #if defined(CONFIG_PM) @@ -204,5 +214,6 @@ int __init armctrl_init(void __iomem * base, unsigned int irq_start, } armctrl_pm_register(base, irq_start, resume_sources); + init_FIQ(FIQ_START); return 0; } diff --git a/arch/arm/mach-bcm2708/bcm2708.c b/arch/arm/mach-bcm2708/bcm2708.c index 221d145..47a66f8 100644 --- a/arch/arm/mach-bcm2708/bcm2708.c +++ b/arch/arm/mach-bcm2708/bcm2708.c @@ -321,12 +321,32 @@ static struct resource bcm2708_usb_resources[] = { .flags = IORESOURCE_MEM, }, [1] = { - .start = IRQ_USB, - .end = IRQ_USB, + .start = MPHI_BASE, + .end = MPHI_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [2] = { + .start = IRQ_HOSTPORT, + .end = IRQ_HOSTPORT, .flags = IORESOURCE_IRQ, }, }; +bool fiq_fix_enable = true; + +static struct resource bcm2708_usb_resources_no_fiq_fix[] = { + [0] = { + .start = USB_BASE, + .end = USB_BASE + SZ_128K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_USB, + .end = IRQ_USB, + .flags = IORESOURCE_IRQ, + }, +}; + static u64 usb_dmamask = DMA_BIT_MASK(DMA_MASK_BITS_COMMON); static struct platform_device bcm2708_usb_device = { @@ -709,6 +729,11 @@ void __init bcm2708_init(void) #endif bcm_register_device(&bcm2708_systemtimer_device); bcm_register_device(&bcm2708_fb_device); + if (!fiq_fix_enable) + { + bcm2708_usb_device.resource = bcm2708_usb_resources_no_fiq_fix; + bcm2708_usb_device.num_resources = ARRAY_SIZE(bcm2708_usb_resources_no_fiq_fix); + } bcm_register_device(&bcm2708_usb_device); bcm_register_device(&bcm2708_uart1_device); bcm_register_device(&bcm2708_powerman_device); diff --git a/arch/arm/mach-bcm2708/include/mach/irqs.h b/arch/arm/mach-bcm2708/include/mach/irqs.h index faf5d1a..4299054 100644 --- a/arch/arm/mach-bcm2708/include/mach/irqs.h +++ b/arch/arm/mach-bcm2708/include/mach/irqs.h @@ -106,89 +106,92 @@ #define IRQ_PENDING1 (IRQ_ARMCTRL_START + INTERRUPT_PENDING1) #define IRQ_PENDING2 (IRQ_ARMCTRL_START + INTERRUPT_PENDING2) +#define FIQ_START HARD_IRQS + /* * FIQ interrupts definitions are the same as the INT definitions. */ -#define FIQ_TIMER0 INT_TIMER0 -#define FIQ_TIMER1 INT_TIMER1 -#define FIQ_TIMER2 INT_TIMER2 -#define FIQ_TIMER3 INT_TIMER3 -#define FIQ_CODEC0 INT_CODEC0 -#define FIQ_CODEC1 INT_CODEC1 -#define FIQ_CODEC2 INT_CODEC2 -#define FIQ_JPEG INT_JPEG -#define FIQ_ISP INT_ISP -#define FIQ_USB INT_USB -#define FIQ_3D INT_3D -#define FIQ_TRANSPOSER INT_TRANSPOSER -#define FIQ_MULTICORESYNC0 INT_MULTICORESYNC0 -#define FIQ_MULTICORESYNC1 INT_MULTICORESYNC1 -#define FIQ_MULTICORESYNC2 INT_MULTICORESYNC2 -#define FIQ_MULTICORESYNC3 INT_MULTICORESYNC3 -#define FIQ_DMA0 INT_DMA0 -#define FIQ_DMA1 INT_DMA1 -#define FIQ_DMA2 INT_DMA2 -#define FIQ_DMA3 INT_DMA3 -#define FIQ_DMA4 INT_DMA4 -#define FIQ_DMA5 INT_DMA5 -#define FIQ_DMA6 INT_DMA6 -#define FIQ_DMA7 INT_DMA7 -#define FIQ_DMA8 INT_DMA8 -#define FIQ_DMA9 INT_DMA9 -#define FIQ_DMA10 INT_DMA10 -#define FIQ_DMA11 INT_DMA11 -#define FIQ_DMA12 INT_DMA12 -#define FIQ_AUX INT_AUX -#define FIQ_ARM INT_ARM -#define FIQ_VPUDMA INT_VPUDMA -#define FIQ_HOSTPORT INT_HOSTPORT -#define FIQ_VIDEOSCALER INT_VIDEOSCALER -#define FIQ_CCP2TX INT_CCP2TX -#define FIQ_SDC INT_SDC -#define FIQ_DSI0 INT_DSI0 -#define FIQ_AVE INT_AVE -#define FIQ_CAM0 INT_CAM0 -#define FIQ_CAM1 INT_CAM1 -#define FIQ_HDMI0 INT_HDMI0 -#define FIQ_HDMI1 INT_HDMI1 -#define FIQ_PIXELVALVE1 INT_PIXELVALVE1 -#define FIQ_I2CSPISLV INT_I2CSPISLV -#define FIQ_DSI1 INT_DSI1 -#define FIQ_PWA0 INT_PWA0 -#define FIQ_PWA1 INT_PWA1 -#define FIQ_CPR INT_CPR -#define FIQ_SMI INT_SMI -#define FIQ_GPIO0 INT_GPIO0 -#define FIQ_GPIO1 INT_GPIO1 -#define FIQ_GPIO2 INT_GPIO2 -#define FIQ_GPIO3 INT_GPIO3 -#define FIQ_I2C INT_I2C -#define FIQ_SPI INT_SPI -#define FIQ_I2SPCM INT_I2SPCM -#define FIQ_SDIO INT_SDIO -#define FIQ_UART INT_UART -#define FIQ_SLIMBUS INT_SLIMBUS -#define FIQ_VEC INT_VEC -#define FIQ_CPG INT_CPG -#define FIQ_RNG INT_RNG -#define FIQ_ARASANSDIO INT_ARASANSDIO -#define FIQ_AVSPMON INT_AVSPMON +#define FIQ_TIMER0 (FIQ_START+INTERRUPT_TIMER0) +#define FIQ_TIMER1 (FIQ_START+INTERRUPT_TIMER1) +#define FIQ_TIMER2 (FIQ_START+INTERRUPT_TIMER2) +#define FIQ_TIMER3 (FIQ_START+INTERRUPT_TIMER3) +#define FIQ_CODEC0 (FIQ_START+INTERRUPT_CODEC0) +#define FIQ_CODEC1 (FIQ_START+INTERRUPT_CODEC1) +#define FIQ_CODEC2 (FIQ_START+INTERRUPT_CODEC2) +#define FIQ_JPEG (FIQ_START+INTERRUPT_JPEG) +#define FIQ_ISP (FIQ_START+INTERRUPT_ISP) +#define FIQ_USB (FIQ_START+INTERRUPT_USB) +#define FIQ_3D (FIQ_START+INTERRUPT_3D) +#define FIQ_TRANSPOSER (FIQ_START+INTERRUPT_TRANSPOSER) +#define FIQ_MULTICORESYNC0 (FIQ_START+INTERRUPT_MULTICORESYNC0) +#define FIQ_MULTICORESYNC1 (FIQ_START+INTERRUPT_MULTICORESYNC1) +#define FIQ_MULTICORESYNC2 (FIQ_START+INTERRUPT_MULTICORESYNC2) +#define FIQ_MULTICORESYNC3 (FIQ_START+INTERRUPT_MULTICORESYNC3) +#define FIQ_DMA0 (FIQ_START+INTERRUPT_DMA0) +#define FIQ_DMA1 (FIQ_START+INTERRUPT_DMA1) +#define FIQ_DMA2 (FIQ_START+INTERRUPT_DMA2) +#define FIQ_DMA3 (FIQ_START+INTERRUPT_DMA3) +#define FIQ_DMA4 (FIQ_START+INTERRUPT_DMA4) +#define FIQ_DMA5 (FIQ_START+INTERRUPT_DMA5) +#define FIQ_DMA6 (FIQ_START+INTERRUPT_DMA6) +#define FIQ_DMA7 (FIQ_START+INTERRUPT_DMA7) +#define FIQ_DMA8 (FIQ_START+INTERRUPT_DMA8) +#define FIQ_DMA9 (FIQ_START+INTERRUPT_DMA9) +#define FIQ_DMA10 (FIQ_START+INTERRUPT_DMA10) +#define FIQ_DMA11 (FIQ_START+INTERRUPT_DMA11) +#define FIQ_DMA12 (FIQ_START+INTERRUPT_DMA12) +#define FIQ_AUX (FIQ_START+INTERRUPT_AUX) +#define FIQ_ARM (FIQ_START+INTERRUPT_ARM) +#define FIQ_VPUDMA (FIQ_START+INTERRUPT_VPUDMA) +#define FIQ_HOSTPORT (FIQ_START+INTERRUPT_HOSTPORT) +#define FIQ_VIDEOSCALER (FIQ_START+INTERRUPT_VIDEOSCALER) +#define FIQ_CCP2TX (FIQ_START+INTERRUPT_CCP2TX) +#define FIQ_SDC (FIQ_START+INTERRUPT_SDC) +#define FIQ_DSI0 (FIQ_START+INTERRUPT_DSI0) +#define FIQ_AVE (FIQ_START+INTERRUPT_AVE) +#define FIQ_CAM0 (FIQ_START+INTERRUPT_CAM0) +#define FIQ_CAM1 (FIQ_START+INTERRUPT_CAM1) +#define FIQ_HDMI0 (FIQ_START+INTERRUPT_HDMI0) +#define FIQ_HDMI1 (FIQ_START+INTERRUPT_HDMI1) +#define FIQ_PIXELVALVE1 (FIQ_START+INTERRUPT_PIXELVALVE1) +#define FIQ_I2CSPISLV (FIQ_START+INTERRUPT_I2CSPISLV) +#define FIQ_DSI1 (FIQ_START+INTERRUPT_DSI1) +#define FIQ_PWA0 (FIQ_START+INTERRUPT_PWA0) +#define FIQ_PWA1 (FIQ_START+INTERRUPT_PWA1) +#define FIQ_CPR (FIQ_START+INTERRUPT_CPR) +#define FIQ_SMI (FIQ_START+INTERRUPT_SMI) +#define FIQ_GPIO0 (FIQ_START+INTERRUPT_GPIO0) +#define FIQ_GPIO1 (FIQ_START+INTERRUPT_GPIO1) +#define FIQ_GPIO2 (FIQ_START+INTERRUPT_GPIO2) +#define FIQ_GPIO3 (FIQ_START+INTERRUPT_GPIO3) +#define FIQ_I2C (FIQ_START+INTERRUPT_I2C) +#define FIQ_SPI (FIQ_START+INTERRUPT_SPI) +#define FIQ_I2SPCM (FIQ_START+INTERRUPT_I2SPCM) +#define FIQ_SDIO (FIQ_START+INTERRUPT_SDIO) +#define FIQ_UART (FIQ_START+INTERRUPT_UART) +#define FIQ_SLIMBUS (FIQ_START+INTERRUPT_SLIMBUS) +#define FIQ_VEC (FIQ_START+INTERRUPT_VEC) +#define FIQ_CPG (FIQ_START+INTERRUPT_CPG) +#define FIQ_RNG (FIQ_START+INTERRUPT_RNG) +#define FIQ_ARASANSDIO (FIQ_START+INTERRUPT_ARASANSDIO) +#define FIQ_AVSPMON (FIQ_START+INTERRUPT_AVSPMON) -#define FIQ_ARM_TIMER INT_ARM_TIMER -#define FIQ_ARM_MAILBOX INT_ARM_MAILBOX -#define FIQ_ARM_DOORBELL_0 INT_ARM_DOORBELL_0 -#define FIQ_ARM_DOORBELL_1 INT_ARM_DOORBELL_1 -#define FIQ_VPU0_HALTED INT_VPU0_HALTED -#define FIQ_VPU1_HALTED INT_VPU1_HALTED -#define FIQ_ILLEGAL_TYPE0 INT_ILLEGAL_TYPE0 -#define FIQ_ILLEGAL_TYPE1 INT_ILLEGAL_TYPE1 -#define FIQ_PENDING1 INT_PENDING1 -#define FIQ_PENDING2 INT_PENDING2 +#define FIQ_ARM_TIMER (FIQ_START+INTERRUPT_ARM_TIMER) +#define FIQ_ARM_MAILBOX (FIQ_START+INTERRUPT_ARM_MAILBOX) +#define FIQ_ARM_DOORBELL_0 (FIQ_START+INTERRUPT_ARM_DOORBELL_0) +#define FIQ_ARM_DOORBELL_1 (FIQ_START+INTERRUPT_ARM_DOORBELL_1) +#define FIQ_VPU0_HALTED (FIQ_START+INTERRUPT_VPU0_HALTED) +#define FIQ_VPU1_HALTED (FIQ_START+INTERRUPT_VPU1_HALTED) +#define FIQ_ILLEGAL_TYPE0 (FIQ_START+INTERRUPT_ILLEGAL_TYPE0) +#define FIQ_ILLEGAL_TYPE1 (FIQ_START+INTERRUPT_ILLEGAL_TYPE1) +#define FIQ_PENDING1 (FIQ_START+INTERRUPT_PENDING1) +#define FIQ_PENDING2 (FIQ_START+INTERRUPT_PENDING2) #define HARD_IRQS (64 + 21) -#define GPIO_IRQ_START (HARD_IRQS) +#define FIQ_IRQS (64 + 21) +#define GPIO_IRQ_START (HARD_IRQS + FIQ_IRQS) #define GPIO_IRQS (32*5) #define SPARE_IRQS (64) -#define NR_IRQS (HARD_IRQS+GPIO_IRQS+SPARE_IRQS) +#define NR_IRQS (HARD_IRQS+FIQ_IRQS+GPIO_IRQS+SPARE_IRQS) #endif /* _BCM2708_IRQS_H_ */ diff --git a/arch/arm/mach-bcm2708/include/mach/platform.h b/arch/arm/mach-bcm2708/include/mach/platform.h index f4bb733..992a630 100644 --- a/arch/arm/mach-bcm2708/include/mach/platform.h +++ b/arch/arm/mach-bcm2708/include/mach/platform.h @@ -56,7 +56,9 @@ */ #define BCM2708_PERI_BASE 0x20000000 +#define IC0_BASE (BCM2708_PERI_BASE + 0x2000) #define ST_BASE (BCM2708_PERI_BASE + 0x3000) /* System Timer */ +#define MPHI_BASE (BCM2708_PERI_BASE + 0x6000) /* Message -based Parallel Host Interface */ #define DMA_BASE (BCM2708_PERI_BASE + 0x7000) /* DMA controller */ #define ARM_BASE (BCM2708_PERI_BASE + 0xB000) /* BCM2708 ARM control block */ #define PM_BASE (BCM2708_PERI_BASE + 0x100000) /* Power Management, Reset controller and Watchdog registers */ diff --git a/drivers/usb/host/dwc_common_port/dwc_common_linux.c b/drivers/usb/host/dwc_common_port/dwc_common_linux.c index 440bcfc..6d01261 100644 --- a/drivers/usb/host/dwc_common_port/dwc_common_linux.c +++ b/drivers/usb/host/dwc_common_port/dwc_common_linux.c @@ -580,7 +580,13 @@ void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value) void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask) { + unsigned long flags; + + local_irq_save(flags); + local_fiq_disable(); writel((readl(reg) & ~clear_mask) | set_mask, reg); + local_fiq_enable(); + local_irq_restore(flags); } #if 0 @@ -991,6 +997,11 @@ void DWC_TASK_SCHEDULE(dwc_tasklet_t *task) tasklet_schedule(&task->t); } +void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task) +{ + tasklet_hi_schedule(&task->t); +} + /* workqueues - run in process context (can sleep) diff --git a/drivers/usb/host/dwc_common_port/dwc_list.h b/drivers/usb/host/dwc_common_port/dwc_list.h index 89cc325..4ce560d 100644 --- a/drivers/usb/host/dwc_common_port/dwc_list.h +++ b/drivers/usb/host/dwc_common_port/dwc_list.h @@ -384,17 +384,17 @@ struct { \ #define DWC_TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define DWC_TAILQ_EMPTY(head) \ - (TAILQ_FIRST(head) == TAILQ_END(head)) + (DWC_TAILQ_FIRST(head) == DWC_TAILQ_END(head)) #define DWC_TAILQ_FOREACH(var, head, field) \ - for((var) = TAILQ_FIRST(head); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_NEXT(var, field)) + for ((var) = DWC_TAILQ_FIRST(head); \ + (var) != DWC_TAILQ_END(head); \ + (var) = DWC_TAILQ_NEXT(var, field)) #define DWC_TAILQ_FOREACH_REVERSE(var, head, headname, field) \ - for((var) = TAILQ_LAST(head, headname); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_PREV(var, headname, field)) + for ((var) = DWC_TAILQ_LAST(head, headname); \ + (var) != DWC_TAILQ_END(head); \ + (var) = DWC_TAILQ_PREV(var, headname, field)) /* * Tail queue functions. diff --git a/drivers/usb/host/dwc_common_port/dwc_os.h b/drivers/usb/host/dwc_common_port/dwc_os.h index 9ffe929..09ed244 100644 --- a/drivers/usb/host/dwc_common_port/dwc_os.h +++ b/drivers/usb/host/dwc_common_port/dwc_os.h @@ -981,6 +981,8 @@ extern void DWC_TASK_FREE(dwc_tasklet_t *task); extern void DWC_TASK_SCHEDULE(dwc_tasklet_t *task); #define dwc_task_schedule DWC_TASK_SCHEDULE +extern void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task); +#define dwc_task_hi_schedule DWC_TASK_HI_SCHEDULE /** @name Timer * diff --git a/drivers/usb/host/dwc_otg/Makefile b/drivers/usb/host/dwc_otg/Makefile index 236c47c..a56f193 100644 --- a/drivers/usb/host/dwc_otg/Makefile +++ b/drivers/usb/host/dwc_otg/Makefile @@ -36,6 +36,7 @@ dwc_otg-objs += dwc_otg_cil.o dwc_otg_cil_intr.o dwc_otg-objs += dwc_otg_pcd_linux.o dwc_otg_pcd.o dwc_otg_pcd_intr.o dwc_otg-objs += dwc_otg_hcd.o dwc_otg_hcd_linux.o dwc_otg_hcd_intr.o dwc_otg_hcd_queue.o dwc_otg_hcd_ddma.o dwc_otg-objs += dwc_otg_adp.o +dwc_otg-objs += dwc_otg_mphi_fix.o ifneq ($(CFI),) dwc_otg-objs += dwc_otg_cfi.o endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_attr.c b/drivers/usb/host/dwc_otg/dwc_otg_attr.c index fab2961..9da0c92 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_attr.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_attr.c @@ -909,7 +909,7 @@ static ssize_t regdump_show(struct device *_dev, return sprintf(buf, "Register Dump\n"); } -DEVICE_ATTR(regdump, S_IRUGO | S_IWUSR, regdump_show, 0); +DEVICE_ATTR(regdump, S_IRUGO, regdump_show, 0); /** * Dump global registers and either host or device registers (depending on the @@ -920,12 +920,12 @@ static ssize_t spramdump_show(struct device *_dev, { dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev); - dwc_otg_dump_spram(otg_dev->core_if); + //dwc_otg_dump_spram(otg_dev->core_if); return sprintf(buf, "SPRAM Dump\n"); } -DEVICE_ATTR(spramdump, S_IRUGO | S_IWUSR, spramdump_show, 0); +DEVICE_ATTR(spramdump, S_IRUGO, spramdump_show, 0); /** * Dump the current hcd state. @@ -940,7 +940,7 @@ static ssize_t hcddump_show(struct device *_dev, return sprintf(buf, "HCD Dump\n"); } -DEVICE_ATTR(hcddump, S_IRUGO | S_IWUSR, hcddump_show, 0); +DEVICE_ATTR(hcddump, S_IRUGO, hcddump_show, 0); /** * Dump the average frame remaining at SOF. This can be used to @@ -958,7 +958,7 @@ static ssize_t hcd_frrem_show(struct device *_dev, return sprintf(buf, "HCD Dump Frame Remaining\n"); } -DEVICE_ATTR(hcd_frrem, S_IRUGO | S_IWUSR, hcd_frrem_show, 0); +DEVICE_ATTR(hcd_frrem, S_IRUGO, hcd_frrem_show, 0); /** * Displays the time required to read the GNPTXFSIZ register many times (the @@ -986,7 +986,7 @@ static ssize_t rd_reg_test_show(struct device *_dev, RW_REG_COUNT, time * MSEC_PER_JIFFIE, time); } -DEVICE_ATTR(rd_reg_test, S_IRUGO | S_IWUSR, rd_reg_test_show, 0); +DEVICE_ATTR(rd_reg_test, S_IRUGO, rd_reg_test_show, 0); /** * Displays the time required to write the GNPTXFSIZ register many times (the @@ -1014,7 +1014,7 @@ static ssize_t wr_reg_test_show(struct device *_dev, RW_REG_COUNT, time * MSEC_PER_JIFFIE, time); } -DEVICE_ATTR(wr_reg_test, S_IRUGO | S_IWUSR, wr_reg_test_show, 0); +DEVICE_ATTR(wr_reg_test, S_IRUGO, wr_reg_test_show, 0); #ifdef CONFIG_USB_DWC_OTG_LPM diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c index 59fc862..2f8b3bd 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c @@ -45,6 +45,7 @@ #include "dwc_otg_driver.h" #include "dwc_otg_pcd.h" #include "dwc_otg_hcd.h" +#include "dwc_otg_mphi_fix.h" #ifdef DEBUG inline const char *op_state_str(dwc_otg_core_if_t * core_if) @@ -1318,7 +1319,7 @@ static int32_t dwc_otg_handle_lpm_intr(dwc_otg_core_if_t * core_if) /** * This function returns the Core Interrupt register. */ -static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if) +static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if, gintmsk_data_t *reenable_gintmsk) { gahbcfg_data_t gahbcfg = {.d32 = 0 }; gintsts_data_t gintsts; @@ -1335,26 +1336,45 @@ static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if) gintmsk_common.b.lpmtranrcvd = 1; #endif gintmsk_common.b.restoredone = 1; - /** @todo: The port interrupt occurs while in device - * mode. Added code to CIL to clear the interrupt for now! - */ - gintmsk_common.b.portintr = 1; - + if(dwc_otg_is_device_mode(core_if)) + { + /** @todo: The port interrupt occurs while in device + * mode. Added code to CIL to clear the interrupt for now! + */ + gintmsk_common.b.portintr = 1; + } gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + { + unsigned long flags; + + // Re-enable the saved interrupts + local_irq_save(flags); + local_fiq_disable(); + gintmsk.d32 |= gintmsk_common.d32; + gintsts_saved.d32 &= ~gintmsk_common.d32; + reenable_gintmsk->d32 = gintmsk.d32; + local_irq_restore(flags); + } + gahbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg); #ifdef DEBUG /* if any common interrupts set */ if (gintsts.d32 & gintmsk_common.d32) { - DWC_DEBUGPL(DBG_ANY, "gintsts=%08x gintmsk=%08x\n", + DWC_DEBUGPL(DBG_ANY, "common_intr: gintsts=%08x gintmsk=%08x\n", gintsts.d32, gintmsk.d32); } #endif - if (gahbcfg.b.glblintrmsk) + if (!fiq_fix_enable){ + if (gahbcfg.b.glblintrmsk) + return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32); + else + return 0; + } + else { return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32); - else - return 0; + } } @@ -1386,6 +1406,7 @@ int32_t dwc_otg_handle_common_intr(void *dev) { int retval = 0; gintsts_data_t gintsts; + gintmsk_data_t reenable_gintmsk; gpwrdn_data_t gpwrdn = {.d32 = 0 }; dwc_otg_device_t *otg_dev = dev; dwc_otg_core_if_t *core_if = otg_dev->core_if; @@ -1407,7 +1428,7 @@ int32_t dwc_otg_handle_common_intr(void *dev) } if (core_if->hibernation_suspend <= 0) { - gintsts.d32 = dwc_otg_read_common_intr(core_if); + gintsts.d32 = dwc_otg_read_common_intr(core_if, &reenable_gintmsk); if (gintsts.b.modemismatch) { retval |= dwc_otg_handle_mode_mismatch_intr(core_if); @@ -1504,8 +1525,12 @@ int32_t dwc_otg_handle_common_intr(void *dev) gintsts.b.portintr = 1; DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32); retval |= 1; + reenable_gintmsk.b.portintr = 1; } + + DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, reenable_gintmsk.d32); + } else { DWC_DEBUGPL(DBG_ANY, "gpwrdn=%08x\n", gpwrdn.d32); diff --git a/drivers/usb/host/dwc_otg/dwc_otg_dbg.h b/drivers/usb/host/dwc_otg/dwc_otg_dbg.h index 8900318..ccc24e0 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_dbg.h +++ b/drivers/usb/host/dwc_otg/dwc_otg_dbg.h @@ -49,6 +49,7 @@ static inline uint32_t SET_DEBUG_LEVEL(const uint32_t new) return old; } +#define DBG_USER (0x1) /** When debug level has the DBG_CIL bit set, display CIL Debug messages. */ #define DBG_CIL (0x2) /** When debug level has the DBG_CILV bit set, display CIL Verbose debug diff --git a/drivers/usb/host/dwc_otg/dwc_otg_driver.c b/drivers/usb/host/dwc_otg/dwc_otg_driver.c index ac2c846..f06c3d22 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_driver.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_driver.c @@ -64,6 +64,8 @@ bool microframe_schedule=true; static const char dwc_driver_name[] = "dwc_otg"; +extern void* dummy_send; + extern int pcd_init( #ifdef LM_INTERFACE struct lm_device *_dev @@ -238,6 +240,14 @@ static struct dwc_otg_driver_module_params dwc_otg_module_params = { .adp_enable = -1, }; +//Global variable to switch the fiq fix on or off (declared in bcm2708.c) +extern bool fiq_fix_enable; +// Global variable to enable the split transaction fix +bool fiq_split_enable = true; +//Global variable to switch the nak holdoff on or off +bool nak_holdoff_enable = true; + + /** * This function shows the Driver Version. */ @@ -779,17 +789,33 @@ static int dwc_otg_driver_probe( _dev->resource->start, _dev->resource->end - _dev->resource->start + 1); #if 1 - if (!request_mem_region(_dev->resource->start, - _dev->resource->end - _dev->resource->start + 1, + if (!request_mem_region(_dev->resource[0].start, + _dev->resource[0].end - _dev->resource[0].start + 1, "dwc_otg")) { dev_dbg(&_dev->dev, "error reserving mapped memory\n"); retval = -EFAULT; goto fail; } - dwc_otg_device->os_dep.base = ioremap_nocache(_dev->resource->start, - _dev->resource->end - - _dev->resource->start+1); + dwc_otg_device->os_dep.base = ioremap_nocache(_dev->resource[0].start, + _dev->resource[0].end - + _dev->resource[0].start+1); + if (fiq_fix_enable) + { + if (!request_mem_region(_dev->resource[1].start, + _dev->resource[1].end - _dev->resource[1].start + 1, + "dwc_otg")) { + dev_dbg(&_dev->dev, "error reserving mapped memory\n"); + retval = -EFAULT; + goto fail; + } + + dwc_otg_device->os_dep.mphi_base = ioremap_nocache(_dev->resource[1].start, + _dev->resource[1].end - + _dev->resource[1].start + 1); + dummy_send = (void *) kmalloc(16, GFP_ATOMIC); + } + #else { struct map_desc desc = { @@ -1044,6 +1070,12 @@ static int __init dwc_otg_driver_init(void) int retval = 0; int error; struct device_driver *drv; + + if(fiq_split_enable && !fiq_fix_enable) { + printk(KERN_WARNING "dwc_otg: fiq_split_enable was set without fiq_fix_enable! Correcting.\n"); + fiq_fix_enable = 1; + } + printk(KERN_INFO "%s: version %s (%s bus)\n", dwc_driver_name, DWC_DRIVER_VERSION, #ifdef LM_INTERFACE @@ -1063,6 +1095,9 @@ static int __init dwc_otg_driver_init(void) printk(KERN_ERR "%s retval=%d\n", __func__, retval); return retval; } + printk(KERN_DEBUG "dwc_otg: FIQ %s\n", fiq_fix_enable ? "enabled":"disabled"); + printk(KERN_DEBUG "dwc_otg: NAK holdoff %s\n", nak_holdoff_enable ? "enabled":"disabled"); + printk(KERN_DEBUG "dwc_otg: FIQ split fix %s\n", fiq_split_enable ? "enabled":"disabled"); error = driver_create_file(drv, &driver_attr_version); #ifdef DEBUG @@ -1343,6 +1378,13 @@ MODULE_PARM_DESC(otg_ver, "OTG revision supported 0=OTG 1.3 1=OTG 2.0"); module_param(microframe_schedule, bool, 0444); MODULE_PARM_DESC(microframe_schedule, "Enable the microframe scheduler"); +module_param(fiq_fix_enable, bool, 0444); +MODULE_PARM_DESC(fiq_fix_enable, "Enable the fiq fix"); +module_param(nak_holdoff_enable, bool, 0444); +MODULE_PARM_DESC(nak_holdoff_enable, "Enable the NAK holdoff"); +module_param(fiq_split_enable, bool, 0444); +MODULE_PARM_DESC(fiq_split_enable, "Enable the FIQ fix on split transactions"); + /** @page "Module Parameters" * * The following parameters may be specified when starting the module. diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c index ab935c0..22300f0 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c @@ -40,10 +40,14 @@ * header file. */ +#include +#include + #include "dwc_otg_hcd.h" #include "dwc_otg_regs.h" +#include "dwc_otg_mphi_fix.h" -extern bool microframe_schedule; +extern bool microframe_schedule, nak_holdoff_enable; //#define DEBUG_HOST_CHANNELS #ifdef DEBUG_HOST_CHANNELS @@ -53,6 +57,13 @@ static int last_sel_trans_num_avail_hc_at_start = 0; static int last_sel_trans_num_avail_hc_at_end = 0; #endif /* DEBUG_HOST_CHANNELS */ +extern int g_next_sched_frame, g_np_count, g_np_sent; + +extern haint_data_t haint_saved; +extern hcintmsk_data_t hcintmsk_saved[MAX_EPS_CHANNELS]; +extern hcint_data_t hcint_saved[MAX_EPS_CHANNELS]; +extern gintsts_data_t ginsts_saved; + dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void) { return DWC_ALLOC(sizeof(dwc_otg_hcd_t)); @@ -162,31 +173,43 @@ static void del_timers(dwc_otg_hcd_t * hcd) /** * Processes all the URBs in a single list of QHs. Completes them with - * -ETIMEDOUT and frees the QTD. + * -ESHUTDOWN and frees the QTD. */ static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) { - dwc_list_link_t *qh_item; + dwc_list_link_t *qh_item, *qh_tmp; dwc_otg_qh_t *qh; dwc_otg_qtd_t *qtd, *qtd_tmp; - DWC_LIST_FOREACH(qh_item, qh_list) { + DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) { qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry); DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); if (qtd->urb != NULL) { hcd->fops->complete(hcd, qtd->urb->priv, - qtd->urb, -DWC_E_TIMEOUT); + qtd->urb, -DWC_E_SHUTDOWN); dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh); } } + if(qh->channel) { + /* Using hcchar.chen == 1 is not a reliable test. + * It is possible that the channel has already halted + * but not yet been through the IRQ handler. + */ + dwc_otg_hc_halt(hcd->core_if, qh->channel, + DWC_OTG_HC_XFER_URB_DEQUEUE); + if(microframe_schedule) + hcd->available_host_channels++; + qh->channel = NULL; + } + dwc_otg_hcd_qh_remove(hcd, qh); } } /** - * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic + * Responds with an error status of ESHUTDOWN to all URBs in the non-periodic * and periodic schedules. The QTD associated with each URB is removed from * the schedule and freed. This function may be called when a disconnect is * detected or when the HCD is being stopped. @@ -272,7 +295,8 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p) */ dwc_otg_hcd->flags.b.port_connect_status_change = 1; dwc_otg_hcd->flags.b.port_connect_status = 0; - + if(fiq_fix_enable) + local_fiq_disable(); /* * Shutdown any transfers in process by clearing the Tx FIFO Empty * interrupt mask and status bits and disabling subsequent host @@ -368,8 +392,22 @@ static int32_t dwc_otg_hcd_disconnect_cb(void *p) channel->qh = NULL; } } + if(fiq_split_enable) { + for(i=0; i < 128; i++) { + dwc_otg_hcd->hub_port[i] = 0; + } + haint_saved.d32 = 0; + for(i=0; i < MAX_EPS_CHANNELS; i++) { + hcint_saved[i].d32 = 0; + hcintmsk_saved[i].d32 = 0; + } + } + } + if(fiq_fix_enable) + local_fiq_enable(); + if (dwc_otg_hcd->fops->disconnect) { dwc_otg_hcd->fops->disconnect(dwc_otg_hcd); } @@ -407,6 +445,7 @@ static int dwc_otg_hcd_sleep_cb(void *p) } #endif + /** * HCD Callback function for Remote Wakeup. * @@ -457,10 +496,12 @@ int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd, dwc_otg_hcd_urb_t * dwc_otg_urb, void **ep_handle, int atomic_alloc) { - dwc_irqflags_t flags; int retval = 0; + uint8_t needs_scheduling = 0; + dwc_otg_transaction_type_e tr_type; dwc_otg_qtd_t *qtd; gintmsk_data_t intr_mask = {.d32 = 0 }; + hprt0_data_t hprt0 = { .d32 = 0 }; #ifdef DEBUG /* integrity checks (Broadcom) */ if (NULL == hcd->core_if) { @@ -475,6 +516,16 @@ int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd, return -DWC_E_NO_DEVICE; } + /* Some core configurations cannot support LS traffic on a FS root port */ + if ((hcd->fops->speed(hcd, dwc_otg_urb->priv) == USB_SPEED_LOW) && + (hcd->core_if->hwcfg2.b.fs_phy_type == 1) && + (hcd->core_if->hwcfg2.b.hs_phy_type == 1)) { + hprt0.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0); + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) { + return -DWC_E_NO_DEVICE; + } + } + qtd = dwc_otg_hcd_qtd_create(dwc_otg_urb, atomic_alloc); if (qtd == NULL) { DWC_ERROR("DWC OTG HCD URB Enqueue failed creating QTD\n"); @@ -490,32 +541,27 @@ int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd, return -DWC_E_NO_MEMORY; } #endif - retval = - dwc_otg_hcd_qtd_add(qtd, hcd, (dwc_otg_qh_t **) ep_handle, atomic_alloc); + intr_mask.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->gintmsk); + if(!intr_mask.b.sofintr) needs_scheduling = 1; + if((((dwc_otg_qh_t *)ep_handle)->ep_type == UE_BULK) && !(qtd->urb->flags & URB_GIVEBACK_ASAP)) + /* Do not schedule SG transactions until qtd has URB_GIVEBACK_ASAP set */ + needs_scheduling = 0; + + retval = dwc_otg_hcd_qtd_add(qtd, hcd, (dwc_otg_qh_t **) ep_handle, atomic_alloc); // creates a new queue in ep_handle if it doesn't exist already if (retval < 0) { DWC_ERROR("DWC OTG HCD URB Enqueue failed adding QTD. " "Error status %d\n", retval); dwc_otg_hcd_qtd_free(qtd); - } else { - qtd->qh = *ep_handle; + return retval; } - intr_mask.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->gintmsk); - if (!intr_mask.b.sofintr && retval == 0) { - dwc_otg_transaction_type_e tr_type; - if ((qtd->qh->ep_type == UE_BULK) - && !(qtd->urb->flags & URB_GIVEBACK_ASAP)) { - /* Do not schedule SG transactions until qtd has URB_GIVEBACK_ASAP set */ - return 0; - } - DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + + if(needs_scheduling) { tr_type = dwc_otg_hcd_select_transactions(hcd); if (tr_type != DWC_OTG_TRANSACTION_NONE) { dwc_otg_hcd_queue_transactions(hcd, tr_type); } - DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); } - return retval; } @@ -524,6 +570,8 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd, { dwc_otg_qh_t *qh; dwc_otg_qtd_t *urb_qtd; + BUG_ON(!hcd); + BUG_ON(!dwc_otg_urb); #ifdef DEBUG /* integrity checks (Broadcom) */ @@ -540,14 +588,17 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd, return -DWC_E_INVALID; } urb_qtd = dwc_otg_urb->qtd; + BUG_ON(!urb_qtd); if (urb_qtd->qh == NULL) { DWC_ERROR("**** DWC OTG HCD URB Dequeue with QTD with NULL Q handler\n"); return -DWC_E_INVALID; } #else urb_qtd = dwc_otg_urb->qtd; + BUG_ON(!urb_qtd); #endif qh = urb_qtd->qh; + BUG_ON(!qh); if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { if (urb_qtd->in_process) { dump_channel_info(hcd, qh); @@ -571,6 +622,8 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd, */ dwc_otg_hc_halt(hcd->core_if, qh->channel, DWC_OTG_HC_XFER_URB_DEQUEUE); + + dwc_otg_hcd_release_port(hcd, qh); } } @@ -687,6 +740,33 @@ static void reset_tasklet_func(void *data) dwc_otg_hcd->flags.b.port_reset_change = 1; } +static void completion_tasklet_func(void *ptr) +{ + dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) ptr; + struct urb *urb; + urb_tq_entry_t *item; + dwc_irqflags_t flags; + + /* This could just be spin_lock_irq */ + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + while (!DWC_TAILQ_EMPTY(&hcd->completed_urb_list)) { + item = DWC_TAILQ_FIRST(&hcd->completed_urb_list); + urb = item->urb; + DWC_TAILQ_REMOVE(&hcd->completed_urb_list, item, + urb_tq_entries); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + DWC_FREE(item); + + usb_hcd_giveback_urb(hcd->priv, urb, urb->status); + + fiq_print(FIQDBG_PORTHUB, "COMPLETE"); + + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + } + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + return; +} + static void qh_list_free(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) { dwc_list_link_t *item; @@ -819,12 +899,14 @@ static void dwc_otg_hcd_free(dwc_otg_hcd_t * dwc_otg_hcd) } else if (dwc_otg_hcd->status_buf != NULL) { DWC_FREE(dwc_otg_hcd->status_buf); } + DWC_SPINLOCK_FREE(dwc_otg_hcd->channel_lock); DWC_SPINLOCK_FREE(dwc_otg_hcd->lock); /* Set core_if's lock pointer to NULL */ dwc_otg_hcd->core_if->lock = NULL; DWC_TIMER_FREE(dwc_otg_hcd->conn_timer); DWC_TASK_FREE(dwc_otg_hcd->reset_tasklet); + DWC_TASK_FREE(dwc_otg_hcd->completion_tasklet); #ifdef DWC_DEV_SRPCAP if (dwc_otg_hcd->core_if->power_down == 2 && @@ -845,6 +927,7 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) dwc_hc_t *channel; hcd->lock = DWC_SPINLOCK_ALLOC(); + hcd->channel_lock = DWC_SPINLOCK_ALLOC(); DWC_DEBUGPL(DBG_HCDV, "init of HCD %p given core_if %p\n", hcd, core_if); if (!hcd->lock) { @@ -868,7 +951,7 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) DWC_LIST_INIT(&hcd->periodic_sched_ready); DWC_LIST_INIT(&hcd->periodic_sched_assigned); DWC_LIST_INIT(&hcd->periodic_sched_queued); - + DWC_TAILQ_INIT(&hcd->completed_urb_list); /* * Create a host channel descriptor for each host channel implemented * in the controller. Initialize the channel descriptor array. @@ -906,6 +989,9 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) /* Initialize reset tasklet. */ hcd->reset_tasklet = DWC_TASK_ALLOC("reset_tasklet", reset_tasklet_func, hcd); + + hcd->completion_tasklet = DWC_TASK_ALLOC("completion_tasklet", + completion_tasklet_func, hcd); #ifdef DWC_DEV_SRPCAP if (hcd->core_if->power_down == 2) { /* Initialize Power on timer for Host power up in case hibernation */ @@ -938,6 +1024,12 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) hcd->frame_list = NULL; hcd->frame_list_dma = 0; hcd->periodic_qh_count = 0; + + DWC_MEMSET(hcd->hub_port, 0, sizeof(hcd->hub_port)); +#ifdef FIQ_DEBUG + DWC_MEMSET(hcd->hub_port_alloc, -1, sizeof(hcd->hub_port_alloc)); +#endif + out: return retval; } @@ -1083,7 +1175,12 @@ static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) uint32_t hub_addr, port_addr; hc->do_split = 1; hc->xact_pos = qtd->isoc_split_pos; - hc->complete_split = qtd->complete_split; + /* We don't need to do complete splits anymore */ + if(fiq_split_enable) + hc->complete_split = qtd->complete_split = 0; + else + hc->complete_split = qtd->complete_split; + hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr); hc->hub_addr = (uint8_t) hub_addr; hc->port_addr = (uint8_t) port_addr; @@ -1230,6 +1327,65 @@ static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) hc->qh = qh; } +/* +** Check the transaction to see if the port / hub has already been assigned for +** a split transaction +** +** Return 0 - Port is already in use +*/ +int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh) +{ + uint32_t hub_addr, port_addr; + + if(!fiq_split_enable) + return 0; + + hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr); + + if(hcd->hub_port[hub_addr] & (1 << port_addr)) + { + fiq_print(FIQDBG_PORTHUB, "H%dP%d:S%02d", hub_addr, port_addr, qh->skip_count); + + qh->skip_count++; + + if(qh->skip_count > 40000) + { + printk_once(KERN_ERR "Error: Having to skip port allocation"); + local_fiq_disable(); + BUG(); + return 0; + } + return 1; + } + else + { + qh->skip_count = 0; + hcd->hub_port[hub_addr] |= 1 << port_addr; + fiq_print(FIQDBG_PORTHUB, "H%dP%d:A %d", hub_addr, port_addr, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->pipe_info.ep_num); +#ifdef FIQ_DEBUG + hcd->hub_port_alloc[hub_addr * 16 + port_addr] = dwc_otg_hcd_get_frame_number(hcd); +#endif + return 0; + } +} +void dwc_otg_hcd_release_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh) +{ + uint32_t hub_addr, port_addr; + + if(!fiq_split_enable) + return; + + hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr); + + hcd->hub_port[hub_addr] &= ~(1 << port_addr); +#ifdef FIQ_DEBUG + hcd->hub_port_alloc[hub_addr * 16 + port_addr] = -1; +#endif + fiq_print(FIQDBG_PORTHUB, "H%dP%d:RO%d", hub_addr, port_addr, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->pipe_info.ep_num); + +} + + /** * This function selects transactions from the HCD transfer schedule and * assigns them to available host channels. It is called from HCD interrupt @@ -1243,9 +1399,10 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) { dwc_list_link_t *qh_ptr; dwc_otg_qh_t *qh; + dwc_otg_qtd_t *qtd; int num_channels; dwc_irqflags_t flags; - dwc_spinlock_t *channel_lock = DWC_SPINLOCK_ALLOC(); + dwc_spinlock_t *channel_lock = hcd->channel_lock; dwc_otg_transaction_type_e ret_val = DWC_OTG_TRANSACTION_NONE; #ifdef DEBUG_SOF @@ -1263,11 +1420,29 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) while (qh_ptr != &hcd->periodic_sched_ready && !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + + if(qh->do_split) { + qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list); + if(!(qh->ep_type == UE_ISOCHRONOUS && + (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID || + qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END))) { + if(dwc_otg_hcd_allocate_port(hcd, qh)) + { + qh_ptr = DWC_LIST_NEXT(qh_ptr); + g_next_sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd), 1); + continue; + } + } + } + if (microframe_schedule) { // Make sure we leave one channel for non periodic transactions. DWC_SPINLOCK_IRQSAVE(channel_lock, &flags); if (hcd->available_host_channels <= 1) { DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags); + if(qh->do_split) dwc_otg_hcd_release_port(hcd, qh); break; } hcd->available_host_channels--; @@ -1288,8 +1463,6 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned, &qh->qh_list_entry); DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags); - - ret_val = DWC_OTG_TRANSACTION_PERIODIC; } /* @@ -1304,6 +1477,31 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) num_channels - hcd->periodic_channels) && !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) { + qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + + /* + * Check to see if this is a NAK'd retransmit, in which case ignore for retransmission + * we hold off on bulk retransmissions to reduce NAK interrupt overhead for full-speed + * cheeky devices that just hold off using NAKs + */ + if (nak_holdoff_enable && qh->do_split) { + if (qh->nak_frame != 0xffff && + dwc_full_frame_num(qh->nak_frame) == + dwc_full_frame_num(dwc_otg_hcd_get_frame_number(hcd))) { + /* + * Revisit: Need to avoid trampling on periodic scheduling. + * Currently we are safe because g_np_count != g_np_sent whenever we hit this, + * but if this behaviour is changed then periodic endpoints will get a slower + * polling rate. + */ + g_next_sched_frame = ((qh->nak_frame + 8) & ~7) & DWC_HFNUM_MAX_FRNUM; + qh_ptr = DWC_LIST_NEXT(qh_ptr); + continue; + } else { + qh->nak_frame = 0xffff; + } + } + if (microframe_schedule) { DWC_SPINLOCK_IRQSAVE(channel_lock, &flags); if (hcd->available_host_channels < 1) { @@ -1316,7 +1514,6 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) last_sel_trans_num_nonper_scheduled++; #endif /* DEBUG_HOST_CHANNELS */ } - qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); assign_and_init_hc(hcd, qh); @@ -1330,21 +1527,22 @@ dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd) &qh->qh_list_entry); DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags); - if (ret_val == DWC_OTG_TRANSACTION_NONE) { - ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC; - } else { - ret_val = DWC_OTG_TRANSACTION_ALL; - } + g_np_sent++; if (!microframe_schedule) hcd->non_periodic_channels++; } + if(!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned)) + ret_val |= DWC_OTG_TRANSACTION_PERIODIC; + + if(!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active)) + ret_val |= DWC_OTG_TRANSACTION_NON_PERIODIC; + + #ifdef DEBUG_HOST_CHANNELS last_sel_trans_num_avail_hc_at_end = hcd->available_host_channels; #endif /* DEBUG_HOST_CHANNELS */ - - DWC_SPINLOCK_FREE(channel_lock); return ret_val; } @@ -1458,6 +1656,15 @@ static void process_periodic_channels(dwc_otg_hcd_t * hcd) qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry); + // Do not send a split start transaction any later than frame .6 + // Note, we have to schedule a periodic in .5 to make it go in .6 + if(fiq_split_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6) + { + qh_ptr = qh_ptr->next; + g_next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7; + continue; + } + /* * Set a flag if we're queuing high-bandwidth in slave mode. * The flag prevents any halts to get into the request queue in @@ -1587,6 +1794,15 @@ static void process_non_periodic_channels(dwc_otg_hcd_t * hcd) qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t, qh_list_entry); + + // Do not send a split start transaction any later than frame .5 + // non periodic transactions will start immediately in this uframe + if(fiq_split_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6) + { + g_next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7; + break; + } + status = queue_transaction(hcd, qh->channel, tx_status.b.nptxfspcavail); @@ -3112,17 +3328,13 @@ dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd, else dwc_otg_urb = DWC_ALLOC(size); - if (NULL != dwc_otg_urb) - dwc_otg_urb->packet_count = iso_desc_count; + if (dwc_otg_urb) + dwc_otg_urb->packet_count = iso_desc_count; else { - dwc_otg_urb->packet_count = 0; - if (size != 0) { - DWC_ERROR("**** DWC OTG HCD URB alloc - " - "%salloc of %db failed\n", - atomic_alloc?"atomic ":"", size); - } - } - + DWC_ERROR("**** DWC OTG HCD URB alloc - " + "%salloc of %db failed\n", + atomic_alloc?"atomic ":"", size); + } return dwc_otg_urb; } diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h index bb4f67a..0007fa1 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.h +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h @@ -168,10 +168,10 @@ typedef enum dwc_otg_control_phase { /** Transaction types. */ typedef enum dwc_otg_transaction_type { - DWC_OTG_TRANSACTION_NONE, - DWC_OTG_TRANSACTION_PERIODIC, - DWC_OTG_TRANSACTION_NON_PERIODIC, - DWC_OTG_TRANSACTION_ALL + DWC_OTG_TRANSACTION_NONE = 0, + DWC_OTG_TRANSACTION_PERIODIC = 1, + DWC_OTG_TRANSACTION_NON_PERIODIC = 2, + DWC_OTG_TRANSACTION_ALL = DWC_OTG_TRANSACTION_PERIODIC + DWC_OTG_TRANSACTION_NON_PERIODIC } dwc_otg_transaction_type_e; struct dwc_otg_qh; @@ -321,6 +321,11 @@ typedef struct dwc_otg_qh { */ uint16_t sched_frame; + /* + ** Frame a NAK was received on this queue head, used to minimise NAK retransmission + */ + uint16_t nak_frame; + /** (micro)frame at which last start split was initialized. */ uint16_t start_split_frame; @@ -365,10 +370,19 @@ typedef struct dwc_otg_qh { uint16_t speed; uint16_t frame_usecs[8]; + + uint32_t skip_count; } dwc_otg_qh_t; DWC_CIRCLEQ_HEAD(hc_list, dwc_hc); +typedef struct urb_tq_entry { + struct urb *urb; + DWC_TAILQ_ENTRY(urb_tq_entry) urb_tq_entries; +} urb_tq_entry_t; + +DWC_TAILQ_HEAD(urb_list, urb_tq_entry); + /** * This structure holds the state of the HCD, including the non-periodic and * periodic schedules. @@ -546,9 +560,12 @@ struct dwc_otg_hcd { /* Tasket to do a reset */ dwc_tasklet_t *reset_tasklet; + dwc_tasklet_t *completion_tasklet; + struct urb_list completed_urb_list; + /* */ dwc_spinlock_t *lock; - + dwc_spinlock_t *channel_lock; /** * Private data that could be used by OS wrapper. */ @@ -559,6 +576,12 @@ struct dwc_otg_hcd { /** Frame List */ uint32_t *frame_list; + /** Hub - Port assignment */ + int hub_port[128]; +#ifdef FIQ_DEBUG + int hub_port_alloc[2048]; +#endif + /** Frame List DMA address */ dma_addr_t frame_list_dma; @@ -589,6 +612,10 @@ extern dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t extern void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd, dwc_otg_transaction_type_e tr_type); +int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh); +void dwc_otg_hcd_release_port(dwc_otg_hcd_t * dwc_otg_hcd, dwc_otg_qh_t *qh); + + /** @} */ /** @name Interrupt Handler Functions */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c index 274967b..ee920c4 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c @@ -276,7 +276,7 @@ void dump_frame_list(dwc_otg_hcd_t * hcd) static void release_channel_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) { dwc_irqflags_t flags; - dwc_spinlock_t *channel_lock = DWC_SPINLOCK_ALLOC(); + dwc_spinlock_t *channel_lock = hcd->channel_lock; dwc_hc_t *hc = qh->channel; if (dwc_qh_is_non_per(qh)) { @@ -306,7 +306,6 @@ static void release_channel_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) dwc_memset(qh->desc_list, 0x00, sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh)); } - DWC_SPINLOCK_FREE(channel_lock); } /** diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h index 4823167..fb57db0 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h @@ -113,6 +113,11 @@ extern void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd); */ extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd); +/** This function is used to handle the fast interrupt + * + */ +extern void __attribute__ ((naked)) dwc_otg_hcd_handle_fiq(void); + /** * Returns private data set by * dwc_otg_hcd_set_priv_data function. diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c index b41e164..64d33a5 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c @@ -34,6 +34,12 @@ #include "dwc_otg_hcd.h" #include "dwc_otg_regs.h" +#include "dwc_otg_mphi_fix.h" + +#include +#include +#include + extern bool microframe_schedule; @@ -41,38 +47,487 @@ extern bool microframe_schedule; * This file contains the implementation of the HCD Interrupt handlers. */ +/* + * Some globals to communicate between the FIQ and INTERRUPT + */ + +void * dummy_send; +mphi_regs_t c_mphi_regs; +volatile void *dwc_regs_base; +int fiq_done, int_done; + +gintsts_data_t gintsts_saved = {.d32 = 0}; +hcint_data_t hcint_saved[MAX_EPS_CHANNELS]; +hcintmsk_data_t hcintmsk_saved[MAX_EPS_CHANNELS]; +int split_out_xfersize[MAX_EPS_CHANNELS]; +haint_data_t haint_saved; + +int g_next_sched_frame, g_np_count, g_np_sent; +static int mphi_int_count = 0 ; + +hcchar_data_t nak_hcchar; +hctsiz_data_t nak_hctsiz; +hcsplt_data_t nak_hcsplt; +int nak_count; + +int complete_sched[MAX_EPS_CHANNELS] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; +int split_start_frame[MAX_EPS_CHANNELS]; +int queued_port[MAX_EPS_CHANNELS]; + +#ifdef FIQ_DEBUG +char buffer[1000*16]; +int wptr; +void notrace _fiq_print(FIQDBG_T dbg_lvl, char *fmt, ...) +{ + FIQDBG_T dbg_lvl_req = FIQDBG_PORTHUB; + va_list args; + char text[17]; + hfnum_data_t hfnum = { .d32 = FIQ_READ(dwc_regs_base + 0x408) }; + unsigned long flags; + + local_irq_save(flags); + local_fiq_disable(); + if(dbg_lvl & dbg_lvl_req || dbg_lvl == FIQDBG_ERR) + { + snprintf(text, 9, "%4d%d:%d ", hfnum.b.frnum/8, hfnum.b.frnum%8, 8 - hfnum.b.frrem/937); + va_start(args, fmt); + vsnprintf(text+8, 9, fmt, args); + va_end(args); + + memcpy(buffer + wptr, text, 16); + wptr = (wptr + 16) % sizeof(buffer); + } + local_irq_restore(flags); +} +#endif + +void notrace fiq_queue_request(int channel, int odd_frame) +{ + hcchar_data_t hcchar = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x0) }; + hcsplt_data_t hcsplt = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x4) }; + hctsiz_data_t hctsiz = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x10) }; + + if(hcsplt.b.spltena == 0) + { + fiq_print(FIQDBG_ERR, "SPLTENA "); + BUG(); + } + + if(hcchar.b.epdir == 1) + { + fiq_print(FIQDBG_SCHED, "IN Ch %d", channel); + } + else + { + hctsiz.b.xfersize = 0; + fiq_print(FIQDBG_SCHED, "OUT Ch %d", channel); + } + FIQ_WRITE((dwc_regs_base + 0x500 + (channel * 0x20) + 0x10), hctsiz.d32); + + hcsplt.b.compsplt = 1; + FIQ_WRITE((dwc_regs_base + 0x500 + (channel * 0x20) + 0x4), hcsplt.d32); + + // Send the Split complete + hcchar.b.chen = 1; + hcchar.b.oddfrm = odd_frame ? 1 : 0; + + // Post this for transmit on the next frame for periodic or this frame for non-periodic + fiq_print(FIQDBG_SCHED, "SND_%s", odd_frame ? "ODD " : "EVEN"); + + FIQ_WRITE((dwc_regs_base + 0x500 + (channel * 0x20) + 0x0), hcchar.d32); +} + +static int last_sof = -1; + +/* +** Function to handle the start of frame interrupt, choose whether we need to do anything and +** therefore trigger the main interrupt +** +** returns int != 0 - interrupt has been handled +*/ +int diff; + +int notrace fiq_sof_handle(hfnum_data_t hfnum) +{ + int handled = 0; + int i; + + // Just check that once we're running we don't miss a SOF + /*if(last_sof != -1 && (hfnum.b.frnum != ((last_sof + 1) & 0x3fff))) + { + fiq_print(FIQDBG_ERR, "LASTSOF "); + fiq_print(FIQDBG_ERR, "%4d%d ", last_sof / 8, last_sof & 7); + fiq_print(FIQDBG_ERR, "%4d%d ", hfnum.b.frnum / 8, hfnum.b.frnum & 7); + BUG(); + }*/ + + // Only start remembering the last sof when the interrupt has been + // enabled (we don't check the mask to come in here...) + if(last_sof != -1 || FIQ_READ(dwc_regs_base + 0x18) & (1<<3)) + last_sof = hfnum.b.frnum; + + for(i = 0; i < MAX_EPS_CHANNELS; i++) + { + if(complete_sched[i] != -1) + { + if(complete_sched[i] <= hfnum.b.frnum || (complete_sched[i] > 0x3f00 && hfnum.b.frnum < 0xf0)) + { + fiq_queue_request(i, hfnum.b.frnum & 1); + complete_sched[i] = -1; + } + } + + if(complete_sched[i] != -1) + { + // This is because we've seen a split complete occur with no start... + // most likely because missed the complete 0x3fff frames ago! + + diff = (hfnum.b.frnum + 0x3fff - complete_sched[i]) & 0x3fff ; + if(diff > 32 && diff < 0x3f00) + { + fiq_print(FIQDBG_ERR, "SPLTMISS"); + BUG(); + } + } + } + + if(g_np_count == g_np_sent && dwc_frame_num_gt(g_next_sched_frame, hfnum.b.frnum)) + { + /* + * If np_count != np_sent that means we need to queue non-periodic (bulk) packets this packet + * g_next_sched_frame is the next frame we have periodic packets for + * + * if neither of these are required for this frame then just clear the interrupt + */ + handled = 1; + + } + + return handled; +} + +int notrace port_id(hcsplt_data_t hcsplt) +{ + return hcsplt.b.prtaddr + (hcsplt.b.hubaddr << 8); +} + +int notrace fiq_hcintr_handle(int channel, hfnum_data_t hfnum) +{ + hcchar_data_t hcchar = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x0) }; + hcsplt_data_t hcsplt = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x4) }; + hcint_data_t hcint = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x8) }; + hcintmsk_data_t hcintmsk = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0xc) }; + hctsiz_data_t hctsiz = { .d32 = FIQ_READ(dwc_regs_base + 0x500 + (channel * 0x20) + 0x10)}; + + hcint_saved[channel].d32 |= hcint.d32; + hcintmsk_saved[channel].d32 = hcintmsk.d32; + + if(hcsplt.b.spltena) + { + fiq_print(FIQDBG_PORTHUB, "ph: %4x", port_id(hcsplt)); + if(hcint.b.chhltd) + { + fiq_print(FIQDBG_SCHED, "CH HLT %d", channel); + fiq_print(FIQDBG_SCHED, "%08x", hcint_saved[channel]); + } + if(hcint.b.stall || hcint.b.xacterr || hcint.b.bblerr || hcint.b.frmovrun || hcint.b.datatglerr) + { + queued_port[channel] = 0; + fiq_print(FIQDBG_ERR, "CHAN ERR"); + } + if(hcint.b.xfercomp) + { + // Clear the port allocation and transmit anything also on this port + queued_port[channel] = 0; + fiq_print(FIQDBG_SCHED, "XFERCOMP"); + } + if(hcint.b.nak) + { + queued_port[channel] = 0; + fiq_print(FIQDBG_SCHED, "NAK"); + } + if(hcint.b.ack && !hcsplt.b.compsplt) + { + int i; + + // Do not complete isochronous out transactions + if(hcchar.b.eptype == 1 && hcchar.b.epdir == 0) + { + queued_port[channel] = 0; + fiq_print(FIQDBG_SCHED, "ISOC_OUT"); + } + else + { + // Make sure we check the port / hub combination that we sent this split on. + // Do not queue a second request to the same port + for(i = 0; i < MAX_EPS_CHANNELS; i++) + { + if(port_id(hcsplt) == queued_port[i]) + { + fiq_print(FIQDBG_ERR, "PORTERR "); + //BUG(); + } + } + + split_start_frame[channel] = (hfnum.b.frnum + 1) & ~7; + + // Note, the size of an OUT is in the start split phase, not + // the complete split + split_out_xfersize[channel] = hctsiz.b.xfersize; + + hcint_saved[channel].b.chhltd = 0; + hcint_saved[channel].b.ack = 0; + + queued_port[channel] = port_id(hcsplt); + + if(hcchar.b.eptype & 1) + { + // Send the periodic complete in the same oddness frame as the ACK went... + fiq_queue_request(channel, !(hfnum.b.frnum & 1)); + // complete_sched[channel] = dwc_frame_num_inc(hfnum.b.frnum, 1); + } + else + { + // Schedule the split complete to occur later + complete_sched[channel] = dwc_frame_num_inc(hfnum.b.frnum, 2); + fiq_print(FIQDBG_SCHED, "ACK%04d%d", complete_sched[channel]/8, complete_sched[channel]%8); + } + } + } + if(hcint.b.nyet) + { + fiq_print(FIQDBG_ERR, "NYETERR1"); + //BUG(); + // Can transmit a split complete up to uframe .0 of the next frame + if(hfnum.b.frnum <= dwc_frame_num_inc(split_start_frame[channel], 8)) + { + // Send it next frame + if(hcchar.b.eptype & 1) // type 1 & 3 are interrupt & isoc + { + fiq_print(FIQDBG_SCHED, "NYT:SEND"); + fiq_queue_request(channel, !(hfnum.b.frnum & 1)); + } + else + { + // Schedule non-periodic access for next frame (the odd-even bit doesn't effect NP) + complete_sched[channel] = dwc_frame_num_inc(hfnum.b.frnum, 1); + fiq_print(FIQDBG_SCHED, "NYT%04d%d", complete_sched[channel]/8, complete_sched[channel]%8); + } + hcint_saved[channel].b.chhltd = 0; + hcint_saved[channel].b.nyet = 0; + } + else + { + queued_port[channel] = 0; + fiq_print(FIQDBG_ERR, "NYETERR2"); + //BUG(); + } + } + } + else + { + /* + * If we have any of NAK, ACK, Datatlgerr active on a + * non-split channel, the sole reason is to reset error + * counts for a previously broken transaction. The FIQ + * will thrash on NAK IN and ACK OUT in particular so + * handle it "once" and allow the IRQ to do the rest. + */ + hcint.d32 &= hcintmsk.d32; + if(hcint.b.nak) + { + hcintmsk.b.nak = 0; + FIQ_WRITE((dwc_regs_base + 0x500 + (channel * 0x20) + 0xc), hcintmsk.d32); + } + if (hcint.b.ack) + { + hcintmsk.b.ack = 0; + FIQ_WRITE((dwc_regs_base + 0x500 + (channel * 0x20) + 0xc), hcintmsk.d32); + } + } + + // Clear the interrupt, this will also clear the HAINT bit + FIQ_WRITE((dwc_regs_base + 0x500 + (channel * 0x20) + 0x8), hcint.d32); + return hcint_saved[channel].d32 == 0; +} + +gintsts_data_t gintsts; +gintmsk_data_t gintmsk; +// triggered: The set of interrupts that were triggered +// handled: The set of interrupts that have been handled (no IRQ is +// required) +// keep: The set of interrupts we want to keep unmasked even though we +// want to trigger an IRQ to handle it (SOF and HCINTR) +gintsts_data_t triggered, handled, keep; +hfnum_data_t hfnum; + +void __attribute__ ((naked)) notrace dwc_otg_hcd_handle_fiq(void) +{ + + /* entry takes care to store registers we will be treading on here */ + asm __volatile__ ( + "mov ip, sp ;" + /* stash FIQ and normal regs */ + "stmdb sp!, {r0-r12, lr};" + /* !! THIS SETS THE FRAME, adjust to > sizeof locals */ + "sub fp, ip, #512 ;" + ); + + // Cannot put local variables at the beginning of the function + // because otherwise 'C' will play with the stack pointer. any locals + // need to be inside the following block + do + { + fiq_done++; + gintsts.d32 = FIQ_READ(dwc_regs_base + 0x14); + gintmsk.d32 = FIQ_READ(dwc_regs_base + 0x18); + hfnum.d32 = FIQ_READ(dwc_regs_base + 0x408); + triggered.d32 = gintsts.d32 & gintmsk.d32; + handled.d32 = 0; + keep.d32 = 0; + fiq_print(FIQDBG_INT, "FIQ "); + fiq_print(FIQDBG_INT, "%08x", gintsts.d32); + fiq_print(FIQDBG_INT, "%08x", gintmsk.d32); + if(gintsts.d32) + { + // If port enabled + if((FIQ_READ(dwc_regs_base + 0x440) & 0xf) == 0x5) + { + if(gintsts.b.sofintr) + { + if(fiq_sof_handle(hfnum)) + { + handled.b.sofintr = 1; /* Handled in FIQ */ + } + else + { + /* Keer interrupt unmasked */ + keep.b.sofintr = 1; + } + { + // Need to make sure the read and clearing of the SOF interrupt is as close as possible to avoid the possibility of missing + // a start of frame interrupt + gintsts_data_t gintsts = { .b.sofintr = 1 }; + FIQ_WRITE((dwc_regs_base + 0x14), gintsts.d32); + } + } + + if(fiq_split_enable && gintsts.b.hcintr) + { + int i; + haint_data_t haint; + haintmsk_data_t haintmsk; + + haint.d32 = FIQ_READ(dwc_regs_base + 0x414); + haintmsk.d32 = FIQ_READ(dwc_regs_base + 0x418); + haint.d32 &= haintmsk.d32; + haint_saved.d32 |= haint.d32; + + fiq_print(FIQDBG_INT, "hcintr"); + fiq_print(FIQDBG_INT, "%08x", FIQ_READ(dwc_regs_base + 0x414)); + + // Go through each channel that has an enabled interrupt + for(i = 0; i < 16; i++) + if((haint.d32 >> i) & 1) + if(fiq_hcintr_handle(i, hfnum)) + haint_saved.d32 &= ~(1 << i); /* this was handled */ + + /* If we've handled all host channel interrupts then don't trigger the interrupt */ + if(haint_saved.d32 == 0) + { + handled.b.hcintr = 1; + } + else + { + /* Make sure we keep the channel interrupt unmasked when triggering the IRQ */ + keep.b.hcintr = 1; + } + + { + gintsts_data_t gintsts = { .b.hcintr = 1 }; + + // Always clear the channel interrupt + FIQ_WRITE((dwc_regs_base + 0x14), gintsts.d32); + } + } + } + else + { + last_sof = -1; + } + } + + // Mask out the interrupts triggered - those handled - don't mask out the ones we want to keep + gintmsk.d32 = keep.d32 | (gintmsk.d32 & ~(triggered.d32 & ~handled.d32)); + // Save those that were triggered but not handled + gintsts_saved.d32 |= triggered.d32 & ~handled.d32; + FIQ_WRITE(dwc_regs_base + 0x18, gintmsk.d32); + + // Clear and save any unhandled interrupts and trigger the interrupt + if(gintsts_saved.d32) + { + /* To enable the MPHI interrupt (INT 32) + */ + FIQ_WRITE( c_mphi_regs.outdda, (int) dummy_send); + FIQ_WRITE( c_mphi_regs.outddb, (1 << 29)); + + mphi_int_count++; + } + } + while(0); + + mb(); + + /* exit back to normal mode restoring everything */ + asm __volatile__ ( + /* return FIQ regs back to pristine state + * and get normal regs back + */ + "ldmia sp!, {r0-r12, lr};" + + /* return */ + "subs pc, lr, #4;" + ); +} + /** This function handles interrupts for the HCD. */ int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd) { int retval = 0; + static int last_time; dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if; gintsts_data_t gintsts; + gintmsk_data_t gintmsk; + hfnum_data_t hfnum; + #ifdef DEBUG dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs; - //GRAYG: debugging - if (NULL == global_regs) { - DWC_DEBUGPL(DBG_HCD, "**** NULL regs: dwc_otg_hcd=%p " - "core_if=%p\n", - dwc_otg_hcd, global_regs); - return retval; - } #endif + gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts); + gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk); + /* Exit from ISR if core is hibernated */ if (core_if->hibernation_suspend == 1) { - return retval; + goto exit_handler_routine; } DWC_SPINLOCK(dwc_otg_hcd->lock); /* Check if HOST Mode */ if (dwc_otg_is_host_mode(core_if)) { - gintsts.d32 = dwc_otg_read_core_intr(core_if); + local_fiq_disable(); + gintmsk.d32 |= gintsts_saved.d32; + gintsts.d32 |= gintsts_saved.d32; + gintsts_saved.d32 = 0; + local_fiq_enable(); if (!gintsts.d32) { - DWC_SPINUNLOCK(dwc_otg_hcd->lock); - return 0; + goto exit_handler_routine; } + gintsts.d32 &= gintmsk.d32; + #ifdef DEBUG + // We should be OK doing this because the common interrupts should already have been serviced /* Don't print debug message in the interrupt handler on SOF */ #ifndef DEBUG_SOF if (gintsts.d32 != DWC_SOF_INTR_MASK) @@ -88,10 +543,16 @@ int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd) "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x core_if=%p\n", gintsts.d32, core_if); #endif - - if (gintsts.b.sofintr) { + hfnum.d32 = DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs->hfnum); + if (gintsts.b.sofintr && g_np_count == g_np_sent && dwc_frame_num_gt(g_next_sched_frame, hfnum.b.frnum)) + { + /* Note, we should never get here if the FIQ is doing it's job properly*/ retval |= dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd); } + else if (gintsts.b.sofintr) { + retval |= dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd); + } + if (gintsts.b.rxstsqlvl) { retval |= dwc_otg_hcd_handle_rx_status_q_level_intr @@ -106,7 +567,10 @@ int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd) /** @todo Implement i2cintr handler. */ } if (gintsts.b.portintr) { + + gintmsk_data_t gintmsk = { .b.portintr = 1}; retval |= dwc_otg_hcd_handle_port_intr(dwc_otg_hcd); + DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, gintmsk.d32); } if (gintsts.b.hcintr) { retval |= dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd); @@ -138,11 +602,48 @@ int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd) #endif } + +exit_handler_routine: + + if (fiq_fix_enable) + { + local_fiq_disable(); + // Make sure that we don't clear the interrupt if we've still got pending work to do + if(gintsts_saved.d32 == 0) + { + /* Clear the MPHI interrupt */ + DWC_WRITE_REG32(c_mphi_regs.intstat, (1<<16)); + if (mphi_int_count >= 60) + { + DWC_WRITE_REG32(c_mphi_regs.ctrl, ((1<<31) + (1<<16))); + while(!(DWC_READ_REG32(c_mphi_regs.ctrl) & (1 << 17))) + ; + DWC_WRITE_REG32(c_mphi_regs.ctrl, (1<<31)); + mphi_int_count = 0; + } + int_done++; + } + + // Unmask handled interrupts + FIQ_WRITE(dwc_regs_base + 0x18, gintmsk.d32); + //DWC_MODIFY_REG32((uint32_t *)IO_ADDRESS(USB_BASE + 0x8), 0 , 1); + + local_fiq_enable(); + + if((jiffies / HZ) > last_time) + { + /* Once a second output the fiq and irq numbers, useful for debug */ + last_time = jiffies / HZ; + DWC_DEBUGPL(DBG_USER, "int_done = %d fiq_done = %d\n", int_done, fiq_done); + } + } + DWC_SPINUNLOCK(dwc_otg_hcd->lock); return retval; } #ifdef DWC_TRACK_MISSED_SOFS + #warning Compiling code to track missed SOFs #define FRAME_NUM_ARRAY_SIZE 1000 /** @@ -188,7 +689,8 @@ int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * hcd) dwc_list_link_t *qh_entry; dwc_otg_qh_t *qh; dwc_otg_transaction_type_e tr_type; - gintsts_data_t gintsts = {.d32 = 0 }; + int did_something = 0; + int32_t next_sched_frame = -1; hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum); @@ -212,17 +714,31 @@ int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * hcd) qh = DWC_LIST_ENTRY(qh_entry, dwc_otg_qh_t, qh_list_entry); qh_entry = qh_entry->next; if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number)) { + /* * Move QH to the ready list to be executed next * (micro)frame. */ DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready, &qh->qh_list_entry); + + did_something = 1; + } + else + { + if(next_sched_frame < 0 || dwc_frame_num_le(qh->sched_frame, next_sched_frame)) + { + next_sched_frame = qh->sched_frame; + } } } + + g_next_sched_frame = next_sched_frame; + tr_type = dwc_otg_hcd_select_transactions(hcd); if (tr_type != DWC_OTG_TRANSACTION_NONE) { dwc_otg_hcd_queue_transactions(hcd, tr_type); + did_something = 1; } /* Clear interrupt */ @@ -511,6 +1027,15 @@ int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd) haint.d32 = dwc_otg_read_host_all_channels_intr(dwc_otg_hcd->core_if); + // Overwrite with saved interrupts from fiq handler + if(fiq_split_enable) + { + local_fiq_disable(); + haint.d32 = haint_saved.d32; + haint_saved.d32 = 0; + local_fiq_enable(); + } + for (i = 0; i < dwc_otg_hcd->core_if->core_params->host_channels; i++) { if (haint.b2.chint & (1 << i)) { retval |= dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd, i); @@ -551,7 +1076,10 @@ static uint32_t get_actual_xfer_length(dwc_hc_t * hc, *short_read = (hctsiz.b.xfersize != 0); } } else if (hc->qh->do_split) { - length = qtd->ssplit_out_xfer_count; + if(fiq_split_enable) + length = split_out_xfersize[hc->hc_num]; + else + length = qtd->ssplit_out_xfer_count; } else { length = hc->xfer_len; } @@ -595,7 +1123,6 @@ static int update_urb_state_xfer_comp(dwc_hc_t * hc, DWC_OTG_HC_XFER_COMPLETE, &short_read); - /* non DWORD-aligned buffer case handling. */ if (hc->align_buff && xfer_length && hc->ep_is_in) { dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf, @@ -797,11 +1324,24 @@ static void release_channel(dwc_otg_hcd_t * hcd, dwc_otg_transaction_type_e tr_type; int free_qtd; dwc_irqflags_t flags; - dwc_spinlock_t *channel_lock = DWC_SPINLOCK_ALLOC(); + dwc_spinlock_t *channel_lock = hcd->channel_lock; +#ifdef FIQ_DEBUG + int endp = qtd->urb ? qtd->urb->pipe_info.ep_num : 0; +#endif + int hog_port = 0; DWC_DEBUGPL(DBG_HCDV, " %s: channel %d, halt_status %d, xfer_len %d\n", __func__, hc->hc_num, halt_status, hc->xfer_len); + if(fiq_split_enable && hc->do_split) { + if(!hc->ep_is_in && hc->ep_type == UE_ISOCHRONOUS) { + if(hc->xact_pos == DWC_HCSPLIT_XACTPOS_MID || + hc->xact_pos == DWC_HCSPLIT_XACTPOS_BEGIN) { + hog_port = 1; + } + } + } + switch (halt_status) { case DWC_OTG_HC_XFER_URB_COMPLETE: free_qtd = 1; @@ -876,15 +1416,32 @@ cleanup: DWC_SPINLOCK_IRQSAVE(channel_lock, &flags); hcd->available_host_channels++; + fiq_print(FIQDBG_PORTHUB, "AHC = %d ", hcd->available_host_channels); DWC_SPINUNLOCK_IRQRESTORE(channel_lock, flags); } + if(fiq_split_enable && hc->do_split) + { + if(!(hcd->hub_port[hc->hub_addr] & (1 << hc->port_addr))) + { + fiq_print(FIQDBG_ERR, "PRTNOTAL"); + //BUG(); + } + if(!hog_port && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC || + hc->ep_type == DWC_OTG_EP_TYPE_INTR)) { + hcd->hub_port[hc->hub_addr] &= ~(1 << hc->port_addr); +#ifdef FIQ_DEBUG + hcd->hub_port_alloc[hc->hub_addr * 16 + hc->port_addr] = -1; +#endif + fiq_print(FIQDBG_PORTHUB, "H%dP%d:RR%d", hc->hub_addr, hc->port_addr, endp); + } + } + /* Try to queue more transfers now that there's a free channel. */ tr_type = dwc_otg_hcd_select_transactions(hcd); if (tr_type != DWC_OTG_TRANSACTION_NONE) { dwc_otg_hcd_queue_transactions(hcd, tr_type); } - DWC_SPINLOCK_FREE(channel_lock); } /** @@ -1295,6 +1852,17 @@ static int32_t handle_hc_nak_intr(dwc_otg_hcd_t * hcd, "NAK Received--\n", hc->hc_num); /* + * When we get bulk NAKs then remember this so we holdoff on this qh until + * the beginning of the next frame + */ + switch(dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) { + case UE_BULK: + case UE_CONTROL: + if (nak_holdoff_enable) + hc->qh->nak_frame = dwc_otg_hcd_get_frame_number(hcd); + } + + /* * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and * interrupt. Re-start the SSPLIT transfer. */ @@ -1316,7 +1884,11 @@ static int32_t handle_hc_nak_intr(dwc_otg_hcd_t * hcd, * transfers in DMA mode for the sole purpose of * resetting the error count after a transaction error * occurs. The core will continue transferring data. + * Disable other interrupts unmasked for the same + * reason. */ + disable_hc_int(hc_regs, datatglerr); + disable_hc_int(hc_regs, ack); qtd->error_count = 0; goto handle_nak_done; } @@ -1428,6 +2000,15 @@ static int32_t handle_hc_ack_intr(dwc_otg_hcd_t * hcd, halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK); } } else { + /* + * An unmasked ACK on a non-split DMA transaction is + * for the sole purpose of resetting error counts. Disable other + * interrupts unmasked for the same reason. + */ + if(hcd->core_if->dma_enable) { + disable_hc_int(hc_regs, datatglerr); + disable_hc_int(hc_regs, nak); + } qtd->error_count = 0; if (hc->qh->ping_state) { @@ -1490,8 +2071,10 @@ static int32_t handle_hc_nyet_intr(dwc_otg_hcd_t * hcd, hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { int frnum = dwc_otg_hcd_get_frame_number(hcd); + // With the FIQ running we only ever see the failed NYET if (dwc_full_frame_num(frnum) != - dwc_full_frame_num(hc->qh->sched_frame)) { + dwc_full_frame_num(hc->qh->sched_frame) || + fiq_split_enable) { /* * No longer in the same full speed frame. * Treat this as a transaction error. @@ -1778,13 +2361,28 @@ static int32_t handle_hc_datatglerr_intr(dwc_otg_hcd_t * hcd, dwc_otg_qtd_t * qtd) { DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " - "Data Toggle Error--\n", hc->hc_num); + "Data Toggle Error on %s transfer--\n", + hc->hc_num, (hc->ep_is_in ? "IN" : "OUT")); - if (hc->ep_is_in) { + /* Data toggles on split transactions cause the hc to halt. + * restart transfer */ + if(hc->qh->do_split) + { + qtd->error_count++; + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); + } else if (hc->ep_is_in) { + /* An unmasked data toggle error on a non-split DMA transaction is + * for the sole purpose of resetting error counts. Disable other + * interrupts unmasked for the same reason. + */ + if(hcd->core_if->dma_enable) { + disable_hc_int(hc_regs, ack); + disable_hc_int(hc_regs, nak); + } qtd->error_count = 0; - } else { - DWC_ERROR("Data Toggle Error on OUT transfer," - "channel %d\n", hc->hc_num); } disable_hc_int(hc_regs, datatglerr); @@ -1862,10 +2460,10 @@ static inline int halt_status_ok(dwc_otg_hcd_t * hcd, static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, dwc_hc_t * hc, dwc_otg_hc_regs_t * hc_regs, - dwc_otg_qtd_t * qtd) + dwc_otg_qtd_t * qtd, + hcint_data_t hcint, + hcintmsk_data_t hcintmsk) { - hcint_data_t hcint; - hcintmsk_data_t hcintmsk; int out_nak_enh = 0; /* For core with OUT NAK enhancement, the flow for high- @@ -1897,8 +2495,11 @@ static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, } /* Read the HCINTn register to determine the cause for the halt. */ - hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); - hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + if(!fiq_split_enable) + { + hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); + } if (hcint.b.xfercomp) { /** @todo This is here because of a possible hardware bug. Spec @@ -1937,6 +2538,8 @@ static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, handle_hc_babble_intr(hcd, hc, hc_regs, qtd); } else if (hcint.b.frmovrun) { handle_hc_frmovrun_intr(hcd, hc, hc_regs, qtd); + } else if (hcint.b.datatglerr) { + handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd); } else if (!out_nak_enh) { if (hcint.b.nyet) { /* @@ -1986,12 +2589,24 @@ static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, DWC_READ_REG32(&hcd-> core_if->core_global_regs-> gintsts)); + /* Failthrough: use 3-strikes rule */ + qtd->error_count++; + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); } } } else { DWC_PRINTF("NYET/NAK/ACK/other in non-error case, 0x%08x\n", hcint.d32); + /* Failthrough: use 3-strikes rule */ + qtd->error_count++; + dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd); + update_urb_state_xfer_intr(hc, hc_regs, + qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR); + halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR); } } @@ -2009,13 +2624,15 @@ static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd, static int32_t handle_hc_chhltd_intr(dwc_otg_hcd_t * hcd, dwc_hc_t * hc, dwc_otg_hc_regs_t * hc_regs, - dwc_otg_qtd_t * qtd) + dwc_otg_qtd_t * qtd, + hcint_data_t hcint, + hcintmsk_data_t hcintmsk) { DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: " "Channel Halted--\n", hc->hc_num); if (hcd->core_if->dma_enable) { - handle_hc_chhltd_intr_dma(hcd, hc, hc_regs, qtd); + handle_hc_chhltd_intr_dma(hcd, hc, hc_regs, qtd, hcint, hcintmsk); } else { #ifdef DEBUG if (!halt_status_ok(hcd, hc, hc_regs, qtd)) { @@ -2032,7 +2649,7 @@ static int32_t handle_hc_chhltd_intr(dwc_otg_hcd_t * hcd, int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) { int retval = 0; - hcint_data_t hcint; + hcint_data_t hcint, hcint_orig; hcintmsk_data_t hcintmsk; dwc_hc_t *hc; dwc_otg_hc_regs_t *hc_regs; @@ -2042,15 +2659,33 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) hc = dwc_otg_hcd->hc_ptr_array[num]; hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num]; + if(hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) { + /* We are responding to a channel disable. Driver + * state is cleared - our qtd has gone away. + */ + release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status); + return 1; + } qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list); hcint.d32 = DWC_READ_REG32(&hc_regs->hcint); + hcint_orig = hcint; hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk); DWC_DEBUGPL(DBG_HCDV, " hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", hcint.d32, hcintmsk.d32, (hcint.d32 & hcintmsk.d32)); hcint.d32 = hcint.d32 & hcintmsk.d32; + if(fiq_split_enable) + { + // replace with the saved interrupts from the fiq handler + local_fiq_disable(); + hcint_orig.d32 = hcint_saved[num].d32; + hcint.d32 = hcint_orig.d32 & hcintmsk_saved[num].d32; + hcint_saved[num].d32 = 0; + local_fiq_enable(); + } + if (!dwc_otg_hcd->core_if->dma_enable) { if (hcint.b.chhltd && hcint.d32 != 0x2) { hcint.b.chhltd = 0; @@ -2068,7 +2703,7 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) hcint.b.nyet = 0; } if (hcint.b.chhltd) { - retval |= handle_hc_chhltd_intr(dwc_otg_hcd, hc, hc_regs, qtd); + retval |= handle_hc_chhltd_intr(dwc_otg_hcd, hc, hc_regs, qtd, hcint_orig, hcintmsk_saved[num]); } if (hcint.b.ahberr) { retval |= handle_hc_ahberr_intr(dwc_otg_hcd, hc, hc_regs, qtd); @@ -2080,7 +2715,8 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) retval |= handle_hc_nak_intr(dwc_otg_hcd, hc, hc_regs, qtd); } if (hcint.b.ack) { - retval |= handle_hc_ack_intr(dwc_otg_hcd, hc, hc_regs, qtd); + if(!hcint.b.chhltd) + retval |= handle_hc_ack_intr(dwc_otg_hcd, hc, hc_regs, qtd); } if (hcint.b.nyet) { retval |= handle_hc_nyet_intr(dwc_otg_hcd, hc, hc_regs, qtd); @@ -2102,5 +2738,4 @@ int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num) return retval; } - #endif /* DWC_DEVICE_ONLY */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c index e4787f5..ee8eec9 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c @@ -1,3 +1,4 @@ + /* ========================================================================== * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_linux.c $ * $Revision: #20 $ @@ -50,6 +51,7 @@ #include #include #include +#include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) #include <../drivers/usb/core/hcd.h> @@ -67,6 +69,8 @@ #include "dwc_otg_dbg.h" #include "dwc_otg_driver.h" #include "dwc_otg_hcd.h" +#include "dwc_otg_mphi_fix.h" + /** * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is * qualified with its direction (possible 32 endpoints per device). @@ -76,6 +80,8 @@ static const char dwc_otg_hcd_name[] = "dwc_otg_hcd"; +extern bool fiq_fix_enable; + /** @name Linux HC Driver API Functions */ /** @{ */ /* manage i/o requests, device state */ @@ -259,13 +265,15 @@ static void free_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw, /** * Sets the final status of an URB and returns it to the device driver. Any - * required cleanup of the URB is performed. + * required cleanup of the URB is performed. The HCD lock should be held on + * entry. */ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, dwc_otg_hcd_urb_t * dwc_otg_urb, int32_t status) { struct urb *urb = (struct urb *)urb_handle; - + urb_tq_entry_t *new_entry; + int rc = 0; if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { DWC_PRINTF("%s: urb %p, device %d, ep %d %s, status=%d\n", __func__, urb, usb_pipedevice(urb->pipe), @@ -279,7 +287,7 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, } } } - + new_entry = DWC_ALLOC_ATOMIC(sizeof(urb_tq_entry_t)); urb->actual_length = dwc_otg_hcd_urb_get_actual_length(dwc_otg_urb); /* Convert status value. */ switch (status) { @@ -301,6 +309,9 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, case -DWC_E_OVERFLOW: status = -EOVERFLOW; break; + case -DWC_E_SHUTDOWN: + status = -ESHUTDOWN; + break; default: if (status) { DWC_PRINTF("Uknown urb status %d\n", status); @@ -342,18 +353,33 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, } DWC_FREE(dwc_otg_urb); - + if (!new_entry) { + DWC_ERROR("dwc_otg_hcd: complete: cannot allocate URB TQ entry\n"); + urb->status = -EPROTO; + /* don't schedule the tasklet - + * directly return the packet here with error. */ #if USB_URB_EP_LINKING - usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); + usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); #endif - DWC_SPINUNLOCK(hcd->lock); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) - usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); #else - usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status); + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); #endif - DWC_SPINLOCK(hcd->lock); - + } else { + new_entry->urb = urb; +#if USB_URB_EP_LINKING + rc = usb_hcd_check_unlink_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); + if(0 == rc) { + usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); + } +#endif + if(0 == rc) { + DWC_TAILQ_INSERT_TAIL(&hcd->completed_urb_list, new_entry, + urb_tq_entries); + DWC_TASK_HI_SCHEDULE(hcd->completion_tasklet); + } + } return 0; } @@ -366,6 +392,16 @@ static struct dwc_otg_hcd_function_ops hcd_fops = { .get_b_hnp_enable = _get_b_hnp_enable, }; +static struct fiq_handler fh = { + .name = "usb_fiq", +}; +struct fiq_stack_s { + int magic1; + uint8_t stack[2048]; + int magic2; +} fiq_stack; + +extern mphi_regs_t c_mphi_regs; /** * Initializes the HCD. This function allocates memory for and initializes the * static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the @@ -379,6 +415,7 @@ int hcd_init(dwc_bus_dev_t *_dev) dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev); int retval = 0; u64 dmamask; + struct pt_regs regs; DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT otg_dev=%p\n", otg_dev); @@ -396,6 +433,20 @@ int hcd_init(dwc_bus_dev_t *_dev) pci_set_consistent_dma_mask(_dev, dmamask); #endif + if (fiq_fix_enable) + { + // Set up fiq + claim_fiq(&fh); + set_fiq_handler(__FIQ_Branch, 4); + memset(®s,0,sizeof(regs)); + regs.ARM_r8 = (long)dwc_otg_hcd_handle_fiq; + regs.ARM_r9 = (long)0; + regs.ARM_sp = (long)fiq_stack.stack + sizeof(fiq_stack.stack) - 4; + set_fiq_regs(®s); + fiq_stack.magic1 = 0xdeadbeef; + fiq_stack.magic2 = 0xaa995566; + } + /* * Allocate memory for the base HCD plus the DWC OTG HCD. * Initialize the base HCD. @@ -415,6 +466,30 @@ int hcd_init(dwc_bus_dev_t *_dev) hcd->regs = otg_dev->os_dep.base; + if (fiq_fix_enable) + { + volatile extern void *dwc_regs_base; + + //Set the mphi periph to the required registers + c_mphi_regs.base = otg_dev->os_dep.mphi_base; + c_mphi_regs.ctrl = otg_dev->os_dep.mphi_base + 0x4c; + c_mphi_regs.outdda = otg_dev->os_dep.mphi_base + 0x28; + c_mphi_regs.outddb = otg_dev->os_dep.mphi_base + 0x2c; + c_mphi_regs.intstat = otg_dev->os_dep.mphi_base + 0x50; + + dwc_regs_base = otg_dev->os_dep.base; + + //Enable mphi peripheral + writel((1<<31),c_mphi_regs.ctrl); +#ifdef DEBUG + if (readl(c_mphi_regs.ctrl) & 0x80000000) + DWC_DEBUGPL(DBG_USER, "MPHI periph has been enabled\n"); + else + DWC_DEBUGPL(DBG_USER, "MPHI periph has NOT been enabled\n"); +#endif + // Enable FIQ interrupt from USB peripheral + enable_fiq(INTERRUPT_VC_USB); + } /* Initialize the DWC OTG HCD. */ dwc_otg_hcd = dwc_otg_hcd_alloc_hcd(); if (!dwc_otg_hcd) { @@ -607,9 +682,7 @@ static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) struct usb_host_endpoint *ep = urb->ep; #endif -#if USB_URB_EP_LINKING dwc_irqflags_t irqflags; -#endif void **ref_ep_hcpriv = &ep->hcpriv; dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd); dwc_otg_hcd_urb_t *dwc_otg_urb; @@ -661,9 +734,8 @@ static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, if(dwc_otg_urb == NULL) return -ENOMEM; - urb->hcpriv = dwc_otg_urb; - if (!dwc_otg_urb && urb->number_of_packets) - return -ENOMEM; + if (!dwc_otg_urb && urb->number_of_packets) + return -ENOMEM; dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_urb, usb_pipedevice(urb->pipe), usb_pipeendpoint(urb->pipe), ep_type, @@ -703,37 +775,42 @@ static int dwc_otg_urb_enqueue(struct usb_hcd *hcd, iso_frame_desc[i].length); } + DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags); + urb->hcpriv = dwc_otg_urb; #if USB_URB_EP_LINKING - DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags); retval = usb_hcd_link_urb_to_ep(hcd, urb); - DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags); if (0 == retval) #endif - { - retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb, - /*(dwc_otg_qh_t **)*/ - ref_ep_hcpriv, - mem_flags == GFP_ATOMIC ? 1 : 0); - if (0 == retval) { - if (alloc_bandwidth) { - allocate_bus_bandwidth(hcd, - dwc_otg_hcd_get_ep_bandwidth( - dwc_otg_hcd, *ref_ep_hcpriv), - urb); - } - } else { + { + retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb, + /*(dwc_otg_qh_t **)*/ + ref_ep_hcpriv, 1); + if (0 == retval) { + if (alloc_bandwidth) { + allocate_bus_bandwidth(hcd, + dwc_otg_hcd_get_ep_bandwidth( + dwc_otg_hcd, *ref_ep_hcpriv), + urb); + } + } else { + DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval); #if USB_URB_EP_LINKING - dwc_irqflags_t irqflags; - DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval); - DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags); - usb_hcd_unlink_urb_from_ep(hcd, urb); - DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags); + usb_hcd_unlink_urb_from_ep(hcd, urb); #endif - if (retval == -DWC_E_NO_DEVICE) { - retval = -ENODEV; - } - } - } + DWC_FREE(dwc_otg_urb); + urb->hcpriv = NULL; + if (retval == -DWC_E_NO_DEVICE) + retval = -ENODEV; + } + } +#if USB_URB_EP_LINKING + else + { + DWC_FREE(dwc_otg_urb); + urb->hcpriv = NULL; + } +#endif + DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags); return retval; } @@ -777,6 +854,8 @@ static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) usb_hcd_unlink_urb_from_ep(hcd, urb); #endif DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags); + + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) usb_hcd_giveback_urb(hcd, urb); #else diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c index 9761566..db95851 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c @@ -41,6 +41,7 @@ #include "dwc_otg_hcd.h" #include "dwc_otg_regs.h" +#include "dwc_otg_mphi_fix.h" extern bool microframe_schedule; @@ -181,6 +182,7 @@ void qh_init(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, dwc_otg_hcd_urb_t * urb) if (microframe_schedule) qh->speed = dev_speed; + qh->nak_frame = 0xffff; if (((dev_speed == USB_SPEED_LOW) || (dev_speed == USB_SPEED_FULL)) && @@ -190,6 +192,7 @@ void qh_init(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, dwc_otg_hcd_urb_t * urb) dwc_otg_hcd_get_ep_num(&urb->pipe_info), hub_addr, hub_port); qh->do_split = 1; + qh->skip_count = 0; } if (qh->ep_type == UE_INTERRUPT || qh->ep_type == UE_ISOCHRONOUS) { @@ -572,6 +575,9 @@ static int check_max_xfer_size(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) return status; } + +extern int g_next_sched_frame, g_np_count, g_np_sent; + /** * Schedules an interrupt or isochronous transfer in the periodic schedule. * @@ -630,8 +636,13 @@ static int schedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_ready, &qh->qh_list_entry); } else { - /* Always start in the inactive schedule. */ - DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_inactive, &qh->qh_list_entry); + if(DWC_LIST_EMPTY(&hcd->periodic_sched_inactive) || dwc_frame_num_le(qh->sched_frame, g_next_sched_frame)) + { + g_next_sched_frame = qh->sched_frame; + + } + /* Always start in the inactive schedule. */ + DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_inactive, &qh->qh_list_entry); } if (!microframe_schedule) { @@ -645,6 +656,7 @@ static int schedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) return status; } + /** * This function adds a QH to either the non periodic or periodic schedule if * it is not already in the schedule. If the QH is already in the schedule, no @@ -667,6 +679,7 @@ int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) /* Always start in the inactive schedule. */ DWC_LIST_INSERT_TAIL(&hcd->non_periodic_sched_inactive, &qh->qh_list_entry); + g_np_count++; } else { status = schedule_periodic(hcd, qh); if ( !hcd->periodic_qh_count ) { @@ -726,6 +739,9 @@ void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh) hcd->non_periodic_qh_ptr->next; } DWC_LIST_REMOVE_INIT(&qh->qh_list_entry); + + // If we've removed the last non-periodic entry then there are none left! + g_np_count = g_np_sent; } else { deschedule_periodic(hcd, qh); hcd->periodic_qh_count--; @@ -754,6 +770,24 @@ void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, int sched_next_periodic_split) { if (dwc_qh_is_non_per(qh)) { + + dwc_otg_qh_t *qh_tmp; + dwc_list_link_t *qh_list; + DWC_LIST_FOREACH(qh_list, &hcd->non_periodic_sched_inactive) + { + qh_tmp = DWC_LIST_ENTRY(qh_list, struct dwc_otg_qh, qh_list_entry); + if(qh_tmp == qh) + { + /* + * FIQ is being disabled because this one nevers gets a np_count increment + * This is still not absolutely correct, but it should fix itself with + * just an unnecessary extra interrupt + */ + g_np_sent = g_np_count; + } + } + + dwc_otg_hcd_qh_remove(hcd, qh); if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) { /* Add back to inactive non-periodic schedule. */ @@ -767,6 +801,7 @@ void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, if (sched_next_periodic_split) { qh->sched_frame = frame_number; + if (dwc_frame_num_le(frame_number, dwc_frame_num_inc (qh->start_split_frame, @@ -815,6 +850,11 @@ void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready, &qh->qh_list_entry); } else { + if(!dwc_frame_num_le(g_next_sched_frame, qh->sched_frame)) + { + g_next_sched_frame = qh->sched_frame; + } + DWC_LIST_MOVE_HEAD (&hcd->periodic_sched_inactive, &qh->qh_list_entry); @@ -879,6 +919,7 @@ void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb) * QH to place the QTD into. If it does not find a QH, then it will create a * new QH. If the QH to which the QTD is added is not currently scheduled, it * is placed into the proper schedule based on its EP type. + * HCD lock must be held and interrupts must be disabled on entry * * @param[in] qtd The QTD to add * @param[in] hcd The DWC HCD structure @@ -891,8 +932,6 @@ int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, dwc_otg_hcd_t * hcd, dwc_otg_qh_t ** qh, int atomic_alloc) { int retval = 0; - dwc_irqflags_t flags; - dwc_otg_hcd_urb_t *urb = qtd->urb; /* @@ -902,18 +941,16 @@ int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, if (*qh == NULL) { *qh = dwc_otg_hcd_qh_create(hcd, urb, atomic_alloc); if (*qh == NULL) { - retval = -1; + retval = -DWC_E_NO_MEMORY; goto done; } } - DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); retval = dwc_otg_hcd_qh_add(hcd, *qh); if (retval == 0) { DWC_CIRCLEQ_INSERT_TAIL(&((*qh)->qtd_list), qtd, qtd_list_entry); + qtd->qh = *qh; } - DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); - done: return retval; diff --git a/drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.c b/drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.c new file mode 100755 index 0000000..50b94a8 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.c @@ -0,0 +1,113 @@ +#include "dwc_otg_regs.h" +#include "dwc_otg_dbg.h" + +void dwc_debug_print_core_int_reg(gintsts_data_t gintsts, const char* function_name) +{ + DWC_DEBUGPL(DBG_USER, "*** Debugging from within the %s function: ***\n" + "curmode: %1i Modemismatch: %1i otgintr: %1i sofintr: %1i\n" + "rxstsqlvl: %1i nptxfempty : %1i ginnakeff: %1i goutnakeff: %1i\n" + "ulpickint: %1i i2cintr: %1i erlysuspend:%1i usbsuspend: %1i\n" + "usbreset: %1i enumdone: %1i isooutdrop: %1i eopframe: %1i\n" + "restoredone: %1i epmismatch: %1i inepint: %1i outepintr: %1i\n" + "incomplisoin:%1i incomplisoout:%1i fetsusp: %1i resetdet: %1i\n" + "portintr: %1i hcintr: %1i ptxfempty: %1i lpmtranrcvd:%1i\n" + "conidstschng:%1i disconnect: %1i sessreqintr:%1i wkupintr: %1i\n", + function_name, + gintsts.b.curmode, + gintsts.b.modemismatch, + gintsts.b.otgintr, + gintsts.b.sofintr, + gintsts.b.rxstsqlvl, + gintsts.b.nptxfempty, + gintsts.b.ginnakeff, + gintsts.b.goutnakeff, + gintsts.b.ulpickint, + gintsts.b.i2cintr, + gintsts.b.erlysuspend, + gintsts.b.usbsuspend, + gintsts.b.usbreset, + gintsts.b.enumdone, + gintsts.b.isooutdrop, + gintsts.b.eopframe, + gintsts.b.restoredone, + gintsts.b.epmismatch, + gintsts.b.inepint, + gintsts.b.outepintr, + gintsts.b.incomplisoin, + gintsts.b.incomplisoout, + gintsts.b.fetsusp, + gintsts.b.resetdet, + gintsts.b.portintr, + gintsts.b.hcintr, + gintsts.b.ptxfempty, + gintsts.b.lpmtranrcvd, + gintsts.b.conidstschng, + gintsts.b.disconnect, + gintsts.b.sessreqintr, + gintsts.b.wkupintr); + return; +} + +void dwc_debug_core_int_mask(gintmsk_data_t gintmsk, const char* function_name) +{ + DWC_DEBUGPL(DBG_USER, "Interrupt Mask status (called from %s) :\n" + "modemismatch: %1i otgintr: %1i sofintr: %1i rxstsqlvl: %1i\n" + "nptxfempty: %1i ginnakeff: %1i goutnakeff: %1i ulpickint: %1i\n" + "i2cintr: %1i erlysuspend:%1i usbsuspend: %1i usbreset: %1i\n" + "enumdone: %1i isooutdrop: %1i eopframe: %1i restoredone: %1i\n" + "epmismatch: %1i inepintr: %1i outepintr: %1i incomplisoin:%1i\n" + "incomplisoout:%1i fetsusp: %1i resetdet: %1i portintr: %1i\n" + "hcintr: %1i ptxfempty: %1i lpmtranrcvd:%1i conidstschng:%1i\n" + "disconnect: %1i sessreqintr:%1i wkupintr: %1i\n", + function_name, + gintmsk.b.modemismatch, + gintmsk.b.otgintr, + gintmsk.b.sofintr, + gintmsk.b.rxstsqlvl, + gintmsk.b.nptxfempty, + gintmsk.b.ginnakeff, + gintmsk.b.goutnakeff, + gintmsk.b.ulpickint, + gintmsk.b.i2cintr, + gintmsk.b.erlysuspend, + gintmsk.b.usbsuspend, + gintmsk.b.usbreset, + gintmsk.b.enumdone, + gintmsk.b.isooutdrop, + gintmsk.b.eopframe, + gintmsk.b.restoredone, + gintmsk.b.epmismatch, + gintmsk.b.inepintr, + gintmsk.b.outepintr, + gintmsk.b.incomplisoin, + gintmsk.b.incomplisoout, + gintmsk.b.fetsusp, + gintmsk.b.resetdet, + gintmsk.b.portintr, + gintmsk.b.hcintr, + gintmsk.b.ptxfempty, + gintmsk.b.lpmtranrcvd, + gintmsk.b.conidstschng, + gintmsk.b.disconnect, + gintmsk.b.sessreqintr, + gintmsk.b.wkupintr); + return; +} + +void dwc_debug_otg_int(gotgint_data_t gotgint, const char* function_name) +{ + DWC_DEBUGPL(DBG_USER, "otg int register (from %s function):\n" + "sesenddet:%1i sesreqsucstschung:%2i hstnegsucstschng:%1i\n" + "hstnegdet:%1i adevtoutchng: %2i debdone: %1i\n" + "mvic: %1i\n", + function_name, + gotgint.b.sesenddet, + gotgint.b.sesreqsucstschng, + gotgint.b.hstnegsucstschng, + gotgint.b.hstnegdet, + gotgint.b.adevtoutchng, + gotgint.b.debdone, + gotgint.b.mvic); + + return; +} diff --git a/drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.h b/drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.h new file mode 100755 index 0000000..ca17379 --- /dev/null +++ b/drivers/usb/host/dwc_otg/dwc_otg_mphi_fix.h @@ -0,0 +1,48 @@ +#ifndef __DWC_OTG_MPHI_FIX_H__ +#define __DWC_OTG_MPHI_FIX_H__ +#define FIQ_WRITE(_addr_,_data_) (*(volatile uint32_t *) (_addr_) = (_data_)) +#define FIQ_READ(_addr_) (*(volatile uint32_t *) (_addr_)) + +typedef struct { + volatile void* base; + volatile void* ctrl; + volatile void* outdda; + volatile void* outddb; + volatile void* intstat; +} mphi_regs_t; + +void dwc_debug_print_core_int_reg(gintsts_data_t gintsts, const char* function_name); +void dwc_debug_core_int_mask(gintsts_data_t gintmsk, const char* function_name); +void dwc_debug_otg_int(gotgint_data_t gotgint, const char* function_name); + +extern gintsts_data_t gintsts_saved; + +#ifdef DEBUG +#define DWC_DBG_PRINT_CORE_INT(_arg_) dwc_debug_print_core_int_reg(_arg_,__func__) +#define DWC_DBG_PRINT_CORE_INT_MASK(_arg_) dwc_debug_core_int_mask(_arg_,__func__) +#define DWC_DBG_PRINT_OTG_INT(_arg_) dwc_debug_otg_int(_arg_,__func__) + +#else +#define DWC_DBG_PRINT_CORE_INT(_arg_) +#define DWC_DBG_PRINT_CORE_INT_MASK(_arg_) +#define DWC_DBG_PRINT_OTG_INT(_arg_) + +#endif + +typedef enum { + FIQDBG_SCHED = (1 << 0), + FIQDBG_INT = (1 << 1), + FIQDBG_ERR = (1 << 2), + FIQDBG_PORTHUB = (1 << 3), +} FIQDBG_T; + +void _fiq_print(FIQDBG_T dbg_lvl, char *fmt, ...); +#ifdef FIQ_DEBUG +#define fiq_print _fiq_print +#else +#define fiq_print(x, y, ...) +#endif + +extern bool fiq_fix_enable, nak_holdoff_enable, fiq_split_enable; + +#endif diff --git a/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h b/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h index e46d9bb..6b2c7d0 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h +++ b/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h @@ -97,6 +97,9 @@ typedef struct os_dependent { /** Register offset for Diagnostic API */ uint32_t reg_offset; + /** Base address for MPHI peripheral */ + void *mphi_base; + #ifdef LM_INTERFACE struct lm_device *lmdev; #elif defined(PCI_INTERFACE) diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c index 1b1f83c..c8590b5 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c @@ -4276,7 +4276,7 @@ do { \ && (pcd->ep0state == EP0_OUT_DATA_PHASE)) status.d32 = core_if->dev_if->out_desc_addr->status.d32; if (pcd->ep0state == EP0_OUT_STATUS_PHASE) - status.d32 = status.d32 = core_if->dev_if-> + status.d32 = core_if->dev_if-> out_desc_addr->status.d32; if (status.b.sr) { -- 1.9.1