All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-10 20:43 ` Matthias Kaehlcke
  0 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-10 20:43 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, Rob Herring, Mark Rutland
  Cc: linux-kernel, devicetree, Douglas Anderson, Brian Norris,
	Guenter Roeck, Dmitry Torokhov, Matthias Kaehlcke

The output voltage of a voltage controlled regulator can be controlled
through the voltage of another regulator. The current version of this
driver assumes that the output voltage is a linear function of the control
voltage.

Signed-off-by: Matthias Kaehlcke <mka@chromium.org>
---
Note: The current version of the driver has a known limitation: If an
input supply is configured besides the control supply the two regulators
must have no common ancestor, otherwise the spinlock of the ancestor
would be acquired twice by the core code. One possible solution would be
to configure the control regulator through a string instead of a phandle,
i.e. not specifying it as supply.

 .../devicetree/bindings/regulator/vctrl.txt        |  56 +++
 drivers/regulator/Kconfig                          |   7 +
 drivers/regulator/Makefile                         |   1 +
 drivers/regulator/vctrl-regulator.c                | 525 +++++++++++++++++++++
 4 files changed, 589 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/vctrl.txt
 create mode 100644 drivers/regulator/vctrl-regulator.c

diff --git a/Documentation/devicetree/bindings/regulator/vctrl.txt b/Documentation/devicetree/bindings/regulator/vctrl.txt
new file mode 100644
index 000000000000..ba3c9b2f61b1
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/vctrl.txt
@@ -0,0 +1,56 @@
+Bindings for Voltage controlled regulators
+==========================================
+
+Required properties:
+--------------------
+- compatible		: must be "vctrl-regulator".
+- ctrl-supply:		: a phandle for the regulator supplying the control
+			  voltage.
+- output-voltage-range	: an array of two integer values specifying the range
+			  (min/max) of the output voltage. The min/max voltage
+			  constraints of the regulator (if specified) must not
+			  exceed this range.
+- ctrl-voltage-range	: an array of two integer values specifying the range
+			  (min/max) of the control voltage. The values of
+			  ctrl-voltage-range and output-voltage-range are used
+			  for the conversion between output and control voltage.
+			  The min/max values of ctrl-voltage-range must specify
+			  the control voltage needed to generate the min/max
+			  output voltage.
+
+Optional properties:
+--------------------
+- ovp-threshold-percent	: overvoltage protection (OVP) threshold of the
+			  regulator in percent. Some regulators have an OVP
+			  circuitry which shuts down the regulator when the
+			  actual output voltage deviates beyond a certain
+			  margin from the expected value for a given control
+			  voltage. On larger voltage decreases this can occur
+			  undesiredly since the output voltage does not adjust
+			  inmediately to changes in the control voltage. To
+			  avoid this situation the vctrl driver breaks down
+			  larger voltage decreases into multiple steps, where
+			  each step is within the OVP threshold.
+- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
+			  down in the worst case (lightest expected load).
+			  Specified in uV / us (like main regulator ramp rate).
+			  This value is required when ovp-threshold-percent is
+			  specified.
+
+Example:
+
+	vctrl_reg {
+		compatible = "vctrl-regulator";
+		regulator-name = "vctrl_reg";
+
+		ctrl-supply = <&ctrl_supply>;
+
+		regulator-min-microvolt = <800000>;
+		regulator-max-microvolt = <1500000>;
+
+		output-voltage-range = <800000 1500000>;
+		ctrl-voltage-range = <200000 500000>;
+
+		slew-rate = <225>;
+		ovp-threshold-percent = <16>;
+	};
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 936f7ccc9736..da83a3abe288 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -843,6 +843,13 @@ config REGULATOR_TWL4030
 	  This driver supports the voltage regulators provided by
 	  this family of companion chips.
 
+config REGULATOR_VCTRL
+	tristate "Voltage controlled regulators"
+	depends on OF
+	help
+	  This driver provides support for voltage regulators whose output
+	  voltage is controlled by the voltage of another regulator.
+
 config REGULATOR_VEXPRESS
 	tristate "Versatile Express regulators"
 	depends on VEXPRESS_CONFIG
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 14294692beb9..e246e148a7f9 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o
 obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o
 obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o
+obj-$(CONFIG_REGULATOR_VCTRL) += vctrl-regulator.o
 obj-$(CONFIG_REGULATOR_VEXPRESS) += vexpress-regulator.o
 obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o
 obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o
diff --git a/drivers/regulator/vctrl-regulator.c b/drivers/regulator/vctrl-regulator.c
new file mode 100644
index 000000000000..6487f23da335
--- /dev/null
+++ b/drivers/regulator/vctrl-regulator.c
@@ -0,0 +1,525 @@
+/*
+ * Driver for voltage controller regulators
+ *
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/sort.h>
+
+struct vctrl_voltage_range {
+	int min_uV;
+	int max_uV;
+};
+
+struct vctrl_voltage_ranges {
+	struct vctrl_voltage_range ctrl;
+	struct vctrl_voltage_range out;
+};
+
+struct vctrl_voltage_table {
+	int ctrl;
+	int out;
+};
+
+struct vctrl_data {
+	struct regulator_dev *rdev;
+	struct regulator_desc desc;
+	struct regulator *ctrl_supply;
+	unsigned int min_slew_down_rate;
+	unsigned int ovp_threshold;
+	struct vctrl_voltage_ranges vrange;
+	struct vctrl_voltage_table *vtable;
+	unsigned int sel;
+};
+
+static int vctrl_calc_ctrl_voltage(struct vctrl_data *vctrl, int out_uV)
+{
+	struct vctrl_voltage_range *ctrl = &vctrl->vrange.ctrl;
+	struct vctrl_voltage_range *out = &vctrl->vrange.out;
+
+	return ctrl->min_uV +
+		DIV_ROUND_CLOSEST_ULL((s64)(out_uV - out->min_uV) *
+				      (ctrl->max_uV - ctrl->min_uV),
+				      out->max_uV - out->min_uV);
+}
+
+static int vctrl_calc_output_voltage(struct vctrl_data *vctrl, int ctrl_uV)
+{
+	struct vctrl_voltage_range *ctrl = &vctrl->vrange.ctrl;
+	struct vctrl_voltage_range *out = &vctrl->vrange.out;
+
+	if (ctrl_uV < 0) {
+		pr_err("vctrl: failed to get control voltage\n");
+		return ctrl_uV;
+	}
+
+	if (ctrl_uV < ctrl->min_uV)
+		return out->min_uV;
+
+	if (ctrl_uV > ctrl->max_uV)
+		return out->max_uV;
+
+	return out->min_uV +
+		DIV_ROUND_CLOSEST_ULL((s64)(ctrl_uV - ctrl->min_uV) *
+				      (out->max_uV - out->min_uV),
+				      ctrl->max_uV - ctrl->min_uV);
+}
+
+static int vctrl_get_voltage(struct regulator_dev *rdev)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+	int ctrl_uV = regulator_get_voltage(vctrl->ctrl_supply);
+
+	return vctrl_calc_output_voltage(vctrl, ctrl_uV);
+}
+
+static int vctrl_set_voltage(struct regulator_dev *rdev,
+			     int req_min_uV, int req_max_uV,
+			     unsigned int *selector)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+	struct regulator *ctrl_supply = vctrl->ctrl_supply;
+	int orig_ctrl_uV = regulator_get_voltage(ctrl_supply);
+	int uV = vctrl_calc_output_voltage(vctrl, orig_ctrl_uV);
+	int ret;
+
+	if (req_min_uV >= uV || !vctrl->ovp_threshold)
+		/* voltage rising or no OVP */
+		return regulator_set_voltage(
+			ctrl_supply,
+			vctrl_calc_ctrl_voltage(vctrl, req_min_uV),
+			vctrl_calc_ctrl_voltage(vctrl, req_max_uV));
+
+	while (uV > req_min_uV) {
+		int max_drop_uV = (uV * vctrl->ovp_threshold) / 100;
+		int next_uV;
+		int next_ctrl_uV;
+		int delay;
+
+		/* Make sure no infinite loop even in crazy cases */
+		if (max_drop_uV == 0)
+			max_drop_uV = 1;
+
+		next_uV = max_t(int, req_min_uV, uV - max_drop_uV);
+		next_ctrl_uV = vctrl_calc_ctrl_voltage(vctrl, next_uV);
+
+		ret = regulator_set_voltage(ctrl_supply,
+					    next_ctrl_uV,
+					    next_ctrl_uV);
+		if (ret)
+			goto err;
+
+		delay = DIV_ROUND_UP(uV - next_uV, vctrl->min_slew_down_rate);
+		usleep_range(delay, delay + DIV_ROUND_UP(delay, 10));
+
+		uV = next_uV;
+	}
+
+	return 0;
+
+err:
+	/* Try to go back to original voltage */
+	regulator_set_voltage(ctrl_supply, orig_ctrl_uV, orig_ctrl_uV);
+
+	return ret;
+}
+
+static int vctrl_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+
+	return vctrl->sel;
+}
+
+static int vctrl_set_voltage_sel(struct regulator_dev *rdev,
+				 unsigned int selector)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+	struct regulator *ctrl_supply = vctrl->ctrl_supply;
+	unsigned int orig_sel = vctrl->sel;
+	int target_uV;
+	int uV;
+	int ret;
+
+	if (selector >= rdev->desc->n_voltages)
+		return -EINVAL;
+
+	uV = vctrl->vtable[orig_sel].out;
+	target_uV = vctrl->vtable[selector].out;
+
+	if (target_uV >= uV || !vctrl->ovp_threshold) {
+		/* voltage rising or no OVP */
+		ret = regulator_set_voltage(ctrl_supply,
+					    vctrl->vtable[selector].ctrl,
+					    vctrl->vtable[selector].ctrl);
+		if (!ret)
+			vctrl->sel = selector;
+
+		return ret;
+	}
+
+	while (vctrl->sel != selector) {
+		int uV = vctrl->vtable[vctrl->sel].out;
+		int max_drop_uV = (uV * vctrl->ovp_threshold) / 100;
+		int next_uV = max_t(int, target_uV, uV - max_drop_uV);
+		unsigned int next_sel = vctrl->sel;
+		int delay;
+
+		/* Find lowest voltage above the OVP threshold */
+		if (vctrl->vtable[0].out >= next_uV) {
+			next_sel = 0;
+		} else {
+			int i;
+			/*
+			 * Backward traversal should be faster on average for
+			 * gradual changes due to OVP.
+			 */
+			for (i = vctrl->sel - 1; i >= 0; i--) {
+				if (vctrl->vtable[i].out < next_uV) {
+					next_sel = i + 1;
+					break;
+				}
+			}
+		}
+
+		if (next_sel == vctrl->sel) {
+			/*
+			 * The voltage change can not be performed without
+			 * exceeding the OVP threshold.
+			 */
+			dev_err(&rdev->dev,
+				"voltage change would exceed OVP threshold\n");
+			ret = -EINVAL;
+			goto err;
+		}
+
+		ret = regulator_set_voltage(ctrl_supply,
+					    vctrl->vtable[next_sel].ctrl,
+					    vctrl->vtable[next_sel].ctrl);
+		if (ret) {
+			dev_err(&rdev->dev,
+				"failed to set control voltage to %duV\n",
+				vctrl->vtable[next_sel].ctrl);
+			goto err;
+		}
+
+		vctrl->sel = next_sel;
+
+		delay = DIV_ROUND_UP(uV - vctrl->vtable[next_sel].out,
+				     vctrl->min_slew_down_rate);
+		usleep_range(delay, delay + DIV_ROUND_UP(delay, 10));
+	}
+
+	return 0;
+
+err:
+	if (vctrl->sel != orig_sel) {
+		/* Try to go back to original voltage */
+		if (!regulator_set_voltage(ctrl_supply,
+					   vctrl->vtable[orig_sel].ctrl,
+					   vctrl->vtable[orig_sel].ctrl))
+			vctrl->sel = orig_sel;
+		else
+			dev_warn(&rdev->dev,
+				 "failed to restore original voltage\n");
+	}
+
+	return ret;
+}
+
+static int vctrl_list_voltage(struct regulator_dev *rdev,
+			      unsigned int selector)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+
+	if (selector >= rdev->desc->n_voltages)
+		return -EINVAL;
+
+	return vctrl->vtable[selector].out;
+}
+
+static int vctrl_parse_dt(struct platform_device *pdev,
+			  struct vctrl_data *vctrl)
+{
+	int ret;
+	struct device_node *np = pdev->dev.of_node;
+	u32 pval;
+	u32 vrange_ctrl[2];
+	u32 vrange_out[2];
+
+	ret = of_property_read_u32(np, "ovp-threshold-percent", &pval);
+	if (!ret) {
+		vctrl->ovp_threshold = pval;
+		if (vctrl->ovp_threshold > 100) {
+			dev_err(&pdev->dev,
+				"ovp-threshold-percent (%u) > 100\n",
+				vctrl->ovp_threshold);
+			return -EINVAL;
+		}
+	}
+
+	ret = of_property_read_u32(np, "min-slew-down-rate", &pval);
+	if (!ret) {
+		vctrl->min_slew_down_rate = pval;
+
+		/* We use the value as int and as divider; sanity check */
+		if (vctrl->min_slew_down_rate == 0) {
+			dev_err(&pdev->dev,
+				"min-slew-down-rate must not be 0\n");
+			return -EINVAL;
+		} else if (vctrl->min_slew_down_rate > INT_MAX) {
+			dev_err(&pdev->dev, "min-slew-down-rate (%u) too big\n",
+				vctrl->min_slew_down_rate);
+			return -EINVAL;
+		}
+	}
+
+	if (vctrl->ovp_threshold && !vctrl->min_slew_down_rate) {
+		dev_err(&pdev->dev,
+			"ovp-threshold-percent requires min-slew-down-rate\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32_array(np, "ctrl-voltage-range", vrange_ctrl,
+					 2);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read ctrl-voltage-range: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (vrange_ctrl[0] >= vrange_ctrl[1]) {
+		dev_err(&pdev->dev, "ctrl-voltage-range is invalid: %d-%d\n",
+			vrange_ctrl[0], vrange_ctrl[1]);
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32_array(np, "output-voltage-range", vrange_out,
+					 2);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read output-voltage-range: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (vrange_out[0] >= vrange_out[1]) {
+		dev_err(&pdev->dev, "output-voltage-range is invalid: %d-%d\n",
+			vrange_out[0], vrange_out[1]);
+		return -EINVAL;
+	}
+
+	vctrl->vrange.ctrl.min_uV = vrange_ctrl[0];
+	vctrl->vrange.ctrl.max_uV = vrange_ctrl[1];
+	vctrl->vrange.out.min_uV = vrange_out[0];
+	vctrl->vrange.out.max_uV = vrange_out[1];
+
+	return 0;
+}
+
+static int vctrl_cmp_ctrl_uV(const void *a, const void *b)
+{
+	const struct vctrl_voltage_table *at = a;
+	const struct vctrl_voltage_table *bt = b;
+
+	return at->ctrl - bt->ctrl;
+}
+
+static int vctrl_init_vtable(struct platform_device *pdev)
+{
+	struct vctrl_data *vctrl = platform_get_drvdata(pdev);
+	struct regulator_desc *rdesc = &vctrl->desc;
+	struct regulator *ctrl_supply = vctrl->ctrl_supply;
+	struct vctrl_voltage_range *vrange_ctrl = &vctrl->vrange.ctrl;
+	int n_voltages;
+	int ctrl_uV;
+	int i, idx_vt;
+
+	n_voltages = regulator_count_voltages(ctrl_supply);
+	rdesc->n_voltages = n_voltages;
+
+	/* determine number of steps within the range of the vctrl regulator */
+	for (i = 0; i < n_voltages; i++) {
+		ctrl_uV = regulator_list_voltage(ctrl_supply, i);
+
+		if (ctrl_uV < vrange_ctrl->min_uV ||
+		    ctrl_uV > vrange_ctrl->max_uV) {
+			rdesc->n_voltages--;
+			continue;
+		}
+	}
+
+	if (rdesc->n_voltages == 0) {
+		dev_err(&pdev->dev, "invalid configuration\n");
+		return -EINVAL;
+	}
+
+	vctrl->vtable = devm_kmalloc_array(
+		&pdev->dev, sizeof(struct vctrl_voltage_table),
+		rdesc->n_voltages, GFP_KERNEL);
+	if (!vctrl->vtable)
+		return -ENOMEM;
+
+	/* create mapping control <=> output voltage */
+	for (i = 0, idx_vt = 0; i < n_voltages; i++) {
+		ctrl_uV = regulator_list_voltage(ctrl_supply, i);
+
+		if (ctrl_uV < vrange_ctrl->min_uV ||
+		    ctrl_uV > vrange_ctrl->max_uV)
+			continue;
+
+		vctrl->vtable[idx_vt].ctrl = ctrl_uV;
+		vctrl->vtable[idx_vt].out =
+			vctrl_calc_output_voltage(vctrl, ctrl_uV);
+		idx_vt++;
+	}
+
+	/* we rely on the table to be ordered by ascending voltage */
+	sort(vctrl->vtable, rdesc->n_voltages,
+	     sizeof(struct vctrl_voltage_table), vctrl_cmp_ctrl_uV,
+	     NULL);
+
+	return 0;
+}
+
+static const struct regulator_ops vctrl_ops_cont = {
+	.get_voltage	  = vctrl_get_voltage,
+	.set_voltage	  = vctrl_set_voltage,
+};
+
+static const struct regulator_ops vctrl_ops_non_cont = {
+	.set_voltage_sel = vctrl_set_voltage_sel,
+	.get_voltage_sel = vctrl_get_voltage_sel,
+	.list_voltage    = vctrl_list_voltage,
+	.map_voltage     = regulator_map_voltage_iterate,
+};
+
+static int vctrl_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct vctrl_data *vctrl;
+	const struct regulator_init_data *init_data;
+	struct regulator_desc *rdesc;
+	struct regulator_config cfg = { };
+	struct vctrl_voltage_range *vrange_ctrl;
+	int ctrl_uV;
+	int ret;
+
+	vctrl = devm_kzalloc(&pdev->dev, sizeof(struct vctrl_data),
+			     GFP_KERNEL);
+	if (!vctrl)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, vctrl);
+
+	vctrl->ctrl_supply = devm_regulator_get(&pdev->dev, "ctrl");
+	if (IS_ERR(vctrl->ctrl_supply))
+		return PTR_ERR(vctrl->ctrl_supply);
+
+	ret = vctrl_parse_dt(pdev, vctrl);
+	if (ret)
+		return ret;
+
+	vrange_ctrl = &vctrl->vrange.ctrl;
+
+	rdesc = &vctrl->desc;
+	rdesc->name = "vctrl";
+	rdesc->type = REGULATOR_VOLTAGE;
+	rdesc->owner = THIS_MODULE;
+
+	/* determine if the voltage range of the control supply is continuous */
+	if ((regulator_count_voltages(vctrl->ctrl_supply) == 1) &&
+	    regulator_is_supported_voltage(vctrl->ctrl_supply,
+					   vrange_ctrl->min_uV,
+					   vrange_ctrl->min_uV) &&
+	    regulator_is_supported_voltage(vctrl->ctrl_supply,
+					   vrange_ctrl->max_uV,
+					   vrange_ctrl->max_uV)) {
+		rdesc->continuous_voltage_range = true;
+		rdesc->ops = &vctrl_ops_cont;
+	} else {
+		rdesc->ops = &vctrl_ops_non_cont;
+	}
+
+	init_data = of_get_regulator_init_data(&pdev->dev, np, rdesc);
+	if (!init_data)
+		return -ENOMEM;
+
+	cfg.of_node = np;
+	cfg.dev = &pdev->dev;
+	cfg.driver_data = vctrl;
+	cfg.init_data = init_data;
+
+	if (!rdesc->continuous_voltage_range) {
+		ret = vctrl_init_vtable(pdev);
+		if (ret)
+			return ret;
+
+		ctrl_uV = regulator_get_voltage(vctrl->ctrl_supply);
+		if (ctrl_uV < 0) {
+			dev_err(&pdev->dev, "failed to get control voltage\n");
+			return ctrl_uV;
+		}
+
+		/* determine current voltage selector from control voltage */
+		if (ctrl_uV < vrange_ctrl->min_uV) {
+			vctrl->sel = 0;
+		} else if (ctrl_uV > vrange_ctrl->max_uV) {
+			vctrl->sel = rdesc->n_voltages - 1;
+		} else {
+			int i;
+
+			for (i = 0; i < rdesc->n_voltages; i++) {
+				if (ctrl_uV == vctrl->vtable[i].ctrl) {
+					vctrl->sel = i;
+					break;
+				}
+			}
+		}
+	}
+
+	vctrl->rdev = devm_regulator_register(&pdev->dev, rdesc, &cfg);
+	if (IS_ERR(vctrl->rdev)) {
+		ret = PTR_ERR(vctrl->rdev);
+		dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id vctrl_of_match[] = {
+	{ .compatible = "vctrl-regulator", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, vctrl_of_match);
+
+static struct platform_driver vctrl_driver = {
+	.probe		= vctrl_probe,
+	.driver		= {
+		.name		= "vctrl-regulator",
+		.of_match_table = of_match_ptr(vctrl_of_match),
+	},
+};
+
+module_platform_driver(vctrl_driver);
+
+MODULE_DESCRIPTION("Voltage Controlled Regulator Driver");
+MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>");
+MODULE_LICENSE("GPL v2");
-- 
2.11.0.483.g087da7b7c-goog

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-10 20:43 ` Matthias Kaehlcke
  0 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-10 20:43 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, Rob Herring, Mark Rutland
  Cc: linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Douglas Anderson,
	Brian Norris, Guenter Roeck, Dmitry Torokhov, Matthias Kaehlcke

