From fd4f99c4c3ce8ccd9b8ea751afc614a7624ecef2 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:17 +0200 Subject: regulator: pwm: Adjust PWM config at probe time The PWM attached to a PWM regulator device might have been previously configured by the bootloader. Make sure the bootloader and linux config are in sync, and adjust the PWM config if that's not the case. Signed-off-by: Boris Brezillon Acked-by: Mark Brown Acked-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 666bc3bb52ef..cb2f22c02469 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -316,11 +316,9 @@ static int pwm_regulator_probe(struct platform_device *pdev) return ret; } - /* - * FIXME: pwm_apply_args() should be removed when switching to the - * atomic PWM API. - */ - pwm_apply_args(drvdata->pwm); + ret = pwm_adjust_config(drvdata->pwm); + if (ret) + return ret; regulator = devm_regulator_register(&pdev->dev, &drvdata->desc, &config); -- cgit v1.2.3 From 3f4eb39be9b1402ea01a5c67441d0b0bcb74b4b2 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:18 +0200 Subject: regulator: pwm: Switch to the atomic PWM API Use the atomic API wherever appropriate and get rid of pwm_apply_args() call (the reference period and polarity are now explicitly set when calling pwm_apply_state()). We also make use of the pwm_set_relative_duty_cycle() helper to ease relative to absolute duty_cycle conversion. Note that changes introduced by commit fd786fb0276a ("regulator: pwm: Try to avoid voltage error in duty cycle calculation") are no longer needed because pwm_set_relative_duty_cycle() takes care of all rounding approximation for us. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Acked-by: Laxman Dewangan Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index cb2f22c02469..7920411057af 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -63,16 +63,14 @@ static int pwm_regulator_set_voltage_sel(struct regulator_dev *rdev, unsigned selector) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); - struct pwm_args pargs; - int dutycycle; + struct pwm_state pstate; int ret; - pwm_get_args(drvdata->pwm, &pargs); + pwm_init_state(drvdata->pwm, &pstate); + pwm_set_relative_duty_cycle(&pstate, + drvdata->duty_cycle_table[selector].dutycycle, 100); - dutycycle = (pargs.period * - drvdata->duty_cycle_table[selector].dutycycle) / 100; - - ret = pwm_config(drvdata->pwm, dutycycle, pargs.period); + ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); return ret; @@ -139,35 +137,19 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev, { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); unsigned int ramp_delay = rdev->constraints->ramp_delay; - struct pwm_args pargs; unsigned int req_diff = min_uV - rdev->constraints->min_uV; + struct pwm_state pstate; unsigned int diff; - unsigned int duty_pulse; - u64 req_period; - u32 rem; int old_uV = pwm_regulator_get_voltage(rdev); int ret; - pwm_get_args(drvdata->pwm, &pargs); + pwm_init_state(drvdata->pwm, &pstate); diff = rdev->constraints->max_uV - rdev->constraints->min_uV; - /* First try to find out if we get the iduty cycle time which is - * factor of PWM period time. If (request_diff_to_min * pwm_period) - * is perfect divided by voltage_range_diff then it is possible to - * get duty cycle time which is factor of PWM period. This will help - * to get output voltage nearer to requested value as there is no - * calculation loss. - */ - req_period = req_diff * pargs.period; - div_u64_rem(req_period, diff, &rem); - if (!rem) { - do_div(req_period, diff); - duty_pulse = (unsigned int)req_period; - } else { - duty_pulse = (pargs.period / 100) * ((req_diff * 100) / diff); - } + /* We pass diff as the scale to get a uV precision. */ + pwm_set_relative_duty_cycle(&pstate, req_diff, diff); - ret = pwm_config(drvdata->pwm, duty_pulse, pargs.period); + ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); return ret; -- cgit v1.2.3 From 87248991a1de28e73dc30057e82d831bc11cdd44 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:19 +0200 Subject: regulator: pwm: Properly initialize the ->state field The ->state field is currently initialized to 0, thus referencing the voltage selector at index 0, which might not reflect the current voltage value. If possible, retrieve the current voltage selector from the PWM state, else return -EINVAL. Signed-off-by: Boris Brezillon Tested-by: Brian Norris Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 7920411057af..7d26d3b0eed6 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -52,10 +52,31 @@ struct pwm_voltages { /** * 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); + + for (i = 0; i < rdev->desc->n_voltages; i++) { + if (dutycycle == drvdata->duty_cycle_table[i].dutycycle) { + drvdata->state = i; + return; + } + } +} + static int pwm_regulator_get_voltage_sel(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + if (drvdata->state < 0) + pwm_regulator_init_state(rdev); + return drvdata->state; } @@ -221,6 +242,7 @@ static int pwm_regulator_init_table(struct platform_device *pdev, return ret; } + drvdata->state = -EINVAL; drvdata->duty_cycle_table = duty_cycle_table; memcpy(&drvdata->ops, &pwm_regulator_voltage_table_ops, sizeof(drvdata->ops)); -- cgit v1.2.3 From d9070fdbe40a04b61262bac0f7ff0c7c29a68015 Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:20 +0200 Subject: regulator: pwm: Retrieve correct voltage The continuous PWM voltage regulator is caching the voltage value in the ->volt_uV field. While most of the time this value should reflect the real voltage, sometime it can be sightly different if the PWM device rounded the set_duty_cycle request. Moreover, this value is not valid until someone has modified the regulator output. Remove the ->volt_uV field and always rely on the PWM state to calculate the regulator output. Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index 7d26d3b0eed6..ae0bebbfda9d 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -37,9 +37,6 @@ struct pwm_regulator_data { int state; - /* Continuous voltage */ - int volt_uV; - /* Enable GPIO */ struct gpio_desc *enb_gpio; }; @@ -148,8 +145,13 @@ static int pwm_regulator_is_enabled(struct regulator_dev *dev) static int pwm_regulator_get_voltage(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); + int min_uV = rdev->constraints->min_uV; + int diff = rdev->constraints->max_uV - min_uV; + struct pwm_state pstate; - return drvdata->volt_uV; + pwm_get_state(drvdata->pwm, &pstate); + + return min_uV + pwm_get_relative_duty_cycle(&pstate, diff); } static int pwm_regulator_set_voltage(struct regulator_dev *rdev, @@ -176,8 +178,6 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev, return ret; } - drvdata->volt_uV = min_uV; - if ((ramp_delay == 0) || !pwm_regulator_is_enabled(rdev)) return 0; -- cgit v1.2.3 From ea398e28739e25651ede7ddf5aeb57cbcbc8ca7d Mon Sep 17 00:00:00 2001 From: Boris Brezillon Date: Tue, 14 Jun 2016 11:13:21 +0200 Subject: regulator: pwm: Support extra continuous mode cases The continuous mode allows one to declare a PWM regulator without having to declare the voltage <-> dutycycle association table. It works fine as long as your voltage(dutycycle) function is linear, but also has the following constraints: - dutycycle for min_uV = 0% - dutycycle for max_uV = 100% - dutycycle for min_uV < dutycycle for max_uV While the linearity constraint is acceptable for now, we sometimes need to restrict of the PWM range (to limit the maximum/minimum voltage for example) or have a min_uV_dutycycle > max_uV_dutycycle (this could be tweaked with PWM polarity, but not all PWMs support inverted polarity). Add the pwm-dutycycle-range and pwm-dutycycle-unit DT properties to define such constraints. If those properties are not defined, the PWM regulator use the default pwm-dutycycle-range = <0 100> and pwm-dutycycle-unit = <100> values (existing behavior). Signed-off-by: Boris Brezillon Reviewed-by: Brian Norris Tested-by: Brian Norris Tested-by: Heiko Stuebner Acked-by: Mark Brown Signed-off-by: Thierry Reding --- drivers/regulator/pwm-regulator.c | 92 ++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 10 deletions(-) (limited to 'drivers/regulator') diff --git a/drivers/regulator/pwm-regulator.c b/drivers/regulator/pwm-regulator.c index ae0bebbfda9d..c24524242da2 100644 --- a/drivers/regulator/pwm-regulator.c +++ b/drivers/regulator/pwm-regulator.c @@ -22,6 +22,12 @@ #include #include +struct pwm_continuous_reg_data { + unsigned int min_uV_dutycycle; + unsigned int max_uV_dutycycle; + unsigned int dutycycle_unit; +}; + struct pwm_regulator_data { /* Shared */ struct pwm_device *pwm; @@ -29,6 +35,9 @@ struct pwm_regulator_data { /* Voltage table */ struct pwm_voltages *duty_cycle_table; + /* Continuous mode info */ + struct pwm_continuous_reg_data continuous; + /* regulator descriptor */ struct regulator_desc desc; @@ -145,32 +154,78 @@ static int pwm_regulator_is_enabled(struct regulator_dev *dev) static int pwm_regulator_get_voltage(struct regulator_dev *rdev) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(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; int min_uV = rdev->constraints->min_uV; - int diff = rdev->constraints->max_uV - 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); - return min_uV + pwm_get_relative_duty_cycle(&pstate, diff); + voltage = pwm_get_relative_duty_cycle(&pstate, duty_unit); + + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) { + voltage = min_uV_duty - voltage; + diff_duty = min_uV_duty - max_uV_duty; + } else { + voltage = voltage - min_uV_duty; + diff_duty = max_uV_duty - min_uV_duty; + } + + voltage = DIV_ROUND_CLOSEST_ULL((u64)voltage * diff_uV, diff_duty); + + return voltage + min_uV; } static int pwm_regulator_set_voltage(struct regulator_dev *rdev, - int min_uV, int max_uV, - unsigned *selector) + int req_min_uV, int req_max_uV, + unsigned int *selector) { struct pwm_regulator_data *drvdata = rdev_get_drvdata(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; unsigned int ramp_delay = rdev->constraints->ramp_delay; - unsigned int req_diff = min_uV - rdev->constraints->min_uV; + 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; int old_uV = pwm_regulator_get_voltage(rdev); + unsigned int diff_duty; + unsigned int dutycycle; int ret; pwm_init_state(drvdata->pwm, &pstate); - diff = rdev->constraints->max_uV - rdev->constraints->min_uV; - /* We pass diff as the scale to get a uV precision. */ - pwm_set_relative_duty_cycle(&pstate, req_diff, diff); + /* + * The dutycycle for min_uV might be greater than the one for max_uV. + * This is happening when the user needs an inversed polarity, but the + * PWM device does not support inversing it in hardware. + */ + if (max_uV_duty < min_uV_duty) + diff_duty = min_uV_duty - max_uV_duty; + else + diff_duty = max_uV_duty - min_uV_duty; + + dutycycle = DIV_ROUND_CLOSEST_ULL((u64)(req_min_uV - min_uV) * + diff_duty, + diff_uV); + + if (max_uV_duty < min_uV_duty) + dutycycle = min_uV_duty - dutycycle; + else + dutycycle = min_uV_duty + dutycycle; + + pwm_set_relative_duty_cycle(&pstate, dutycycle, duty_unit); ret = pwm_apply_state(drvdata->pwm, &pstate); if (ret) { @@ -182,7 +237,7 @@ static int pwm_regulator_set_voltage(struct regulator_dev *rdev, return 0; /* Ramp delay is in uV/uS. Adjust to uS and delay */ - ramp_delay = DIV_ROUND_UP(abs(min_uV - old_uV), ramp_delay); + ramp_delay = DIV_ROUND_UP(abs(req_min_uV - old_uV), ramp_delay); usleep_range(ramp_delay, ramp_delay + DIV_ROUND_UP(ramp_delay, 10)); return 0; @@ -255,11 +310,28 @@ static int pwm_regulator_init_table(struct platform_device *pdev, static int pwm_regulator_init_continuous(struct platform_device *pdev, struct pwm_regulator_data *drvdata) { + u32 dutycycle_range[2] = { 0, 100 }; + u32 dutycycle_unit = 100; + memcpy(&drvdata->ops, &pwm_regulator_voltage_continuous_ops, sizeof(drvdata->ops)); drvdata->desc.ops = &drvdata->ops; drvdata->desc.continuous_voltage_range = true; + of_property_read_u32_array(pdev->dev.of_node, + "pwm-dutycycle-range", + dutycycle_range, 2); + of_property_read_u32(pdev->dev.of_node, "pwm-dutycycle-unit", + &dutycycle_unit); + + if (dutycycle_range[0] > dutycycle_unit || + dutycycle_range[1] > dutycycle_unit) + return -EINVAL; + + drvdata->continuous.dutycycle_unit = dutycycle_unit; + drvdata->continuous.min_uV_dutycycle = dutycycle_range[0]; + drvdata->continuous.max_uV_dutycycle = dutycycle_range[1]; + return 0; } -- cgit v1.2.3