1 From ad842bfb2eb10a75050dd69145ca59de982eb0e9 Mon Sep 17 00:00:00 2001
2 From: Samuel Holland <samuel@sholland.org>
3 Date: Sun, 17 Jul 2022 11:46:52 -0500
4 Subject: [PATCH 021/117] regulator: sun20i: Add support for Allwinner D1 LDOs
6 D1 contains two pairs of LDOs. Since they have similar bindings, and
7 they always exist together, put them in a single driver.
9 The analog LDOs are relatively boring, with a single linear range. Their
10 one quirk is that a bandgap reference must be calibrated for them to
11 produce the correct voltage.
13 The system LDOs have the complication that their voltage step is not an
14 integer, so a custom .list_voltage is needed to get the rounding right.
17 - Use decimal numbers for .n_voltages instead of field widths
18 - Get the regmap from the parent device instead of a property/phandle
21 - Adjust control flow in sun20i_regulator_get_regmap() for clarity
23 Reviewed-by: Heiko Stuebner <heiko@sntech.de>
24 Tested-by: Heiko Stuebner <heiko@sntech.de>
25 Signed-off-by: Samuel Holland <samuel@sholland.org>
27 drivers/regulator/Kconfig | 8 +
28 drivers/regulator/Makefile | 1 +
29 drivers/regulator/sun20i-regulator.c | 232 +++++++++++++++++++++++++++
30 3 files changed, 241 insertions(+)
31 create mode 100644 drivers/regulator/sun20i-regulator.c
33 --- a/drivers/regulator/Kconfig
34 +++ b/drivers/regulator/Kconfig
35 @@ -1280,6 +1280,14 @@ config REGULATOR_STW481X_VMMC
36 This driver supports the internal VMMC regulator in the STw481x
39 +config REGULATOR_SUN20I
40 + tristate "Allwinner D1 internal LDOs"
41 + depends on ARCH_SUNXI || COMPILE_TEST
42 + depends on MFD_SYSCON && NVMEM
45 + This driver supports the internal LDOs in the Allwinner D1 SoC.
47 config REGULATOR_SY7636A
48 tristate "Silergy SY7636A voltage regulator"
49 depends on MFD_SY7636A
50 --- a/drivers/regulator/Makefile
51 +++ b/drivers/regulator/Makefile
52 @@ -150,6 +150,7 @@ obj-$(CONFIG_REGULATOR_STM32_VREFBUF) +=
53 obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o
54 obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o
55 obj-$(CONFIG_REGULATOR_STW481X_VMMC) += stw481x-vmmc.o
56 +obj-$(CONFIG_REGULATOR_SUN20I) += sun20i-regulator.o
57 obj-$(CONFIG_REGULATOR_SY7636A) += sy7636a-regulator.o
58 obj-$(CONFIG_REGULATOR_SY8106A) += sy8106a-regulator.o
59 obj-$(CONFIG_REGULATOR_SY8824X) += sy8824x.o
61 +++ b/drivers/regulator/sun20i-regulator.c
63 +// SPDX-License-Identifier: GPL-2.0-only
65 +// Copyright (c) 2021-2022 Samuel Holland <samuel@sholland.org>
68 +#include <linux/mfd/syscon.h>
69 +#include <linux/module.h>
70 +#include <linux/nvmem-consumer.h>
71 +#include <linux/of_device.h>
72 +#include <linux/platform_device.h>
73 +#include <linux/regmap.h>
74 +#include <linux/regulator/driver.h>
76 +#define SUN20I_POWER_REG 0x348
78 +#define SUN20I_SYS_LDO_CTRL_REG 0x150
80 +struct sun20i_regulator_data {
81 + int (*init)(struct device *dev,
82 + struct regmap *regmap);
83 + const struct regulator_desc *descs;
84 + unsigned int ndescs;
87 +static int sun20i_d1_analog_ldos_init(struct device *dev, struct regmap *regmap)
92 + ret = nvmem_cell_read_u8(dev, "bg_trim", &bg_trim);
94 + return dev_err_probe(dev, ret, "Failed to get bg_trim value\n");
96 + /* The default value corresponds to 900 mV. */
100 + return regmap_update_bits(regmap, SUN20I_POWER_REG,
101 + GENMASK(7, 0), bg_trim);
104 +static const struct regulator_ops sun20i_d1_analog_ldo_ops = {
105 + .list_voltage = regulator_list_voltage_linear,
106 + .map_voltage = regulator_map_voltage_linear,
107 + .set_voltage_sel = regulator_set_voltage_sel_regmap,
108 + .get_voltage_sel = regulator_get_voltage_sel_regmap,
109 + .enable = regulator_enable_regmap,
110 + .disable = regulator_disable_regmap,
111 + .is_enabled = regulator_is_enabled_regmap,
114 +static const struct regulator_desc sun20i_d1_analog_ldo_descs[] = {
117 + .supply_name = "vdd33",
118 + .of_match = "aldo",
119 + .ops = &sun20i_d1_analog_ldo_ops,
120 + .type = REGULATOR_VOLTAGE,
121 + .owner = THIS_MODULE,
125 + .vsel_reg = SUN20I_POWER_REG,
126 + .vsel_mask = GENMASK(14, 12),
127 + .enable_reg = SUN20I_POWER_REG,
128 + .enable_mask = BIT(31),
132 + .supply_name = "hpldoin",
133 + .of_match = "hpldo",
134 + .ops = &sun20i_d1_analog_ldo_ops,
135 + .type = REGULATOR_VOLTAGE,
136 + .owner = THIS_MODULE,
140 + .vsel_reg = SUN20I_POWER_REG,
141 + .vsel_mask = GENMASK(10, 8),
142 + .enable_reg = SUN20I_POWER_REG,
143 + .enable_mask = BIT(30),
147 +static const struct sun20i_regulator_data sun20i_d1_analog_ldos = {
148 + .init = sun20i_d1_analog_ldos_init,
149 + .descs = sun20i_d1_analog_ldo_descs,
150 + .ndescs = ARRAY_SIZE(sun20i_d1_analog_ldo_descs),
153 +/* regulator_list_voltage_linear() modified for the non-integral uV_step. */
154 +static int sun20i_d1_system_ldo_list_voltage(struct regulator_dev *rdev,
155 + unsigned int selector)
157 + const struct regulator_desc *desc = rdev->desc;
160 + if (selector >= desc->n_voltages)
163 + uV = desc->min_uV + (desc->uV_step * selector);
165 + /* Produce correctly-rounded absolute voltages. */
166 + return uV + ((selector + 1 + (desc->min_uV % 4)) / 3);
169 +static const struct regulator_ops sun20i_d1_system_ldo_ops = {
170 + .list_voltage = sun20i_d1_system_ldo_list_voltage,
171 + .map_voltage = regulator_map_voltage_ascend,
172 + .set_voltage_sel = regulator_set_voltage_sel_regmap,
173 + .get_voltage_sel = regulator_get_voltage_sel_regmap,
176 +static const struct regulator_desc sun20i_d1_system_ldo_descs[] = {
179 + .supply_name = "ldo-in",
180 + .of_match = "ldoa",
181 + .ops = &sun20i_d1_system_ldo_ops,
182 + .type = REGULATOR_VOLTAGE,
183 + .owner = THIS_MODULE,
186 + .uV_step = 13333, /* repeating */
187 + .vsel_reg = SUN20I_SYS_LDO_CTRL_REG,
188 + .vsel_mask = GENMASK(7, 0),
192 + .supply_name = "ldo-in",
193 + .of_match = "ldob",
194 + .ops = &sun20i_d1_system_ldo_ops,
195 + .type = REGULATOR_VOLTAGE,
196 + .owner = THIS_MODULE,
199 + .uV_step = 13333, /* repeating */
200 + .vsel_reg = SUN20I_SYS_LDO_CTRL_REG,
201 + .vsel_mask = GENMASK(15, 8),
205 +static const struct sun20i_regulator_data sun20i_d1_system_ldos = {
206 + .descs = sun20i_d1_system_ldo_descs,
207 + .ndescs = ARRAY_SIZE(sun20i_d1_system_ldo_descs),
210 +static const struct of_device_id sun20i_regulator_of_match[] = {
212 + .compatible = "allwinner,sun20i-d1-analog-ldos",
213 + .data = &sun20i_d1_analog_ldos,
216 + .compatible = "allwinner,sun20i-d1-system-ldos",
217 + .data = &sun20i_d1_system_ldos,
221 +MODULE_DEVICE_TABLE(of, sun20i_regulator_of_match);
223 +static struct regmap *sun20i_regulator_get_regmap(struct device *dev)
225 + struct regmap *regmap;
228 + * First try the syscon interface. The system control device is not
229 + * compatible with "syscon", so fall back to getting the regmap from
230 + * its platform device. This is ugly, but required for devicetree
231 + * backward compatibility.
233 + regmap = syscon_node_to_regmap(dev->parent->of_node);
234 + if (!IS_ERR(regmap))
237 + regmap = dev_get_regmap(dev->parent, NULL);
241 + return ERR_PTR(-EPROBE_DEFER);
244 +static int sun20i_regulator_probe(struct platform_device *pdev)
246 + const struct sun20i_regulator_data *data;
247 + struct device *dev = &pdev->dev;
248 + struct regulator_config config;
249 + struct regmap *regmap;
252 + data = of_device_get_match_data(dev);
256 + regmap = sun20i_regulator_get_regmap(dev);
257 + if (IS_ERR(regmap))
258 + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to get regmap\n");
261 + ret = data->init(dev, regmap);
266 + config = (struct regulator_config) {
271 + for (unsigned int i = 0; i < data->ndescs; ++i) {
272 + const struct regulator_desc *desc = &data->descs[i];
273 + struct regulator_dev *rdev;
275 + rdev = devm_regulator_register(dev, desc, &config);
277 + return PTR_ERR(rdev);
283 +static struct platform_driver sun20i_regulator_driver = {
284 + .probe = sun20i_regulator_probe,
286 + .name = "sun20i-regulator",
287 + .of_match_table = sun20i_regulator_of_match,
290 +module_platform_driver(sun20i_regulator_driver);
292 +MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
293 +MODULE_DESCRIPTION("Allwinner D1 internal LDO driver");
294 +MODULE_LICENSE("GPL");