Re: [RFC PATCH] regulator: pwm-regulator: Make assumptions about disabled PWM consistent

From: Martin Blumenstingl
Date: Sat Jun 15 2024 - 19:10:54 EST


Hello Uwe,

On Mon, Jun 10, 2024 at 12:46 PM Uwe Kleine-König
<u.kleine-koenig@xxxxxxxxxxxx> wrote:
[...]
> this is my suggestion to fix the concerns I expressed in
> https://lore.kernel.org/all/hf24mrplr76xtziztkqiscowkh2f3vmceuarecqcwnr6udggs6@uiof2rvvqq5v/
> .
>
> It's only compile tested as I don't have a machine with a pwm-regulator.
> So getting test feedback before applying it would be great.
Unfortunately this approach breaks my Odroid-C1 board again with the
same symptoms as before the mentioned commits: random memory
corruption, preventing the board from booting to userspace.

The cause also seems to be the same as before my commits:
- period (3666ns) and duty cycle (3333ns) in the hardware registers
the PWM controller when Linux boots, but the PWM output is disabled
- with a duty cycle of 0 or PWM output being disabled the output of
the voltage regulator is 1140mV, which is the only allowed voltage for
that rail (even though the regulator can achieve other voltages)
- pwm_regulator_enable() calls pwm_enable() which simply takes the
period and and duty cycle that was read back from the hardware and
enables the output, resulting in undervolting of a main voltage rail
of the SoC

I hope that this (especially the last item) also clarifies the
question you had in the linked mail on whether updating
pwm_regulator_init_state() would help/work.

Regarding your alternative and preferred approach from the other mail:
> Alternatively (and IMHO nicer) just keep the pwm_state around and don't
> use the (mis) feature of the PWM core that pwm_get_state only returns
> the last set state.
I tried this to see if it would work also for my Odroid-C1 board and
I'm happy to report it does - see the attached diff.
In case you are happy with this approach I can submit it as a proper patch.


Best regards,
Martin
diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c
index 7434b6b22d32..ab6fc9905bb2 100644
--- a/drivers/regulator/pwm-regulator.c
+++ b/drivers/regulator/pwm-regulator.c
@@ -41,6 +41,8 @@ struct pwm_regulator_data {

/* Enable GPIO */
struct gpio_desc *enb_gpio;
+
+ struct pwm_state pwm_state;
};

struct pwm_voltages {
@@ -48,18 +50,33 @@ struct pwm_voltages {
unsigned int dutycycle;
};

+static int pwm_regulator_apply_state(struct regulator_dev *rdev,
+ struct pwm_state *new_state)
+{
+ struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev);
+ int ret;
+
+ ret = pwm_apply_might_sleep(drvdata->pwm, new_state);
+ if (ret) {
+ dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret);
+ return ret;
+ }
+
+ drvdata->pwm_state = *new_state;
+
+ return 0;
+}
+
/*
* Voltage table call-backs
*/
static void pwm_regulator_init_state(struct regulator_dev *rdev)
{
struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev);
- struct pwm_state pwm_state;
unsigned int dutycycle;
int i;

- pwm_get_state(drvdata->pwm, &pwm_state);
- dutycycle = pwm_get_relative_duty_cycle(&pwm_state, 100);
+ dutycycle = pwm_get_relative_duty_cycle(&drvdata->pwm_state, 100);