The output voltage of a voltage controlled regulator can be controlled
through the voltage of another regulator. The current version of this
driver assumes that the output voltage is a linear function of the control
voltage.

Signed-off-by: Matthias Kaehlcke <mka-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
---
Note: The current version of the driver has a known limitation: If an
input supply is configured besides the control supply the two regulators
must have no common ancestor, otherwise the spinlock of the ancestor
would be acquired twice by the core code. One possible solution would be
to configure the control regulator through a string instead of a phandle,
i.e. not specifying it as supply.

 .../devicetree/bindings/regulator/vctrl.txt        |  56 +++
 drivers/regulator/Kconfig                          |   7 +
 drivers/regulator/Makefile                         |   1 +
 drivers/regulator/vctrl-regulator.c                | 525 +++++++++++++++++++++
 4 files changed, 589 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/regulator/vctrl.txt
 create mode 100644 drivers/regulator/vctrl-regulator.c

diff --git a/Documentation/devicetree/bindings/regulator/vctrl.txt b/Documentation/devicetree/bindings/regulator/vctrl.txt
new file mode 100644
index 000000000000..ba3c9b2f61b1
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/vctrl.txt
@@ -0,0 +1,56 @@
+Bindings for Voltage controlled regulators
+==========================================
+
+Required properties:
+--------------------
+- compatible		: must be "vctrl-regulator".
+- ctrl-supply:		: a phandle for the regulator supplying the control
+			  voltage.
+- output-voltage-range	: an array of two integer values specifying the range
+			  (min/max) of the output voltage. The min/max voltage
+			  constraints of the regulator (if specified) must not
+			  exceed this range.
+- ctrl-voltage-range	: an array of two integer values specifying the range
+			  (min/max) of the control voltage. The values of
+			  ctrl-voltage-range and output-voltage-range are used
+			  for the conversion between output and control voltage.
+			  The min/max values of ctrl-voltage-range must specify
+			  the control voltage needed to generate the min/max
+			  output voltage.
+
+Optional properties:
+--------------------
+- ovp-threshold-percent	: overvoltage protection (OVP) threshold of the
+			  regulator in percent. Some regulators have an OVP
+			  circuitry which shuts down the regulator when the
+			  actual output voltage deviates beyond a certain
+			  margin from the expected value for a given control
+			  voltage. On larger voltage decreases this can occur
+			  undesiredly since the output voltage does not adjust
+			  inmediately to changes in the control voltage. To
+			  avoid this situation the vctrl driver breaks down
+			  larger voltage decreases into multiple steps, where
+			  each step is within the OVP threshold.
+- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
+			  down in the worst case (lightest expected load).
+			  Specified in uV / us (like main regulator ramp rate).
+			  This value is required when ovp-threshold-percent is
+			  specified.
+
+Example:
+
+	vctrl_reg {
+		compatible = "vctrl-regulator";
+		regulator-name = "vctrl_reg";
+
+		ctrl-supply = <&ctrl_supply>;
+
+		regulator-min-microvolt = <800000>;
+		regulator-max-microvolt = <1500000>;
+
+		output-voltage-range = <800000 1500000>;
+		ctrl-voltage-range = <200000 500000>;
+
+		slew-rate = <225>;
+		ovp-threshold-percent = <16>;
+	};
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 936f7ccc9736..da83a3abe288 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -843,6 +843,13 @@ config REGULATOR_TWL4030
 	  This driver supports the voltage regulators provided by
 	  this family of companion chips.
 
