1 // SPDX-License-Identifier: GPL-2.0-only
3 // Copyright (c) 2021-2022 Samuel Holland <samuel@sholland.org>
6 #include <linux/mfd/syscon.h>
7 #include <linux/module.h>
9 #include <linux/of_device.h>
10 #include <linux/platform_device.h>
11 #include <linux/regmap.h>
12 #include <linux/regulator/driver.h>
14 #define SUN20I_SYS_LDO_CTRL_REG 0x150
16 struct sun20i_regulator_data
{
17 const struct regulator_desc
*descs
;
21 /* regulator_list_voltage_linear() modified for the non-integral uV_step. */
22 static int sun20i_d1_system_ldo_list_voltage(struct regulator_dev
*rdev
,
23 unsigned int selector
)
25 const struct regulator_desc
*desc
= rdev
->desc
;
26 unsigned int fraction
, uV
;
28 if (selector
>= desc
->n_voltages
)
31 uV
= desc
->min_uV
+ (desc
->uV_step
* selector
);
32 fraction
= selector
+ (desc
->min_uV
% 4);
39 /* Produce correctly-rounded absolute voltages. */
40 return uV
+ (fraction
/ 3);
43 static const struct regulator_ops sun20i_d1_system_ldo_ops
= {
44 .list_voltage
= sun20i_d1_system_ldo_list_voltage
,
45 .map_voltage
= regulator_map_voltage_ascend
,
46 .set_voltage_sel
= regulator_set_voltage_sel_regmap
,
47 .get_voltage_sel
= regulator_get_voltage_sel_regmap
,
50 static const struct regulator_desc sun20i_d1_system_ldo_descs
[] = {
53 .supply_name
= "ldo-in",
55 .ops
= &sun20i_d1_system_ldo_ops
,
56 .type
= REGULATOR_VOLTAGE
,
60 .uV_step
= 13333, /* repeating */
61 .vsel_reg
= SUN20I_SYS_LDO_CTRL_REG
,
62 .vsel_mask
= GENMASK(7, 0),
66 .supply_name
= "ldo-in",
68 .ops
= &sun20i_d1_system_ldo_ops
,
69 .type
= REGULATOR_VOLTAGE
,
73 .uV_step
= 13333, /* repeating */
74 .vsel_reg
= SUN20I_SYS_LDO_CTRL_REG
,
75 .vsel_mask
= GENMASK(15, 8),
79 static const struct sun20i_regulator_data sun20i_d1_system_ldos
= {
80 .descs
= sun20i_d1_system_ldo_descs
,
81 .ndescs
= ARRAY_SIZE(sun20i_d1_system_ldo_descs
),
84 static struct regmap
*sun20i_regulator_get_regmap(struct device
*dev
)
86 struct regmap
*regmap
;
89 * First try the syscon interface. The system control device is not
90 * compatible with "syscon", so fall back to getting the regmap from
91 * its platform device. This is ugly, but required for devicetree
92 * backward compatibility.
94 regmap
= syscon_node_to_regmap(dev
->parent
->of_node
);
98 regmap
= dev_get_regmap(dev
->parent
, NULL
);
102 return ERR_PTR(-EPROBE_DEFER
);
105 static int sun20i_regulator_probe(struct platform_device
*pdev
)
107 const struct sun20i_regulator_data
*data
;
108 struct device
*dev
= &pdev
->dev
;
109 struct regulator_config config
;
110 struct regmap
*regmap
;
112 data
= of_device_get_match_data(dev
);
116 regmap
= sun20i_regulator_get_regmap(dev
);
118 return dev_err_probe(dev
, PTR_ERR(regmap
), "Failed to get regmap\n");
120 config
= (struct regulator_config
) {
125 for (unsigned int i
= 0; i
< data
->ndescs
; ++i
) {
126 const struct regulator_desc
*desc
= &data
->descs
[i
];
127 struct regulator_dev
*rdev
;
129 rdev
= devm_regulator_register(dev
, desc
, &config
);
131 return PTR_ERR(rdev
);
137 static const struct of_device_id sun20i_regulator_of_match
[] = {
139 .compatible
= "allwinner,sun20i-d1-system-ldos",
140 .data
= &sun20i_d1_system_ldos
,
144 MODULE_DEVICE_TABLE(of
, sun20i_regulator_of_match
);
146 static struct platform_driver sun20i_regulator_driver
= {
147 .probe
= sun20i_regulator_probe
,
149 .name
= "sun20i-regulator",
150 .of_match_table
= sun20i_regulator_of_match
,
153 module_platform_driver(sun20i_regulator_driver
);
155 MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
156 MODULE_DESCRIPTION("Allwinner D1 internal LDO driver");
157 MODULE_LICENSE("GPL");