diff options
| -rw-r--r-- | drivers/input/keyboard/samsung-keypad.c | 87 | 
1 files changed, 80 insertions, 7 deletions
| diff --git a/drivers/input/keyboard/samsung-keypad.c b/drivers/input/keyboard/samsung-keypad.c index a6e010e016f8..b746fce2d120 100644 --- a/drivers/input/keyboard/samsung-keypad.c +++ b/drivers/input/keyboard/samsung-keypad.c @@ -20,6 +20,8 @@  #include <linux/io.h>  #include <linux/module.h>  #include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h>  #include <linux/slab.h>  #include <linux/sched.h>  #include <linux/input/samsung-keypad.h> @@ -63,10 +65,12 @@ enum samsung_keypad_type {  struct samsung_keypad {  	struct input_dev *input_dev; +	struct platform_device *pdev;  	struct clk *clk;  	void __iomem *base;  	wait_queue_head_t wait;  	bool stopped; +	bool wake_enabled;  	int irq;  	unsigned int row_shift;  	unsigned int rows; @@ -158,6 +162,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)  	unsigned int val;  	bool key_down; +	pm_runtime_get_sync(&keypad->pdev->dev); +  	do {  		val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);  		/* Clear interrupt. */ @@ -172,6 +178,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)  	} while (key_down && !keypad->stopped); +	pm_runtime_put_sync(&keypad->pdev->dev); +  	return IRQ_HANDLED;  } @@ -179,6 +187,8 @@ static void samsung_keypad_start(struct samsung_keypad *keypad)  {  	unsigned int val; +	pm_runtime_get_sync(&keypad->pdev->dev); +  	/* Tell IRQ thread that it may poll the device. */  	keypad->stopped = false; @@ -191,12 +201,16 @@ static void samsung_keypad_start(struct samsung_keypad *keypad)  	/* KEYIFCOL reg clear. */  	writel(0, keypad->base + SAMSUNG_KEYIFCOL); + +	pm_runtime_put_sync(&keypad->pdev->dev);  }  static void samsung_keypad_stop(struct samsung_keypad *keypad)  {  	unsigned int val; +	pm_runtime_get_sync(&keypad->pdev->dev); +  	/* Signal IRQ thread to stop polling and disable the handler. */  	keypad->stopped = true;  	wake_up(&keypad->wait); @@ -217,6 +231,8 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad)  	 * re-enable the handler.  	 */  	enable_irq(keypad->irq); + +	pm_runtime_put_sync(&keypad->pdev->dev);  }  static int samsung_keypad_open(struct input_dev *input_dev) @@ -298,9 +314,11 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev)  	}  	keypad->input_dev = input_dev; +	keypad->pdev = pdev;  	keypad->row_shift = row_shift;  	keypad->rows = pdata->rows;  	keypad->cols = pdata->cols; +	keypad->stopped = true;  	init_waitqueue_head(&keypad->wait);  	input_dev->name = pdev->name; @@ -337,16 +355,21 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev)  		goto err_put_clk;  	} +	device_init_wakeup(&pdev->dev, pdata->wakeup); +	platform_set_drvdata(pdev, keypad); +	pm_runtime_enable(&pdev->dev); +  	error = input_register_device(keypad->input_dev);  	if (error)  		goto err_free_irq; -	device_init_wakeup(&pdev->dev, pdata->wakeup); -	platform_set_drvdata(pdev, keypad);  	return 0;  err_free_irq:  	free_irq(keypad->irq, keypad); +	pm_runtime_disable(&pdev->dev); +	device_init_wakeup(&pdev->dev, 0); +	platform_set_drvdata(pdev, NULL);  err_put_clk:  	clk_put(keypad->clk);  err_unmap_base: @@ -362,6 +385,7 @@ static int __devexit samsung_keypad_remove(struct platform_device *pdev)  {  	struct samsung_keypad *keypad = platform_get_drvdata(pdev); +	pm_runtime_disable(&pdev->dev);  	device_init_wakeup(&pdev->dev, 0);  	platform_set_drvdata(pdev, NULL); @@ -381,11 +405,57 @@ static int __devexit samsung_keypad_remove(struct platform_device *pdev)  	return 0;  } +#ifdef CONFIG_PM_RUNTIME +static int samsung_keypad_runtime_suspend(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct samsung_keypad *keypad = platform_get_drvdata(pdev); +	unsigned int val; +	int error; + +	if (keypad->stopped) +		return 0; + +	/* This may fail on some SoCs due to lack of controller support */ +	error = enable_irq_wake(keypad->irq); +	if (!error) +		keypad->wake_enabled = true; + +	val = readl(keypad->base + SAMSUNG_KEYIFCON); +	val |= SAMSUNG_KEYIFCON_WAKEUPEN; +	writel(val, keypad->base + SAMSUNG_KEYIFCON); + +	clk_disable(keypad->clk); + +	return 0; +} + +static int samsung_keypad_runtime_resume(struct device *dev) +{ +	struct platform_device *pdev = to_platform_device(dev); +	struct samsung_keypad *keypad = platform_get_drvdata(pdev); +	unsigned int val; + +	if (keypad->stopped) +		return 0; + +	clk_enable(keypad->clk); + +	val = readl(keypad->base + SAMSUNG_KEYIFCON); +	val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; +	writel(val, keypad->base + SAMSUNG_KEYIFCON); + +	if (keypad->wake_enabled) +		disable_irq_wake(keypad->irq); + +	return 0; +} +#endif +  #ifdef CONFIG_PM_SLEEP  static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,  					 bool enable)  { -	struct device *dev = keypad->input_dev->dev.parent;  	unsigned int val;  	clk_enable(keypad->clk); @@ -393,11 +463,11 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,  	val = readl(keypad->base + SAMSUNG_KEYIFCON);  	if (enable) {  		val |= SAMSUNG_KEYIFCON_WAKEUPEN; -		if (device_may_wakeup(dev)) +		if (device_may_wakeup(&keypad->pdev->dev))  			enable_irq_wake(keypad->irq);  	} else {  		val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; -		if (device_may_wakeup(dev)) +		if (device_may_wakeup(&keypad->pdev->dev))  			disable_irq_wake(keypad->irq);  	}  	writel(val, keypad->base + SAMSUNG_KEYIFCON); @@ -442,8 +512,11 @@ static int samsung_keypad_resume(struct device *dev)  }  #endif -static SIMPLE_DEV_PM_OPS(samsung_keypad_pm_ops, -			 samsung_keypad_suspend, samsung_keypad_resume); +static const struct dev_pm_ops samsung_keypad_pm_ops = { +	SET_SYSTEM_SLEEP_PM_OPS(samsung_keypad_suspend, samsung_keypad_resume) +	SET_RUNTIME_PM_OPS(samsung_keypad_runtime_suspend, +			   samsung_keypad_runtime_resume, NULL) +};  static struct platform_device_id samsung_keypad_driver_ids[] = {  	{ | 
