diff options
Diffstat (limited to 'drivers/usb/core/hcd-pci.c')
| -rw-r--r-- | drivers/usb/core/hcd-pci.c | 204 | 
1 files changed, 138 insertions, 66 deletions
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 1cf2d1e79a5..3799573bd38 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -66,10 +66,7 @@ static void companion_common(struct pci_dev *pdev, struct usb_hcd *hcd,  	 * vice versa.  	 */  	companion = NULL; -	for (;;) { -		companion = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, companion); -		if (!companion) -			break; +	for_each_pci_dev(companion) {  		if (companion->bus != pdev->bus ||  				PCI_SLOT(companion->devfn) != slot)  			continue; @@ -250,6 +247,9 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)  	if (retval != 0)  		goto err4;  	set_hs_companion(dev, hcd); + +	if (pci_dev_run_wake(dev)) +		pm_runtime_put_noidle(&dev->dev);  	return retval;   err4: @@ -292,6 +292,17 @@ void usb_hcd_pci_remove(struct pci_dev *dev)  	if (!hcd)  		return; +	if (pci_dev_run_wake(dev)) +		pm_runtime_get_noresume(&dev->dev); + +	/* Fake an interrupt request in order to give the driver a chance +	 * to test whether the controller hardware has been removed (e.g., +	 * cardbus physical eject). +	 */ +	local_irq_disable(); +	usb_hcd_irq(0, hcd); +	local_irq_enable(); +  	usb_remove_hcd(hcd);  	if (hcd->driver->flags & HCD_MEMORY) {  		iounmap(hcd->regs); @@ -317,12 +328,36 @@ void usb_hcd_pci_shutdown(struct pci_dev *dev)  	if (!hcd)  		return; -	if (hcd->driver->shutdown) +	if (test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) && +			hcd->driver->shutdown) {  		hcd->driver->shutdown(hcd); +		pci_disable_device(dev); +	}  }  EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown); -#ifdef	CONFIG_PM_SLEEP +#ifdef	CONFIG_PM_OPS + +#ifdef	CONFIG_PPC_PMAC +static void powermac_set_asic(struct pci_dev *pci_dev, int enable) +{ +	/* Enanble or disable ASIC clocks for USB */ +	if (machine_is(powermac)) { +		struct device_node	*of_node; + +		of_node = pci_device_to_OF_node(pci_dev); +		if (of_node) +			pmac_call_feature(PMAC_FTR_USB_ENABLE, +					of_node, 0, enable); +	} +} + +#else + +static inline void powermac_set_asic(struct pci_dev *pci_dev, int enable) +{} + +#endif	/* CONFIG_PPC_PMAC */  static int check_root_hub_suspended(struct device *dev)  { @@ -337,7 +372,7 @@ static int check_root_hub_suspended(struct device *dev)  	return 0;  } -static int hcd_pci_suspend(struct device *dev) +static int suspend_common(struct device *dev, bool do_wakeup)  {  	struct pci_dev		*pci_dev = to_pci_dev(dev);  	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev); @@ -352,13 +387,21 @@ static int hcd_pci_suspend(struct device *dev)  	if (retval)  		return retval; -	/* We might already be suspended (runtime PM -- not yet written) */ -	if (pci_dev->current_state != PCI_D0) -		return retval; -  	if (hcd->driver->pci_suspend) { -		retval = hcd->driver->pci_suspend(hcd); +		/* Optimization: Don't suspend if a root-hub wakeup is +		 * pending and it would cause the HCD to wake up anyway. +		 */ +		if (do_wakeup && HCD_WAKEUP_PENDING(hcd)) +			return -EBUSY; +		retval = hcd->driver->pci_suspend(hcd, do_wakeup);  		suspend_report_result(hcd->driver->pci_suspend, retval); + +		/* Check again in case wakeup raced with pci_suspend */ +		if (retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) { +			if (hcd->driver->pci_resume) +				hcd->driver->pci_resume(hcd, false); +			retval = -EBUSY; +		}  		if (retval)  			return retval;  	} @@ -374,6 +417,48 @@ static int hcd_pci_suspend(struct device *dev)  	return retval;  } +static int resume_common(struct device *dev, int event) +{ +	struct pci_dev		*pci_dev = to_pci_dev(dev); +	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev); +	int			retval; + +	if (hcd->state != HC_STATE_SUSPENDED) { +		dev_dbg(dev, "can't resume, not suspended!\n"); +		return 0; +	} + +	retval = pci_enable_device(pci_dev); +	if (retval < 0) { +		dev_err(dev, "can't re-enable after resume, %d!\n", retval); +		return retval; +	} + +	pci_set_master(pci_dev); + +	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); + +	if (hcd->driver->pci_resume) { +		if (event != PM_EVENT_AUTO_RESUME) +			wait_for_companions(pci_dev, hcd); + +		retval = hcd->driver->pci_resume(hcd, +				event == PM_EVENT_RESTORE); +		if (retval) { +			dev_err(dev, "PCI post-resume error %d!\n", retval); +			usb_hc_died(hcd); +		} +	} +	return retval; +} + +#ifdef	CONFIG_PM_SLEEP + +static int hcd_pci_suspend(struct device *dev) +{ +	return suspend_common(dev, device_may_wakeup(dev)); +} +  static int hcd_pci_suspend_noirq(struct device *dev)  {  	struct pci_dev		*pci_dev = to_pci_dev(dev); @@ -408,16 +493,7 @@ static int hcd_pci_suspend_noirq(struct device *dev)  		return retval;  	} -#ifdef CONFIG_PPC_PMAC -	/* Disable ASIC clocks for USB */ -	if (machine_is(powermac)) { -		struct device_node	*of_node; - -		of_node = pci_device_to_OF_node(pci_dev); -		if (of_node) -			pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0); -	} -#endif +	powermac_set_asic(pci_dev, 0);  	return retval;  } @@ -425,69 +501,63 @@ static int hcd_pci_resume_noirq(struct device *dev)  {  	struct pci_dev		*pci_dev = to_pci_dev(dev); -#ifdef CONFIG_PPC_PMAC -	/* Reenable ASIC clocks for USB */ -	if (machine_is(powermac)) { -		struct device_node *of_node; - -		of_node = pci_device_to_OF_node(pci_dev); -		if (of_node) -			pmac_call_feature(PMAC_FTR_USB_ENABLE, -						of_node, 0, 1); -	} -#endif +	powermac_set_asic(pci_dev, 1);  	/* Go back to D0 and disable remote wakeup */  	pci_back_from_sleep(pci_dev);  	return 0;  } -static int resume_common(struct device *dev, bool hibernated) +static int hcd_pci_resume(struct device *dev)  { -	struct pci_dev		*pci_dev = to_pci_dev(dev); -	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev); -	int			retval; +	return resume_common(dev, PM_EVENT_RESUME); +} -	if (hcd->state != HC_STATE_SUSPENDED) { -		dev_dbg(dev, "can't resume, not suspended!\n"); -		return 0; -	} +static int hcd_pci_restore(struct device *dev) +{ +	return resume_common(dev, PM_EVENT_RESTORE); +} -	retval = pci_enable_device(pci_dev); -	if (retval < 0) { -		dev_err(dev, "can't re-enable after resume, %d!\n", retval); -		return retval; -	} +#else -	pci_set_master(pci_dev); +#define hcd_pci_suspend		NULL +#define hcd_pci_suspend_noirq	NULL +#define hcd_pci_resume_noirq	NULL +#define hcd_pci_resume		NULL +#define hcd_pci_restore		NULL -	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); +#endif	/* CONFIG_PM_SLEEP */ -	if (hcd->driver->pci_resume) { -		/* This call should be made only during system resume, -		 * not during runtime resume. -		 */ -		wait_for_companions(pci_dev, hcd); +#ifdef	CONFIG_PM_RUNTIME -		retval = hcd->driver->pci_resume(hcd, hibernated); -		if (retval) { -			dev_err(dev, "PCI post-resume error %d!\n", retval); -			usb_hc_died(hcd); -		} -	} +static int hcd_pci_runtime_suspend(struct device *dev) +{ +	int	retval; + +	retval = suspend_common(dev, true); +	if (retval == 0) +		powermac_set_asic(to_pci_dev(dev), 0); +	dev_dbg(dev, "hcd_pci_runtime_suspend: %d\n", retval);  	return retval;  } -static int hcd_pci_resume(struct device *dev) +static int hcd_pci_runtime_resume(struct device *dev)  { -	return resume_common(dev, false); -} +	int	retval; -static int hcd_pci_restore(struct device *dev) -{ -	return resume_common(dev, true); +	powermac_set_asic(to_pci_dev(dev), 1); +	retval = resume_common(dev, PM_EVENT_AUTO_RESUME); +	dev_dbg(dev, "hcd_pci_runtime_resume: %d\n", retval); +	return retval;  } +#else + +#define hcd_pci_runtime_suspend	NULL +#define hcd_pci_runtime_resume	NULL + +#endif	/* CONFIG_PM_RUNTIME */ +  const struct dev_pm_ops usb_hcd_pci_pm_ops = {  	.suspend	= hcd_pci_suspend,  	.suspend_noirq	= hcd_pci_suspend_noirq, @@ -501,7 +571,9 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = {  	.poweroff_noirq	= hcd_pci_suspend_noirq,  	.restore_noirq	= hcd_pci_resume_noirq,  	.restore	= hcd_pci_restore, +	.runtime_suspend = hcd_pci_runtime_suspend, +	.runtime_resume	= hcd_pci_runtime_resume,  };  EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops); -#endif	/* CONFIG_PM_SLEEP */ +#endif	/* CONFIG_PM_OPS */  | 
