Merge branch 'for-4.8/regulator' into for-next

This commit is contained in:
Thierry Reding 2016-07-25 16:23:40 +02:00
commit 53de7c26de
2 changed files with 173 additions and 49 deletions

View File

@ -34,20 +34,44 @@ Only required for Voltage Table Mode:
First cell is voltage in microvolts (uV) First cell is voltage in microvolts (uV)
Second cell is duty-cycle in percent (%) Second cell is duty-cycle in percent (%)
Optional properties for Continuous mode:
- pwm-dutycycle-unit: Integer value encoding the duty cycle unit. If not
defined, <100> is assumed, meaning that
pwm-dutycycle-range contains values expressed in
percent.
- pwm-dutycycle-range: Should contain 2 entries. The first entry is encoding
the dutycycle for regulator-min-microvolt and the
second one the dutycycle for regulator-max-microvolt.
Duty cycle values are expressed in pwm-dutycycle-unit.
If not defined, <0 100> is assumed.
NB: To be clear, if voltage-table is provided, then the device will be used NB: To be clear, if voltage-table is provided, then the device will be used
in Voltage Table Mode. If no voltage-table is provided, then the device will in Voltage Table Mode. If no voltage-table is provided, then the device will
be used in Continuous Voltage Mode. be used in Continuous Voltage Mode.
Optional properties:
--------------------
- enable-gpios: GPIO to use to enable/disable the regulator
Any property defined as part of the core regulator binding can also be used. Any property defined as part of the core regulator binding can also be used.
(See: ../regulator/regulator.txt) (See: ../regulator/regulator.txt)
Continuous Voltage Example: Continuous Voltage With Enable GPIO Example:
pwm_regulator { pwm_regulator {
compatible = "pwm-regulator; compatible = "pwm-regulator;
pwms = <&pwm1 0 8448 0>; pwms = <&pwm1 0 8448 0>;
enable-gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>;
regulator-min-microvolt = <1016000>; regulator-min-microvolt = <1016000>;
regulator-max-microvolt = <1114000>; regulator-max-microvolt = <1114000>;
regulator-name = "vdd_logic"; regulator-name = "vdd_logic";
/* unit == per-mille */
pwm-dutycycle-unit = <1000>;
/*
* Inverted PWM logic, and the duty cycle range is limited
* to 30%-70%.
*/
pwm-dutycycle-range <700 300>; /* */
}; };
Voltage Table Example: Voltage Table Example:

View File

