qup_i2c: Add runtime PM support to I2C QUP controller
Runtime PM is used to turn clocks on and off when the
controller is not in use. When the controller is used
for messaging, pm_runtime_get_sync is called before
sending the message. pm_runtime_mark_last_busy and
pm_runtime_put_autosuspend are called when message
is sent and/or response is received.
Change-Id: Icb1d6e6ad222663c9a3598da10bdc154845083a0
CRs-Fixed: 406910
Signed-off-by: Alok Chauhan <alokc@codeaurora.org>
Signed-off-by: Sudhir Sharma <sudsha@codeaurora.org>
Signed-off-by: Manish Kumar <manishku@codeaurora.org>
diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c
index 085b632..92d162b 100644
--- a/drivers/i2c/busses/i2c-qup.c
+++ b/drivers/i2c/busses/i2c-qup.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009-2012, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2009-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -164,7 +164,6 @@
struct msm_i2c_platform_data *pdata;
int suspended;
int clk_state;
- struct timer_list pwr_timer;
struct mutex mlock;
void *complete;
int i2c_gpios[ARRAY_SIZE(i2c_rsrcs)];
@@ -197,6 +196,9 @@
uint32_t op_flgs = readl_relaxed(dev->base + QUP_OPERATIONAL);
int err = 0;
+ if (pm_runtime_suspended(dev->dev))
+ return IRQ_NONE;
+
if (!dev->msg || !dev->complete) {
/* Clear Error interrupt if it's a level triggered interrupt*/
if (dev->num_irqs == 1) {
@@ -327,27 +329,18 @@
{
dev->clk_state = state;
if (state != 0) {
- clk_enable(dev->clk);
+ clk_prepare_enable(dev->clk);
if (!dev->pdata->keep_ahb_clk_on)
- clk_enable(dev->pclk);
+ clk_prepare_enable(dev->pclk);
} else {
qup_update_state(dev, QUP_RESET_STATE);
- clk_disable(dev->clk);
+ clk_disable_unprepare(dev->clk);
qup_config_core_on_en(dev);
if (!dev->pdata->keep_ahb_clk_on)
- clk_disable(dev->pclk);
+ clk_disable_unprepare(dev->pclk);
}
}
-static void
-qup_i2c_pwr_timer(unsigned long data)
-{
- struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data;
- dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n");
- if (dev->clk_state == 1)
- qup_i2c_pwr_mgmt(dev, 0);
-}
-
static int
qup_i2c_poll_writeready(struct qup_i2c_dev *dev, int rem)
{
@@ -756,7 +749,7 @@
long timeout;
int err;
- del_timer_sync(&dev->pwr_timer);
+ pm_runtime_get_sync(dev->dev);
mutex_lock(&dev->mlock);
if (dev->suspended) {
@@ -764,9 +757,6 @@
return -EIO;
}
- if (dev->clk_state == 0)
- qup_i2c_pwr_mgmt(dev, 1);
-
/* Initialize QUP registers during first transfer */
if (dev->clk_ctl == 0) {
int fs_div;
@@ -1068,9 +1058,9 @@
dev->pos = 0;
dev->err = 0;
dev->cnt = 0;
- dev->pwr_timer.expires = jiffies + 3*HZ;
- add_timer(&dev->pwr_timer);
mutex_unlock(&dev->mlock);
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
return ret;
}
@@ -1322,21 +1312,14 @@
if (pdata->msm_i2c_config_gpio)
pdata->msm_i2c_config_gpio(dev->adapter.nr, 1);
- dev->suspended = 0;
mutex_init(&dev->mlock);
dev->clk_state = 0;
- clk_prepare(dev->clk);
- clk_prepare(dev->pclk);
/* If the same AHB clock is used on Modem side
* switch it on here itself and don't switch it
* on and off during suspend and resume.
*/
if (dev->pdata->keep_ahb_clk_on)
- clk_enable(dev->pclk);
- setup_timer(&dev->pwr_timer, qup_i2c_pwr_timer, (unsigned long) dev);
-
- pm_runtime_set_active(&pdev->dev);
- pm_runtime_enable(&pdev->dev);
+ clk_prepare_enable(dev->pclk);
ret = i2c_add_numbered_adapter(&dev->adapter);
if (ret) {
@@ -1351,6 +1334,10 @@
dev->adapter.dev.of_node = pdev->dev.of_node;
of_i2c_register_devices(&dev->adapter);
}
+
+ pm_runtime_set_autosuspend_delay(&pdev->dev, MSEC_PER_SEC);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
return 0;
}
@@ -1393,7 +1380,6 @@
dev->suspended = 1;
mutex_unlock(&dev->mlock);
mutex_destroy(&dev->mlock);
- del_timer_sync(&dev->pwr_timer);
if (dev->clk_state != 0)
qup_i2c_pwr_mgmt(dev, 0);
platform_set_drvdata(pdev, NULL);
@@ -1403,9 +1389,7 @@
}
free_irq(dev->err_irq, dev);
i2c_del_adapter(&dev->adapter);
- clk_unprepare(dev->clk);
if (!dev->pdata->keep_ahb_clk_on) {
- clk_unprepare(dev->pclk);
clk_put(dev->pclk);
}
clk_put(dev->clk);
@@ -1415,6 +1399,7 @@
iounmap(dev->base);
pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
if (!(dev->pdata->use_gsbi_shared_mode)) {
gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
@@ -1431,67 +1416,63 @@
}
#ifdef CONFIG_PM
-static int qup_i2c_suspend(struct device *device)
+static int i2c_qup_pm_suspend_runtime(struct device *device)
{
struct platform_device *pdev = to_platform_device(device);
struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
-
+ dev_dbg(device, "pm_runtime: suspending...\n");
/* Grab mutex to ensure ongoing transaction is over */
mutex_lock(&dev->mlock);
dev->suspended = 1;
mutex_unlock(&dev->mlock);
- del_timer_sync(&dev->pwr_timer);
if (dev->clk_state != 0)
qup_i2c_pwr_mgmt(dev, 0);
- clk_unprepare(dev->clk);
- if (!dev->pdata->keep_ahb_clk_on)
- clk_unprepare(dev->pclk);
qup_i2c_free_gpios(dev);
return 0;
}
+static int i2c_qup_pm_resume_runtime(struct device *device)
+{
+ struct platform_device *pdev = to_platform_device(device);
+ struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
+ dev_dbg(device, "pm_runtime: resuming...\n");
+ BUG_ON(qup_i2c_request_gpios(dev) != 0);
+ if (dev->clk_state == 0)
+ qup_i2c_pwr_mgmt(dev, 1);
+ dev->suspended = 0;
+ return 0;
+}
+
+static int qup_i2c_suspend(struct device *device)
+{
+ if (!pm_runtime_enabled(device) || !pm_runtime_suspended(device)) {
+ dev_dbg(device, "system suspend");
+ i2c_qup_pm_suspend_runtime(device);
+ }
+ return 0;
+}
+
static int qup_i2c_resume(struct device *device)
{
- struct platform_device *pdev = to_platform_device(device);
- struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
- BUG_ON(qup_i2c_request_gpios(dev) != 0);
- clk_prepare(dev->clk);
- if (!dev->pdata->keep_ahb_clk_on)
- clk_prepare(dev->pclk);
- dev->suspended = 0;
+ if (!pm_runtime_enabled(device) || !pm_runtime_suspended(device)) {
+ dev_dbg(device, "system resume");
+ i2c_qup_pm_resume_runtime(device);
+ pm_runtime_mark_last_busy(device);
+ pm_request_autosuspend(device);
+ }
return 0;
}
#endif /* CONFIG_PM */
-#ifdef CONFIG_PM_RUNTIME
-static int i2c_qup_runtime_idle(struct device *dev)
-{
- dev_dbg(dev, "pm_runtime: idle...\n");
- return 0;
-}
-
-static int i2c_qup_runtime_suspend(struct device *dev)
-{
- dev_dbg(dev, "pm_runtime: suspending...\n");
- return 0;
-}
-
-static int i2c_qup_runtime_resume(struct device *dev)
-{
- dev_dbg(dev, "pm_runtime: resuming...\n");
- return 0;
-}
-#endif
-
static const struct dev_pm_ops i2c_qup_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(
qup_i2c_suspend,
qup_i2c_resume
)
SET_RUNTIME_PM_OPS(
- i2c_qup_runtime_suspend,
- i2c_qup_runtime_resume,
- i2c_qup_runtime_idle
+ i2c_qup_pm_suspend_runtime,
+ i2c_qup_pm_resume_runtime,
+ NULL
)
};