Message ID | 20200708211432.28612-6-hdegoede@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | acpi/pwm/i915: Convert pwm-crc and i915 driver's PWM code to use the atomic PWM API | expand |
On Wed, Jul 08, 2020 at 11:14:21PM +0200, Hans de Goede wrote: > Before this commit a suspend + resume of the LPSS PWM controller > would result in the controller being reset to its defaults of > output-freq = clock/256, duty-cycle=100%, until someone changes > to the output-freq and/or duty-cycle are made. > > This problem has been masked so far because the main consumer > (the i915 driver) was always making duty-cycle changes on resume. > With the conversion of the i915 driver to the atomic PWM API the > driver now only disables/enables the PWM on suspend/resume leaving > the output-freq and duty as is, triggering this problem. > > The LPSS PWM controller has a mechanism where the ctrl register value > and the actual base-unit and on-time-div values used are latched. When > software sets the SW_UPDATE bit then at the end of the current PWM cycle, > the new values from the ctrl-register will be latched into the actual > registers, and the SW_UPDATE bit will be cleared. > > The problem is that before this commit our suspend/resume handling > consisted of simply saving the PWM ctrl register on suspend and > restoring it on resume, without setting the PWM_SW_UPDATE bit. > When the controller has lost its state over a suspend/resume and thus > has been reset to the defaults, just restoring the register is not > enough. We must also set the SW_UPDATE bit to tell the controller to > latch the restored values into the actual registers. > > Fixing this problem is not as simple as just or-ing in the value which > is being restored with SW_UPDATE. If the PWM was enabled before we must > write the new settings + PWM_SW_UPDATE before setting PWM_ENABLE. > We must also wait for PWM_SW_UPDATE to become 0 again and depending on the > model we must do this either before or after the setting of PWM_ENABLE. > > All the necessary logic for doing this is already present inside > pwm_lpss_apply(), so instead of duplicating this inside the resume > handler, this commit makes the resume handler use pwm_lpss_apply() to > restore the settings when necessary. This fixes the output-freq and > duty-cycle being reset to their defaults on resume. ... > +static int __pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, > + const struct pwm_state *state, bool from_resume) > { > struct pwm_lpss_chip *lpwm = to_lpwm(chip); > int ret; > > if (state->enabled) { > if (!pwm_is_enabled(pwm)) { > - pm_runtime_get_sync(chip->dev); > + if (!from_resume) > + pm_runtime_get_sync(chip->dev); > + > ret = pwm_lpss_is_updating(pwm); > if (ret) { > - pm_runtime_put(chip->dev); > + if (!from_resume) > + pm_runtime_put(chip->dev); > + > return ret; > } > pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); > pwm_lpss_cond_enable(pwm, lpwm->info->bypass == false); > ret = pwm_lpss_wait_for_update(pwm); > if (ret) { > - pm_runtime_put(chip->dev); > + if (!from_resume) > + pm_runtime_put(chip->dev); > + > return ret; > } > pwm_lpss_cond_enable(pwm, lpwm->info->bypass == true); > } > } else if (pwm_is_enabled(pwm)) { > pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); > - pm_runtime_put(chip->dev); > + > + if (!from_resume) > + pm_runtime_put(chip->dev); > } I'm wondering if splitting more will make this look better, like: ... if (from_resume) { ret = pwm_lpss_prepare_enable(...); // whatever name you think suits better } else { pm_runtime_get_sync(...); ret = pwm_lpss_prepare_enable(...); if (ret) pm_runtime_put(...); } ...
Hi, On 7/9/20 3:36 PM, Andy Shevchenko wrote: > On Wed, Jul 08, 2020 at 11:14:21PM +0200, Hans de Goede wrote: >> Before this commit a suspend + resume of the LPSS PWM controller >> would result in the controller being reset to its defaults of >> output-freq = clock/256, duty-cycle=100%, until someone changes >> to the output-freq and/or duty-cycle are made. >> >> This problem has been masked so far because the main consumer >> (the i915 driver) was always making duty-cycle changes on resume. >> With the conversion of the i915 driver to the atomic PWM API the >> driver now only disables/enables the PWM on suspend/resume leaving >> the output-freq and duty as is, triggering this problem. >> >> The LPSS PWM controller has a mechanism where the ctrl register value >> and the actual base-unit and on-time-div values used are latched. When >> software sets the SW_UPDATE bit then at the end of the current PWM cycle, >> the new values from the ctrl-register will be latched into the actual >> registers, and the SW_UPDATE bit will be cleared. >> >> The problem is that before this commit our suspend/resume handling >> consisted of simply saving the PWM ctrl register on suspend and >> restoring it on resume, without setting the PWM_SW_UPDATE bit. >> When the controller has lost its state over a suspend/resume and thus >> has been reset to the defaults, just restoring the register is not >> enough. We must also set the SW_UPDATE bit to tell the controller to >> latch the restored values into the actual registers. >> >> Fixing this problem is not as simple as just or-ing in the value which >> is being restored with SW_UPDATE. If the PWM was enabled before we must >> write the new settings + PWM_SW_UPDATE before setting PWM_ENABLE. >> We must also wait for PWM_SW_UPDATE to become 0 again and depending on the >> model we must do this either before or after the setting of PWM_ENABLE. >> >> All the necessary logic for doing this is already present inside >> pwm_lpss_apply(), so instead of duplicating this inside the resume >> handler, this commit makes the resume handler use pwm_lpss_apply() to >> restore the settings when necessary. This fixes the output-freq and >> duty-cycle being reset to their defaults on resume. > > ... > >> +static int __pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, >> + const struct pwm_state *state, bool from_resume) >> { >> struct pwm_lpss_chip *lpwm = to_lpwm(chip); >> int ret; >> >> if (state->enabled) { >> if (!pwm_is_enabled(pwm)) { >> - pm_runtime_get_sync(chip->dev); >> + if (!from_resume) >> + pm_runtime_get_sync(chip->dev); >> + >> ret = pwm_lpss_is_updating(pwm); >> if (ret) { >> - pm_runtime_put(chip->dev); >> + if (!from_resume) >> + pm_runtime_put(chip->dev); >> + >> return ret; >> } >> pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); >> pwm_lpss_cond_enable(pwm, lpwm->info->bypass == false); >> ret = pwm_lpss_wait_for_update(pwm); >> if (ret) { >> - pm_runtime_put(chip->dev); >> + if (!from_resume) >> + pm_runtime_put(chip->dev); >> + >> return ret; >> } >> pwm_lpss_cond_enable(pwm, lpwm->info->bypass == true); > >> } >> } else if (pwm_is_enabled(pwm)) { >> pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); >> - pm_runtime_put(chip->dev); >> + >> + if (!from_resume) >> + pm_runtime_put(chip->dev); >> } > > I'm wondering if splitting more will make this look better, like: > > ... > if (from_resume) { > ret = pwm_lpss_prepare_enable(...); // whatever name you think suits better > } else { > pm_runtime_get_sync(...); > ret = pwm_lpss_prepare_enable(...); > if (ret) > pm_runtime_put(...); > } > ... > That is a good idea, I like it. We already had multiple pm_runtime_put() calls before for the error handlig and this patch did not make it any better. So adding a pwm_lpss_prepare_enable() helper (the name works for) will also cleanup the original code. I will add this helper as a separate preparation patch for this one in v5 of the patch-set. Regards, Hans
diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index 80d0f9c64f9d..4f3d60ce9929 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -123,25 +123,31 @@ static inline void pwm_lpss_cond_enable(struct pwm_device *pwm, bool cond) pwm_lpss_write(pwm, pwm_lpss_read(pwm) | PWM_ENABLE); } -static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) +static int __pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state, bool from_resume) { struct pwm_lpss_chip *lpwm = to_lpwm(chip); int ret; if (state->enabled) { if (!pwm_is_enabled(pwm)) { - pm_runtime_get_sync(chip->dev); + if (!from_resume) + pm_runtime_get_sync(chip->dev); + ret = pwm_lpss_is_updating(pwm); if (ret) { - pm_runtime_put(chip->dev); + if (!from_resume) + pm_runtime_put(chip->dev); + return ret; } pwm_lpss_prepare(lpwm, pwm, state->duty_cycle, state->period); pwm_lpss_cond_enable(pwm, lpwm->info->bypass == false); ret = pwm_lpss_wait_for_update(pwm); if (ret) { - pm_runtime_put(chip->dev); + if (!from_resume) + pm_runtime_put(chip->dev); + return ret; } pwm_lpss_cond_enable(pwm, lpwm->info->bypass == true); @@ -154,12 +160,20 @@ static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, } } else if (pwm_is_enabled(pwm)) { pwm_lpss_write(pwm, pwm_lpss_read(pwm) & ~PWM_ENABLE); - pm_runtime_put(chip->dev); + + if (!from_resume) + pm_runtime_put(chip->dev); } return 0; } +static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + return __pwm_lpss_apply(chip, pwm, state, false); +} + static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { @@ -272,10 +286,40 @@ EXPORT_SYMBOL_GPL(pwm_lpss_suspend); int pwm_lpss_resume(struct device *dev) { struct pwm_lpss_chip *lpwm = dev_get_drvdata(dev); - int i; + struct pwm_state saved_state; + struct pwm_device *pwm; + int i, ret; + u32 ctrl; - for (i = 0; i < lpwm->info->npwm; i++) - writel(lpwm->saved_ctrl[i], lpwm->regs + i * PWM_SIZE + PWM); + for (i = 0; i < lpwm->info->npwm; i++) { + pwm = &lpwm->chip.pwms[i]; + + ctrl = pwm_lpss_read(pwm); + /* If we did not reach S0i3/S3 the controller keeps its state */ + if (ctrl == lpwm->saved_ctrl[i]) + continue; + + /* + * We cannot just blindly restore the old value here. Since we + * are changing the settings we must set SW_UPDATE and if the + * PWM was enabled before we must write the new settings + + * PWM_SW_UPDATE before setting PWM_ENABLE. We must also wait + * for PWM_SW_UPDATE to become 0 again and depending on the + * model we must do this either before or after the setting of + * PWM_ENABLE. + * So instead of reproducing all the code from pwm_apply() here, + * we just reapply the state as stored in pwm->state. + */ + saved_state = pwm->state; + /* + * Update enabled to its actual setting for the + * enabled<->disabled transitions inside apply(). + */ + pwm->state.enabled = !!(ctrl & PWM_ENABLE); + ret = __pwm_lpss_apply(&lpwm->chip, pwm, &saved_state, true); + if (ret) + dev_err(dev, "Error restoring state on resume\n"); + } return 0; }
Before this commit a suspend + resume of the LPSS PWM controller would result in the controller being reset to its defaults of output-freq = clock/256, duty-cycle=100%, until someone changes to the output-freq and/or duty-cycle are made. This problem has been masked so far because the main consumer (the i915 driver) was always making duty-cycle changes on resume. With the conversion of the i915 driver to the atomic PWM API the driver now only disables/enables the PWM on suspend/resume leaving the output-freq and duty as is, triggering this problem. The LPSS PWM controller has a mechanism where the ctrl register value and the actual base-unit and on-time-div values used are latched. When software sets the SW_UPDATE bit then at the end of the current PWM cycle, the new values from the ctrl-register will be latched into the actual registers, and the SW_UPDATE bit will be cleared. The problem is that before this commit our suspend/resume handling consisted of simply saving the PWM ctrl register on suspend and restoring it on resume, without setting the PWM_SW_UPDATE bit. When the controller has lost its state over a suspend/resume and thus has been reset to the defaults, just restoring the register is not enough. We must also set the SW_UPDATE bit to tell the controller to latch the restored values into the actual registers. Fixing this problem is not as simple as just or-ing in the value which is being restored with SW_UPDATE. If the PWM was enabled before we must write the new settings + PWM_SW_UPDATE before setting PWM_ENABLE. We must also wait for PWM_SW_UPDATE to become 0 again and depending on the model we must do this either before or after the setting of PWM_ENABLE. All the necessary logic for doing this is already present inside pwm_lpss_apply(), so instead of duplicating this inside the resume handler, this commit makes the resume handler use pwm_lpss_apply() to restore the settings when necessary. This fixes the output-freq and duty-cycle being reset to their defaults on resume. Signed-off-by: Hans de Goede <hdegoede@redhat.com> --- Changes in v3: - This replaces the "pwm: lpss: Set SW_UPDATE bit when enabling the PWM" patch from previous versions of this patch-set, which really was a hack working around the resume issue which this patch fixes properly. --- drivers/pwm/pwm-lpss.c | 62 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-)