diff options
author | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2010-01-25 14:41:57 -0500 |
---|---|---|
committer | Liam Girdwood <lrg@slimlogic.co.uk> | 2010-03-03 09:49:25 -0500 |
commit | 69dc16c325bef32b0a1a1abf15ae4047174cafc1 (patch) | |
tree | bb41933b94da89526efabaead5d693c46d904b70 /drivers/regulator | |
parent | a71b797fdc672714bfff1fdc142042a95e97d7ba (diff) |
regulator: Add WM8994 regulator support
The WM8994 contains two LDOs with mixed hardware/software control to
minimise the number of external supplies required while delivering
optimal voltages to minimise power consumption.
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Liam Girdwood <lrg@slimlogic.co.uk>
Diffstat (limited to 'drivers/regulator')
-rw-r--r-- | drivers/regulator/Kconfig | 7 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/wm8994-regulator.c | 304 |
3 files changed, 312 insertions, 0 deletions
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 2bc01ee9d9f2..3c07169498cf 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig | |||
@@ -111,6 +111,13 @@ config REGULATOR_WM8400 | |||
111 | This driver provides support for the voltage regulators of the | 111 | This driver provides support for the voltage regulators of the |
112 | WM8400 AudioPlus PMIC. | 112 | WM8400 AudioPlus PMIC. |
113 | 113 | ||
114 | config REGULATOR_WM8994 | ||
115 | tristate "Wolfson Microelectronics WM8994 CODEC" | ||
116 | depends on MFD_WM8994 | ||
117 | help | ||
118 | This driver provides support for the voltage regulators on the | ||
119 | WM8994 CODEC. | ||
120 | |||
114 | config REGULATOR_DA903X | 121 | config REGULATOR_DA903X |
115 | tristate "Support regulators on Dialog Semiconductor DA9030/DA9034 PMIC" | 122 | tristate "Support regulators on Dialog Semiconductor DA9030/DA9034 PMIC" |
116 | depends on PMIC_DA903X | 123 | depends on PMIC_DA903X |
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 075835be4396..7c59bcb10613 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile | |||
@@ -19,6 +19,7 @@ obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o | |||
19 | obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o | 19 | obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o |
20 | obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o | 20 | obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o |
21 | obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o | 21 | obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o |
22 | obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o | ||
22 | obj-$(CONFIG_REGULATOR_DA903X) += da903x.o | 23 | obj-$(CONFIG_REGULATOR_DA903X) += da903x.o |
23 | obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o | 24 | obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o |
24 | obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o | 25 | obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o |
diff --git a/drivers/regulator/wm8994-regulator.c b/drivers/regulator/wm8994-regulator.c new file mode 100644 index 000000000000..d09e018b3177 --- /dev/null +++ b/drivers/regulator/wm8994-regulator.c | |||
@@ -0,0 +1,304 @@ | |||
1 | /* | ||
2 | * wm8994-regulator.c -- Regulator driver for the WM8994 | ||
3 | * | ||
4 | * Copyright 2009 Wolfson Microelectronics PLC. | ||
5 | * | ||
6 | * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the | ||
10 | * Free Software Foundation; either version 2 of the License, or (at your | ||
11 | * option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/moduleparam.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/bitops.h> | ||
18 | #include <linux/err.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/regulator/driver.h> | ||
21 | #include <linux/gpio.h> | ||
22 | |||
23 | #include <linux/mfd/wm8994/core.h> | ||
24 | #include <linux/mfd/wm8994/registers.h> | ||
25 | #include <linux/mfd/wm8994/pdata.h> | ||
26 | |||
27 | struct wm8994_ldo { | ||
28 | int enable; | ||
29 | int is_enabled; | ||
30 | struct regulator_dev *regulator; | ||
31 | struct wm8994 *wm8994; | ||
32 | }; | ||
33 | |||
34 | #define WM8994_LDO1_MAX_SELECTOR 0x7 | ||
35 | #define WM8994_LDO2_MAX_SELECTOR 0x3 | ||
36 | |||
37 | static int wm8994_ldo_enable(struct regulator_dev *rdev) | ||
38 | { | ||
39 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
40 | |||
41 | /* If we have no soft control assume that the LDO is always enabled. */ | ||
42 | if (!ldo->enable) | ||
43 | return 0; | ||
44 | |||
45 | gpio_set_value(ldo->enable, 1); | ||
46 | ldo->is_enabled = 1; | ||
47 | |||
48 | return 0; | ||
49 | } | ||
50 | |||
51 | static int wm8994_ldo_disable(struct regulator_dev *rdev) | ||
52 | { | ||
53 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
54 | |||
55 | /* If we have no soft control assume that the LDO is always enabled. */ | ||
56 | if (!ldo->enable) | ||
57 | return -EINVAL; | ||
58 | |||
59 | gpio_set_value(ldo->enable, 0); | ||
60 | ldo->is_enabled = 0; | ||
61 | |||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | static int wm8994_ldo_is_enabled(struct regulator_dev *rdev) | ||
66 | { | ||
67 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
68 | |||
69 | return ldo->is_enabled; | ||
70 | } | ||
71 | |||
72 | static int wm8994_ldo_enable_time(struct regulator_dev *rdev) | ||
73 | { | ||
74 | /* 3ms is fairly conservative but this shouldn't be too performance | ||
75 | * critical; can be tweaked per-system if required. */ | ||
76 | return 3000; | ||
77 | } | ||
78 | |||
79 | static int wm8994_ldo1_list_voltage(struct regulator_dev *rdev, | ||
80 | unsigned int selector) | ||
81 | { | ||
82 | if (selector > WM8994_LDO1_MAX_SELECTOR) | ||
83 | return -EINVAL; | ||
84 | |||
85 | return (selector * 100000) + 2400000; | ||
86 | } | ||
87 | |||
88 | static int wm8994_ldo1_get_voltage(struct regulator_dev *rdev) | ||
89 | { | ||
90 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
91 | int val; | ||
92 | |||
93 | val = wm8994_reg_read(ldo->wm8994, WM8994_LDO_1); | ||
94 | if (val < 0) | ||
95 | return val; | ||
96 | |||
97 | val = (val & WM8994_LDO1_VSEL_MASK) >> WM8994_LDO1_VSEL_SHIFT; | ||
98 | |||
99 | return wm8994_ldo1_list_voltage(rdev, val); | ||
100 | } | ||
101 | |||
102 | static int wm8994_ldo1_set_voltage(struct regulator_dev *rdev, | ||
103 | int min_uV, int max_uV) | ||
104 | { | ||
105 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
106 | int selector, v; | ||
107 | |||
108 | selector = (min_uV - 2400000) / 100000; | ||
109 | v = wm8994_ldo1_list_voltage(rdev, selector); | ||
110 | if (v < 0 || v > max_uV) | ||
111 | return -EINVAL; | ||
112 | |||
113 | selector <<= WM8994_LDO1_VSEL_SHIFT; | ||
114 | |||
115 | return wm8994_set_bits(ldo->wm8994, WM8994_LDO_1, | ||
116 | WM8994_LDO1_VSEL_MASK, selector); | ||
117 | } | ||
118 | |||
119 | static struct regulator_ops wm8994_ldo1_ops = { | ||
120 | .enable = wm8994_ldo_enable, | ||
121 | .disable = wm8994_ldo_disable, | ||
122 | .is_enabled = wm8994_ldo_is_enabled, | ||
123 | .enable_time = wm8994_ldo_enable_time, | ||
124 | |||
125 | .list_voltage = wm8994_ldo1_list_voltage, | ||
126 | .get_voltage = wm8994_ldo1_get_voltage, | ||
127 | .set_voltage = wm8994_ldo1_set_voltage, | ||
128 | }; | ||
129 | |||
130 | static int wm8994_ldo2_list_voltage(struct regulator_dev *rdev, | ||
131 | unsigned int selector) | ||
132 | { | ||
133 | if (selector > WM8994_LDO2_MAX_SELECTOR) | ||
134 | return -EINVAL; | ||
135 | |||
136 | return (selector * 100000) + 900000; | ||
137 | } | ||
138 | |||
139 | static int wm8994_ldo2_get_voltage(struct regulator_dev *rdev) | ||
140 | { | ||
141 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
142 | int val; | ||
143 | |||
144 | val = wm8994_reg_read(ldo->wm8994, WM8994_LDO_2); | ||
145 | if (val < 0) | ||
146 | return val; | ||
147 | |||
148 | val = (val & WM8994_LDO2_VSEL_MASK) >> WM8994_LDO2_VSEL_SHIFT; | ||
149 | |||
150 | return wm8994_ldo2_list_voltage(rdev, val); | ||
151 | } | ||
152 | |||
153 | static int wm8994_ldo2_set_voltage(struct regulator_dev *rdev, | ||
154 | int min_uV, int max_uV) | ||
155 | { | ||
156 | struct wm8994_ldo *ldo = rdev_get_drvdata(rdev); | ||
157 | int selector, v; | ||
158 | |||
159 | selector = (min_uV - 900000) / 100000; | ||
160 | v = wm8994_ldo2_list_voltage(rdev, selector); | ||
161 | if (v < 0 || v > max_uV) | ||
162 | return -EINVAL; | ||
163 | |||
164 | selector <<= WM8994_LDO2_VSEL_SHIFT; | ||
165 | |||
166 | return wm8994_set_bits(ldo->wm8994, WM8994_LDO_2, | ||
167 | WM8994_LDO2_VSEL_MASK, selector); | ||
168 | } | ||
169 | |||
170 | static struct regulator_ops wm8994_ldo2_ops = { | ||
171 | .enable = wm8994_ldo_enable, | ||
172 | .disable = wm8994_ldo_disable, | ||
173 | .is_enabled = wm8994_ldo_is_enabled, | ||
174 | .enable_time = wm8994_ldo_enable_time, | ||
175 | |||
176 | .list_voltage = wm8994_ldo2_list_voltage, | ||
177 | .get_voltage = wm8994_ldo2_get_voltage, | ||
178 | .set_voltage = wm8994_ldo2_set_voltage, | ||
179 | }; | ||
180 | |||
181 | static struct regulator_desc wm8994_ldo_desc[] = { | ||
182 | { | ||
183 | .name = "LDO1", | ||
184 | .id = 1, | ||
185 | .type = REGULATOR_VOLTAGE, | ||
186 | .n_voltages = WM8994_LDO1_MAX_SELECTOR + 1, | ||
187 | .ops = &wm8994_ldo1_ops, | ||
188 | .owner = THIS_MODULE, | ||
189 | }, | ||
190 | { | ||
191 | .name = "LDO2", | ||
192 | .id = 2, | ||
193 | .type = REGULATOR_VOLTAGE, | ||
194 | .n_voltages = WM8994_LDO2_MAX_SELECTOR + 1, | ||
195 | .ops = &wm8994_ldo2_ops, | ||
196 | .owner = THIS_MODULE, | ||
197 | }, | ||
198 | }; | ||
199 | |||
200 | static __devinit int wm8994_ldo_probe(struct platform_device *pdev) | ||
201 | { | ||
202 | struct wm8994 *wm8994 = dev_get_drvdata(pdev->dev.parent); | ||
203 | struct wm8994_pdata *pdata = wm8994->dev->platform_data; | ||
204 | int id = pdev->id % ARRAY_SIZE(pdata->ldo); | ||
205 | struct wm8994_ldo *ldo; | ||
206 | int ret; | ||
207 | |||
208 | dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1); | ||
209 | |||
210 | if (!pdata) | ||
211 | return -ENODEV; | ||
212 | |||
213 | ldo = kzalloc(sizeof(struct wm8994_ldo), GFP_KERNEL); | ||
214 | if (ldo == NULL) { | ||
215 | dev_err(&pdev->dev, "Unable to allocate private data\n"); | ||
216 | return -ENOMEM; | ||
217 | } | ||
218 | |||
219 | ldo->wm8994 = wm8994; | ||
220 | |||
221 | ldo->is_enabled = 1; | ||
222 | |||
223 | if (pdata->ldo[id].enable && gpio_is_valid(pdata->ldo[id].enable)) { | ||
224 | ldo->enable = pdata->ldo[id].enable; | ||
225 | |||
226 | ret = gpio_request(ldo->enable, "WM8994 LDO enable"); | ||
227 | if (ret < 0) { | ||
228 | dev_err(&pdev->dev, "Failed to get enable GPIO: %d\n", | ||
229 | ret); | ||
230 | goto err; | ||
231 | } | ||
232 | |||
233 | ret = gpio_direction_output(ldo->enable, ldo->is_enabled); | ||
234 | if (ret < 0) { | ||
235 | dev_err(&pdev->dev, "Failed to set GPIO up: %d\n", | ||
236 | ret); | ||
237 | goto err_gpio; | ||
238 | } | ||
239 | } | ||
240 | |||
241 | ldo->regulator = regulator_register(&wm8994_ldo_desc[id], &pdev->dev, | ||
242 | pdata->ldo[id].init_data, ldo); | ||
243 | if (IS_ERR(ldo->regulator)) { | ||
244 | ret = PTR_ERR(ldo->regulator); | ||
245 | dev_err(wm8994->dev, "Failed to register LDO%d: %d\n", | ||
246 | id + 1, ret); | ||
247 | goto err_gpio; | ||
248 | } | ||
249 | |||
250 | platform_set_drvdata(pdev, ldo); | ||
251 | |||
252 | return 0; | ||
253 | |||
254 | err_gpio: | ||
255 | if (gpio_is_valid(ldo->enable)) | ||
256 | gpio_free(ldo->enable); | ||
257 | err: | ||
258 | kfree(ldo); | ||
259 | return ret; | ||
260 | } | ||
261 | |||
262 | static __devexit int wm8994_ldo_remove(struct platform_device *pdev) | ||
263 | { | ||
264 | struct wm8994_ldo *ldo = platform_get_drvdata(pdev); | ||
265 | |||
266 | regulator_unregister(ldo->regulator); | ||
267 | if (gpio_is_valid(ldo->enable)) | ||
268 | gpio_free(ldo->enable); | ||
269 | kfree(ldo); | ||
270 | |||
271 | return 0; | ||
272 | } | ||
273 | |||
274 | static struct platform_driver wm8994_ldo_driver = { | ||
275 | .probe = wm8994_ldo_probe, | ||
276 | .remove = __devexit_p(wm8994_ldo_remove), | ||
277 | .driver = { | ||
278 | .name = "wm8994-ldo", | ||
279 | }, | ||
280 | }; | ||
281 | |||
282 | static int __init wm8994_ldo_init(void) | ||
283 | { | ||
284 | int ret; | ||
285 | |||
286 | ret = platform_driver_register(&wm8994_ldo_driver); | ||
287 | if (ret != 0) | ||
288 | pr_err("Failed to register Wm8994 GP LDO driver: %d\n", ret); | ||
289 | |||
290 | return ret; | ||
291 | } | ||
292 | subsys_initcall(wm8994_ldo_init); | ||
293 | |||
294 | static void __exit wm8994_ldo_exit(void) | ||
295 | { | ||
296 | platform_driver_unregister(&wm8994_ldo_driver); | ||
297 | } | ||
298 | module_exit(wm8994_ldo_exit); | ||
299 | |||
300 | /* Module information */ | ||
301 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); | ||
302 | MODULE_DESCRIPTION("WM8994 LDO driver"); | ||
303 | MODULE_LICENSE("GPL"); | ||
304 | MODULE_ALIAS("platform:wm8994-ldo"); | ||