1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun //
3*4882a593Smuzhiyun // Regulator support for WM8400
4*4882a593Smuzhiyun //
5*4882a593Smuzhiyun // Copyright 2008 Wolfson Microelectronics PLC.
6*4882a593Smuzhiyun //
7*4882a593Smuzhiyun // Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun #include <linux/bug.h>
10*4882a593Smuzhiyun #include <linux/err.h>
11*4882a593Smuzhiyun #include <linux/kernel.h>
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/regulator/driver.h>
14*4882a593Smuzhiyun #include <linux/mfd/wm8400-private.h>
15*4882a593Smuzhiyun
16*4882a593Smuzhiyun static const struct linear_range wm8400_ldo_ranges[] = {
17*4882a593Smuzhiyun REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000),
18*4882a593Smuzhiyun REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000),
19*4882a593Smuzhiyun };
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun static const struct regulator_ops wm8400_ldo_ops = {
22*4882a593Smuzhiyun .is_enabled = regulator_is_enabled_regmap,
23*4882a593Smuzhiyun .enable = regulator_enable_regmap,
24*4882a593Smuzhiyun .disable = regulator_disable_regmap,
25*4882a593Smuzhiyun .list_voltage = regulator_list_voltage_linear_range,
26*4882a593Smuzhiyun .get_voltage_sel = regulator_get_voltage_sel_regmap,
27*4882a593Smuzhiyun .set_voltage_sel = regulator_set_voltage_sel_regmap,
28*4882a593Smuzhiyun .map_voltage = regulator_map_voltage_linear_range,
29*4882a593Smuzhiyun };
30*4882a593Smuzhiyun
wm8400_dcdc_get_mode(struct regulator_dev * dev)31*4882a593Smuzhiyun static unsigned int wm8400_dcdc_get_mode(struct regulator_dev *dev)
32*4882a593Smuzhiyun {
33*4882a593Smuzhiyun struct regmap *rmap = rdev_get_regmap(dev);
34*4882a593Smuzhiyun int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2;
35*4882a593Smuzhiyun u16 data[2];
36*4882a593Smuzhiyun int ret;
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun ret = regmap_bulk_read(rmap, WM8400_DCDC1_CONTROL_1 + offset, data, 2);
39*4882a593Smuzhiyun if (ret != 0)
40*4882a593Smuzhiyun return 0;
41*4882a593Smuzhiyun
42*4882a593Smuzhiyun /* Datasheet: hibernate */
43*4882a593Smuzhiyun if (data[0] & WM8400_DC1_SLEEP)
44*4882a593Smuzhiyun return REGULATOR_MODE_STANDBY;
45*4882a593Smuzhiyun
46*4882a593Smuzhiyun /* Datasheet: standby */
47*4882a593Smuzhiyun if (!(data[0] & WM8400_DC1_ACTIVE))
48*4882a593Smuzhiyun return REGULATOR_MODE_IDLE;
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun /* Datasheet: active with or without force PWM */
51*4882a593Smuzhiyun if (data[1] & WM8400_DC1_FRC_PWM)
52*4882a593Smuzhiyun return REGULATOR_MODE_FAST;
53*4882a593Smuzhiyun else
54*4882a593Smuzhiyun return REGULATOR_MODE_NORMAL;
55*4882a593Smuzhiyun }
56*4882a593Smuzhiyun
wm8400_dcdc_set_mode(struct regulator_dev * dev,unsigned int mode)57*4882a593Smuzhiyun static int wm8400_dcdc_set_mode(struct regulator_dev *dev, unsigned int mode)
58*4882a593Smuzhiyun {
59*4882a593Smuzhiyun struct regmap *rmap = rdev_get_regmap(dev);
60*4882a593Smuzhiyun int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2;
61*4882a593Smuzhiyun int ret;
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun switch (mode) {
64*4882a593Smuzhiyun case REGULATOR_MODE_FAST:
65*4882a593Smuzhiyun /* Datasheet: active with force PWM */
66*4882a593Smuzhiyun ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset,
67*4882a593Smuzhiyun WM8400_DC1_FRC_PWM, WM8400_DC1_FRC_PWM);
68*4882a593Smuzhiyun if (ret != 0)
69*4882a593Smuzhiyun return ret;
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset,
72*4882a593Smuzhiyun WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP,
73*4882a593Smuzhiyun WM8400_DC1_ACTIVE);
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun case REGULATOR_MODE_NORMAL:
76*4882a593Smuzhiyun /* Datasheet: active */
77*4882a593Smuzhiyun ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset,
78*4882a593Smuzhiyun WM8400_DC1_FRC_PWM, 0);
79*4882a593Smuzhiyun if (ret != 0)
80*4882a593Smuzhiyun return ret;
81*4882a593Smuzhiyun
82*4882a593Smuzhiyun return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset,
83*4882a593Smuzhiyun WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP,
84*4882a593Smuzhiyun WM8400_DC1_ACTIVE);
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun case REGULATOR_MODE_IDLE:
87*4882a593Smuzhiyun /* Datasheet: standby */
88*4882a593Smuzhiyun return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset,
89*4882a593Smuzhiyun WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 0);
90*4882a593Smuzhiyun default:
91*4882a593Smuzhiyun return -EINVAL;
92*4882a593Smuzhiyun }
93*4882a593Smuzhiyun }
94*4882a593Smuzhiyun
wm8400_dcdc_get_optimum_mode(struct regulator_dev * dev,int input_uV,int output_uV,int load_uA)95*4882a593Smuzhiyun static unsigned int wm8400_dcdc_get_optimum_mode(struct regulator_dev *dev,
96*4882a593Smuzhiyun int input_uV, int output_uV,
97*4882a593Smuzhiyun int load_uA)
98*4882a593Smuzhiyun {
99*4882a593Smuzhiyun return REGULATOR_MODE_NORMAL;
100*4882a593Smuzhiyun }
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun static const struct regulator_ops wm8400_dcdc_ops = {
103*4882a593Smuzhiyun .is_enabled = regulator_is_enabled_regmap,
104*4882a593Smuzhiyun .enable = regulator_enable_regmap,
105*4882a593Smuzhiyun .disable = regulator_disable_regmap,
106*4882a593Smuzhiyun .list_voltage = regulator_list_voltage_linear,
107*4882a593Smuzhiyun .map_voltage = regulator_map_voltage_linear,
108*4882a593Smuzhiyun .get_voltage_sel = regulator_get_voltage_sel_regmap,
109*4882a593Smuzhiyun .set_voltage_sel = regulator_set_voltage_sel_regmap,
110*4882a593Smuzhiyun .get_mode = wm8400_dcdc_get_mode,
111*4882a593Smuzhiyun .set_mode = wm8400_dcdc_set_mode,
112*4882a593Smuzhiyun .get_optimum_mode = wm8400_dcdc_get_optimum_mode,
113*4882a593Smuzhiyun };
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun static struct regulator_desc regulators[] = {
116*4882a593Smuzhiyun {
117*4882a593Smuzhiyun .name = "LDO1",
118*4882a593Smuzhiyun .id = WM8400_LDO1,
119*4882a593Smuzhiyun .ops = &wm8400_ldo_ops,
120*4882a593Smuzhiyun .enable_reg = WM8400_LDO1_CONTROL,
121*4882a593Smuzhiyun .enable_mask = WM8400_LDO1_ENA,
122*4882a593Smuzhiyun .n_voltages = WM8400_LDO1_VSEL_MASK + 1,
123*4882a593Smuzhiyun .linear_ranges = wm8400_ldo_ranges,
124*4882a593Smuzhiyun .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
125*4882a593Smuzhiyun .vsel_reg = WM8400_LDO1_CONTROL,
126*4882a593Smuzhiyun .vsel_mask = WM8400_LDO1_VSEL_MASK,
127*4882a593Smuzhiyun .type = REGULATOR_VOLTAGE,
128*4882a593Smuzhiyun .owner = THIS_MODULE,
129*4882a593Smuzhiyun },
130*4882a593Smuzhiyun {
131*4882a593Smuzhiyun .name = "LDO2",
132*4882a593Smuzhiyun .id = WM8400_LDO2,
133*4882a593Smuzhiyun .ops = &wm8400_ldo_ops,
134*4882a593Smuzhiyun .enable_reg = WM8400_LDO2_CONTROL,
135*4882a593Smuzhiyun .enable_mask = WM8400_LDO2_ENA,
136*4882a593Smuzhiyun .n_voltages = WM8400_LDO2_VSEL_MASK + 1,
137*4882a593Smuzhiyun .linear_ranges = wm8400_ldo_ranges,
138*4882a593Smuzhiyun .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
139*4882a593Smuzhiyun .type = REGULATOR_VOLTAGE,
140*4882a593Smuzhiyun .vsel_reg = WM8400_LDO2_CONTROL,
141*4882a593Smuzhiyun .vsel_mask = WM8400_LDO2_VSEL_MASK,
142*4882a593Smuzhiyun .owner = THIS_MODULE,
143*4882a593Smuzhiyun },
144*4882a593Smuzhiyun {
145*4882a593Smuzhiyun .name = "LDO3",
146*4882a593Smuzhiyun .id = WM8400_LDO3,
147*4882a593Smuzhiyun .ops = &wm8400_ldo_ops,
148*4882a593Smuzhiyun .enable_reg = WM8400_LDO3_CONTROL,
149*4882a593Smuzhiyun .enable_mask = WM8400_LDO3_ENA,
150*4882a593Smuzhiyun .n_voltages = WM8400_LDO3_VSEL_MASK + 1,
151*4882a593Smuzhiyun .linear_ranges = wm8400_ldo_ranges,
152*4882a593Smuzhiyun .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
153*4882a593Smuzhiyun .vsel_reg = WM8400_LDO3_CONTROL,
154*4882a593Smuzhiyun .vsel_mask = WM8400_LDO3_VSEL_MASK,
155*4882a593Smuzhiyun .type = REGULATOR_VOLTAGE,
156*4882a593Smuzhiyun .owner = THIS_MODULE,
157*4882a593Smuzhiyun },
158*4882a593Smuzhiyun {
159*4882a593Smuzhiyun .name = "LDO4",
160*4882a593Smuzhiyun .id = WM8400_LDO4,
161*4882a593Smuzhiyun .ops = &wm8400_ldo_ops,
162*4882a593Smuzhiyun .enable_reg = WM8400_LDO4_CONTROL,
163*4882a593Smuzhiyun .enable_mask = WM8400_LDO4_ENA,
164*4882a593Smuzhiyun .n_voltages = WM8400_LDO4_VSEL_MASK + 1,
165*4882a593Smuzhiyun .linear_ranges = wm8400_ldo_ranges,
166*4882a593Smuzhiyun .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
167*4882a593Smuzhiyun .vsel_reg = WM8400_LDO4_CONTROL,
168*4882a593Smuzhiyun .vsel_mask = WM8400_LDO4_VSEL_MASK,
169*4882a593Smuzhiyun .type = REGULATOR_VOLTAGE,
170*4882a593Smuzhiyun .owner = THIS_MODULE,
171*4882a593Smuzhiyun },
172*4882a593Smuzhiyun {
173*4882a593Smuzhiyun .name = "DCDC1",
174*4882a593Smuzhiyun .id = WM8400_DCDC1,
175*4882a593Smuzhiyun .ops = &wm8400_dcdc_ops,
176*4882a593Smuzhiyun .enable_reg = WM8400_DCDC1_CONTROL_1,
177*4882a593Smuzhiyun .enable_mask = WM8400_DC1_ENA_MASK,
178*4882a593Smuzhiyun .n_voltages = WM8400_DC1_VSEL_MASK + 1,
179*4882a593Smuzhiyun .vsel_reg = WM8400_DCDC1_CONTROL_1,
180*4882a593Smuzhiyun .vsel_mask = WM8400_DC1_VSEL_MASK,
181*4882a593Smuzhiyun .min_uV = 850000,
182*4882a593Smuzhiyun .uV_step = 25000,
183*4882a593Smuzhiyun .type = REGULATOR_VOLTAGE,
184*4882a593Smuzhiyun .owner = THIS_MODULE,
185*4882a593Smuzhiyun },
186*4882a593Smuzhiyun {
187*4882a593Smuzhiyun .name = "DCDC2",
188*4882a593Smuzhiyun .id = WM8400_DCDC2,
189*4882a593Smuzhiyun .ops = &wm8400_dcdc_ops,
190*4882a593Smuzhiyun .enable_reg = WM8400_DCDC2_CONTROL_1,
191*4882a593Smuzhiyun .enable_mask = WM8400_DC2_ENA_MASK,
192*4882a593Smuzhiyun .n_voltages = WM8400_DC2_VSEL_MASK + 1,
193*4882a593Smuzhiyun .vsel_reg = WM8400_DCDC2_CONTROL_1,
194*4882a593Smuzhiyun .vsel_mask = WM8400_DC2_VSEL_MASK,
195*4882a593Smuzhiyun .min_uV = 850000,
196*4882a593Smuzhiyun .uV_step = 25000,
197*4882a593Smuzhiyun .type = REGULATOR_VOLTAGE,
198*4882a593Smuzhiyun .owner = THIS_MODULE,
199*4882a593Smuzhiyun },
200*4882a593Smuzhiyun };
201*4882a593Smuzhiyun
wm8400_regulator_probe(struct platform_device * pdev)202*4882a593Smuzhiyun static int wm8400_regulator_probe(struct platform_device *pdev)
203*4882a593Smuzhiyun {
204*4882a593Smuzhiyun struct wm8400 *wm8400 = container_of(pdev, struct wm8400, regulators[pdev->id]);
205*4882a593Smuzhiyun struct regulator_config config = { };
206*4882a593Smuzhiyun struct regulator_dev *rdev;
207*4882a593Smuzhiyun
208*4882a593Smuzhiyun config.dev = &pdev->dev;
209*4882a593Smuzhiyun config.init_data = dev_get_platdata(&pdev->dev);
210*4882a593Smuzhiyun config.driver_data = wm8400;
211*4882a593Smuzhiyun config.regmap = wm8400->regmap;
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun rdev = devm_regulator_register(&pdev->dev, ®ulators[pdev->id],
214*4882a593Smuzhiyun &config);
215*4882a593Smuzhiyun if (IS_ERR(rdev))
216*4882a593Smuzhiyun return PTR_ERR(rdev);
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun platform_set_drvdata(pdev, rdev);
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun return 0;
221*4882a593Smuzhiyun }
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun static struct platform_driver wm8400_regulator_driver = {
224*4882a593Smuzhiyun .driver = {
225*4882a593Smuzhiyun .name = "wm8400-regulator",
226*4882a593Smuzhiyun },
227*4882a593Smuzhiyun .probe = wm8400_regulator_probe,
228*4882a593Smuzhiyun };
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun /**
231*4882a593Smuzhiyun * wm8400_register_regulator - enable software control of a WM8400 regulator
232*4882a593Smuzhiyun *
233*4882a593Smuzhiyun * This function enables software control of a WM8400 regulator via
234*4882a593Smuzhiyun * the regulator API. It is intended to be called from the
235*4882a593Smuzhiyun * platform_init() callback of the WM8400 MFD driver.
236*4882a593Smuzhiyun *
237*4882a593Smuzhiyun * @dev: The WM8400 device to operate on.
238*4882a593Smuzhiyun * @reg: The regulator to control.
239*4882a593Smuzhiyun * @initdata: Regulator initdata for the regulator.
240*4882a593Smuzhiyun */
wm8400_register_regulator(struct device * dev,int reg,struct regulator_init_data * initdata)241*4882a593Smuzhiyun int wm8400_register_regulator(struct device *dev, int reg,
242*4882a593Smuzhiyun struct regulator_init_data *initdata)
243*4882a593Smuzhiyun {
244*4882a593Smuzhiyun struct wm8400 *wm8400 = dev_get_drvdata(dev);
245*4882a593Smuzhiyun
246*4882a593Smuzhiyun if (wm8400->regulators[reg].name)
247*4882a593Smuzhiyun return -EBUSY;
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun initdata->driver_data = wm8400;
250*4882a593Smuzhiyun
251*4882a593Smuzhiyun wm8400->regulators[reg].name = "wm8400-regulator";
252*4882a593Smuzhiyun wm8400->regulators[reg].id = reg;
253*4882a593Smuzhiyun wm8400->regulators[reg].dev.parent = dev;
254*4882a593Smuzhiyun wm8400->regulators[reg].dev.platform_data = initdata;
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun return platform_device_register(&wm8400->regulators[reg]);
257*4882a593Smuzhiyun }
258*4882a593Smuzhiyun EXPORT_SYMBOL_GPL(wm8400_register_regulator);
259*4882a593Smuzhiyun
wm8400_regulator_init(void)260*4882a593Smuzhiyun static int __init wm8400_regulator_init(void)
261*4882a593Smuzhiyun {
262*4882a593Smuzhiyun return platform_driver_register(&wm8400_regulator_driver);
263*4882a593Smuzhiyun }
264*4882a593Smuzhiyun subsys_initcall(wm8400_regulator_init);
265*4882a593Smuzhiyun
wm8400_regulator_exit(void)266*4882a593Smuzhiyun static void __exit wm8400_regulator_exit(void)
267*4882a593Smuzhiyun {
268*4882a593Smuzhiyun platform_driver_unregister(&wm8400_regulator_driver);
269*4882a593Smuzhiyun }
270*4882a593Smuzhiyun module_exit(wm8400_regulator_exit);
271*4882a593Smuzhiyun
272*4882a593Smuzhiyun MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
273*4882a593Smuzhiyun MODULE_DESCRIPTION("WM8400 regulator driver");
274*4882a593Smuzhiyun MODULE_LICENSE("GPL");
275*4882a593Smuzhiyun MODULE_ALIAS("platform:wm8400-regulator");
276