aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans de Goede <hdegoede@redhat.com>2018-04-26 08:10:23 -0400
committerThierry Reding <thierry.reding@gmail.com>2018-06-06 04:00:39 -0400
commit1d375b58c12f08d8570b30b865def4734517f04f (patch)
tree9e515a630a8cbc88b57bf5e1fa789e83ad347ff2
parent692099cdcf275df272936b31c8409f2a5a1f7239 (diff)
pwm: lpss: platform: Save/restore the ctrl register over a suspend/resume
On some devices the contents of the ctrl register get lost over a suspend/resume and the PWM comes back up disabled after the resume. This is seen on some Bay Trail devices with the PWM in ACPI enumerated mode, so it shows up as a platform device instead of a PCI device. If we still think it is enabled and then try to change the duty-cycle after this, we end up with a "PWM_SW_UPDATE was not cleared" error and the PWM is stuck in that state from then on. This commit adds suspend and resume pm callbacks to the pwm-lpss-platform code, which save/restore the ctrl register over a suspend/resume, fixing this. Note that: 1) There is no need to do this over a runtime suspend, since we only runtime suspend when disabled and then we properly set the enable bit and reprogram the timings when we re-enable the PWM. 2) This may be happening on more systems then we realize, but has been covered up sofar by a bug in the acpi-lpss.c code which was save/restoring the regular device registers instead of the lpss private registers due to lpss_device_desc.prv_offset not being set. This is fixed by a later patch in this series. Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r--drivers/pwm/pwm-lpss-platform.c5
-rw-r--r--drivers/pwm/pwm-lpss.c30
-rw-r--r--drivers/pwm/pwm-lpss.h2
3 files changed, 37 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-lpss-platform.c b/drivers/pwm/pwm-lpss-platform.c
index 5d6ed1507d29..5561b9e190f8 100644
--- a/drivers/pwm/pwm-lpss-platform.c
+++ b/drivers/pwm/pwm-lpss-platform.c
@@ -74,6 +74,10 @@ static int pwm_lpss_remove_platform(struct platform_device *pdev)
74 return pwm_lpss_remove(lpwm); 74 return pwm_lpss_remove(lpwm);
75} 75}
76 76
77static SIMPLE_DEV_PM_OPS(pwm_lpss_platform_pm_ops,
78 pwm_lpss_suspend,
79 pwm_lpss_resume);
80
77static const struct acpi_device_id pwm_lpss_acpi_match[] = { 81static const struct acpi_device_id pwm_lpss_acpi_match[] = {
78 { "80860F09", (unsigned long)&pwm_lpss_byt_info }, 82 { "80860F09", (unsigned long)&pwm_lpss_byt_info },
79 { "80862288", (unsigned long)&pwm_lpss_bsw_info }, 83 { "80862288", (unsigned long)&pwm_lpss_bsw_info },
@@ -86,6 +90,7 @@ static struct platform_driver pwm_lpss_driver_platform = {
86 .driver = { 90 .driver = {
87 .name = "pwm-lpss", 91 .name = "pwm-lpss",
88 .acpi_match_table = pwm_lpss_acpi_match, 92 .acpi_match_table = pwm_lpss_acpi_match,
93 .pm = &pwm_lpss_platform_pm_ops,
89 }, 94 },
90 .probe = pwm_lpss_probe_platform, 95 .probe = pwm_lpss_probe_platform,
91 .remove = pwm_lpss_remove_platform, 96 .remove = pwm_lpss_remove_platform,
diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c
index 8db0d40ccacd..4721a264bac2 100644
--- a/drivers/pwm/pwm-lpss.c
+++ b/drivers/pwm/pwm-lpss.c
@@ -32,10 +32,13 @@
32/* Size of each PWM register space if multiple */ 32/* Size of each PWM register space if multiple */
33#define PWM_SIZE 0x400 33#define PWM_SIZE 0x400
34 34
35#define MAX_PWMS 4
36
35struct pwm_lpss_chip { 37struct pwm_lpss_chip {
36 struct pwm_chip chip; 38 struct pwm_chip chip;
37 void __iomem *regs; 39 void __iomem *regs;
38 const struct pwm_lpss_boardinfo *info; 40 const struct pwm_lpss_boardinfo *info;
41 u32 saved_ctrl[MAX_PWMS];
39}; 42};
40 43
41static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) 44static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip)
@@ -177,6 +180,9 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
177 unsigned long c; 180 unsigned long c;
178 int ret; 181 int ret;
179 182
183 if (WARN_ON(info->npwm > MAX_PWMS))
184 return ERR_PTR(-ENODEV);
185
180 lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); 186 lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL);
181 if (!lpwm) 187 if (!lpwm)
182 return ERR_PTR(-ENOMEM); 188 return ERR_PTR(-ENOMEM);
@@ -212,6 +218,30 @@ int pwm_lpss_remove(struct pwm_lpss_chip *lpwm)
212} 218}
213EXPORT_SYMBOL_GPL(pwm_lpss_remove); 219EXPORT_SYMBOL_GPL(pwm_lpss_remove);
214 220
221int pwm_lpss_suspend(struct device *dev)
222{
223 struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev);
224 int i;
225
226 for (i = 0; i < lpwm->info->npwm; i++)
227 lpwm->saved_ctrl[i] = readl(lpwm->regs + i * PWM_SIZE + PWM);
228
229 return 0;
230}
231EXPORT_SYMBOL_GPL(pwm_lpss_suspend);
232
233int pwm_lpss_resume(struct device *dev)
234{
235 struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev);
236 int i;
237
238 for (i = 0; i < lpwm->info->npwm; i++)
239 writel(lpwm->saved_ctrl[i], lpwm->regs + i * PWM_SIZE + PWM);
240
241 return 0;
242}
243EXPORT_SYMBOL_GPL(pwm_lpss_resume);
244
215MODULE_DESCRIPTION("PWM driver for Intel LPSS"); 245MODULE_DESCRIPTION("PWM driver for Intel LPSS");
216MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); 246MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
217MODULE_LICENSE("GPL v2"); 247MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h
index 98306bb02cfe..7a4238ad1fcb 100644
--- a/drivers/pwm/pwm-lpss.h
+++ b/drivers/pwm/pwm-lpss.h
@@ -28,5 +28,7 @@ struct pwm_lpss_boardinfo {
28struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, 28struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
29 const struct pwm_lpss_boardinfo *info); 29 const struct pwm_lpss_boardinfo *info);
30int pwm_lpss_remove(struct pwm_lpss_chip *lpwm); 30int pwm_lpss_remove(struct pwm_lpss_chip *lpwm);
31int pwm_lpss_suspend(struct device *dev);
32int pwm_lpss_resume(struct device *dev);
31 33
32#endif /* __PWM_LPSS_H */ 34#endif /* __PWM_LPSS_H */