forked from mirrors/linux
		
	usb: chipidea: add runtime power management support
Add runtime power management support. Signed-off-by: Peter Chen <peter.chen@freescale.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									a4cf1b14cf
								
							
						
					
					
						commit
						1f874edcb7
					
				
					 4 changed files with 103 additions and 6 deletions
				
			
		| 
						 | 
					@ -169,6 +169,9 @@ struct hw_bank {
 | 
				
			||||||
 * @b_sess_valid_event: indicates there is a vbus event, and handled
 | 
					 * @b_sess_valid_event: indicates there is a vbus event, and handled
 | 
				
			||||||
 * at ci_otg_work
 | 
					 * at ci_otg_work
 | 
				
			||||||
 * @imx28_write_fix: Freescale imx28 needs swp instruction for writing
 | 
					 * @imx28_write_fix: Freescale imx28 needs swp instruction for writing
 | 
				
			||||||
 | 
					 * @supports_runtime_pm: if runtime pm is supported
 | 
				
			||||||
 | 
					 * @in_lpm: if the core in low power mode
 | 
				
			||||||
 | 
					 * @wakeup_int: if wakeup interrupt occur
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
struct ci_hdrc {
 | 
					struct ci_hdrc {
 | 
				
			||||||
	struct device			*dev;
 | 
						struct device			*dev;
 | 
				
			||||||
| 
						 | 
					@ -211,6 +214,9 @@ struct ci_hdrc {
 | 
				
			||||||
	bool				id_event;
 | 
						bool				id_event;
 | 
				
			||||||
	bool				b_sess_valid_event;
 | 
						bool				b_sess_valid_event;
 | 
				
			||||||
	bool				imx28_write_fix;
 | 
						bool				imx28_write_fix;
 | 
				
			||||||
 | 
						bool				supports_runtime_pm;
 | 
				
			||||||
 | 
						bool				in_lpm;
 | 
				
			||||||
 | 
						bool				wakeup_int;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
 | 
					static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -491,6 +491,13 @@ static irqreturn_t ci_irq(int irq, void *data)
 | 
				
			||||||
	irqreturn_t ret = IRQ_NONE;
 | 
						irqreturn_t ret = IRQ_NONE;
 | 
				
			||||||
	u32 otgsc = 0;
 | 
						u32 otgsc = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ci->in_lpm) {
 | 
				
			||||||
 | 
							disable_irq_nosync(irq);
 | 
				
			||||||
 | 
							ci->wakeup_int = true;
 | 
				
			||||||
 | 
							pm_runtime_get(ci->dev);
 | 
				
			||||||
 | 
							return IRQ_HANDLED;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (ci->is_otg) {
 | 
						if (ci->is_otg) {
 | 
				
			||||||
		otgsc = hw_read_otgsc(ci, ~0);
 | 
							otgsc = hw_read_otgsc(ci, ~0);
 | 
				
			||||||
		if (ci_otg_is_fsm_mode(ci)) {
 | 
							if (ci_otg_is_fsm_mode(ci)) {
 | 
				
			||||||
| 
						 | 
					@ -673,6 +680,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 | 
				
			||||||
	ci->platdata = dev_get_platdata(dev);
 | 
						ci->platdata = dev_get_platdata(dev);
 | 
				
			||||||
	ci->imx28_write_fix = !!(ci->platdata->flags &
 | 
						ci->imx28_write_fix = !!(ci->platdata->flags &
 | 
				
			||||||
		CI_HDRC_IMX28_WRITE_FIX);
 | 
							CI_HDRC_IMX28_WRITE_FIX);
 | 
				
			||||||
 | 
						ci->supports_runtime_pm = !!(ci->platdata->flags &
 | 
				
			||||||
 | 
							CI_HDRC_SUPPORTS_RUNTIME_PM);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = hw_device_init(ci, base);
 | 
						ret = hw_device_init(ci, base);
 | 
				
			||||||
	if (ret < 0) {
 | 
						if (ret < 0) {
 | 
				
			||||||
| 
						 | 
					@ -788,6 +797,14 @@ static int ci_hdrc_probe(struct platform_device *pdev)
 | 
				
			||||||
	if (ret)
 | 
						if (ret)
 | 
				
			||||||
		goto stop;
 | 
							goto stop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ci->supports_runtime_pm) {
 | 
				
			||||||
 | 
							pm_runtime_set_active(&pdev->dev);
 | 
				
			||||||
 | 
							pm_runtime_enable(&pdev->dev);
 | 
				
			||||||
 | 
							pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
 | 
				
			||||||
 | 
							pm_runtime_mark_last_busy(ci->dev);
 | 
				
			||||||
 | 
							pm_runtime_use_autosuspend(&pdev->dev);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (ci_otg_is_fsm_mode(ci))
 | 
						if (ci_otg_is_fsm_mode(ci))
 | 
				
			||||||
		ci_hdrc_otg_fsm_start(ci);
 | 
							ci_hdrc_otg_fsm_start(ci);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -807,6 +824,12 @@ static int ci_hdrc_remove(struct platform_device *pdev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct ci_hdrc *ci = platform_get_drvdata(pdev);
 | 
						struct ci_hdrc *ci = platform_get_drvdata(pdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ci->supports_runtime_pm) {
 | 
				
			||||||
 | 
							pm_runtime_get_sync(&pdev->dev);
 | 
				
			||||||
 | 
							pm_runtime_disable(&pdev->dev);
 | 
				
			||||||
 | 
							pm_runtime_put_noidle(&pdev->dev);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dbg_remove_files(ci);
 | 
						dbg_remove_files(ci);
 | 
				
			||||||
	ci_role_destroy(ci);
 | 
						ci_role_destroy(ci);
 | 
				
			||||||
	ci_hdrc_enter_lpm(ci, true);
 | 
						ci_hdrc_enter_lpm(ci, true);
 | 
				
			||||||
| 
						 | 
					@ -815,13 +838,14 @@ static int ci_hdrc_remove(struct platform_device *pdev)
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef CONFIG_PM_SLEEP
 | 
					#ifdef CONFIG_PM
 | 
				
			||||||
static void ci_controller_suspend(struct ci_hdrc *ci)
 | 
					static void ci_controller_suspend(struct ci_hdrc *ci)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						disable_irq(ci->irq);
 | 
				
			||||||
	ci_hdrc_enter_lpm(ci, true);
 | 
						ci_hdrc_enter_lpm(ci, true);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (ci->usb_phy)
 | 
					 | 
				
			||||||
	usb_phy_set_suspend(ci->usb_phy, 1);
 | 
						usb_phy_set_suspend(ci->usb_phy, 1);
 | 
				
			||||||
 | 
						ci->in_lpm = true;
 | 
				
			||||||
 | 
						enable_irq(ci->irq);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int ci_controller_resume(struct device *dev)
 | 
					static int ci_controller_resume(struct device *dev)
 | 
				
			||||||
| 
						 | 
					@ -830,23 +854,49 @@ static int ci_controller_resume(struct device *dev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dev_dbg(dev, "at %s\n", __func__);
 | 
						dev_dbg(dev, "at %s\n", __func__);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ci_hdrc_enter_lpm(ci, false);
 | 
						if (!ci->in_lpm) {
 | 
				
			||||||
 | 
							WARN_ON(1);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ci_hdrc_enter_lpm(ci, false);
 | 
				
			||||||
	if (ci->usb_phy) {
 | 
						if (ci->usb_phy) {
 | 
				
			||||||
		usb_phy_set_suspend(ci->usb_phy, 0);
 | 
							usb_phy_set_suspend(ci->usb_phy, 0);
 | 
				
			||||||
		usb_phy_set_wakeup(ci->usb_phy, false);
 | 
							usb_phy_set_wakeup(ci->usb_phy, false);
 | 
				
			||||||
		hw_wait_phy_stable();
 | 
							hw_wait_phy_stable();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ci->in_lpm = false;
 | 
				
			||||||
 | 
						if (ci->wakeup_int) {
 | 
				
			||||||
 | 
							ci->wakeup_int = false;
 | 
				
			||||||
 | 
							pm_runtime_mark_last_busy(ci->dev);
 | 
				
			||||||
 | 
							pm_runtime_put_autosuspend(ci->dev);
 | 
				
			||||||
 | 
							enable_irq(ci->irq);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef CONFIG_PM_SLEEP
 | 
				
			||||||
static int ci_suspend(struct device *dev)
 | 
					static int ci_suspend(struct device *dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct ci_hdrc *ci = dev_get_drvdata(dev);
 | 
						struct ci_hdrc *ci = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (ci->wq)
 | 
						if (ci->wq)
 | 
				
			||||||
		flush_workqueue(ci->wq);
 | 
							flush_workqueue(ci->wq);
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Controller needs to be active during suspend, otherwise the core
 | 
				
			||||||
 | 
						 * may run resume when the parent is at suspend if other driver's
 | 
				
			||||||
 | 
						 * suspend fails, it occurs before parent's suspend has not started,
 | 
				
			||||||
 | 
						 * but the core suspend has finished.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (ci->in_lpm)
 | 
				
			||||||
 | 
							pm_runtime_resume(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ci->in_lpm) {
 | 
				
			||||||
 | 
							WARN_ON(1);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ci_controller_suspend(ci);
 | 
						ci_controller_suspend(ci);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -855,13 +905,51 @@ static int ci_suspend(struct device *dev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int ci_resume(struct device *dev)
 | 
					static int ci_resume(struct device *dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return ci_controller_resume(dev);
 | 
						struct ci_hdrc *ci = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
						int ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = ci_controller_resume(dev);
 | 
				
			||||||
 | 
						if (ret)
 | 
				
			||||||
 | 
							return ret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ci->supports_runtime_pm) {
 | 
				
			||||||
 | 
							pm_runtime_disable(dev);
 | 
				
			||||||
 | 
							pm_runtime_set_active(dev);
 | 
				
			||||||
 | 
							pm_runtime_enable(dev);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
#endif /* CONFIG_PM_SLEEP */
 | 
					#endif /* CONFIG_PM_SLEEP */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ci_runtime_suspend(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct ci_hdrc *ci = dev_get_drvdata(dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_dbg(dev, "at %s\n", __func__);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ci->in_lpm) {
 | 
				
			||||||
 | 
							WARN_ON(1);
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						usb_phy_set_wakeup(ci->usb_phy, true);
 | 
				
			||||||
 | 
						ci_controller_suspend(ci);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int ci_runtime_resume(struct device *dev)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return ci_controller_resume(dev);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif /* CONFIG_PM */
 | 
				
			||||||
static const struct dev_pm_ops ci_pm_ops = {
 | 
					static const struct dev_pm_ops ci_pm_ops = {
 | 
				
			||||||
	SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume)
 | 
						SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume)
 | 
				
			||||||
 | 
						SET_RUNTIME_PM_OPS(ci_runtime_suspend, ci_runtime_resume, NULL)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct platform_driver ci_hdrc_driver = {
 | 
					static struct platform_driver ci_hdrc_driver = {
 | 
				
			||||||
	.probe	= ci_hdrc_probe,
 | 
						.probe	= ci_hdrc_probe,
 | 
				
			||||||
	.remove	= ci_hdrc_remove,
 | 
						.remove	= ci_hdrc_remove,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -96,6 +96,7 @@ static void ci_otg_work(struct work_struct *work)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pm_runtime_get_sync(ci->dev);
 | 
				
			||||||
	if (ci->id_event) {
 | 
						if (ci->id_event) {
 | 
				
			||||||
		ci->id_event = false;
 | 
							ci->id_event = false;
 | 
				
			||||||
		ci_handle_id_switch(ci);
 | 
							ci_handle_id_switch(ci);
 | 
				
			||||||
| 
						 | 
					@ -104,6 +105,7 @@ static void ci_otg_work(struct work_struct *work)
 | 
				
			||||||
		ci_handle_vbus_change(ci);
 | 
							ci_handle_vbus_change(ci);
 | 
				
			||||||
	} else
 | 
						} else
 | 
				
			||||||
		dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
 | 
							dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
 | 
				
			||||||
 | 
						pm_runtime_put_sync(ci->dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	enable_irq(ci->irq);
 | 
						enable_irq(ci->irq);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,6 +19,7 @@ struct ci_hdrc_platform_data {
 | 
				
			||||||
	enum usb_phy_interface phy_mode;
 | 
						enum usb_phy_interface phy_mode;
 | 
				
			||||||
	unsigned long	 flags;
 | 
						unsigned long	 flags;
 | 
				
			||||||
#define CI_HDRC_REGS_SHARED		BIT(0)
 | 
					#define CI_HDRC_REGS_SHARED		BIT(0)
 | 
				
			||||||
 | 
					#define CI_HDRC_SUPPORTS_RUNTIME_PM	BIT(2)
 | 
				
			||||||
#define CI_HDRC_DISABLE_STREAMING	BIT(3)
 | 
					#define CI_HDRC_DISABLE_STREAMING	BIT(3)
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
	 * Only set it when DCCPARAMS.DC==1 and DCCPARAMS.HC==1,
 | 
						 * Only set it when DCCPARAMS.DC==1 and DCCPARAMS.HC==1,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue