Commit 48c98b1b authored by Mark Brown's avatar Mark Brown Committed by Dmitry Torokhov

Input: samsung-keypad - implement runtime power management support

When runtime power management is enabled put the Samsung keypad driver
into suspend mode with wakeups disabled whenever the device is open but
a key is not actually been pressed. As well as saving a trivial amount of
power this will support the use of SoC wide idle modes which put the entire
device into a retention mode and use explicit wakeup sources to exit.

Since not all of the interrupt controllers used with the driver support
set_irq_wake() (though they all do the right thing) and there's a nasty
WARN() when we disable wake after failing to enable it keep track of the
current wake status from runtime PM and only disable wake if we managed
to enable it; I'm not entirely sure why this doesn't affect the existing
uses of the API in the driver.

System suspend is unaffected as the driver core will runtime resume any
suspended devices prior to system suspend.
Signed-off-by: default avatarMark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: default avatarDmitry Torokhov <dtor@mail.ru>

Conflicts:

	drivers/input/keyboard/samsung-keypad.c
parent fd0fc213
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
#include <linux/io.h> #include <linux/io.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/input/samsung-keypad.h> #include <linux/input/samsung-keypad.h>
...@@ -63,10 +65,12 @@ enum samsung_keypad_type { ...@@ -63,10 +65,12 @@ enum samsung_keypad_type {
struct samsung_keypad { struct samsung_keypad {
struct input_dev *input_dev; struct input_dev *input_dev;
struct platform_device *pdev;
struct clk *clk; struct clk *clk;
void __iomem *base; void __iomem *base;
wait_queue_head_t wait; wait_queue_head_t wait;
bool stopped; bool stopped;
bool wake_enabled;
int irq; int irq;
unsigned int row_shift; unsigned int row_shift;
unsigned int rows; unsigned int rows;
...@@ -158,6 +162,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) ...@@ -158,6 +162,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)
unsigned int val; unsigned int val;
bool key_down; bool key_down;
pm_runtime_get_sync(&keypad->pdev->dev);
do { do {
val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR); val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);
/* Clear interrupt. */ /* Clear interrupt. */
...@@ -172,6 +178,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id) ...@@ -172,6 +178,8 @@ static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)
} while (key_down && !keypad->stopped); } while (key_down && !keypad->stopped);
pm_runtime_put_sync(&keypad->pdev->dev);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
...@@ -179,6 +187,8 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) ...@@ -179,6 +187,8 @@ static void samsung_keypad_start(struct samsung_keypad *keypad)
{ {
unsigned int val; unsigned int val;
pm_runtime_get_sync(&keypad->pdev->dev);
/* Tell IRQ thread that it may poll the device. */ /* Tell IRQ thread that it may poll the device. */
keypad->stopped = false; keypad->stopped = false;
...@@ -191,12 +201,16 @@ static void samsung_keypad_start(struct samsung_keypad *keypad) ...@@ -191,12 +201,16 @@ static void samsung_keypad_start(struct samsung_keypad *keypad)
/* KEYIFCOL reg clear. */ /* KEYIFCOL reg clear. */
writel(0, keypad->base + SAMSUNG_KEYIFCOL); writel(0, keypad->base + SAMSUNG_KEYIFCOL);
pm_runtime_put_sync(&keypad->pdev->dev);
} }
static void samsung_keypad_stop(struct samsung_keypad *keypad) static void samsung_keypad_stop(struct samsung_keypad *keypad)
{ {
unsigned int val; unsigned int val;
pm_runtime_get_sync(&keypad->pdev->dev);
/* Signal IRQ thread to stop polling and disable the handler. */ /* Signal IRQ thread to stop polling and disable the handler. */
keypad->stopped = true; keypad->stopped = true;
wake_up(&keypad->wait); wake_up(&keypad->wait);
...@@ -217,6 +231,8 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad) ...@@ -217,6 +231,8 @@ static void samsung_keypad_stop(struct samsung_keypad *keypad)
* re-enable the handler. * re-enable the handler.
*/ */
enable_irq(keypad->irq); enable_irq(keypad->irq);
pm_runtime_put_sync(&keypad->pdev->dev);
} }
static int samsung_keypad_open(struct input_dev *input_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) ...@@ -298,9 +314,11 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev)
} }
keypad->input_dev = input_dev; keypad->input_dev = input_dev;
keypad->pdev = pdev;
keypad->row_shift = row_shift; keypad->row_shift = row_shift;
keypad->rows = pdata->rows; keypad->rows = pdata->rows;
keypad->cols = pdata->cols; keypad->cols = pdata->cols;
keypad->stopped = true;
init_waitqueue_head(&keypad->wait); init_waitqueue_head(&keypad->wait);
input_dev->name = pdev->name; input_dev->name = pdev->name;
...@@ -337,16 +355,21 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev) ...@@ -337,16 +355,21 @@ static int __devinit samsung_keypad_probe(struct platform_device *pdev)
goto err_put_clk; 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); error = input_register_device(keypad->input_dev);
if (error) if (error)
goto err_free_irq; goto err_free_irq;
device_init_wakeup(&pdev->dev, pdata->wakeup);
platform_set_drvdata(pdev, keypad);
return 0; return 0;
err_free_irq: err_free_irq:
free_irq(keypad->irq, keypad); free_irq(keypad->irq, keypad);
pm_runtime_disable(&pdev->dev);
device_init_wakeup(&pdev->dev, 0);
platform_set_drvdata(pdev, NULL);
err_put_clk: err_put_clk:
clk_put(keypad->clk); clk_put(keypad->clk);
err_unmap_base: err_unmap_base:
...@@ -362,6 +385,7 @@ static int __devexit samsung_keypad_remove(struct platform_device *pdev) ...@@ -362,6 +385,7 @@ static int __devexit samsung_keypad_remove(struct platform_device *pdev)
{ {
struct samsung_keypad *keypad = platform_get_drvdata(pdev); struct samsung_keypad *keypad = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
device_init_wakeup(&pdev->dev, 0); device_init_wakeup(&pdev->dev, 0);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
...@@ -381,11 +405,57 @@ static int __devexit samsung_keypad_remove(struct platform_device *pdev) ...@@ -381,11 +405,57 @@ static int __devexit samsung_keypad_remove(struct platform_device *pdev)
return 0; 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 #ifdef CONFIG_PM_SLEEP
static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,
bool enable) bool enable)
{ {
struct device *dev = keypad->input_dev->dev.parent;
unsigned int val; unsigned int val;
clk_enable(keypad->clk); clk_enable(keypad->clk);
...@@ -393,11 +463,11 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, ...@@ -393,11 +463,11 @@ static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,
val = readl(keypad->base + SAMSUNG_KEYIFCON); val = readl(keypad->base + SAMSUNG_KEYIFCON);
if (enable) { if (enable) {
val |= SAMSUNG_KEYIFCON_WAKEUPEN; val |= SAMSUNG_KEYIFCON_WAKEUPEN;
if (device_may_wakeup(dev)) if (device_may_wakeup(&keypad->pdev->dev))
enable_irq_wake(keypad->irq); enable_irq_wake(keypad->irq);
} else { } else {
val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; val &= ~SAMSUNG_KEYIFCON_WAKEUPEN;
if (device_may_wakeup(dev)) if (device_may_wakeup(&keypad->pdev->dev))
disable_irq_wake(keypad->irq); disable_irq_wake(keypad->irq);
} }
writel(val, keypad->base + SAMSUNG_KEYIFCON); writel(val, keypad->base + SAMSUNG_KEYIFCON);
...@@ -442,8 +512,11 @@ static int samsung_keypad_resume(struct device *dev) ...@@ -442,8 +512,11 @@ static int samsung_keypad_resume(struct device *dev)
} }
#endif #endif
static SIMPLE_DEV_PM_OPS(samsung_keypad_pm_ops, static const struct dev_pm_ops samsung_keypad_pm_ops = {
samsung_keypad_suspend, samsung_keypad_resume); 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[] = { static struct platform_device_id samsung_keypad_driver_ids[] = {
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment