Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>

Conflicts:

drivers/input/keyboard/samsung-keypad.c

authored by

Mark Brown and committed by
Dmitry Torokhov
48c98b1b fd0fc213

+80 -7
+80 -7
drivers/input/keyboard/samsung-keypad.c
··· 20 20 #include <linux/io.h> 21 21 #include <linux/module.h> 22 22 #include <linux/platform_device.h> 23 + #include <linux/pm.h> 24 + #include <linux/pm_runtime.h> 23 25 #include <linux/slab.h> 24 26 #include <linux/sched.h> 25 27 #include <linux/input/samsung-keypad.h> ··· 65 63 66 64 struct samsung_keypad { 67 65 struct input_dev *input_dev; 66 + struct platform_device *pdev; 68 67 struct clk *clk; 69 68 void __iomem *base; 70 69 wait_queue_head_t wait; 71 70 bool stopped; 71 + bool wake_enabled; 72 72 int irq; 73 73 unsigned int row_shift; 74 74 unsigned int rows; ··· 162 158 unsigned int val; 163 159 bool key_down; 164 160 161 + pm_runtime_get_sync(&keypad->pdev->dev); 162 + 165 163 do { 166 164 val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR); 167 165 /* Clear interrupt. */ ··· 178 172 179 173 } while (key_down && !keypad->stopped); 180 174 175 + pm_runtime_put_sync(&keypad->pdev->dev); 176 + 181 177 return IRQ_HANDLED; 182 178 } 183 179 184 180 static void samsung_keypad_start(struct samsung_keypad *keypad) 185 181 { 186 182 unsigned int val; 183 + 184 + pm_runtime_get_sync(&keypad->pdev->dev); 187 185 188 186 /* Tell IRQ thread that it may poll the device. */ 189 187 keypad->stopped = false; ··· 201 191 202 192 /* KEYIFCOL reg clear. */ 203 193 writel(0, keypad->base + SAMSUNG_KEYIFCOL); 194 + 195 + pm_runtime_put_sync(&keypad->pdev->dev); 204 196 } 205 197 206 198 static void samsung_keypad_stop(struct samsung_keypad *keypad) 207 199 { 208 200 unsigned int val; 201 + 202 + pm_runtime_get_sync(&keypad->pdev->dev); 209 203 210 204 /* Signal IRQ thread to stop polling and disable the handler. */ 211 205 keypad->stopped = true; ··· 231 217 * re-enable the handler. 232 218 */ 233 219 enable_irq(keypad->irq); 220 + 221 + pm_runtime_put_sync(&keypad->pdev->dev); 234 222 } 235 223 236 224 static int samsung_keypad_open(struct input_dev *input_dev) ··· 314 298 } 315 299 316 300 keypad->input_dev = input_dev; 301 + keypad->pdev = pdev; 317 302 keypad->row_shift = row_shift; 318 303 keypad->rows = pdata->rows; 319 304 keypad->cols = pdata->cols; 305 + keypad->stopped = true; 320 306 init_waitqueue_head(&keypad->wait); 321 307 322 308 input_dev->name = pdev->name; ··· 355 337 goto err_put_clk; 356 338 } 357 339 340 + device_init_wakeup(&pdev->dev, pdata->wakeup); 341 + platform_set_drvdata(pdev, keypad); 342 + pm_runtime_enable(&pdev->dev); 343 + 358 344 error = input_register_device(keypad->input_dev); 359 345 if (error) 360 346 goto err_free_irq; 361 347 362 - device_init_wakeup(&pdev->dev, pdata->wakeup); 363 - platform_set_drvdata(pdev, keypad); 364 348 return 0; 365 349 366 350 err_free_irq: 367 351 free_irq(keypad->irq, keypad); 352 + pm_runtime_disable(&pdev->dev); 353 + device_init_wakeup(&pdev->dev, 0); 354 + platform_set_drvdata(pdev, NULL); 368 355 err_put_clk: 369 356 clk_put(keypad->clk); 370 357 err_unmap_base: ··· 385 362 { 386 363 struct samsung_keypad *keypad = platform_get_drvdata(pdev); 387 364 365 + pm_runtime_disable(&pdev->dev); 388 366 device_init_wakeup(&pdev->dev, 0); 389 367 platform_set_drvdata(pdev, NULL); 390 368 ··· 405 381 return 0; 406 382 } 407 383 384 + #ifdef CONFIG_PM_RUNTIME 385 + static int samsung_keypad_runtime_suspend(struct device *dev) 386 + { 387 + struct platform_device *pdev = to_platform_device(dev); 388 + struct samsung_keypad *keypad = platform_get_drvdata(pdev); 389 + unsigned int val; 390 + int error; 391 + 392 + if (keypad->stopped) 393 + return 0; 394 + 395 + /* This may fail on some SoCs due to lack of controller support */ 396 + error = enable_irq_wake(keypad->irq); 397 + if (!error) 398 + keypad->wake_enabled = true; 399 + 400 + val = readl(keypad->base + SAMSUNG_KEYIFCON); 401 + val |= SAMSUNG_KEYIFCON_WAKEUPEN; 402 + writel(val, keypad->base + SAMSUNG_KEYIFCON); 403 + 404 + clk_disable(keypad->clk); 405 + 406 + return 0; 407 + } 408 + 409 + static int samsung_keypad_runtime_resume(struct device *dev) 410 + { 411 + struct platform_device *pdev = to_platform_device(dev); 412 + struct samsung_keypad *keypad = platform_get_drvdata(pdev); 413 + unsigned int val; 414 + 415 + if (keypad->stopped) 416 + return 0; 417 + 418 + clk_enable(keypad->clk); 419 + 420 + val = readl(keypad->base + SAMSUNG_KEYIFCON); 421 + val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; 422 + writel(val, keypad->base + SAMSUNG_KEYIFCON); 423 + 424 + if (keypad->wake_enabled) 425 + disable_irq_wake(keypad->irq); 426 + 427 + return 0; 428 + } 429 + #endif 430 + 408 431 #ifdef CONFIG_PM_SLEEP 409 432 static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad, 410 433 bool enable) 411 434 { 412 - struct device *dev = keypad->input_dev->dev.parent; 413 435 unsigned int val; 414 436 415 437 clk_enable(keypad->clk); ··· 463 393 val = readl(keypad->base + SAMSUNG_KEYIFCON); 464 394 if (enable) { 465 395 val |= SAMSUNG_KEYIFCON_WAKEUPEN; 466 - if (device_may_wakeup(dev)) 396 + if (device_may_wakeup(&keypad->pdev->dev)) 467 397 enable_irq_wake(keypad->irq); 468 398 } else { 469 399 val &= ~SAMSUNG_KEYIFCON_WAKEUPEN; 470 - if (device_may_wakeup(dev)) 400 + if (device_may_wakeup(&keypad->pdev->dev)) 471 401 disable_irq_wake(keypad->irq); 472 402 } 473 403 writel(val, keypad->base + SAMSUNG_KEYIFCON); ··· 512 442 } 513 443 #endif 514 444 515 - static SIMPLE_DEV_PM_OPS(samsung_keypad_pm_ops, 516 - samsung_keypad_suspend, samsung_keypad_resume); 445 + static const struct dev_pm_ops samsung_keypad_pm_ops = { 446 + SET_SYSTEM_SLEEP_PM_OPS(samsung_keypad_suspend, samsung_keypad_resume) 447 + SET_RUNTIME_PM_OPS(samsung_keypad_runtime_suspend, 448 + samsung_keypad_runtime_resume, NULL) 449 + }; 517 450 518 451 static struct platform_device_id samsung_keypad_driver_ids[] = { 519 452 {