diff options
Diffstat (limited to 'drivers/usb/core/hcd-pci.c')
| -rw-r--r-- | drivers/usb/core/hcd-pci.c | 200 | 
1 files changed, 106 insertions, 94 deletions
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 5b87ae7f0a6..99432785f43 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -191,17 +191,15 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);  /**   * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD   * @dev: USB Host Controller being suspended - * @message: semantics in flux + * @message: Power Management message describing this state transition   * - * Store this function in the HCD's struct pci_driver as suspend(). + * Store this function in the HCD's struct pci_driver as .suspend.   */  int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)  { -	struct usb_hcd		*hcd; +	struct usb_hcd		*hcd = pci_get_drvdata(dev);  	int			retval = 0; -	int			has_pci_pm; - -	hcd = pci_get_drvdata(dev); +	int			wake, w;  	/* Root hub suspend should have stopped all downstream traffic,  	 * and all bus master traffic.  And done so for both the interface @@ -212,8 +210,15 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)  	 * otherwise the swsusp will save (and restore) garbage state.  	 */  	if (!(hcd->state == HC_STATE_SUSPENDED || -			hcd->state == HC_STATE_HALT)) -		return -EBUSY; +			hcd->state == HC_STATE_HALT)) { +		dev_warn(&dev->dev, "Root hub is not suspended\n"); +		retval = -EBUSY; +		goto done; +	} + +	/* We might already be suspended (runtime PM -- not yet written) */ +	if (dev->current_state != PCI_D0) +		goto done;  	if (hcd->driver->pci_suspend) {  		retval = hcd->driver->pci_suspend(hcd, message); @@ -221,49 +226,60 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)  		if (retval)  			goto done;  	} -	synchronize_irq(dev->irq); -	/* FIXME until the generic PM interfaces change a lot more, this -	 * can't use PCI D1 and D2 states.  For example, the confusion -	 * between messages and states will need to vanish, and messages -	 * will need to provide a target system state again. -	 * -	 * It'll be important to learn characteristics of the target state, -	 * especially on embedded hardware where the HCD will often be in -	 * charge of an external VBUS power supply and one or more clocks. -	 * Some target system states will leave them active; others won't. -	 * (With PCI, that's often handled by platform BIOS code.) -	 */ +	synchronize_irq(dev->irq); -	/* even when the PCI layer rejects some of the PCI calls -	 * below, HCs can try global suspend and reduce DMA traffic. -	 * PM-sensitive HCDs may already have done this. +	/* Don't fail on error to enable wakeup.  We rely on pci code +	 * to reject requests the hardware can't implement, rather +	 * than coding the same thing.  	 */ -	has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); +	wake = (hcd->state == HC_STATE_SUSPENDED && +			device_may_wakeup(&dev->dev)); +	w = pci_wake_from_d3(dev, wake); +	if (w < 0) +		wake = w; +	dev_dbg(&dev->dev, "wakeup: %d\n", wake);  	/* Downstream ports from this root hub should already be quiesced, so  	 * there will be no DMA activity.  Now we can shut down the upstream  	 * link (except maybe for PME# resume signaling) and enter some PCI  	 * low power state, if the hardware allows.  	 */ -	if (hcd->state == HC_STATE_SUSPENDED) { +	pci_disable_device(dev); + done: +	return retval; +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); -		/* no DMA or IRQs except when HC is active */ -		if (dev->current_state == PCI_D0) { -			pci_save_state(dev); -			pci_disable_device(dev); -		} +/** + * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled + * @dev: USB Host Controller being suspended + * @message: Power Management message describing this state transition + * + * Store this function in the HCD's struct pci_driver as .suspend_late. + */ +int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message) +{ +	int			retval = 0; +	int			has_pci_pm; -		if (message.event == PM_EVENT_FREEZE || -				message.event == PM_EVENT_PRETHAW) { -			dev_dbg(hcd->self.controller, "--> no state change\n"); -			goto done; -		} +	/* We might already be suspended (runtime PM -- not yet written) */ +	if (dev->current_state != PCI_D0) +		goto done; -		if (!has_pci_pm) { -			dev_dbg(hcd->self.controller, "--> PCI D0/legacy\n"); -			goto done; -		} +	pci_save_state(dev); + +	/* Don't change state if we don't need to */ +	if (message.event == PM_EVENT_FREEZE || +			message.event == PM_EVENT_PRETHAW) { +		dev_dbg(&dev->dev, "--> no state change\n"); +		goto done; +	} + +	has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); +	if (!has_pci_pm) { +		dev_dbg(&dev->dev, "--> PCI D0 legacy\n"); +	} else {  		/* NOTE:  dev->current_state becomes nonzero only here, and  		 * only for devices that support PCI PM.  Also, exiting @@ -273,35 +289,16 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)  		retval = pci_set_power_state(dev, PCI_D3hot);  		suspend_report_result(pci_set_power_state, retval);  		if (retval == 0) { -			int wake = device_can_wakeup(&hcd->self.root_hub->dev); - -			wake = wake && device_may_wakeup(hcd->self.controller); - -			dev_dbg(hcd->self.controller, "--> PCI D3%s\n", -					wake ? "/wakeup" : ""); - -			/* Ignore these return values.  We rely on pci code to -			 * reject requests the hardware can't implement, rather -			 * than coding the same thing. -			 */ -			(void) pci_enable_wake(dev, PCI_D3hot, wake); -			(void) pci_enable_wake(dev, PCI_D3cold, wake); +			dev_dbg(&dev->dev, "--> PCI D3\n");  		} else {  			dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",  					retval); -			(void) usb_hcd_pci_resume(dev); +			pci_restore_state(dev);  		} - -	} else if (hcd->state != HC_STATE_HALT) { -		dev_dbg(hcd->self.controller, "hcd state %d; not suspended\n", -			hcd->state); -		WARN_ON(1); -		retval = -EINVAL;  	} -done: -	if (retval == 0) {  #ifdef CONFIG_PPC_PMAC +	if (retval == 0) {  		/* Disable ASIC clocks for USB */  		if (machine_is(powermac)) {  			struct device_node	*of_node; @@ -311,30 +308,24 @@ done:  				pmac_call_feature(PMAC_FTR_USB_ENABLE,  							of_node, 0, 0);  		} -#endif  	} +#endif + done:  	return retval;  } -EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); +EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late);  /** - * usb_hcd_pci_resume - power management resume of a PCI-based HCD + * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled   * @dev: USB Host Controller being resumed   * - * Store this function in the HCD's struct pci_driver as resume(). + * Store this function in the HCD's struct pci_driver as .resume_early.   */ -int usb_hcd_pci_resume(struct pci_dev *dev) +int usb_hcd_pci_resume_early(struct pci_dev *dev)  { -	struct usb_hcd		*hcd; -	int			retval; - -	hcd = pci_get_drvdata(dev); -	if (hcd->state != HC_STATE_SUSPENDED) { -		dev_dbg(hcd->self.controller, -				"can't resume, not suspended!\n"); -		return 0; -	} +	int		retval = 0; +	pci_power_t	state = dev->current_state;  #ifdef CONFIG_PPC_PMAC  	/* Reenable ASIC clocks for USB */ @@ -352,7 +343,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev)  	 * calls "standby", "suspend to RAM", and so on).  There are also  	 * dirty cases when swsusp fakes a suspend in "shutdown" mode.  	 */ -	if (dev->current_state != PCI_D0) { +	if (state != PCI_D0) {  #ifdef	DEBUG  		int	pci_pm;  		u16	pmcr; @@ -364,8 +355,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev)  			/* Clean case:  power to USB and to HC registers was  			 * maintained; remote wakeup is easy.  			 */ -			dev_dbg(hcd->self.controller, "resume from PCI D%d\n", -					pmcr); +			dev_dbg(&dev->dev, "resume from PCI D%d\n", pmcr);  		} else {  			/* Clean:  HC lost Vcc power, D0 uninitialized  			 *   + Vaux may have preserved port and transceiver @@ -376,32 +366,55 @@ int usb_hcd_pci_resume(struct pci_dev *dev)  			 *   + after BIOS init  			 *   + after Linux init (HCD statically linked)  			 */ -			dev_dbg(hcd->self.controller, -				"PCI D0, from previous PCI D%d\n", -				dev->current_state); +			dev_dbg(&dev->dev, "resume from previous PCI D%d\n", +					state);  		}  #endif -		/* yes, ignore these results too... */ -		(void) pci_enable_wake(dev, dev->current_state, 0); -		(void) pci_enable_wake(dev, PCI_D3cold, 0); + +		retval = pci_set_power_state(dev, PCI_D0);  	} else {  		/* Same basic cases: clean (powered/not), dirty */ -		dev_dbg(hcd->self.controller, "PCI legacy resume\n"); +		dev_dbg(&dev->dev, "PCI legacy resume\n"); +	} + +	if (retval < 0) +		dev_err(&dev->dev, "can't resume: %d\n", retval); +	else +		pci_restore_state(dev); + +	return retval; +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early); + +/** + * usb_hcd_pci_resume - power management resume of a PCI-based HCD + * @dev: USB Host Controller being resumed + * + * Store this function in the HCD's struct pci_driver as .resume. + */ +int usb_hcd_pci_resume(struct pci_dev *dev) +{ +	struct usb_hcd		*hcd; +	int			retval; + +	hcd = pci_get_drvdata(dev); +	if (hcd->state != HC_STATE_SUSPENDED) { +		dev_dbg(hcd->self.controller, +				"can't resume, not suspended!\n"); +		return 0;  	} -	/* NOTE:  the PCI API itself is asymmetric here.  We don't need to -	 * pci_set_power_state(PCI_D0) since that's part of re-enabling; -	 * but that won't re-enable bus mastering.  Yet pci_disable_device() -	 * explicitly disables bus mastering... -	 */  	retval = pci_enable_device(dev);  	if (retval < 0) { -		dev_err(hcd->self.controller, -			"can't re-enable after resume, %d!\n", retval); +		dev_err(&dev->dev, "can't re-enable after resume, %d!\n", +				retval);  		return retval;  	} +  	pci_set_master(dev); -	pci_restore_state(dev); + +	/* yes, ignore this result too... */ +	(void) pci_wake_from_d3(dev, 0);  	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); @@ -413,7 +426,6 @@ int usb_hcd_pci_resume(struct pci_dev *dev)  			usb_hc_died(hcd);  		}  	} -  	return retval;  }  EXPORT_SYMBOL_GPL(usb_hcd_pci_resume);  |