+config REGULATOR_VCTRL
+	tristate "Voltage controlled regulators"
+	depends on OF
+	help
+	  This driver provides support for voltage regulators whose output
+	  voltage is controlled by the voltage of another regulator.
+
 config REGULATOR_VEXPRESS
 	tristate "Versatile Express regulators"
 	depends on VEXPRESS_CONFIG
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 14294692beb9..e246e148a7f9 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o
 obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o
 obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o
+obj-$(CONFIG_REGULATOR_VCTRL) += vctrl-regulator.o
 obj-$(CONFIG_REGULATOR_VEXPRESS) += vexpress-regulator.o
 obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o
 obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o
diff --git a/drivers/regulator/vctrl-regulator.c b/drivers/regulator/vctrl-regulator.c
new file mode 100644
index 000000000000..6487f23da335
--- /dev/null
+++ b/drivers/regulator/vctrl-regulator.c
@@ -0,0 +1,525 @@
+/*
+ * Driver for voltage controller regulators
+ *
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/sort.h>
+
+struct vctrl_voltage_range {
+	int min_uV;
+	int max_uV;
+};
+
+struct vctrl_voltage_ranges {
+	struct vctrl_voltage_range ctrl;
+	struct vctrl_voltage_range out;
+};
+
+struct vctrl_voltage_table {
+	int ctrl;
+	int out;
+};
+
+struct vctrl_data {
+	struct regulator_dev *rdev;
+	struct regulator_desc desc;
+	struct regulator *ctrl_supply;
+	unsigned int min_slew_down_rate;
+	unsigned int ovp_threshold;
+	struct vctrl_voltage_ranges vrange;
+	struct vctrl_voltage_table *vtable;
+	unsigned int sel;
+};
+
+static int vctrl_calc_ctrl_voltage(struct vctrl_data *vctrl, int out_uV)
+{
+	struct vctrl_voltage_range *ctrl = &vctrl->vrange.ctrl;
+	struct vctrl_voltage_range *out = &vctrl->vrange.out;
+
+	return ctrl->min_uV +
+		DIV_ROUND_CLOSEST_ULL((s64)(out_uV - out->min_uV) *
+				      (ctrl->max_uV - ctrl->min_uV),
+				      out->max_uV - out->min_uV);
+}
+
+static int vctrl_calc_output_voltage(struct vctrl_data *vctrl, int ctrl_uV)
+{
+	struct vctrl_voltage_range *ctrl = &vctrl->vrange.ctrl;
+	struct vctrl_voltage_range *out = &vctrl->vrange.out;
+
+	if (ctrl_uV < 0) {
+		pr_err("vctrl: failed to get control voltage\n");
+		return ctrl_uV;
+	}
+
+	if (ctrl_uV < ctrl->min_uV)
+		return out->min_uV;
+
+	if (ctrl_uV > ctrl->max_uV)
+		return out->max_uV;
+
+	return out->min_uV +
+		DIV_ROUND_CLOSEST_ULL((s64)(ctrl_uV - ctrl->min_uV) *
+				      (out->max_uV - out->min_uV),
+				      ctrl->max_uV - ctrl->min_uV);
+}
+
+static int vctrl_get_voltage(struct regulator_dev *rdev)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+	int ctrl_uV = regulator_get_voltage(vctrl->ctrl_supply);
+
+	return vctrl_calc_output_voltage(vctrl, ctrl_uV);
+}
+
+static int vctrl_set_voltage(struct regulator_dev *rdev,
+			     int req_min_uV, int req_max_uV,
+			     unsigned int *selector)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+	struct regulator *ctrl_supply = vctrl->ctrl_supply;
+	int orig_ctrl_uV = regulator_get_voltage(ctrl_supply);
+	int uV = vctrl_calc_output_voltage(vctrl, orig_ctrl_uV);
+	int ret;
+
+	if (req_min_uV >= uV || !vctrl->ovp_threshold)
+		/* voltage rising or no OVP */
+		return regulator_set_voltage(
+			ctrl_supply,
+			vctrl_calc_ctrl_voltage(vctrl, req_min_uV),
+			vctrl_calc_ctrl_voltage(vctrl, req_max_uV));
+
+	while (uV > req_min_uV) {
+		int max_drop_uV = (uV * vctrl->ovp_threshold) / 100;
+		int next_uV;
+		int next_ctrl_uV;
+		int delay;
+
+		/* Make sure no infinite loop even in crazy cases */
+		if (max_drop_uV == 0)
+			max_drop_uV = 1;
+
+		next_uV = max_t(int, req_min_uV, uV - max_drop_uV);
+		next_ctrl_uV = vctrl_calc_ctrl_voltage(vctrl, next_uV);
+
+		ret = regulator_set_voltage(ctrl_supply,
+					    next_ctrl_uV,
+					    next_ctrl_uV);
+		if (ret)
+			goto err;
+
+		delay = DIV_ROUND_UP(uV - next_uV, vctrl->min_slew_down_rate);
+		usleep_range(delay, delay + DIV_ROUND_UP(delay, 10));
+
+		uV = next_uV;
+	}
+
+	return 0;
+
+err:
+	/* Try to go back to original voltage */
+	regulator_set_voltage(ctrl_supply, orig_ctrl_uV, orig_ctrl_uV);
+
+	return ret;
+}
+
+static int vctrl_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+
+	return vctrl->sel;
+}
+
+static int vctrl_set_voltage_sel(struct regulator_dev *rdev,
+				 unsigned int selector)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+	struct regulator *ctrl_supply = vctrl->ctrl_supply;
+	unsigned int orig_sel = vctrl->sel;
+	int target_uV;
+	int uV;
+	int ret;
+
+	if (selector >= rdev->desc->n_voltages)
+		return -EINVAL;
+
+	uV = vctrl->vtable[orig_sel].out;
+	target_uV = vctrl->vtable[selector].out;
+
+	if (target_uV >= uV || !vctrl->ovp_threshold) {
+		/* voltage rising or no OVP */
+		ret = regulator_set_voltage(ctrl_supply,
+					    vctrl->vtable[selector].ctrl,
+					    vctrl->vtable[selector].ctrl);
+		if (!ret)
+			vctrl->sel = selector;
+
+		return ret;
+	}
+
+	while (vctrl->sel != selector) {
+		int uV = vctrl->vtable[vctrl->sel].out;
+		int max_drop_uV = (uV * vctrl->ovp_threshold) / 100;
+		int next_uV = max_t(int, target_uV, uV - max_drop_uV);
+		unsigned int next_sel = vctrl->sel;
+		int delay;
+
+		/* Find lowest voltage above the OVP threshold */
+		if (vctrl->vtable[0].out >= next_uV) {
+			next_sel = 0;
+		} else {
+			int i;
+			/*
+			 * Backward traversal should be faster on average for
+			 * gradual changes due to OVP.
+			 */
+			for (i = vctrl->sel - 1; i >= 0; i--) {
+				if (vctrl->vtable[i].out < next_uV) {
+					next_sel = i + 1;
+					break;
+				}
+			}
+		}
+
+		if (next_sel == vctrl->sel) {
+			/*
+			 * The voltage change can not be performed without
+			 * exceeding the OVP threshold.
+			 */
+			dev_err(&rdev->dev,
+				"voltage change would exceed OVP threshold\n");
+			ret = -EINVAL;
+			goto err;
+		}
+
+		ret = regulator_set_voltage(ctrl_supply,
+					    vctrl->vtable[next_sel].ctrl,
+					    vctrl->vtable[next_sel].ctrl);
+		if (ret) {
+			dev_err(&rdev->dev,
+				"failed to set control voltage to %duV\n",
+				vctrl->vtable[next_sel].ctrl);
+			goto err;
+		}
+
+		vctrl->sel = next_sel;
+
+		delay = DIV_ROUND_UP(uV - vctrl->vtable[next_sel].out,
+				     vctrl->min_slew_down_rate);
+		usleep_range(delay, delay + DIV_ROUND_UP(delay, 10));
+	}
+
+	return 0;
+
+err:
+	if (vctrl->sel != orig_sel) {
+		/* Try to go back to original voltage */
+		if (!regulator_set_voltage(ctrl_supply,
+					   vctrl->vtable[orig_sel].ctrl,
+					   vctrl->vtable[orig_sel].ctrl))
+			vctrl->sel = orig_sel;
+		else
+			dev_warn(&rdev->dev,
+				 "failed to restore original voltage\n");
+	}
+
+	return ret;
+}
+
+static int vctrl_list_voltage(struct regulator_dev *rdev,
+			      unsigned int selector)
+{
+	struct vctrl_data *vctrl = rdev_get_drvdata(rdev);
+
+	if (selector >= rdev->desc->n_voltages)
+		return -EINVAL;
+
+	return vctrl->vtable[selector].out;
+}
+
+static int vctrl_parse_dt(struct platform_device *pdev,
+			  struct vctrl_data *vctrl)
+{
+	int ret;
+	struct device_node *np = pdev->dev.of_node;
+	u32 pval;
+	u32 vrange_ctrl[2];
+	u32 vrange_out[2];
+
+	ret = of_property_read_u32(np, "ovp-threshold-percent", &pval);
+	if (!ret) {
+		vctrl->ovp_threshold = pval;
+		if (vctrl->ovp_threshold > 100) {
+			dev_err(&pdev->dev,
+				"ovp-threshold-percent (%u) > 100\n",
+				vctrl->ovp_threshold);
+			return -EINVAL;
+		}
+	}
+
+	ret = of_property_read_u32(np, "min-slew-down-rate", &pval);
+	if (!ret) {
+		vctrl->min_slew_down_rate = pval;
+
+		/* We use the value as int and as divider; sanity check */
+		if (vctrl->min_slew_down_rate == 0) {
+			dev_err(&pdev->dev,
+				"min-slew-down-rate must not be 0\n");
+			return -EINVAL;
+		} else if (vctrl->min_slew_down_rate > INT_MAX) {
+			dev_err(&pdev->dev, "min-slew-down-rate (%u) too big\n",
+				vctrl->min_slew_down_rate);
+			return -EINVAL;
+		}
+	}
+
+	if (vctrl->ovp_threshold && !vctrl->min_slew_down_rate) {
+		dev_err(&pdev->dev,
+			"ovp-threshold-percent requires min-slew-down-rate\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32_array(np, "ctrl-voltage-range", vrange_ctrl,
+					 2);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read ctrl-voltage-range: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (vrange_ctrl[0] >= vrange_ctrl[1]) {
+		dev_err(&pdev->dev, "ctrl-voltage-range is invalid: %d-%d\n",
+			vrange_ctrl[0], vrange_ctrl[1]);
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32_array(np, "output-voltage-range", vrange_out,
+					 2);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read output-voltage-range: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (vrange_out[0] >= vrange_out[1]) {
+		dev_err(&pdev->dev, "output-voltage-range is invalid: %d-%d\n",
+			vrange_out[0], vrange_out[1]);
+		return -EINVAL;
+	}
+
+	vctrl->vrange.ctrl.min_uV = vrange_ctrl[0];
+	vctrl->vrange.ctrl.max_uV = vrange_ctrl[1];
+	vctrl->vrange.out.min_uV = vrange_out[0];
+	vctrl->vrange.out.max_uV = vrange_out[1];
+
+	return 0;
+}
+
+static int vctrl_cmp_ctrl_uV(const void *a, const void *b)
+{
+	const struct vctrl_voltage_table *at = a;
+	const struct vctrl_voltage_table *bt = b;
+
+	return at->ctrl - bt->ctrl;
+}
+
+static int vctrl_init_vtable(struct platform_device *pdev)
+{
+	struct vctrl_data *vctrl = platform_get_drvdata(pdev);
+	struct regulator_desc *rdesc = &vctrl->desc;
+	struct regulator *ctrl_supply = vctrl->ctrl_supply;
+	struct vctrl_voltage_range *vrange_ctrl = &vctrl->vrange.ctrl;
+	int n_voltages;
+	int ctrl_uV;
+	int i, idx_vt;
+
+	n_voltages = regulator_count_voltages(ctrl_supply);
+	rdesc->n_voltages = n_voltages;
+
+	/* determine number of steps within the range of the vctrl regulator */
+	for (i = 0; i < n_voltages; i++) {
+		ctrl_uV = regulator_list_voltage(ctrl_supply, i);
+
+		if (ctrl_uV < vrange_ctrl->min_uV ||
+		    ctrl_uV > vrange_ctrl->max_uV) {
+			rdesc->n_voltages--;
+			continue;
+		}
+	}
+
+	if (rdesc->n_voltages == 0) {
+		dev_err(&pdev->dev, "invalid configuration\n");
+		return -EINVAL;
+	}
+
+	vctrl->vtable = devm_kmalloc_array(
+		&pdev->dev, sizeof(struct vctrl_voltage_table),
+		rdesc->n_voltages, GFP_KERNEL);
+	if (!vctrl->vtable)
+		return -ENOMEM;
+
+	/* create mapping control <=> output voltage */
+	for (i = 0, idx_vt = 0; i < n_voltages; i++) {
+		ctrl_uV = regulator_list_voltage(ctrl_supply, i);
+
+		if (ctrl_uV < vrange_ctrl->min_uV ||
+		    ctrl_uV > vrange_ctrl->max_uV)
+			continue;
+
+		vctrl->vtable[idx_vt].ctrl = ctrl_uV;
+		vctrl->vtable[idx_vt].out =
+			vctrl_calc_output_voltage(vctrl, ctrl_uV);
+		idx_vt++;
+	}
+
+	/* we rely on the table to be ordered by ascending voltage */
+	sort(vctrl->vtable, rdesc->n_voltages,
+	     sizeof(struct vctrl_voltage_table), vctrl_cmp_ctrl_uV,
+	     NULL);
+
+	return 0;
+}
+
+static const struct regulator_ops vctrl_ops_cont = {
+	.get_voltage	  = vctrl_get_voltage,
+	.set_voltage	  = vctrl_set_voltage,
+};
+
+static const struct regulator_ops vctrl_ops_non_cont = {
+	.set_voltage_sel = vctrl_set_voltage_sel,
+	.get_voltage_sel = vctrl_get_voltage_sel,
+	.list_voltage    = vctrl_list_voltage,
+	.map_voltage     = regulator_map_voltage_iterate,
+};
+
+static int vctrl_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct vctrl_data *vctrl;
+	const struct regulator_init_data *init_data;
+	struct regulator_desc *rdesc;
+	struct regulator_config cfg = { };
+	struct vctrl_voltage_range *vrange_ctrl;
+	int ctrl_uV;
+	int ret;
+
+	vctrl = devm_kzalloc(&pdev->dev, sizeof(struct vctrl_data),
+			     GFP_KERNEL);
+	if (!vctrl)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, vctrl);
+
+	vctrl->ctrl_supply = devm_regulator_get(&pdev->dev, "ctrl");
+	if (IS_ERR(vctrl->ctrl_supply))
+		return PTR_ERR(vctrl->ctrl_supply);
+
+	ret = vctrl_parse_dt(pdev, vctrl);
+	if (ret)
+		return ret;
+
+	vrange_ctrl = &vctrl->vrange.ctrl;
+
+	rdesc = &vctrl->desc;
+	rdesc->name = "vctrl";
+	rdesc->type = REGULATOR_VOLTAGE;
+	rdesc->owner = THIS_MODULE;
+
+	/* determine if the voltage range of the control supply is continuous */
+	if ((regulator_count_voltages(vctrl->ctrl_supply) == 1) &&
+	    regulator_is_supported_voltage(vctrl->ctrl_supply,
+					   vrange_ctrl->min_uV,
+					   vrange_ctrl->min_uV) &&
+	    regulator_is_supported_voltage(vctrl->ctrl_supply,
+					   vrange_ctrl->max_uV,
+					   vrange_ctrl->max_uV)) {
+		rdesc->continuous_voltage_range = true;
+		rdesc->ops = &vctrl_ops_cont;
+	} else {
+		rdesc->ops = &vctrl_ops_non_cont;
+	}
+
+	init_data = of_get_regulator_init_data(&pdev->dev, np, rdesc);
+	if (!init_data)
+		return -ENOMEM;
+
+	cfg.of_node = np;
+	cfg.dev = &pdev->dev;
+	cfg.driver_data = vctrl;
+	cfg.init_data = init_data;
+
+	if (!rdesc->continuous_voltage_range) {
+		ret = vctrl_init_vtable(pdev);
+		if (ret)
+			return ret;
+
+		ctrl_uV = regulator_get_voltage(vctrl->ctrl_supply);
+		if (ctrl_uV < 0) {
+			dev_err(&pdev->dev, "failed to get control voltage\n");
+			return ctrl_uV;
+		}
+
+		/* determine current voltage selector from control voltage */
+		if (ctrl_uV < vrange_ctrl->min_uV) {
+			vctrl->sel = 0;
+		} else if (ctrl_uV > vrange_ctrl->max_uV) {
+			vctrl->sel = rdesc->n_voltages - 1;
+		} else {
+			int i;
+
+			for (i = 0; i < rdesc->n_voltages; i++) {
+				if (ctrl_uV == vctrl->vtable[i].ctrl) {
+					vctrl->sel = i;
+					break;
+				}
+			}
+		}
+	}
+
+	vctrl->rdev = devm_regulator_register(&pdev->dev, rdesc, &cfg);
+	if (IS_ERR(vctrl->rdev)) {
+		ret = PTR_ERR(vctrl->rdev);
+		dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id vctrl_of_match[] = {
+	{ .compatible = "vctrl-regulator", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, vctrl_of_match);
+
+static struct platform_driver vctrl_driver = {
+	.probe		= vctrl_probe,
+	.driver		= {
+		.name		= "vctrl-regulator",
+		.of_match_table = of_match_ptr(vctrl_of_match),
+	},
+};
+
+module_platform_driver(vctrl_driver);
+
+MODULE_DESCRIPTION("Voltage Controlled Regulator Driver");
+MODULE_AUTHOR("Matthias Kaehlcke <mka-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>");
+MODULE_LICENSE("GPL v2");
-- 
2.11.0.483.g087da7b7c-goog

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
  2017-02-10 20:43 ` Matthias Kaehlcke
  (?)
@ 2017-02-11  0:32 ` Matthias Kaehlcke
  -1 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-11  0:32 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, Rob Herring, Mark Rutland
  Cc: linux-kernel, devicetree, Douglas Anderson, Brian Norris,
	Guenter Roeck, Dmitry Torokhov

El Fri, Feb 10, 2017 at 12:43:48PM -0800 Matthias Kaehlcke ha dit:

> The output voltage of a voltage controlled regulator can be controlled
> through the voltage of another regulator. The current version of this
> driver assumes that the output voltage is a linear function of the control
> voltage.
> 
> ...
>
> +static int vctrl_probe(struct platform_device *pdev)
> +{
> ...
> +	/* determine if the voltage range of the control supply is continuous */
> +	if ((regulator_count_voltages(vctrl->ctrl_supply) == 1) &&
> +	    regulator_is_supported_voltage(vctrl->ctrl_supply,
> +					   vrange_ctrl->min_uV,
> +					   vrange_ctrl->min_uV) &&
> +	    regulator_is_supported_voltage(vctrl->ctrl_supply,
> +					   vrange_ctrl->max_uV,
> +					   vrange_ctrl->max_uV)) {
> +		rdesc->continuous_voltage_range = true;
> +		rdesc->ops = &vctrl_ops_cont;
> +	} else {
> +		rdesc->ops = &vctrl_ops_non_cont;
> +	}

This creature of indisputable beauty seemed to do the job on my
test systen, however I just realized that the condition is BS. It
turns out that on my system the voltage count of 1 stems from the
parent, since the voltage count of the control supply itself is zero.

int regulator_count_voltages(struct regulator *regulator)
{
	struct regulator_dev    *rdev = regulator->rdev;

	if (rdev->desc->n_voltages)
		return rdev->desc->n_voltages;

	if (!rdev->supply)
		return -EINVAL;

	return regulator_count_voltages(rdev->supply);
}

This certainly doesn't help to determine if the regulator has a
continous voltage range. It seems we need a function that evaluates
rdesc->continuous_voltage_range

--

Matthias

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-22  0:22   ` Rob Herring
  0 siblings, 0 replies; 9+ messages in thread
From: Rob Herring @ 2017-02-22  0:22 UTC (permalink / raw)
  To: Matthias Kaehlcke
  Cc: Liam Girdwood, Mark Brown, Mark Rutland, linux-kernel,
	devicetree, Douglas Anderson, Brian Norris, Guenter Roeck,
	Dmitry Torokhov

On Fri, Feb 10, 2017 at 12:43:48PM -0800, Matthias Kaehlcke wrote:
> The output voltage of a voltage controlled regulator can be controlled
> through the voltage of another regulator. The current version of this
> driver assumes that the output voltage is a linear function of the control
> voltage.
> 
> Signed-off-by: Matthias Kaehlcke <mka@chromium.org>
> ---
> Note: The current version of the driver has a known limitation: If an
> input supply is configured besides the control supply the two regulators
> must have no common ancestor, otherwise the spinlock of the ancestor
> would be acquired twice by the core code. One possible solution would be
> to configure the control regulator through a string instead of a phandle,
> i.e. not specifying it as supply.
> 
>  .../devicetree/bindings/regulator/vctrl.txt        |  56 +++

Please split bindings to separate patch.

>  drivers/regulator/Kconfig                          |   7 +
>  drivers/regulator/Makefile                         |   1 +
>  drivers/regulator/vctrl-regulator.c                | 525 +++++++++++++++++++++
>  4 files changed, 589 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/regulator/vctrl.txt
>  create mode 100644 drivers/regulator/vctrl-regulator.c
> 
> diff --git a/Documentation/devicetree/bindings/regulator/vctrl.txt b/Documentation/devicetree/bindings/regulator/vctrl.txt
> new file mode 100644
> index 000000000000..ba3c9b2f61b1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/regulator/vctrl.txt
> @@ -0,0 +1,56 @@
> +Bindings for Voltage controlled regulators
> +==========================================
> +
> +Required properties:
> +--------------------
> +- compatible		: must be "vctrl-regulator".
> +- ctrl-supply:		: a phandle for the regulator supplying the control
> +			  voltage.
> +- output-voltage-range	: an array of two integer values specifying the range
> +			  (min/max) of the output voltage. The min/max voltage
> +			  constraints of the regulator (if specified) must not
> +			  exceed this range.
> +- ctrl-voltage-range	: an array of two integer values specifying the range
> +			  (min/max) of the control voltage. The values of
> +			  ctrl-voltage-range and output-voltage-range are used
> +			  for the conversion between output and control voltage.
> +			  The min/max values of ctrl-voltage-range must specify
> +			  the control voltage needed to generate the min/max
> +			  output voltage.
> +
> +Optional properties:
> +--------------------
> +- ovp-threshold-percent	: overvoltage protection (OVP) threshold of the
> +			  regulator in percent. Some regulators have an OVP
> +			  circuitry which shuts down the regulator when the
> +			  actual output voltage deviates beyond a certain
> +			  margin from the expected value for a given control
> +			  voltage. On larger voltage decreases this can occur
> +			  undesiredly since the output voltage does not adjust
> +			  inmediately to changes in the control voltage. To
> +			  avoid this situation the vctrl driver breaks down
> +			  larger voltage decreases into multiple steps, where
> +			  each step is within the OVP threshold.
> +- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
> +			  down in the worst case (lightest expected load).
> +			  Specified in uV / us (like main regulator ramp rate).
> +			  This value is required when ovp-threshold-percent is
> +			  specified.

Don't we have a standard prop for this or that's just for ramp? Perhaps 
this should be common?

> +
> +Example:
> +
> +	vctrl_reg {

Don't use '_' in node names.

> +		compatible = "vctrl-regulator";
> +		regulator-name = "vctrl_reg";
> +
> +		ctrl-supply = <&ctrl_supply>;
> +

> +		regulator-min-microvolt = <800000>;
> +		regulator-max-microvolt = <1500000>;
> +
> +		output-voltage-range = <800000 1500000>;

Why do you need both?

> +		ctrl-voltage-range = <200000 500000>;
> +
> +		slew-rate = <225>;

Not documented.

> +		ovp-threshold-percent = <16>;
> +	};

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-22  0:22   ` Rob Herring
  0 siblings, 0 replies; 9+ messages in thread
From: Rob Herring @ 2017-02-22  0:22 UTC (permalink / raw)
  To: Matthias Kaehlcke
  Cc: Liam Girdwood, Mark Brown, Mark Rutland,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Douglas Anderson,
	Brian Norris, Guenter Roeck, Dmitry Torokhov

On Fri, Feb 10, 2017 at 12:43:48PM -0800, Matthias Kaehlcke wrote:
> The output voltage of a voltage controlled regulator can be controlled
> through the voltage of another regulator. The current version of this
> driver assumes that the output voltage is a linear function of the control
> voltage.
> 
> Signed-off-by: Matthias Kaehlcke <mka-F7+t8E8rja9g9hUCZPvPmw@public.gmane.org>
> ---
> Note: The current version of the driver has a known limitation: If an
> input supply is configured besides the control supply the two regulators
> must have no common ancestor, otherwise the spinlock of the ancestor
> would be acquired twice by the core code. One possible solution would be
> to configure the control regulator through a string instead of a phandle,
> i.e. not specifying it as supply.
> 
>  .../devicetree/bindings/regulator/vctrl.txt        |  56 +++

Please split bindings to separate patch.

>  drivers/regulator/Kconfig                          |   7 +
>  drivers/regulator/Makefile                         |   1 +
>  drivers/regulator/vctrl-regulator.c                | 525 +++++++++++++++++++++
>  4 files changed, 589 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/regulator/vctrl.txt
>  create mode 100644 drivers/regulator/vctrl-regulator.c
> 
> diff --git a/Documentation/devicetree/bindings/regulator/vctrl.txt b/Documentation/devicetree/bindings/regulator/vctrl.txt
> new file mode 100644
> index 000000000000..ba3c9b2f61b1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/regulator/vctrl.txt
> @@ -0,0 +1,56 @@
> +Bindings for Voltage controlled regulators
> +==========================================
> +
> +Required properties:
> +--------------------
> +- compatible		: must be "vctrl-regulator".
> +- ctrl-supply:		: a phandle for the regulator supplying the control
> +			  voltage.
> +- output-voltage-range	: an array of two integer values specifying the range
> +			  (min/max) of the output voltage. The min/max voltage
> +			  constraints of the regulator (if specified) must not
> +			  exceed this range.
> +- ctrl-voltage-range	: an array of two integer values specifying the range
> +			  (min/max) of the control voltage. The values of
> +			  ctrl-voltage-range and output-voltage-range are used
> +			  for the conversion between output and control voltage.
> +			  The min/max values of ctrl-voltage-range must specify
> +			  the control voltage needed to generate the min/max
> +			  output voltage.
> +
> +Optional properties:
> +--------------------
> +- ovp-threshold-percent	: overvoltage protection (OVP) threshold of the
> +			  regulator in percent. Some regulators have an OVP
> +			  circuitry which shuts down the regulator when the
> +			  actual output voltage deviates beyond a certain
> +			  margin from the expected value for a given control
> +			  voltage. On larger voltage decreases this can occur
> +			  undesiredly since the output voltage does not adjust
> +			  inmediately to changes in the control voltage. To
> +			  avoid this situation the vctrl driver breaks down
> +			  larger voltage decreases into multiple steps, where
> +			  each step is within the OVP threshold.
> +- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
> +			  down in the worst case (lightest expected load).
> +			  Specified in uV / us (like main regulator ramp rate).
> +			  This value is required when ovp-threshold-percent is
> +			  specified.

Don't we have a standard prop for this or that's just for ramp? Perhaps 
this should be common?

> +
> +Example:
> +
> +	vctrl_reg {

Don't use '_' in node names.

> +		compatible = "vctrl-regulator";
> +		regulator-name = "vctrl_reg";
> +
> +		ctrl-supply = <&ctrl_supply>;
> +

> +		regulator-min-microvolt = <800000>;
> +		regulator-max-microvolt = <1500000>;
> +
> +		output-voltage-range = <800000 1500000>;

Why do you need both?

> +		ctrl-voltage-range = <200000 500000>;
> +
> +		slew-rate = <225>;

Not documented.

> +		ovp-threshold-percent = <16>;
> +	};
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
  2017-02-22  0:22   ` Rob Herring
@ 2017-02-25  3:19     ` Matthias Kaehlcke
  -1 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-25  3:19 UTC (permalink / raw)
  To: Rob Herring
  Cc: Liam Girdwood, Mark Brown, Mark Rutland, linux-kernel,
	devicetree, Douglas Anderson, Brian Norris, Guenter Roeck,
	Dmitry Torokhov

Rob, thanks for your comments!

El Tue, Feb 21, 2017 at 06:22:14PM -0600 Rob Herring ha dit:

> On Fri, Feb 10, 2017 at 12:43:48PM -0800, Matthias Kaehlcke wrote:
> > The output voltage of a voltage controlled regulator can be controlled
> > through the voltage of another regulator. The current version of this
> > driver assumes that the output voltage is a linear function of the control
> > voltage.
> >
> > ...
> > 
> >  .../devicetree/bindings/regulator/vctrl.txt        |  56 +++
> 
> Please split bindings to separate patch.

The driver is not functional without the bindings, is a separate patch
preferred nevertheless? I saw other recentish regulator drivers
(ltc3676, pv88080) added the driver and the bindings together.

> > +Optional properties:
> > +--------------------
> > ...
> > +- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
> > +			  down in the worst case (lightest expected load).
> > +			  Specified in uV / us (like main regulator ramp rate).
> > +			  This value is required when ovp-threshold-percent is
> > +			  specified.
> 
> Don't we have a standard prop for this or that's just for ramp?

regulator-ramp-delay is related, but not exactly the same. The
ramp-delay is applied at the end of an up- or downward transition,
while this prop only specifies the downward rate and is applied in
between partial transitions towards the final voltage.

We possibly could use ramp-delay and add a set_voltage_time() op to
vctrl to prevent the core code from adding the "normal" ramp-delay at
the end of the transition. However it could be confusing that vctrl
handles the ramp-delay differently than other drivers, especially we
don't want a delay in the upward transition for vctrl. But maybe
nobody would care about the different behavior, as long as the
regulator does its job ...

> Perhaps this should be common?

I agree this could be a property other regulators might have. I think
it could be common as long as it is a descriptive property which
regulator drivers can use or not, without adding functionality to the
core code (except for DT parsing).

Mark, what do you think?

> > +
> > +Example:
> > +
> > +	vctrl_reg {
> 
> Don't use '_' in node names.

Will fix in next revision.

> > +		compatible = "vctrl-regulator";
> > +		regulator-name = "vctrl_reg";
> > +
> > +		ctrl-supply = <&ctrl_supply>;
> > +
> 
> > +		regulator-min-microvolt = <800000>;
> > +		regulator-max-microvolt = <1500000>;
> > +
> > +		output-voltage-range = <800000 1500000>;
> 
> Why do you need both?

We don't necessarily need both. The constraints are not available yet
when the driver parses and validates the DT node, but we can read
regulator-min/max-microvolt manually in the driver instead of
duplicating the values.

> > +		ctrl-voltage-range = <200000 500000>;
> > +
> > +		slew-rate = <225>;
> 
> Not documented.

Should be min-slew-down-rate. Will fix in next revision.

Thanks

Matthias

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-25  3:19     ` Matthias Kaehlcke
  0 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-25  3:19 UTC (permalink / raw)
  To: Rob Herring
  Cc: Liam Girdwood, Mark Brown, Mark Rutland,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Douglas Anderson,
	Brian Norris, Guenter Roeck, Dmitry Torokhov

Rob, thanks for your comments!

El Tue, Feb 21, 2017 at 06:22:14PM -0600 Rob Herring ha dit:

> On Fri, Feb 10, 2017 at 12:43:48PM -0800, Matthias Kaehlcke wrote:
> > The output voltage of a voltage controlled regulator can be controlled
> > through the voltage of another regulator. The current version of this
> > driver assumes that the output voltage is a linear function of the control
> > voltage.
> >
> > ...
> > 
> >  .../devicetree/bindings/regulator/vctrl.txt        |  56 +++
> 
> Please split bindings to separate patch.

The driver is not functional without the bindings, is a separate patch
preferred nevertheless? I saw other recentish regulator drivers
(ltc3676, pv88080) added the driver and the bindings together.

> > +Optional properties:
> > +--------------------
> > ...
> > +- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
> > +			  down in the worst case (lightest expected load).
> > +			  Specified in uV / us (like main regulator ramp rate).
> > +			  This value is required when ovp-threshold-percent is
> > +			  specified.
> 
> Don't we have a standard prop for this or that's just for ramp?

regulator-ramp-delay is related, but not exactly the same. The
ramp-delay is applied at the end of an up- or downward transition,
while this prop only specifies the downward rate and is applied in
between partial transitions towards the final voltage.

We possibly could use ramp-delay and add a set_voltage_time() op to
vctrl to prevent the core code from adding the "normal" ramp-delay at
the end of the transition. However it could be confusing that vctrl
handles the ramp-delay differently than other drivers, especially we
don't want a delay in the upward transition for vctrl. But maybe
nobody would care about the different behavior, as long as the
regulator does its job ...

> Perhaps this should be common?

I agree this could be a property other regulators might have. I think
it could be common as long as it is a descriptive property which
regulator drivers can use or not, without adding functionality to the
core code (except for DT parsing).

Mark, what do you think?

> > +
> > +Example:
> > +
> > +	vctrl_reg {
> 
> Don't use '_' in node names.

Will fix in next revision.

> > +		compatible = "vctrl-regulator";
> > +		regulator-name = "vctrl_reg";
> > +
> > +		ctrl-supply = <&ctrl_supply>;
> > +
> 
> > +		regulator-min-microvolt = <800000>;
> > +		regulator-max-microvolt = <1500000>;
> > +
> > +		output-voltage-range = <800000 1500000>;
> 
> Why do you need both?

We don't necessarily need both. The constraints are not available yet
when the driver parses and validates the DT node, but we can read
regulator-min/max-microvolt manually in the driver instead of
duplicating the values.

> > +		ctrl-voltage-range = <200000 500000>;
> > +
> > +		slew-rate = <225>;
> 
> Not documented.

Should be min-slew-down-rate. Will fix in next revision.

Thanks

Matthias
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-27 18:53       ` Matthias Kaehlcke
  0 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-27 18:53 UTC (permalink / raw)
  To: Rob Herring
  Cc: Liam Girdwood, Mark Brown, Mark Rutland, linux-kernel,
	devicetree, Douglas Anderson, Brian Norris, Guenter Roeck,
	Dmitry Torokhov

El Fri, Feb 24, 2017 at 07:19:19PM -0800 Matthias Kaehlcke ha dit:

> El Tue, Feb 21, 2017 at 06:22:14PM -0600 Rob Herring ha dit:
> >
> > > +Optional properties:
> > > +--------------------
> > > ...
> > > +- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
> > > +			  down in the worst case (lightest expected load).
> > > +			  Specified in uV / us (like main regulator ramp rate).
> > > +			  This value is required when ovp-threshold-percent is
> > > +			  specified.
> > 
> > Don't we have a standard prop for this or that's just for ramp?
> 
> regulator-ramp-delay is related, but not exactly the same. The
> ramp-delay is applied at the end of an up- or downward transition,
> while this prop only specifies the downward rate and is applied in
> between partial transitions towards the final voltage.
> 
> We possibly could use ramp-delay and add a set_voltage_time() op to
> vctrl to prevent the core code from adding the "normal" ramp-delay at
> the end of the transition. However it could be confusing that vctrl
> handles the ramp-delay differently than other drivers, especially we
> don't want a delay in the upward transition for vctrl. But maybe
> nobody would care about the different behavior, as long as the
> regulator does its job ...

Actually the behavior of a delay on the downward transition and no
delay on the upward transition is hardware specific. Other users of
this driver might need a standard ramp delay, therefore I think it's
not a good idea to (re-)use this property in vctrl.

Matthias

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v1] regulator: Add driver for voltage controlled regulators
@ 2017-02-27 18:53       ` Matthias Kaehlcke
  0 siblings, 0 replies; 9+ messages in thread
From: Matthias Kaehlcke @ 2017-02-27 18:53 UTC (permalink / raw)
  To: Rob Herring
  Cc: Liam Girdwood, Mark Brown, Mark Rutland,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA, Douglas Anderson,
	Brian Norris, Guenter Roeck, Dmitry Torokhov

El Fri, Feb 24, 2017 at 07:19:19PM -0800 Matthias Kaehlcke ha dit:

> El Tue, Feb 21, 2017 at 06:22:14PM -0600 Rob Herring ha dit:
> >
> > > +Optional properties:
> > > +--------------------
> > > ...
> > > +- min-slew-down-rate	: Describes how slowly the regulator voltage will decay
> > > +			  down in the worst case (lightest expected load).
> > > +			  Specified in uV / us (like main regulator ramp rate).
> > > +			  This value is required when ovp-threshold-percent is
> > > +			  specified.
> > 
> > Don't we have a standard prop for this or that's just for ramp?
> 
> regulator-ramp-delay is related, but not exactly the same. The
> ramp-delay is applied at the end of an up- or downward transition,
> while this prop only specifies the downward rate and is applied in
> between partial transitions towards the final voltage.
> 
> We possibly could use ramp-delay and add a set_voltage_time() op to
> vctrl to prevent the core code from adding the "normal" ramp-delay at
> the end of the transition. However it could be confusing that vctrl
> handles the ramp-delay differently than other drivers, especially we
> don't want a delay in the upward transition for vctrl. But maybe
> nobody would care about the different behavior, as long as the
> regulator does its job ...

Actually the behavior of a delay on the downward transition and no
delay on the upward transition is hardware specific. Other users of
this driver might need a standard ramp delay, therefore I think it's
not a good idea to (re-)use this property in vctrl.

Matthias
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2017-02-28  9:02 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-10 20:43 [PATCH v1] regulator: Add driver for voltage controlled regulators Matthias Kaehlcke
2017-02-10 20:43 ` Matthias Kaehlcke
2017-02-11  0:32 ` Matthias Kaehlcke
2017-02-22  0:22 ` Rob Herring
2017-02-22  0:22   ` Rob Herring
2017-02-25  3:19   ` Matthias Kaehlcke
2017-02-25  3:19     ` Matthias Kaehlcke
2017-02-27 18:53     ` Matthias Kaehlcke
2017-02-27 18:53       ` Matthias Kaehlcke

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.