@ -20,6 +20,13 @@
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/pwm.h> #include <linux/pwm.h>
#include <linux/gpio/consumer.h>
struct pwm_continuous_reg_data {
unsigned int min_uV_dutycycle;
unsigned int max_uV_dutycycle;
unsigned int dutycycle_unit;
};
struct pwm_regulator_data { struct pwm_regulator_data {
/* Shared */ /* Shared */
@ -28,6 +35,9 @@ struct pwm_regulator_data {
/* Voltage table */ /* Voltage table */
struct pwm_voltages *duty_cycle_table; struct pwm_voltages *duty_cycle_table;
/* Continuous mode info */
struct pwm_continuous_reg_data continuous;
/* regulator descriptor */ /* regulator descriptor */
struct regulator_desc desc; struct regulator_desc desc;
@ -36,8 +46,8 @@ struct pwm_regulator_data {
int state; int state;
/* Continuous voltage */ /* Enable GPIO */
int volt_uV; struct gpio_desc *enb_gpio;
}; };
struct pwm_voltages { struct pwm_voltages {
@ -48,10 +58,31 @@ struct pwm_voltages {
/** /**
* Voltage table call-backs * 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) static int pwm_regulator_get_voltage_sel(struct regulator_dev *rdev)
{ {
struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev);
if (drvdata->state < 0)
pwm_regulator_init_state(rdev);
return drvdata->state; return drvdata->state;
} }
@ -59,16 +90,14 @@ static int pwm_regulator_set_voltage_sel(struct regulator_dev *rdev,
unsigned selector) unsigned selector)
{ {
struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev); struct pwm_regulator_data *drvdata = rdev_get_drvdata(rdev);
struct pwm_args pargs; struct pwm_state pstate;
int dutycycle;
int ret; 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 * ret = pwm_apply_state(drvdata->pwm, &pstate);
drvdata->duty_cycle_table[selector].dutycycle) / 100;
ret = pwm_config(drvdata->pwm, dutycycle, pargs.period);
if (ret) { if (ret) {
dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret);
return ret; return ret;
@ -94,6 +123,9 @@ static int pwm_regulator_enable(struct regulator_dev *dev)
{ {
struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev); struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev);
if (drvdata->enb_gpio)
gpiod_set_value_cansleep(drvdata->enb_gpio, 1);
return pwm_enable(drvdata->pwm); return pwm_enable(drvdata->pwm);
} }
@ -103,6 +135,9 @@ static int pwm_regulator_disable(struct regulator_dev *dev)
pwm_disable(drvdata->pwm); pwm_disable(drvdata->pwm);
if (drvdata->enb_gpio)
gpiod_set_value_cansleep(drvdata->enb_gpio, 0);
return 0; return 0;
} }
@ -110,64 +145,100 @@ static int pwm_regulator_is_enabled(struct regulator_dev *dev)
{ {
struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev); struct pwm_regulator_data *drvdata = rdev_get_drvdata(dev);
if (drvdata->enb_gpio && !gpiod_get_value_cansleep(drvdata->enb_gpio))
return false;
return pwm_is_enabled(drvdata->pwm); return pwm_is_enabled(drvdata->pwm);
} }
static int pwm_regulator_get_voltage(struct regulator_dev *rdev) static int pwm_regulator_get_voltage(struct regulator_dev *rdev)
{ {
struct pwm_regulator_data *drvdata = rdev_get_drvdata(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 max_uV = rdev->constraints->max_uV;
int diff_uV = max_uV - min_uV;
struct pwm_state pstate;
unsigned int diff_duty;
unsigned int voltage;
return drvdata->volt_uV; pwm_get_state(drvdata->pwm, &pstate);
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, static int pwm_regulator_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV, int req_min_uV, int req_max_uV,
unsigned *selector) unsigned int *selector)
{ {
struct pwm_regulator_data *drvdata = rdev_get_drvdata(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;
unsigned int ramp_delay = rdev->constraints->ramp_delay; unsigned int ramp_delay = rdev->constraints->ramp_delay;
struct pwm_args pargs; int min_uV = rdev->constraints->min_uV;
unsigned int req_diff = min_uV - rdev->constraints->min_uV; int max_uV = rdev->constraints->max_uV;
unsigned int diff; int diff_uV = max_uV - min_uV;
unsigned int duty_pulse; struct pwm_state pstate;
u64 req_period; int old_uV = pwm_regulator_get_voltage(rdev);
u32 rem; unsigned int diff_duty;
unsigned int dutycycle;
int ret; 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) * The dutycycle for min_uV might be greater than the one for max_uV.
* is perfect divided by voltage_range_diff then it is possible to * This is happening when the user needs an inversed polarity, but the
* get duty cycle time which is factor of PWM period. This will help * PWM device does not support inversing it in hardware.
* to get output voltage nearer to requested value as there is no
* calculation loss.
*/ */
req_period = req_diff * pargs.period; if (max_uV_duty < min_uV_duty)
div_u64_rem(req_period, diff, &rem); diff_duty = min_uV_duty - max_uV_duty;
if (!rem) { else
do_div(req_period, diff); diff_duty = max_uV_duty - min_uV_duty;
duty_pulse = (unsigned int)req_period;
} else {
duty_pulse = (pargs.period / 100) * ((req_diff * 100) / diff);
}
ret = pwm_config(drvdata->pwm, duty_pulse, pargs.period); 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) { if (ret) {
dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret); dev_err(&rdev->dev, "Failed to configure PWM: %d\n", ret);
return ret; return ret;
} }
ret = pwm_enable(drvdata->pwm); if ((ramp_delay == 0) || !pwm_regulator_is_enabled(rdev))
if (ret) { return 0;
dev_err(&rdev->dev, "Failed to enable PWM: %d\n", ret);
return ret;
}
drvdata->volt_uV = min_uV;
/* Delay required by PWM regulator to settle to the new voltage */ /* Ramp delay is in uV/uS. Adjust to uS and delay */
usleep_range(ramp_delay, ramp_delay + 1000); 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; return 0;
} }
@ -226,6 +297,7 @@ static int pwm_regulator_init_table(struct platform_device *pdev,
return ret; return ret;
} }
drvdata->state = -EINVAL;
drvdata->duty_cycle_table = duty_cycle_table; drvdata->duty_cycle_table = duty_cycle_table;
memcpy(&drvdata->ops, &pwm_regulator_voltage_table_ops, memcpy(&drvdata->ops, &pwm_regulator_voltage_table_ops,
sizeof(drvdata->ops)); sizeof(drvdata->ops));
@ -238,11 +310,28 @@ static int pwm_regulator_init_table(struct platform_device *pdev,
static int pwm_regulator_init_continuous(struct platform_device *pdev, static int pwm_regulator_init_continuous(struct platform_device *pdev,
struct pwm_regulator_data *drvdata) struct pwm_regulator_data *drvdata)
{ {
u32 dutycycle_range[2] = { 0, 100 };
u32 dutycycle_unit = 100;
memcpy(&drvdata->ops, &pwm_regulator_voltage_continuous_ops, memcpy(&drvdata->ops, &pwm_regulator_voltage_continuous_ops,
sizeof(drvdata->ops)); sizeof(drvdata->ops));
drvdata->desc.ops = &drvdata->ops; drvdata->desc.ops = &drvdata->ops;
drvdata->desc.continuous_voltage_range = true; 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; return 0;
} }
@ -253,6 +342,7 @@ static int pwm_regulator_probe(struct platform_device *pdev)
struct regulator_dev *regulator; struct regulator_dev *regulator;
struct regulator_config config = { }; struct regulator_config config = { };
struct device_node *np = pdev->dev.of_node; struct device_node *np = pdev->dev.of_node;
enum gpiod_flags gpio_flags;
int ret; int ret;
if (!np) { if (!np) {
@ -290,11 +380,21 @@ static int pwm_regulator_probe(struct platform_device *pdev)
return ret; return ret;
} }
/* if (init_data->constraints.boot_on || init_data->constraints.always_on)
* FIXME: pwm_apply_args() should be removed when switching to the gpio_flags = GPIOD_OUT_HIGH;
* atomic PWM API. else
*/ gpio_flags = GPIOD_OUT_LOW;
pwm_apply_args(drvdata->pwm); drvdata->enb_gpio = devm_gpiod_get_optional(&pdev->dev, "enable",
gpio_flags);
if (IS_ERR(drvdata->enb_gpio)) {
ret = PTR_ERR(drvdata->enb_gpio);
dev_err(&pdev->dev, "Failed to get enable GPIO: %d\n", ret);
return ret;
}
ret = pwm_adjust_config(drvdata->pwm);
if (ret)
return ret;
regulator = devm_regulator_register(&pdev->dev, regulator = devm_regulator_register(&pdev->dev,
&drvdata->desc, &config); &drvdata->desc, &config);