Commit 1d375b58 authored by Hans de Goede's avatar Hans de Goede Committed by Thierry Reding

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: default avatarHans de Goede <hdegoede@redhat.com>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: default avatarThierry Reding <thierry.reding@gmail.com>
parent 692099cd
...@@ -74,6 +74,10 @@ static int pwm_lpss_remove_platform(struct platform_device *pdev) ...@@ -74,6 +74,10 @@ static int pwm_lpss_remove_platform(struct platform_device *pdev)
return pwm_lpss_remove(lpwm); return pwm_lpss_remove(lpwm);
} }
static SIMPLE_DEV_PM_OPS(pwm_lpss_platform_pm_ops,
pwm_lpss_suspend,
pwm_lpss_resume);
static const struct acpi_device_id pwm_lpss_acpi_match[] = { static const struct acpi_device_id pwm_lpss_acpi_match[] = {
{ "80860F09", (unsigned long)&pwm_lpss_byt_info }, { "80860F09", (unsigned long)&pwm_lpss_byt_info },
{ "80862288", (unsigned long)&pwm_lpss_bsw_info }, { "80862288", (unsigned long)&pwm_lpss_bsw_info },
...@@ -86,6 +90,7 @@ static struct platform_driver pwm_lpss_driver_platform = { ...@@ -86,6 +90,7 @@ static struct platform_driver pwm_lpss_driver_platform = {
.driver = { .driver = {
.name = "pwm-lpss", .name = "pwm-lpss",
.acpi_match_table = pwm_lpss_acpi_match, .acpi_match_table = pwm_lpss_acpi_match,
.pm = &pwm_lpss_platform_pm_ops,
}, },
.probe = pwm_lpss_probe_platform, .probe = pwm_lpss_probe_platform,
.remove = pwm_lpss_remove_platform, .remove = pwm_lpss_remove_platform,
......
...@@ -32,10 +32,13 @@ ...@@ -32,10 +32,13 @@
/* Size of each PWM register space if multiple */ /* Size of each PWM register space if multiple */
#define PWM_SIZE 0x400 #define PWM_SIZE 0x400
#define MAX_PWMS 4
struct pwm_lpss_chip { struct pwm_lpss_chip {
struct pwm_chip chip; struct pwm_chip chip;
void __iomem *regs; void __iomem *regs;
const struct pwm_lpss_boardinfo *info; const struct pwm_lpss_boardinfo *info;
u32 saved_ctrl[MAX_PWMS];
}; };
static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) static 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,6 +180,9 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
unsigned long c; unsigned long c;
int ret; int ret;
if (WARN_ON(info->npwm > MAX_PWMS))
return ERR_PTR(-ENODEV);
lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL);
if (!lpwm) if (!lpwm)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -212,6 +218,30 @@ int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) ...@@ -212,6 +218,30 @@ int pwm_lpss_remove(struct pwm_lpss_chip *lpwm)
} }
EXPORT_SYMBOL_GPL(pwm_lpss_remove); EXPORT_SYMBOL_GPL(pwm_lpss_remove);
int pwm_lpss_suspend(struct device *dev)
{
struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev);
int i;
for (i = 0; i < lpwm->info->npwm; i++)
lpwm->saved_ctrl[i] = readl(lpwm->regs + i * PWM_SIZE + PWM);
return 0;
}
EXPORT_SYMBOL_GPL(pwm_lpss_suspend);
int pwm_lpss_resume(struct device *dev)
{
struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev);
int i;
for (i = 0; i < lpwm->info->npwm; i++)
writel(lpwm->saved_ctrl[i], lpwm->regs + i * PWM_SIZE + PWM);
return 0;
}
EXPORT_SYMBOL_GPL(pwm_lpss_resume);
MODULE_DESCRIPTION("PWM driver for Intel LPSS"); MODULE_DESCRIPTION("PWM driver for Intel LPSS");
MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
...@@ -28,5 +28,7 @@ struct pwm_lpss_boardinfo { ...@@ -28,5 +28,7 @@ struct pwm_lpss_boardinfo {
struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
const struct pwm_lpss_boardinfo *info); const struct pwm_lpss_boardinfo *info);
int pwm_lpss_remove(struct pwm_lpss_chip *lpwm); int pwm_lpss_remove(struct pwm_lpss_chip *lpwm);
int pwm_lpss_suspend(struct device *dev);
int pwm_lpss_resume(struct device *dev);
#endif /* __PWM_LPSS_H */ #endif /* __PWM_LPSS_H */
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