Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

[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
Loading
Loading
Loading
Loading
+96 −46
Original line number Diff line number Diff line
@@ -9,6 +9,12 @@
 */

#include <linux/config.h>
#ifdef CONFIG_USB_DEBUG
#define DEBUG
#else
#undef DEBUG
#endif

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/pci.h>
@@ -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_USBCMD		0		/* command register */
#define UHCI_USBSTS		2		/* status register */
#define UHCI_USBINTR		4		/* interrupt register */
#define UHCI_USBLEGSUP_DEFAULT	0x2000		/* only PIRQ enable set */
#define UHCI_USBCMD_RUN		(1 << 0)	/* RUN/STOP bit */
#define UHCI_USBCMD_GRESET	(1 << 2)	/* Global reset */
#define UHCI_USBCMD_CONFIGURE	(1 << 6)	/* config semaphore */
#define UHCI_USBSTS_HALTED	(1 << 5)	/* HCHalted bit */
#define UHCI_USBLEGSUP_RWC	0x8f00		/* the R/WC bits */
#define UHCI_USBLEGSUP_RO	0x5040		/* R/O and reserved bits */
#define UHCI_USBCMD_RUN		0x0001		/* RUN/STOP bit */
#define UHCI_USBCMD_HCRESET	0x0002		/* Host Controller reset */
#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_CMDSTATUS		0x08
@@ -84,57 +91,100 @@ static int __init usb_handoff_early(char *str)
}
__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;
	int wait_time, delta;
	u16 val, sts;
	int i;
	/* 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(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;
	/* 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(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);
}

	if (!base)
		return;
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);
	val = inw(base + UHCI_USBCMD);
	val &= ~(u16)(UHCI_USBCMD_RUN | UHCI_USBCMD_CONFIGURE);
	outw(val, base + UHCI_USBCMD);
int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)
{
	u16 legsup;
	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)) {
		dev_dbg(&pdev->dev, "%s: legsup = 0x%04x\n",
				__FUNCTION__, legsup);
		goto reset_needed;
	}

	cmd = inw(base + UHCI_USBCMD);
	if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) ||
			!(cmd & UHCI_USBCMD_EGSM)) {
		dev_dbg(&pdev->dev, "%s: cmd = 0x%04x\n",
				__FUNCTION__, cmd);
		goto reset_needed;
	}

	intr = inw(base + UHCI_USBINTR);
	if (intr & (~UHCI_USBINTR_RESUME)) {
		dev_dbg(&pdev->dev, "%s: intr = 0x%04x\n",
				__FUNCTION__, intr);
		goto reset_needed;
	}
	return 0;

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)
{
		wait_time = 1000;
		delta = 100;
	unsigned long base = 0;
	int i;

		do {
			outw(0x1f, base + UHCI_USBSTS);
			udelay(delta);
			wait_time -= delta;
			val = inw(base + UHCI_USBSTS);
			if (val & UHCI_USBSTS_HALTED)
	for (i = 0; i < PCI_ROM_RESOURCE; i++)
		if ((pci_resource_flags(pdev, i) & IORESOURCE_IO)) {
			base = pci_resource_start(pdev, i);
			break;
		} while (wait_time > 0);
		}

	/*
	 * disable interrupts & legacy support
	 */
	outw(0, base + UHCI_USBINTR);
	outw(0x1f, base + UHCI_USBSTS);
	pci_read_config_word(pdev, UHCI_USBLEGSUP, &val);
	if (val & 0xbf)
		pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT);

	if (base)
		uhci_check_and_reset_hc(pdev, base);
}

static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev)
+10 −66
Original line number Diff line number Diff line
@@ -101,37 +101,16 @@ static void uhci_get_current_frame_number(struct uhci_hcd *uhci);
#include "uhci-q.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
 * generate interrupts or do DMA.
 * Finish up a host controller reset and update the recorded state.
 */
static void reset_hc(struct uhci_hcd *uhci)
static void finish_reset(struct uhci_hcd *uhci)
{
	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
	 * bits in the port status and control registers.
	 * We have to clear them by hand.
@@ -153,7 +132,8 @@ static void reset_hc(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;
}

@@ -163,44 +143,8 @@ static void hc_died(struct uhci_hcd *uhci)
 */
static void check_and_reset_hc(struct uhci_hcd *uhci)
{
	u16 legsup;
	unsigned int cmd, intr;

	/*
	 * 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);
	if (uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr))
		finish_reset(uhci);
}

/*
@@ -714,7 +658,7 @@ static void uhci_stop(struct usb_hcd *hcd)

	spin_lock_irq(&uhci->lock);
	if (!uhci->hc_inaccessible)
		reset_hc(uhci);
		hc_died(uhci);
	uhci_scan_schedule(uhci, NULL);
	spin_unlock_irq(&uhci->lock);