summaryrefslogtreecommitdiff
path: root/drivers/i2c
diff options
context:
space:
mode:
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>2017-09-25 02:30:51 +0300
committerWolfram Sang <wsa@the-dreams.de>2017-10-05 14:00:29 +0300
commit541527728341b4bb9a5e3428b0eec450f1b3d8d5 (patch)
treee6d10b5567973cdee07209939d3f2cbf298e01a3 /drivers/i2c
parente00952c7904a34359872813c3b072eba55065090 (diff)
downloadlinux-541527728341b4bb9a5e3428b0eec450f1b3d8d5.tar.xz
PM: i2c-designware-platdrv: Suspend/resume at the late/early stages
As reported by Rajat Jain, there are problems when ACPI operation region handlers or similar, called at the ->resume_early() time, for I2C client devices try to access an I2C controller that has already been suspended at that point. To avoid that, move the suspend/resume of i2c-designware-platdrv to the late/early stages, respectively. While at it, avoid resuming the device from runtime suspend in the driver's ->suspend callback which isn't particularly nice. [A better approach would be to make the driver track the PM state of the device so that it doesn't need to resume it in ->suspend, so implement it.] First, drop dw_i2c_plat_suspend() added by commit a23318feeff6 (i2c: designware: Fix system suspend) and rename dw_i2c_plat_runtime_suspend() back to dw_i2c_plat_suspend(). Second, point the driver's ->late_suspend and ->early_resume callbacks, rather than its ->suspend and ->resume callbacks, to dw_i2c_plat_suspend() and dw_i2c_plat_resume(), respectively, so that they are not executed in parallel with each other, for example if runtime resume of the device takes place during system suspend. Finally, add "suspended" and "skip_resume" flags to struct dw_i2c_dev and make dw_i2c_plat_suspend() and dw_i2c_plat_resume() use them to avoid suspending or resuming the device twice in a row and to avoid resuming a previously runtime-suspended device during system resume. Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com> Tested-by: Jarkko Nikula <jarkko.nikula@linux.intel.com> Tested-by: Mika Westerberg <mika.westerberg@linux.intel.com> Tested-by: Johannes Stezenbach <js@sig21.net> Tested-by: Rajat Jain <rajatja@google.com> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
Diffstat (limited to 'drivers/i2c')
-rw-r--r--drivers/i2c/busses/i2c-designware-core.h2
-rw-r--r--drivers/i2c/busses/i2c-designware-platdrv.c33
2 files changed, 22 insertions, 13 deletions
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 9fee4c054d3d..21bf619a86c5 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -280,6 +280,8 @@ struct dw_i2c_dev {
int (*acquire_lock)(struct dw_i2c_dev *dev);
void (*release_lock)(struct dw_i2c_dev *dev);
bool pm_disabled;
+ bool suspended;
+ bool skip_resume;
void (*disable)(struct dw_i2c_dev *dev);
void (*disable_int)(struct dw_i2c_dev *dev);
int (*init)(struct dw_i2c_dev *dev);
diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c
index 532e5a6e8512..16c027b62a20 100644
--- a/drivers/i2c/busses/i2c-designware-platdrv.c
+++ b/drivers/i2c/busses/i2c-designware-platdrv.c
@@ -447,13 +447,20 @@ static void dw_i2c_plat_complete(struct device *dev)
#endif
#ifdef CONFIG_PM
-static int dw_i2c_plat_runtime_suspend(struct device *dev)
+static int dw_i2c_plat_suspend(struct device *dev)
{
struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
+ if (i_dev->suspended) {
+ i_dev->skip_resume = true;
+ return 0;
+ }
+
i_dev->disable(i_dev);
i2c_dw_plat_prepare_clk(i_dev, false);
+ i_dev->suspended = true;
+
return 0;
}
@@ -461,27 +468,27 @@ static int dw_i2c_plat_resume(struct device *dev)
{
struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
+ if (!i_dev->suspended)
+ return 0;
+
+ if (i_dev->skip_resume) {
+ i_dev->skip_resume = false;
+ return 0;
+ }
+
i2c_dw_plat_prepare_clk(i_dev, true);
i_dev->init(i_dev);
- return 0;
-}
+ i_dev->suspended = false;
-#ifdef CONFIG_PM_SLEEP
-static int dw_i2c_plat_suspend(struct device *dev)
-{
- pm_runtime_resume(dev);
- return dw_i2c_plat_runtime_suspend(dev);
+ return 0;
}
-#endif
static const struct dev_pm_ops dw_i2c_dev_pm_ops = {
.prepare = dw_i2c_plat_prepare,
.complete = dw_i2c_plat_complete,
- SET_SYSTEM_SLEEP_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume)
- SET_RUNTIME_PM_OPS(dw_i2c_plat_runtime_suspend,
- dw_i2c_plat_resume,
- NULL)
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume)
+ SET_RUNTIME_PM_OPS(dw_i2c_plat_suspend, dw_i2c_plat_resume, NULL)
};
#define DW_I2C_DEV_PMOPS (&dw_i2c_dev_pm_ops)