for (i = 0; i < rdev->desc->n_voltages; i++) {
if (dutycycle == drvdata->duty_cycle_table[i].dutycycle) {
@@ -83,18 +100,15 @@ static int pwm_regulator_set_voltage_sel(struct regulator_dev *rdev,
unsigned selector)
{
struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev);
- struct pwm_state pstate;
+ struct pwm_state pstate = drvdata->pwm_state;
int ret;

- pwm_init_state(drvdata->pwm, &pstate);
pwm_set_relative_duty_cycle(&pstate,
drvdata->duty_cycle_table[selector].dutycycle, 100);

- ret = pwm_apply_might_sleep(drvdata->pwm, &pstate);
- if (ret) {
- dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret);
+ ret = pwm_regulator_apply_state(rdev, &pstate);
+ if (ret)
return ret;
- }

drvdata->state = selector;

@@ -115,17 +129,26 @@ static int pwm_regulator_list_voltage(struct regulator_dev *rdev,
static int pwm_regulator_enable(struct regulator_dev *dev)
{
struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev);
+ struct pwm_state pstate = drvdata->pwm_state;

gpiod_set_value_cansleep(drvdata->enb_gpio, 1);

- return pwm_enable(drvdata->pwm);
+ pstate.enabled = true;
+
+ return pwm_regulator_apply_state(dev, &pstate);
}

static int pwm_regulator_disable(struct regulator_dev *dev)
{
struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev);
+ struct pwm_state pstate = drvdata->pwm_state;
+ int ret;
+
+ pstate.enabled = false;

- pwm_disable(drvdata->pwm);
+ ret = pwm_regulator_apply_state(dev, &pstate);
+ if (ret)
+ return ret;

gpiod_set_value_cansleep(drvdata->enb_gpio, 0);

@@ -151,20 +174,10 @@ static int pwm_regulator_get_voltage(struct regulator_dev *rdev)
int min_uV = rdev->constraints->min_uV;
int max_uV = rdev->constraints->max_uV;
int diff_uV = max_uV - min_uV;
- struct pwm_state pstate;
unsigned int diff_duty;
unsigned int voltage;

- pwm_get_state(drvdata->pwm, &pstate);
-
- if (!pstate.enabled) {
- if (pstate.polarity == PWM_POLARITY_INVERSED)
- pstate.duty_cycle = pstate.period;
- else
- pstate.duty_cycle = 0;
- }
-
- voltage = pwm_get_relative_duty_cycle(&pstate, duty_unit);
+ voltage = pwm_get_relative_duty_cycle(&drvdata->pwm_state, duty_unit);
if (voltage < min(max_uV_duty, min_uV_duty) ||
voltage > max(max_uV_duty, min_uV_duty))
return -ENOTRECOVERABLE;
@@ -195,15 +208,12 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev,
unsigned int min_uV_duty = drvdata->continuous.min_uV_dutycycle;
unsigned int max_uV_duty = drvdata->continuous.max_uV_dutycycle;
unsigned int duty_unit = drvdata->continuous.dutycycle_unit;
+ struct pwm_state pstate = drvdata->pwm_state;
int min_uV = rdev->constraints->min_uV;
int max_uV = rdev->constraints->max_uV;
int diff_uV = max_uV - min_uV;
- struct pwm_state pstate;
unsigned int diff_duty;
unsigned int dutycycle;
- int ret;
-
- pwm_init_state(drvdata->pwm, &pstate);

/*
* The dutycycle for min_uV might be greater than the one for max_uV.
@@ -226,13 +236,7 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev,

pwm_set_relative_duty_cycle(&pstate, dutycycle, duty_unit);

- ret = pwm_apply_might_sleep(drvdata->pwm, &pstate);
- if (ret) {
- dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret);
- return ret;
- }
-
- return 0;
+ return pwm_regulator_apply_state(rdev, &pstate);
}

static const struct regulator_ops pwm_regulator_voltage_table_ops = {
@@ -321,32 +325,6 @@ static int pwm_regulator_init_continuous(struct platform_device *pdev,
return 0;
}

-static int pwm_regulator_init_boot_on(struct platform_device *pdev,
- struct pwm_regulator_data *drvdata,
- const struct regulator_init_data *init_data)
-{
- struct pwm_state pstate;
-
- if (!init_data->constraints.boot_on || drvdata->enb_gpio)
- return 0;
-
- pwm_get_state(drvdata->pwm, &pstate);
- if (pstate.enabled)
- return 0;
-
- /*
- * Update the duty cycle so the output does not change
- * when the regulator core enables the regulator (and
- * thus the PWM channel).
- */
- if (pstate.polarity == PWM_POLARITY_INVERSED)
- pstate.duty_cycle = pstate.period;
- else
- pstate.duty_cycle = 0;
-
- return pwm_apply_might_sleep(drvdata->pwm, &pstate);
-}
-
static int pwm_regulator_probe(struct platform_device *pdev)
{
const struct regulator_init_data *init_data;
@@ -404,10 +382,23 @@ static int pwm_regulator_probe(struct platform_device *pdev)
if (ret)
return ret;

- ret = pwm_regulator_init_boot_on(pdev, drvdata, init_data);
- if (ret)
- return dev_err_probe(&pdev->dev, ret,
- "Failed to apply boot_on settings\n");
+ pwm_init_state(drvdata->pwm, &drvdata->pwm_state);
+
+ if (init_data->constraints.boot_on && !drvdata->enb_gpio &&
+ !drvdata->pwm_state.enabled)
+ /*
+ * In general it's unknown what the output of a disabled PWM is.
+ * The only sane assumption here is it emits the inactive level
+ * which corresponds to duty_cycle = 0 (independent of the
+ * polarity).
+ *
+ * Update the duty cycle so the output does not change when the
+ * regulator core enables the regulator (and thus the PWM
+ * channel) and there's no change to the duty cycle because the
+ * voltage that is achieved with a disabled PWM is already the
+ * desired one.
+ */
+ drvdata->pwm_state.duty_cycle = 0;

regulator = devm_regulator_register(&pdev->dev,
&drvdata->desc, &config);