Commit bb200f6e authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] UHCI: unify BIOS handoff and driver reset code

This patch (as574) updates the PCI BIOS usb-handoff code for UHCI
controllers, making it work like the reset routines in uhci-hcd.  This
allows uhci-hcd to drop its own routines in favor of the new ones
(code-sharing).

Once the patch is merged we can turn the usb-handoff option on
permanently, as far as UHCI is concerned.  OHCI and EHCI may still have
some issues.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent a922c687
...@@ -9,6 +9,12 @@ ...@@ -9,6 +9,12 @@
*/ */
#include <linux/config.h> #include <linux/config.h>
#ifdef CONFIG_USB_DEBUG
#define DEBUG
#else
#undef DEBUG
#endif
#include <linux/types.h> #include <linux/types.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/pci.h> #include <linux/pci.h>
...@@ -46,13 +52,14 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_2, qui ...@@ -46,13 +52,14 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_2, qui
#define UHCI_USBLEGSUP 0xc0 /* legacy support */ #define UHCI_USBLEGSUP 0xc0 /* legacy support */
#define UHCI_USBCMD 0 /* command register */ #define UHCI_USBCMD 0 /* command register */
#define UHCI_USBSTS 2 /* status register */
#define UHCI_USBINTR 4 /* interrupt register */ #define UHCI_USBINTR 4 /* interrupt register */
#define UHCI_USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ #define UHCI_USBLEGSUP_RWC 0x8f00 /* the R/WC bits */
#define UHCI_USBCMD_RUN (1 << 0) /* RUN/STOP bit */ #define UHCI_USBLEGSUP_RO 0x5040 /* R/O and reserved bits */
#define UHCI_USBCMD_GRESET (1 << 2) /* Global reset */ #define UHCI_USBCMD_RUN 0x0001 /* RUN/STOP bit */
#define UHCI_USBCMD_CONFIGURE (1 << 6) /* config semaphore */ #define UHCI_USBCMD_HCRESET 0x0002 /* Host Controller reset */
#define UHCI_USBSTS_HALTED (1 << 5) /* HCHalted bit */ #define UHCI_USBCMD_EGSM 0x0008 /* Global Suspend Mode */
#define UHCI_USBCMD_CONFIGURE 0x0040 /* Config Flag */
#define UHCI_USBINTR_RESUME 0x0002 /* Resume interrupt enable */
#define OHCI_CONTROL 0x04 #define OHCI_CONTROL 0x04
#define OHCI_CMDSTATUS 0x08 #define OHCI_CMDSTATUS 0x08
...@@ -84,57 +91,100 @@ static int __init usb_handoff_early(char *str) ...@@ -84,57 +91,100 @@ static int __init usb_handoff_early(char *str)
} }
__setup("usb-handoff", usb_handoff_early); __setup("usb-handoff", usb_handoff_early);
static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev) /*
* Make sure the controller is completely inactive, unable to
* generate interrupts or do DMA.
*/
void uhci_reset_hc(struct pci_dev *pdev, unsigned long base)
{ {
unsigned long base = 0; /* Turn off PIRQ enable and SMI enable. (This also turns off the
int wait_time, delta; * BIOS's USB Legacy Support.) Turn off all the R/WC bits too.
u16 val, sts; */
int i; pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);
for (i = 0; i < PCI_ROM_RESOURCE; i++)
if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) {
base = pci_resource_start(pdev, i);
break;
}
if (!base) /* Reset the HC - this will force us to get a
return; * new notification of any already connected
* ports due to the virtual disconnect that it
* implies.
*/
outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD);
mb();
udelay(5);
if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)
dev_warn(&pdev->dev, "HCRESET not completed yet!\n");
/* Just to be safe, disable interrupt requests and
* make sure the controller is stopped.
*/
outw(0, base + UHCI_USBINTR);
outw(0, base + UHCI_USBCMD);
}
EXPORT_SYMBOL_GPL(uhci_reset_hc);
/* /*
* stop controller * Initialize a controller that was newly discovered or has just been
* resumed. In either case we can't be sure of its previous state.
*
* Returns: 1 if the controller was reset, 0 otherwise.
*/ */
sts = inw(base + UHCI_USBSTS); int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)
val = inw(base + UHCI_USBCMD); {
val &= ~(u16)(UHCI_USBCMD_RUN | UHCI_USBCMD_CONFIGURE); u16 legsup;
outw(val, base + UHCI_USBCMD); unsigned int cmd, intr;
/* /*
* wait while it stops if it was running * When restarting a suspended controller, we expect all the
* settings to be the same as we left them:
*
* PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
* Controller is stopped and configured with EGSM set;
* No interrupts enabled except possibly Resume Detect.
*
* If any of these conditions are violated we do a complete reset.
*/ */
if ((sts & UHCI_USBSTS_HALTED) == 0) pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup);
{ if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) {
wait_time = 1000; dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n",
delta = 100; __FUNCTION__, legsup);
goto reset_needed;
}
do { cmd = inw(base + UHCI_USBCMD);
outw(0x1f, base + UHCI_USBSTS); if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) ||
udelay(delta); !(cmd & UHCI_USBCMD_EGSM)) {
wait_time -= delta; dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n",
val = inw(base + UHCI_USBSTS); __FUNCTION__, cmd);
if (val & UHCI_USBSTS_HALTED) goto reset_needed;
break;
} while (wait_time > 0);
} }
/* intr = inw(base + UHCI_USBINTR);
* disable interrupts & legacy support if (intr & (~UHCI_USBINTR_RESUME)) {
*/ dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n",
outw(0, base + UHCI_USBINTR); __FUNCTION__, intr);
outw(0x1f, base + UHCI_USBSTS); goto reset_needed;
pci_read_config_word(pdev, UHCI_USBLEGSUP, &val); }
if (val & 0xbf) return 0;
pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT);
reset_needed:
dev_dbg(&pdev->dev, "Performing full reset\n");
uhci_reset_hc(pdev, base);
return 1;
}
EXPORT_SYMBOL_GPL(uhci_check_and_reset_hc);
static void __devinit quirk_usb_handoff_uhci(struct pci_dev *pdev)
{
unsigned long base = 0;
int i;
for (i = 0; i < PCI_ROM_RESOURCE; i++)
if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) {
base = pci_resource_start(pdev, i);
break;
}
if (base)
uhci_check_and_reset_hc(pdev, base);
} }
static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev)
......
...@@ -101,37 +101,16 @@ static void uhci_get_current_frame_number(struct uhci_hcd *uhci); ...@@ -101,37 +101,16 @@ static void uhci_get_current_frame_number(struct uhci_hcd *uhci);
#include "uhci-q.c" #include "uhci-q.c"
#include "uhci-hub.c" #include "uhci-hub.c"
extern void uhci_reset_hc(struct pci_dev *pdev, unsigned long base);
extern int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base);
/* /*
* Make sure the controller is completely inactive, unable to * Finish up a host controller reset and update the recorded state.
* generate interrupts or do DMA.
*/ */
static void reset_hc(struct uhci_hcd *uhci) static void finish_reset(struct uhci_hcd *uhci)
{ {
int port; int port;
/* Turn off PIRQ enable and SMI enable. (This also turns off the
* BIOS's USB Legacy Support.) Turn off all the R/WC bits too.
*/
pci_write_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP,
USBLEGSUP_RWC);
/* Reset the HC - this will force us to get a
* new notification of any already connected
* ports due to the virtual disconnect that it
* implies.
*/
outw(USBCMD_HCRESET, uhci->io_addr + USBCMD);
mb();
udelay(5);
if (inw(uhci->io_addr + USBCMD) & USBCMD_HCRESET)
dev_warn(uhci_dev(uhci), "HCRESET not completed yet!\n");
/* Just to be safe, disable interrupt requests and
* make sure the controller is stopped.
*/
outw(0, uhci->io_addr + USBINTR);
outw(0, uhci->io_addr + USBCMD);
/* HCRESET doesn't affect the Suspend, Reset, and Resume Detect /* HCRESET doesn't affect the Suspend, Reset, and Resume Detect
* bits in the port status and control registers. * bits in the port status and control registers.
* We have to clear them by hand. * We have to clear them by hand.
...@@ -153,7 +132,8 @@ static void reset_hc(struct uhci_hcd *uhci) ...@@ -153,7 +132,8 @@ static void reset_hc(struct uhci_hcd *uhci)
*/ */
static void hc_died(struct uhci_hcd *uhci) static void hc_died(struct uhci_hcd *uhci)
{ {
reset_hc(uhci); uhci_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr);
finish_reset(uhci);
uhci->hc_inaccessible = 1; uhci->hc_inaccessible = 1;
} }
...@@ -163,44 +143,8 @@ static void hc_died(struct uhci_hcd *uhci) ...@@ -163,44 +143,8 @@ static void hc_died(struct uhci_hcd *uhci)
*/ */
static void check_and_reset_hc(struct uhci_hcd *uhci) static void check_and_reset_hc(struct uhci_hcd *uhci)
{ {
u16 legsup; if (uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr))
unsigned int cmd, intr; finish_reset(uhci);
/*
* When restarting a suspended controller, we expect all the
* settings to be the same as we left them:
*
* PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
* Controller is stopped and configured with EGSM set;
* No interrupts enabled except possibly Resume Detect.
*
* If any of these conditions are violated we do a complete reset.
*/
pci_read_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, &legsup);
if (legsup & ~(USBLEGSUP_RO | USBLEGSUP_RWC)) {
dev_dbg(uhci_dev(uhci), "%s: legsup = 0x%04x\n",
__FUNCTION__, legsup);
goto reset_needed;
}
cmd = inw(uhci->io_addr + USBCMD);
if ((cmd & USBCMD_RS) || !(cmd & USBCMD_CF) || !(cmd & USBCMD_EGSM)) {
dev_dbg(uhci_dev(uhci), "%s: cmd = 0x%04x\n",
__FUNCTION__, cmd);
goto reset_needed;
}
intr = inw(uhci->io_addr + USBINTR);
if (intr & (~USBINTR_RESUME)) {
dev_dbg(uhci_dev(uhci), "%s: intr = 0x%04x\n",
__FUNCTION__, intr);
goto reset_needed;
}
return;
reset_needed:
dev_dbg(uhci_dev(uhci), "Performing full reset\n");
reset_hc(uhci);
} }
/* /*
...@@ -714,7 +658,7 @@ static void uhci_stop(struct usb_hcd *hcd) ...@@ -714,7 +658,7 @@ static void uhci_stop(struct usb_hcd *hcd)
spin_lock_irq(&uhci->lock); spin_lock_irq(&uhci->lock);
if (!uhci->hc_inaccessible) if (!uhci->hc_inaccessible)
reset_hc(uhci); hc_died(uhci);
uhci_scan_schedule(uhci, NULL); uhci_scan_schedule(uhci, NULL);
spin_unlock_irq(&uhci->lock); spin_unlock_irq(&uhci->lock);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment