This series adds the binding and driver for one of the two pairs of LDOs
inside the Allwinner D1 SoC. I am splitting up the two pairs of LDOs to
unblock merging the SoC devicetree; the analog LDOs depend on the audio
codec binding, but they are not required to boot.
A binding and driver change is required for the SRAM controller, to
accept the regulators device as its child node.
The example for the regulator device binding is in SRAM controller
binding document, per Rob's request to keep MFD examples in one place.
Because of this, at least the first 3 patches need to be taken together
through the regulator tree, though it should be fine to merge the whole
series that way.
Changes in v5:
- Correct the voltage calculation for the non-linearity around 1.6 V.
Changes in v4:
- Fix the order of the maintainer/description sections
- Replace unevaluatedProperties with "additionalProperties: false"
- Drop the analog LDOs until the codec binding is ready
- Drop the analog LDOs until the codec binding is ready
- Remove unevaluatedProperties from regulators schema reference
- Check the compatible string instead of the node name
Changes in v3:
- Add "reg" property to bindings
- Add "unevaluatedProperties: true" to regulator nodes
- Minor changes to regulator node name patterns
- Remove system-ldos example (now added in the parent binding)
- Adjust control flow in sun20i_regulator_get_regmap() for clarity
- Require the regulators node to have a unit address
- Reference the regulator schema from the SRAM controller schema
- Move the system LDOs example to the SRAM controller schema
- Reorder the patches so the example passes validation
Changes in v2:
- Remove syscon property from bindings
- Update binding examples to fix warnings and provide context
- Use decimal numbers for .n_voltages instead of field widths
- Get the regmap from the parent device instead of a property/phandle
Samuel Holland (4):
regulator: dt-bindings: Add Allwinner D1 system LDOs
regulator: sun20i: Add Allwinner D1 LDOs driver
dt-bindings: sram: sunxi-sram: Add regulators child
soc: sunxi: sram: Only iterate over SRAM children
.../allwinner,sun20i-d1-system-ldos.yaml | 37 +++++
.../allwinner,sun4i-a10-system-control.yaml | 28 ++++
drivers/regulator/Kconfig | 8 +
drivers/regulator/Makefile | 1 +
drivers/regulator/sun20i-regulator.c | 156 ++++++++++++++++++
drivers/soc/sunxi/sunxi_sram.c | 3 +
6 files changed, 233 insertions(+)
create mode 100644 Documentation/devicetree/bindings/regulator/allwinner,sun20i-d1-system-ldos.yaml
create mode 100644 drivers/regulator/sun20i-regulator.c
--
2.37.4
Some sunxi SoCs have in-package regulators controlled by a register in
the system control MMIO block. Allow a child node for this regulator
device in addition to SRAM child nodes.
Reviewed-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Samuel Holland <[email protected]>
---
(no changes since v4)
Changes in v4:
- Remove unevaluatedProperties from regulators schema reference
Changes in v3:
- Require the regulators node to have a unit address
- Reference the regulator schema from the SRAM controller schema
- Move the system LDOs example to the SRAM controller schema
- Reorder the patches so the example passes validation
Changes in v2:
- New patch for v2
.../allwinner,sun4i-a10-system-control.yaml | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml b/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml
index 98a7dc7f467d..c487ac9521e9 100644
--- a/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml
+++ b/Documentation/devicetree/bindings/sram/allwinner,sun4i-a10-system-control.yaml
@@ -56,6 +56,9 @@ properties:
ranges: true
patternProperties:
+ "^regulators@[0-9a-f]+$":
+ $ref: /schemas/regulator/allwinner,sun20i-d1-system-ldos.yaml#
+
"^sram@[a-z0-9]+":
type: object
@@ -130,3 +133,28 @@ examples:
};
};
};
+
+ - |
+ syscon@3000000 {
+ compatible = "allwinner,sun20i-d1-system-control";
+ reg = <0x3000000 0x1000>;
+ ranges;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ regulators@3000150 {
+ compatible = "allwinner,sun20i-d1-system-ldos";
+ reg = <0x3000150 0x4>;
+
+ reg_ldoa: ldoa {
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_ldob: ldob {
+ regulator-name = "vcc-dram";
+ regulator-min-microvolt = <1500000>;
+ regulator-max-microvolt = <1500000>;
+ };
+ };
+ };
--
2.37.4
Now that a regulators child is accepted by the controller binding, the
debugfs show routine must be explicitly limited to mmio-sram children.
Acked-by: Jernej Skrabec <[email protected]>
Signed-off-by: Samuel Holland <[email protected]>
---
(no changes since v4)
Changes in v4:
- Check the compatible string instead of the node name
Changes in v2:
- New patch for v2
drivers/soc/sunxi/sunxi_sram.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/soc/sunxi/sunxi_sram.c b/drivers/soc/sunxi/sunxi_sram.c
index 92f9186c1c42..f09918c59042 100644
--- a/drivers/soc/sunxi/sunxi_sram.c
+++ b/drivers/soc/sunxi/sunxi_sram.c
@@ -120,6 +120,9 @@ static int sunxi_sram_show(struct seq_file *s, void *data)
seq_puts(s, "--------------------\n\n");
for_each_child_of_node(sram_dev->of_node, sram_node) {
+ if (!of_device_is_compatible(sram_node, "mmio-sram"))
+ continue;
+
sram_addr_p = of_get_address(sram_node, 0, NULL, NULL);
seq_printf(s, "sram@%08x\n",
--
2.37.4
D1 contains two pairs of LDOs, "analog" LDOs and "system" LDOs. They are
similar and can share a driver, but only the system LDOs have a DT
binding defined so far.
The system LDOs have a single linear range. The voltage step is not an
integer, so a custom .list_voltage is needed to get the rounding right.
Signed-off-by: Samuel Holland <[email protected]>
---
Changes in v5:
- Correct the voltage calculation for the non-linearity around 1.6 V.
Changes in v4:
- Drop the analog LDOs until the codec binding is ready
Changes in v3:
- Adjust control flow in sun20i_regulator_get_regmap() for clarity
Changes in v2:
- Use decimal numbers for .n_voltages instead of field widths
- Get the regmap from the parent device instead of a property/phandle
drivers/regulator/Kconfig | 8 ++
drivers/regulator/Makefile | 1 +
drivers/regulator/sun20i-regulator.c | 156 +++++++++++++++++++++++++++
3 files changed, 165 insertions(+)
create mode 100644 drivers/regulator/sun20i-regulator.c
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 070e4403c6c2..8480532114c1 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1280,6 +1280,14 @@ config REGULATOR_STW481X_VMMC
This driver supports the internal VMMC regulator in the STw481x
PMIC chips.
+config REGULATOR_SUN20I
+ tristate "Allwinner D1 internal LDOs"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select MFD_SYSCON
+ default ARCH_SUNXI
+ help
+ This driver supports the internal LDOs in the Allwinner D1 SoC.
+
config REGULATOR_SY7636A
tristate "Silergy SY7636A voltage regulator"
depends on MFD_SY7636A
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 5962307e1130..8e9b5a21123d 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -150,6 +150,7 @@ obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o
obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o
obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o
obj-$(CONFIG_REGULATOR_STW481X_VMMC) += stw481x-vmmc.o
+obj-$(CONFIG_REGULATOR_SUN20I) += sun20i-regulator.o
obj-$(CONFIG_REGULATOR_SY7636A) += sy7636a-regulator.o
obj-$(CONFIG_REGULATOR_SY8106A) += sy8106a-regulator.o
obj-$(CONFIG_REGULATOR_SY8824X) += sy8824x.o
diff --git a/drivers/regulator/sun20i-regulator.c b/drivers/regulator/sun20i-regulator.c
new file mode 100644
index 000000000000..8af6b8037ee0
--- /dev/null
+++ b/drivers/regulator/sun20i-regulator.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (c) 2021-2022 Samuel Holland <[email protected]>
+//
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+
+#define SUN20I_SYS_LDO_CTRL_REG 0x150
+
+struct sun20i_regulator_data {
+ const struct regulator_desc *descs;
+ unsigned int ndescs;
+};
+
+/* regulator_list_voltage_linear() modified for the non-integral uV_step. */
+static int sun20i_d1_system_ldo_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ const struct regulator_desc *desc = rdev->desc;
+ unsigned int fraction, uV;
+
+ if (selector >= desc->n_voltages)
+ return -EINVAL;
+
+ uV = desc->min_uV + (desc->uV_step * selector);
+ fraction = selector + (desc->min_uV % 4);
+
+ if (uV > 1606667)
+ uV += 6667;
+ else
+ fraction++;
+
+ /* Produce correctly-rounded absolute voltages. */
+ return uV + (fraction / 3);
+}
+
+static const struct regulator_ops sun20i_d1_system_ldo_ops = {
+ .list_voltage = sun20i_d1_system_ldo_list_voltage,
+ .map_voltage = regulator_map_voltage_ascend,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+};
+
+static const struct regulator_desc sun20i_d1_system_ldo_descs[] = {
+ {
+ .name = "ldoa",
+ .supply_name = "ldo-in",
+ .of_match = "ldoa",
+ .ops = &sun20i_d1_system_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 32,
+ .min_uV = 1593333,
+ .uV_step = 13333, /* repeating */
+ .vsel_reg = SUN20I_SYS_LDO_CTRL_REG,
+ .vsel_mask = GENMASK(7, 0),
+ },
+ {
+ .name = "ldob",
+ .supply_name = "ldo-in",
+ .of_match = "ldob",
+ .ops = &sun20i_d1_system_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 64,
+ .min_uV = 1166666,
+ .uV_step = 13333, /* repeating */
+ .vsel_reg = SUN20I_SYS_LDO_CTRL_REG,
+ .vsel_mask = GENMASK(15, 8),
+ },
+};
+
+static const struct sun20i_regulator_data sun20i_d1_system_ldos = {
+ .descs = sun20i_d1_system_ldo_descs,
+ .ndescs = ARRAY_SIZE(sun20i_d1_system_ldo_descs),
+};
+
+static struct regmap *sun20i_regulator_get_regmap(struct device *dev)
+{
+ struct regmap *regmap;
+
+ /*
+ * First try the syscon interface. The system control device is not
+ * compatible with "syscon", so fall back to getting the regmap from
+ * its platform device. This is ugly, but required for devicetree
+ * backward compatibility.
+ */
+ regmap = syscon_node_to_regmap(dev->parent->of_node);
+ if (!IS_ERR(regmap))
+ return regmap;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (regmap)
+ return regmap;
+
+ return ERR_PTR(-EPROBE_DEFER);
+}
+
+static int sun20i_regulator_probe(struct platform_device *pdev)
+{
+ const struct sun20i_regulator_data *data;
+ struct device *dev = &pdev->dev;
+ struct regulator_config config;
+ struct regmap *regmap;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -EINVAL;
+
+ regmap = sun20i_regulator_get_regmap(dev);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to get regmap\n");
+
+ config = (struct regulator_config) {
+ .dev = dev,
+ .regmap = regmap,
+ };
+
+ for (unsigned int i = 0; i < data->ndescs; ++i) {
+ const struct regulator_desc *desc = &data->descs[i];
+ struct regulator_dev *rdev;
+
+ rdev = devm_regulator_register(dev, desc, &config);
+ if (IS_ERR(rdev))
+ return PTR_ERR(rdev);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id sun20i_regulator_of_match[] = {
+ {
+ .compatible = "allwinner,sun20i-d1-system-ldos",
+ .data = &sun20i_d1_system_ldos,
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sun20i_regulator_of_match);
+
+static struct platform_driver sun20i_regulator_driver = {
+ .probe = sun20i_regulator_probe,
+ .driver = {
+ .name = "sun20i-regulator",
+ .of_match_table = sun20i_regulator_of_match,
+ },
+};
+module_platform_driver(sun20i_regulator_driver);
+
+MODULE_AUTHOR("Samuel Holland <[email protected]>");
+MODULE_DESCRIPTION("Allwinner D1 internal LDO driver");
+MODULE_LICENSE("GPL");
--
2.37.4
Dne četrtek, 08. december 2022 ob 09:41:27 CET je Samuel Holland napisal(a):
> Now that a regulators child is accepted by the controller binding, the
> debugfs show routine must be explicitly limited to mmio-sram children.
>
> Acked-by: Jernej Skrabec <[email protected]>
> Signed-off-by: Samuel Holland <[email protected]>
Merged, thanks!
Best regards,
Jernej
On Thu, 8 Dec 2022 02:41:25 -0600
Samuel Holland <[email protected]> wrote:
> D1 contains two pairs of LDOs, "analog" LDOs and "system" LDOs. They are
> similar and can share a driver, but only the system LDOs have a DT
> binding defined so far.
>
> The system LDOs have a single linear range. The voltage step is not an
> integer, so a custom .list_voltage is needed to get the rounding right.
>
I put sun20i_d1_system_ldo_list_voltage() into a userland program, and
compared the full generated list with the manual: they fully match now.
The formula looks mind-boggling, but works: well done!
I have no real experience with the regulator boilerplate, but it looks
alright, and the register offset and masks also match:
> Signed-off-by: Samuel Holland <[email protected]>
Reviewed-by: Andre Przywara <[email protected]>
Cheers,
Andre
> ---
>
> Changes in v5:
> - Correct the voltage calculation for the non-linearity around 1.6 V.
>
> Changes in v4:
> - Drop the analog LDOs until the codec binding is ready
>
> Changes in v3:
> - Adjust control flow in sun20i_regulator_get_regmap() for clarity
>
> Changes in v2:
> - Use decimal numbers for .n_voltages instead of field widths
> - Get the regmap from the parent device instead of a property/phandle
>
> drivers/regulator/Kconfig | 8 ++
> drivers/regulator/Makefile | 1 +
> drivers/regulator/sun20i-regulator.c | 156 +++++++++++++++++++++++++++
> 3 files changed, 165 insertions(+)
> create mode 100644 drivers/regulator/sun20i-regulator.c
>
> diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
> index 070e4403c6c2..8480532114c1 100644
> --- a/drivers/regulator/Kconfig
> +++ b/drivers/regulator/Kconfig
> @@ -1280,6 +1280,14 @@ config REGULATOR_STW481X_VMMC
> This driver supports the internal VMMC regulator in the STw481x
> PMIC chips.
>
> +config REGULATOR_SUN20I
> + tristate "Allwinner D1 internal LDOs"
> + depends on ARCH_SUNXI || COMPILE_TEST
> + select MFD_SYSCON
> + default ARCH_SUNXI
> + help
> + This driver supports the internal LDOs in the Allwinner D1 SoC.
> +
> config REGULATOR_SY7636A
> tristate "Silergy SY7636A voltage regulator"
> depends on MFD_SY7636A
> diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
> index 5962307e1130..8e9b5a21123d 100644
> --- a/drivers/regulator/Makefile
> +++ b/drivers/regulator/Makefile
> @@ -150,6 +150,7 @@ obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o
> obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o
> obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o
> obj-$(CONFIG_REGULATOR_STW481X_VMMC) += stw481x-vmmc.o
> +obj-$(CONFIG_REGULATOR_SUN20I) += sun20i-regulator.o
> obj-$(CONFIG_REGULATOR_SY7636A) += sy7636a-regulator.o
> obj-$(CONFIG_REGULATOR_SY8106A) += sy8106a-regulator.o
> obj-$(CONFIG_REGULATOR_SY8824X) += sy8824x.o
> diff --git a/drivers/regulator/sun20i-regulator.c b/drivers/regulator/sun20i-regulator.c
> new file mode 100644
> index 000000000000..8af6b8037ee0
> --- /dev/null
> +++ b/drivers/regulator/sun20i-regulator.c
> @@ -0,0 +1,156 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// Copyright (c) 2021-2022 Samuel Holland <[email protected]>
> +//
> +
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/driver.h>
> +
> +#define SUN20I_SYS_LDO_CTRL_REG 0x150
> +
> +struct sun20i_regulator_data {
> + const struct regulator_desc *descs;
> + unsigned int ndescs;
> +};
> +
> +/* regulator_list_voltage_linear() modified for the non-integral uV_step. */
> +static int sun20i_d1_system_ldo_list_voltage(struct regulator_dev *rdev,
> + unsigned int selector)
> +{
> + const struct regulator_desc *desc = rdev->desc;
> + unsigned int fraction, uV;
> +
> + if (selector >= desc->n_voltages)
> + return -EINVAL;
> +
> + uV = desc->min_uV + (desc->uV_step * selector);
> + fraction = selector + (desc->min_uV % 4);
> +
> + if (uV > 1606667)
> + uV += 6667;
> + else
> + fraction++;
> +
> + /* Produce correctly-rounded absolute voltages. */
> + return uV + (fraction / 3);
> +}
> +
> +static const struct regulator_ops sun20i_d1_system_ldo_ops = {
> + .list_voltage = sun20i_d1_system_ldo_list_voltage,
> + .map_voltage = regulator_map_voltage_ascend,
> + .set_voltage_sel = regulator_set_voltage_sel_regmap,
> + .get_voltage_sel = regulator_get_voltage_sel_regmap,
> +};
> +
> +static const struct regulator_desc sun20i_d1_system_ldo_descs[] = {
> + {
> + .name = "ldoa",
> + .supply_name = "ldo-in",
> + .of_match = "ldoa",
> + .ops = &sun20i_d1_system_ldo_ops,
> + .type = REGULATOR_VOLTAGE,
> + .owner = THIS_MODULE,
> + .n_voltages = 32,
> + .min_uV = 1593333,
> + .uV_step = 13333, /* repeating */
> + .vsel_reg = SUN20I_SYS_LDO_CTRL_REG,
> + .vsel_mask = GENMASK(7, 0),
> + },
> + {
> + .name = "ldob",
> + .supply_name = "ldo-in",
> + .of_match = "ldob",
> + .ops = &sun20i_d1_system_ldo_ops,
> + .type = REGULATOR_VOLTAGE,
> + .owner = THIS_MODULE,
> + .n_voltages = 64,
> + .min_uV = 1166666,
> + .uV_step = 13333, /* repeating */
> + .vsel_reg = SUN20I_SYS_LDO_CTRL_REG,
> + .vsel_mask = GENMASK(15, 8),
> + },
> +};
> +
> +static const struct sun20i_regulator_data sun20i_d1_system_ldos = {
> + .descs = sun20i_d1_system_ldo_descs,
> + .ndescs = ARRAY_SIZE(sun20i_d1_system_ldo_descs),
> +};
> +
> +static struct regmap *sun20i_regulator_get_regmap(struct device *dev)
> +{
> + struct regmap *regmap;
> +
> + /*
> + * First try the syscon interface. The system control device is not
> + * compatible with "syscon", so fall back to getting the regmap from
> + * its platform device. This is ugly, but required for devicetree
> + * backward compatibility.
> + */
> + regmap = syscon_node_to_regmap(dev->parent->of_node);
> + if (!IS_ERR(regmap))
> + return regmap;
> +
> + regmap = dev_get_regmap(dev->parent, NULL);
> + if (regmap)
> + return regmap;
> +
> + return ERR_PTR(-EPROBE_DEFER);
> +}
> +
> +static int sun20i_regulator_probe(struct platform_device *pdev)
> +{
> + const struct sun20i_regulator_data *data;
> + struct device *dev = &pdev->dev;
> + struct regulator_config config;
> + struct regmap *regmap;
> +
> + data = of_device_get_match_data(dev);
> + if (!data)
> + return -EINVAL;
> +
> + regmap = sun20i_regulator_get_regmap(dev);
> + if (IS_ERR(regmap))
> + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to get regmap\n");
> +
> + config = (struct regulator_config) {
> + .dev = dev,
> + .regmap = regmap,
> + };
> +
> + for (unsigned int i = 0; i < data->ndescs; ++i) {
> + const struct regulator_desc *desc = &data->descs[i];
> + struct regulator_dev *rdev;
> +
> + rdev = devm_regulator_register(dev, desc, &config);
> + if (IS_ERR(rdev))
> + return PTR_ERR(rdev);
> + }
> +
> + return 0;
> +}
> +
> +static const struct of_device_id sun20i_regulator_of_match[] = {
> + {
> + .compatible = "allwinner,sun20i-d1-system-ldos",
> + .data = &sun20i_d1_system_ldos,
> + },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, sun20i_regulator_of_match);
> +
> +static struct platform_driver sun20i_regulator_driver = {
> + .probe = sun20i_regulator_probe,
> + .driver = {
> + .name = "sun20i-regulator",
> + .of_match_table = sun20i_regulator_of_match,
> + },
> +};
> +module_platform_driver(sun20i_regulator_driver);
> +
> +MODULE_AUTHOR("Samuel Holland <[email protected]>");
> +MODULE_DESCRIPTION("Allwinner D1 internal LDO driver");
> +MODULE_LICENSE("GPL");
Dne četrtek, 08. december 2022 ob 09:41:25 CET je Samuel Holland napisal(a):
> D1 contains two pairs of LDOs, "analog" LDOs and "system" LDOs. They are
> similar and can share a driver, but only the system LDOs have a DT
> binding defined so far.
>
> The system LDOs have a single linear range. The voltage step is not an
> integer, so a custom .list_voltage is needed to get the rounding right.
>
> Signed-off-by: Samuel Holland <[email protected]>
Reviewed-by: Jernej Skrabec <[email protected]>
Best regards,
Jernej