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