13cad5fc8SAxel Lin // SPDX-License-Identifier: GPL-2.0+
23cad5fc8SAxel Lin //
33cad5fc8SAxel Lin // wm831x-ldo.c  --  LDO driver for the WM831x series
43cad5fc8SAxel Lin //
53cad5fc8SAxel Lin // Copyright 2009 Wolfson Microelectronics PLC.
63cad5fc8SAxel Lin //
73cad5fc8SAxel Lin // Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
8d1c6b4feSMark Brown 
9d1c6b4feSMark Brown #include <linux/module.h>
10d1c6b4feSMark Brown #include <linux/moduleparam.h>
11d1c6b4feSMark Brown #include <linux/init.h>
12d1c6b4feSMark Brown #include <linux/bitops.h>
13d1c6b4feSMark Brown #include <linux/err.h>
14d1c6b4feSMark Brown #include <linux/i2c.h>
15d1c6b4feSMark Brown #include <linux/platform_device.h>
16d1c6b4feSMark Brown #include <linux/regulator/driver.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
18d1c6b4feSMark Brown 
19d1c6b4feSMark Brown #include <linux/mfd/wm831x/core.h>
20d1c6b4feSMark Brown #include <linux/mfd/wm831x/regulator.h>
21d1c6b4feSMark Brown #include <linux/mfd/wm831x/pdata.h>
22d1c6b4feSMark Brown 
23f1aba13fSMark Brown #define WM831X_LDO_MAX_NAME 9
24d1c6b4feSMark Brown 
25d1c6b4feSMark Brown #define WM831X_LDO_CONTROL       0
26d1c6b4feSMark Brown #define WM831X_LDO_ON_CONTROL    1
27d1c6b4feSMark Brown #define WM831X_LDO_SLEEP_CONTROL 2
28d1c6b4feSMark Brown 
29d1c6b4feSMark Brown #define WM831X_ALIVE_LDO_ON_CONTROL    0
30d1c6b4feSMark Brown #define WM831X_ALIVE_LDO_SLEEP_CONTROL 1
31d1c6b4feSMark Brown 
32d1c6b4feSMark Brown struct wm831x_ldo {
33d1c6b4feSMark Brown 	char name[WM831X_LDO_MAX_NAME];
34f1aba13fSMark Brown 	char supply_name[WM831X_LDO_MAX_NAME];
35d1c6b4feSMark Brown 	struct regulator_desc desc;
36d1c6b4feSMark Brown 	int base;
37d1c6b4feSMark Brown 	struct wm831x *wm831x;
38d1c6b4feSMark Brown 	struct regulator_dev *regulator;
39d1c6b4feSMark Brown };
40d1c6b4feSMark Brown 
41d1c6b4feSMark Brown /*
42d1c6b4feSMark Brown  * Shared
43d1c6b4feSMark Brown  */
44d1c6b4feSMark Brown 
wm831x_ldo_uv_irq(int irq,void * data)45d1c6b4feSMark Brown static irqreturn_t wm831x_ldo_uv_irq(int irq, void *data)
46d1c6b4feSMark Brown {
47d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = data;
48d1c6b4feSMark Brown 
49d1c6b4feSMark Brown 	regulator_notifier_call_chain(ldo->regulator,
50d1c6b4feSMark Brown 				      REGULATOR_EVENT_UNDER_VOLTAGE,
51d1c6b4feSMark Brown 				      NULL);
52d1c6b4feSMark Brown 
53d1c6b4feSMark Brown 	return IRQ_HANDLED;
54d1c6b4feSMark Brown }
55d1c6b4feSMark Brown 
56d1c6b4feSMark Brown /*
57d1c6b4feSMark Brown  * General purpose LDOs
58d1c6b4feSMark Brown  */
59d1c6b4feSMark Brown 
6060ab7f41SMatti Vaittinen static const struct linear_range wm831x_gp_ldo_ranges[] = {
618828bae4SAxel Lin 	REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000),
628828bae4SAxel Lin 	REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000),
635ff26a14SMark Brown };
64d1c6b4feSMark Brown 
wm831x_gp_ldo_set_suspend_voltage(struct regulator_dev * rdev,int uV)65d1c6b4feSMark Brown static int wm831x_gp_ldo_set_suspend_voltage(struct regulator_dev *rdev,
66d1c6b4feSMark Brown 					     int uV)
67d1c6b4feSMark Brown {
68d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
690a479689SAxel Lin 	struct wm831x *wm831x = ldo->wm831x;
700a479689SAxel Lin 	int sel, reg = ldo->base + WM831X_LDO_SLEEP_CONTROL;
71d1c6b4feSMark Brown 
725ff26a14SMark Brown 	sel = regulator_map_voltage_linear_range(rdev, uV, uV);
730a479689SAxel Lin 	if (sel < 0)
740a479689SAxel Lin 		return sel;
750a479689SAxel Lin 
760a479689SAxel Lin 	return wm831x_set_bits(wm831x, reg, WM831X_LDO1_ON_VSEL_MASK, sel);
77d1c6b4feSMark Brown }
78d1c6b4feSMark Brown 
wm831x_gp_ldo_get_mode(struct regulator_dev * rdev)79d1c6b4feSMark Brown static unsigned int wm831x_gp_ldo_get_mode(struct regulator_dev *rdev)
80d1c6b4feSMark Brown {
81d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
82d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
83d1c6b4feSMark Brown 	int ctrl_reg = ldo->base + WM831X_LDO_CONTROL;
84d1c6b4feSMark Brown 	int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
859a767d43SRoel Kluin 	int ret;
86d1c6b4feSMark Brown 
87d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, on_reg);
88d1c6b4feSMark Brown 	if (ret < 0)
899a767d43SRoel Kluin 		return ret;
90d1c6b4feSMark Brown 
91d1c6b4feSMark Brown 	if (!(ret & WM831X_LDO1_ON_MODE))
92d1c6b4feSMark Brown 		return REGULATOR_MODE_NORMAL;
93d1c6b4feSMark Brown 
94d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, ctrl_reg);
95d1c6b4feSMark Brown 	if (ret < 0)
969a767d43SRoel Kluin 		return ret;
97d1c6b4feSMark Brown 
98d1c6b4feSMark Brown 	if (ret & WM831X_LDO1_LP_MODE)
99d1c6b4feSMark Brown 		return REGULATOR_MODE_STANDBY;
100d1c6b4feSMark Brown 	else
101d1c6b4feSMark Brown 		return REGULATOR_MODE_IDLE;
102d1c6b4feSMark Brown }
103d1c6b4feSMark Brown 
wm831x_gp_ldo_set_mode(struct regulator_dev * rdev,unsigned int mode)104d1c6b4feSMark Brown static int wm831x_gp_ldo_set_mode(struct regulator_dev *rdev,
105d1c6b4feSMark Brown 				  unsigned int mode)
106d1c6b4feSMark Brown {
107d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
108d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
109d1c6b4feSMark Brown 	int ctrl_reg = ldo->base + WM831X_LDO_CONTROL;
110d1c6b4feSMark Brown 	int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
111d1c6b4feSMark Brown 	int ret;
112d1c6b4feSMark Brown 
113d1c6b4feSMark Brown 
114d1c6b4feSMark Brown 	switch (mode) {
115d1c6b4feSMark Brown 	case REGULATOR_MODE_NORMAL:
116d1c6b4feSMark Brown 		ret = wm831x_set_bits(wm831x, on_reg,
117d1c6b4feSMark Brown 				      WM831X_LDO1_ON_MODE, 0);
118d1c6b4feSMark Brown 		if (ret < 0)
119d1c6b4feSMark Brown 			return ret;
120d1c6b4feSMark Brown 		break;
121d1c6b4feSMark Brown 
122d1c6b4feSMark Brown 	case REGULATOR_MODE_IDLE:
123d1c6b4feSMark Brown 		ret = wm831x_set_bits(wm831x, ctrl_reg,
124e260999cSAxel Lin 				      WM831X_LDO1_LP_MODE, 0);
125d1c6b4feSMark Brown 		if (ret < 0)
126d1c6b4feSMark Brown 			return ret;
127d1c6b4feSMark Brown 
128d1c6b4feSMark Brown 		ret = wm831x_set_bits(wm831x, on_reg,
129d1c6b4feSMark Brown 				      WM831X_LDO1_ON_MODE,
130d1c6b4feSMark Brown 				      WM831X_LDO1_ON_MODE);
131d1c6b4feSMark Brown 		if (ret < 0)
132d1c6b4feSMark Brown 			return ret;
133e260999cSAxel Lin 		break;
134d1c6b4feSMark Brown 
135d1c6b4feSMark Brown 	case REGULATOR_MODE_STANDBY:
136d1c6b4feSMark Brown 		ret = wm831x_set_bits(wm831x, ctrl_reg,
137e260999cSAxel Lin 				      WM831X_LDO1_LP_MODE,
138e260999cSAxel Lin 				      WM831X_LDO1_LP_MODE);
139d1c6b4feSMark Brown 		if (ret < 0)
140d1c6b4feSMark Brown 			return ret;
141d1c6b4feSMark Brown 
142d1c6b4feSMark Brown 		ret = wm831x_set_bits(wm831x, on_reg,
143d1c6b4feSMark Brown 				      WM831X_LDO1_ON_MODE,
144d1c6b4feSMark Brown 				      WM831X_LDO1_ON_MODE);
145d1c6b4feSMark Brown 		if (ret < 0)
146d1c6b4feSMark Brown 			return ret;
147d1c6b4feSMark Brown 		break;
148d1c6b4feSMark Brown 
149d1c6b4feSMark Brown 	default:
150d1c6b4feSMark Brown 		return -EINVAL;
151d1c6b4feSMark Brown 	}
152d1c6b4feSMark Brown 
153d1c6b4feSMark Brown 	return 0;
154d1c6b4feSMark Brown }
155d1c6b4feSMark Brown 
wm831x_gp_ldo_get_status(struct regulator_dev * rdev)156d1c6b4feSMark Brown static int wm831x_gp_ldo_get_status(struct regulator_dev *rdev)
157d1c6b4feSMark Brown {
158d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
159d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
160d1c6b4feSMark Brown 	int mask = 1 << rdev_get_id(rdev);
161d1c6b4feSMark Brown 	int ret;
162d1c6b4feSMark Brown 
163d1c6b4feSMark Brown 	/* Is the regulator on? */
164d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS);
165d1c6b4feSMark Brown 	if (ret < 0)
166d1c6b4feSMark Brown 		return ret;
167d1c6b4feSMark Brown 	if (!(ret & mask))
168d1c6b4feSMark Brown 		return REGULATOR_STATUS_OFF;
169d1c6b4feSMark Brown 
170d1c6b4feSMark Brown 	/* Is it reporting under voltage? */
171d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS);
172363506cdSAxel Lin 	if (ret < 0)
173363506cdSAxel Lin 		return ret;
174d1c6b4feSMark Brown 	if (ret & mask)
175d1c6b4feSMark Brown 		return REGULATOR_STATUS_ERROR;
176d1c6b4feSMark Brown 
177d1c6b4feSMark Brown 	ret = wm831x_gp_ldo_get_mode(rdev);
178d1c6b4feSMark Brown 	if (ret < 0)
179d1c6b4feSMark Brown 		return ret;
180d1c6b4feSMark Brown 	else
181d1c6b4feSMark Brown 		return regulator_mode_to_status(ret);
182d1c6b4feSMark Brown }
183d1c6b4feSMark Brown 
wm831x_gp_ldo_get_optimum_mode(struct regulator_dev * rdev,int input_uV,int output_uV,int load_uA)184d1c6b4feSMark Brown static unsigned int wm831x_gp_ldo_get_optimum_mode(struct regulator_dev *rdev,
185d1c6b4feSMark Brown 						   int input_uV,
186d1c6b4feSMark Brown 						   int output_uV, int load_uA)
187d1c6b4feSMark Brown {
188d1c6b4feSMark Brown 	if (load_uA < 20000)
189d1c6b4feSMark Brown 		return REGULATOR_MODE_STANDBY;
190d1c6b4feSMark Brown 	if (load_uA < 50000)
191d1c6b4feSMark Brown 		return REGULATOR_MODE_IDLE;
192d1c6b4feSMark Brown 	return REGULATOR_MODE_NORMAL;
193d1c6b4feSMark Brown }
194d1c6b4feSMark Brown 
195d1c6b4feSMark Brown 
196b0d6dd3bSJulia Lawall static const struct regulator_ops wm831x_gp_ldo_ops = {
1975ff26a14SMark Brown 	.list_voltage = regulator_list_voltage_linear_range,
1985ff26a14SMark Brown 	.map_voltage = regulator_map_voltage_linear_range,
199ac663b47SMark Brown 	.get_voltage_sel = regulator_get_voltage_sel_regmap,
2000a479689SAxel Lin 	.set_voltage_sel = regulator_set_voltage_sel_regmap,
201d1c6b4feSMark Brown 	.set_suspend_voltage = wm831x_gp_ldo_set_suspend_voltage,
202d1c6b4feSMark Brown 	.get_mode = wm831x_gp_ldo_get_mode,
203d1c6b4feSMark Brown 	.set_mode = wm831x_gp_ldo_set_mode,
204d1c6b4feSMark Brown 	.get_status = wm831x_gp_ldo_get_status,
205d1c6b4feSMark Brown 	.get_optimum_mode = wm831x_gp_ldo_get_optimum_mode,
20622c5fb6aSMark Brown 	.get_bypass = regulator_get_bypass_regmap,
20722c5fb6aSMark Brown 	.set_bypass = regulator_set_bypass_regmap,
208d1c6b4feSMark Brown 
209ca8c361bSMark Brown 	.is_enabled = regulator_is_enabled_regmap,
210ca8c361bSMark Brown 	.enable = regulator_enable_regmap,
211ca8c361bSMark Brown 	.disable = regulator_disable_regmap,
212d1c6b4feSMark Brown };
213d1c6b4feSMark Brown 
wm831x_gp_ldo_probe(struct platform_device * pdev)214a5023574SBill Pemberton static int wm831x_gp_ldo_probe(struct platform_device *pdev)
215d1c6b4feSMark Brown {
216d1c6b4feSMark Brown 	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
217dff91d0bSJingoo Han 	struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev);
218c172708dSMark Brown 	struct regulator_config config = { };
219137a6354SMark Brown 	int id;
220d1c6b4feSMark Brown 	struct wm831x_ldo *ldo;
221d1c6b4feSMark Brown 	struct resource *res;
222d1c6b4feSMark Brown 	int ret, irq;
223d1c6b4feSMark Brown 
224137a6354SMark Brown 	if (pdata && pdata->wm831x_num)
225137a6354SMark Brown 		id = (pdata->wm831x_num * 10) + 1;
226137a6354SMark Brown 	else
227137a6354SMark Brown 		id = 0;
228137a6354SMark Brown 	id = pdev->id - id;
229137a6354SMark Brown 
230d1c6b4feSMark Brown 	dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
231d1c6b4feSMark Brown 
232fded2f4fSMark Brown 	ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ldo), GFP_KERNEL);
233fae3b836SSachin Kamat 	if (!ldo)
234d1c6b4feSMark Brown 		return -ENOMEM;
235d1c6b4feSMark Brown 
236d1c6b4feSMark Brown 	ldo->wm831x = wm831x;
237d1c6b4feSMark Brown 
2385656098eSMark Brown 	res = platform_get_resource(pdev, IORESOURCE_REG, 0);
239d1c6b4feSMark Brown 	if (res == NULL) {
2405656098eSMark Brown 		dev_err(&pdev->dev, "No REG resource\n");
241d1c6b4feSMark Brown 		ret = -EINVAL;
242d1c6b4feSMark Brown 		goto err;
243d1c6b4feSMark Brown 	}
244d1c6b4feSMark Brown 	ldo->base = res->start;
245d1c6b4feSMark Brown 
246d1c6b4feSMark Brown 	snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1);
247d1c6b4feSMark Brown 	ldo->desc.name = ldo->name;
248f1aba13fSMark Brown 
249f1aba13fSMark Brown 	snprintf(ldo->supply_name, sizeof(ldo->supply_name),
250f1aba13fSMark Brown 		 "LDO%dVDD", id + 1);
251f1aba13fSMark Brown 	ldo->desc.supply_name = ldo->supply_name;
252f1aba13fSMark Brown 
253d1c6b4feSMark Brown 	ldo->desc.id = id;
254d1c6b4feSMark Brown 	ldo->desc.type = REGULATOR_VOLTAGE;
2555ff26a14SMark Brown 	ldo->desc.n_voltages = 32;
256d1c6b4feSMark Brown 	ldo->desc.ops = &wm831x_gp_ldo_ops;
257d1c6b4feSMark Brown 	ldo->desc.owner = THIS_MODULE;
258ac663b47SMark Brown 	ldo->desc.vsel_reg = ldo->base + WM831X_LDO_ON_CONTROL;
259ac663b47SMark Brown 	ldo->desc.vsel_mask = WM831X_LDO1_ON_VSEL_MASK;
260ca8c361bSMark Brown 	ldo->desc.enable_reg = WM831X_LDO_ENABLE;
261ca8c361bSMark Brown 	ldo->desc.enable_mask = 1 << id;
26222c5fb6aSMark Brown 	ldo->desc.bypass_reg = ldo->base;
26322c5fb6aSMark Brown 	ldo->desc.bypass_mask = WM831X_LDO1_SWI;
2645ff26a14SMark Brown 	ldo->desc.linear_ranges = wm831x_gp_ldo_ranges;
2655ff26a14SMark Brown 	ldo->desc.n_linear_ranges = ARRAY_SIZE(wm831x_gp_ldo_ranges);
266d1c6b4feSMark Brown 
267c172708dSMark Brown 	config.dev = pdev->dev.parent;
268b7ca8788SMark Brown 	if (pdata)
269c172708dSMark Brown 		config.init_data = pdata->ldo[id];
270c172708dSMark Brown 	config.driver_data = ldo;
271ac663b47SMark Brown 	config.regmap = wm831x->regmap;
272c172708dSMark Brown 
273fc7c60e3SMark Brown 	ldo->regulator = devm_regulator_register(&pdev->dev, &ldo->desc,
274fc7c60e3SMark Brown 						 &config);
275d1c6b4feSMark Brown 	if (IS_ERR(ldo->regulator)) {
276d1c6b4feSMark Brown 		ret = PTR_ERR(ldo->regulator);
277d1c6b4feSMark Brown 		dev_err(wm831x->dev, "Failed to register LDO%d: %d\n",
278d1c6b4feSMark Brown 			id + 1, ret);
279d1c6b4feSMark Brown 		goto err;
280d1c6b4feSMark Brown 	}
281d1c6b4feSMark Brown 
282cd99758bSMark Brown 	irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV"));
28341c7a879SMark Brown 	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
28441c7a879SMark Brown 					wm831x_ldo_uv_irq,
28529454738SFabio Estevam 					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
28629454738SFabio Estevam 					ldo->name,
287d1c6b4feSMark Brown 					ldo);
288d1c6b4feSMark Brown 	if (ret != 0) {
289d1c6b4feSMark Brown 		dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
290d1c6b4feSMark Brown 			irq, ret);
291fc7c60e3SMark Brown 		goto err;
292d1c6b4feSMark Brown 	}
293d1c6b4feSMark Brown 
294d1c6b4feSMark Brown 	platform_set_drvdata(pdev, ldo);
295d1c6b4feSMark Brown 
296d1c6b4feSMark Brown 	return 0;
297d1c6b4feSMark Brown 
298d1c6b4feSMark Brown err:
299d1c6b4feSMark Brown 	return ret;
300d1c6b4feSMark Brown }
301d1c6b4feSMark Brown 
302d1c6b4feSMark Brown static struct platform_driver wm831x_gp_ldo_driver = {
303d1c6b4feSMark Brown 	.probe = wm831x_gp_ldo_probe,
304d1c6b4feSMark Brown 	.driver		= {
305d1c6b4feSMark Brown 		.name	= "wm831x-ldo",
306*259b93b2SDouglas Anderson 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
307d1c6b4feSMark Brown 	},
308d1c6b4feSMark Brown };
309d1c6b4feSMark Brown 
310d1c6b4feSMark Brown /*
311d1c6b4feSMark Brown  * Analogue LDOs
312d1c6b4feSMark Brown  */
313d1c6b4feSMark Brown 
31460ab7f41SMatti Vaittinen static const struct linear_range wm831x_aldo_ranges[] = {
3158828bae4SAxel Lin 	REGULATOR_LINEAR_RANGE(1000000, 0, 12, 50000),
3168828bae4SAxel Lin 	REGULATOR_LINEAR_RANGE(1700000, 13, 31, 100000),
3175ff26a14SMark Brown };
318d1c6b4feSMark Brown 
wm831x_aldo_set_suspend_voltage(struct regulator_dev * rdev,int uV)319d1c6b4feSMark Brown static int wm831x_aldo_set_suspend_voltage(struct regulator_dev *rdev,
320d1c6b4feSMark Brown 					     int uV)
321d1c6b4feSMark Brown {
322d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
3230a479689SAxel Lin 	struct wm831x *wm831x = ldo->wm831x;
3240a479689SAxel Lin 	int sel, reg = ldo->base + WM831X_LDO_SLEEP_CONTROL;
325d1c6b4feSMark Brown 
3265ff26a14SMark Brown 	sel = regulator_map_voltage_linear_range(rdev, uV, uV);
3270a479689SAxel Lin 	if (sel < 0)
3280a479689SAxel Lin 		return sel;
3290a479689SAxel Lin 
3300a479689SAxel Lin 	return wm831x_set_bits(wm831x, reg, WM831X_LDO7_ON_VSEL_MASK, sel);
331d1c6b4feSMark Brown }
332d1c6b4feSMark Brown 
wm831x_aldo_get_mode(struct regulator_dev * rdev)333d1c6b4feSMark Brown static unsigned int wm831x_aldo_get_mode(struct regulator_dev *rdev)
334d1c6b4feSMark Brown {
335d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
336d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
337d1c6b4feSMark Brown 	int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
3386f17c652SRoel Kluin 	int ret;
339d1c6b4feSMark Brown 
340d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, on_reg);
341d1c6b4feSMark Brown 	if (ret < 0)
342d1c6b4feSMark Brown 		return 0;
343d1c6b4feSMark Brown 
344d1c6b4feSMark Brown 	if (ret & WM831X_LDO7_ON_MODE)
345d1c6b4feSMark Brown 		return REGULATOR_MODE_IDLE;
346d1c6b4feSMark Brown 	else
347d1c6b4feSMark Brown 		return REGULATOR_MODE_NORMAL;
348d1c6b4feSMark Brown }
349d1c6b4feSMark Brown 
wm831x_aldo_set_mode(struct regulator_dev * rdev,unsigned int mode)350d1c6b4feSMark Brown static int wm831x_aldo_set_mode(struct regulator_dev *rdev,
351d1c6b4feSMark Brown 				  unsigned int mode)
352d1c6b4feSMark Brown {
353d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
354d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
355d1c6b4feSMark Brown 	int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
356d1c6b4feSMark Brown 	int ret;
357d1c6b4feSMark Brown 
358d1c6b4feSMark Brown 
359d1c6b4feSMark Brown 	switch (mode) {
360d1c6b4feSMark Brown 	case REGULATOR_MODE_NORMAL:
361e841a36aSAxel Lin 		ret = wm831x_set_bits(wm831x, on_reg, WM831X_LDO7_ON_MODE, 0);
362d1c6b4feSMark Brown 		if (ret < 0)
363d1c6b4feSMark Brown 			return ret;
364d1c6b4feSMark Brown 		break;
365d1c6b4feSMark Brown 
366d1c6b4feSMark Brown 	case REGULATOR_MODE_IDLE:
367e841a36aSAxel Lin 		ret = wm831x_set_bits(wm831x, on_reg, WM831X_LDO7_ON_MODE,
368d1c6b4feSMark Brown 				      WM831X_LDO7_ON_MODE);
369d1c6b4feSMark Brown 		if (ret < 0)
370d1c6b4feSMark Brown 			return ret;
371d1c6b4feSMark Brown 		break;
372d1c6b4feSMark Brown 
373d1c6b4feSMark Brown 	default:
374d1c6b4feSMark Brown 		return -EINVAL;
375d1c6b4feSMark Brown 	}
376d1c6b4feSMark Brown 
377d1c6b4feSMark Brown 	return 0;
378d1c6b4feSMark Brown }
379d1c6b4feSMark Brown 
wm831x_aldo_get_status(struct regulator_dev * rdev)380d1c6b4feSMark Brown static int wm831x_aldo_get_status(struct regulator_dev *rdev)
381d1c6b4feSMark Brown {
382d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
383d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
384d1c6b4feSMark Brown 	int mask = 1 << rdev_get_id(rdev);
385d1c6b4feSMark Brown 	int ret;
386d1c6b4feSMark Brown 
387d1c6b4feSMark Brown 	/* Is the regulator on? */
388d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS);
389d1c6b4feSMark Brown 	if (ret < 0)
390d1c6b4feSMark Brown 		return ret;
391d1c6b4feSMark Brown 	if (!(ret & mask))
392d1c6b4feSMark Brown 		return REGULATOR_STATUS_OFF;
393d1c6b4feSMark Brown 
394d1c6b4feSMark Brown 	/* Is it reporting under voltage? */
395d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS);
396363506cdSAxel Lin 	if (ret < 0)
397363506cdSAxel Lin 		return ret;
398d1c6b4feSMark Brown 	if (ret & mask)
399d1c6b4feSMark Brown 		return REGULATOR_STATUS_ERROR;
400d1c6b4feSMark Brown 
401d1c6b4feSMark Brown 	ret = wm831x_aldo_get_mode(rdev);
402d1c6b4feSMark Brown 	if (ret < 0)
403d1c6b4feSMark Brown 		return ret;
404d1c6b4feSMark Brown 	else
405d1c6b4feSMark Brown 		return regulator_mode_to_status(ret);
406d1c6b4feSMark Brown }
407d1c6b4feSMark Brown 
408b0d6dd3bSJulia Lawall static const struct regulator_ops wm831x_aldo_ops = {
4095ff26a14SMark Brown 	.list_voltage = regulator_list_voltage_linear_range,
4105ff26a14SMark Brown 	.map_voltage = regulator_map_voltage_linear_range,
411ac663b47SMark Brown 	.get_voltage_sel = regulator_get_voltage_sel_regmap,
4120a479689SAxel Lin 	.set_voltage_sel = regulator_set_voltage_sel_regmap,
413d1c6b4feSMark Brown 	.set_suspend_voltage = wm831x_aldo_set_suspend_voltage,
414d1c6b4feSMark Brown 	.get_mode = wm831x_aldo_get_mode,
415d1c6b4feSMark Brown 	.set_mode = wm831x_aldo_set_mode,
416d1c6b4feSMark Brown 	.get_status = wm831x_aldo_get_status,
41722c5fb6aSMark Brown 	.set_bypass = regulator_set_bypass_regmap,
41822c5fb6aSMark Brown 	.get_bypass = regulator_get_bypass_regmap,
419d1c6b4feSMark Brown 
420ca8c361bSMark Brown 	.is_enabled = regulator_is_enabled_regmap,
421ca8c361bSMark Brown 	.enable = regulator_enable_regmap,
422ca8c361bSMark Brown 	.disable = regulator_disable_regmap,
423d1c6b4feSMark Brown };
424d1c6b4feSMark Brown 
wm831x_aldo_probe(struct platform_device * pdev)425a5023574SBill Pemberton static int wm831x_aldo_probe(struct platform_device *pdev)
426d1c6b4feSMark Brown {
427d1c6b4feSMark Brown 	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
428dff91d0bSJingoo Han 	struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev);
429c172708dSMark Brown 	struct regulator_config config = { };
430137a6354SMark Brown 	int id;
431d1c6b4feSMark Brown 	struct wm831x_ldo *ldo;
432d1c6b4feSMark Brown 	struct resource *res;
433d1c6b4feSMark Brown 	int ret, irq;
434d1c6b4feSMark Brown 
435137a6354SMark Brown 	if (pdata && pdata->wm831x_num)
436137a6354SMark Brown 		id = (pdata->wm831x_num * 10) + 1;
437137a6354SMark Brown 	else
438137a6354SMark Brown 		id = 0;
439137a6354SMark Brown 	id = pdev->id - id;
440137a6354SMark Brown 
441d1c6b4feSMark Brown 	dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
442d1c6b4feSMark Brown 
443fded2f4fSMark Brown 	ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ldo), GFP_KERNEL);
444fae3b836SSachin Kamat 	if (!ldo)
445d1c6b4feSMark Brown 		return -ENOMEM;
446d1c6b4feSMark Brown 
447d1c6b4feSMark Brown 	ldo->wm831x = wm831x;
448d1c6b4feSMark Brown 
4495656098eSMark Brown 	res = platform_get_resource(pdev, IORESOURCE_REG, 0);
450d1c6b4feSMark Brown 	if (res == NULL) {
4515656098eSMark Brown 		dev_err(&pdev->dev, "No REG resource\n");
452d1c6b4feSMark Brown 		ret = -EINVAL;
453d1c6b4feSMark Brown 		goto err;
454d1c6b4feSMark Brown 	}
455d1c6b4feSMark Brown 	ldo->base = res->start;
456d1c6b4feSMark Brown 
457d1c6b4feSMark Brown 	snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1);
458d1c6b4feSMark Brown 	ldo->desc.name = ldo->name;
459f1aba13fSMark Brown 
460f1aba13fSMark Brown 	snprintf(ldo->supply_name, sizeof(ldo->supply_name),
461f1aba13fSMark Brown 		 "LDO%dVDD", id + 1);
462f1aba13fSMark Brown 	ldo->desc.supply_name = ldo->supply_name;
463f1aba13fSMark Brown 
464d1c6b4feSMark Brown 	ldo->desc.id = id;
465d1c6b4feSMark Brown 	ldo->desc.type = REGULATOR_VOLTAGE;
4665ff26a14SMark Brown 	ldo->desc.n_voltages = 32;
4675ff26a14SMark Brown 	ldo->desc.linear_ranges = wm831x_aldo_ranges;
4685ff26a14SMark Brown 	ldo->desc.n_linear_ranges = ARRAY_SIZE(wm831x_aldo_ranges);
469d1c6b4feSMark Brown 	ldo->desc.ops = &wm831x_aldo_ops;
470d1c6b4feSMark Brown 	ldo->desc.owner = THIS_MODULE;
471ac663b47SMark Brown 	ldo->desc.vsel_reg = ldo->base + WM831X_LDO_ON_CONTROL;
472ac663b47SMark Brown 	ldo->desc.vsel_mask = WM831X_LDO7_ON_VSEL_MASK;
473ca8c361bSMark Brown 	ldo->desc.enable_reg = WM831X_LDO_ENABLE;
474ca8c361bSMark Brown 	ldo->desc.enable_mask = 1 << id;
47522c5fb6aSMark Brown 	ldo->desc.bypass_reg = ldo->base;
47622c5fb6aSMark Brown 	ldo->desc.bypass_mask = WM831X_LDO7_SWI;
477d1c6b4feSMark Brown 
478c172708dSMark Brown 	config.dev = pdev->dev.parent;
479b7ca8788SMark Brown 	if (pdata)
480c172708dSMark Brown 		config.init_data = pdata->ldo[id];
481c172708dSMark Brown 	config.driver_data = ldo;
482ac663b47SMark Brown 	config.regmap = wm831x->regmap;
483c172708dSMark Brown 
484fc7c60e3SMark Brown 	ldo->regulator = devm_regulator_register(&pdev->dev, &ldo->desc,
485fc7c60e3SMark Brown 						 &config);
486d1c6b4feSMark Brown 	if (IS_ERR(ldo->regulator)) {
487d1c6b4feSMark Brown 		ret = PTR_ERR(ldo->regulator);
488d1c6b4feSMark Brown 		dev_err(wm831x->dev, "Failed to register LDO%d: %d\n",
489d1c6b4feSMark Brown 			id + 1, ret);
490d1c6b4feSMark Brown 		goto err;
491d1c6b4feSMark Brown 	}
492d1c6b4feSMark Brown 
493cd99758bSMark Brown 	irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "UV"));
49441c7a879SMark Brown 	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
49541c7a879SMark Brown 					wm831x_ldo_uv_irq,
49629454738SFabio Estevam 					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
49729454738SFabio Estevam 					ldo->name, ldo);
498d1c6b4feSMark Brown 	if (ret != 0) {
499d1c6b4feSMark Brown 		dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
500d1c6b4feSMark Brown 			irq, ret);
501fc7c60e3SMark Brown 		goto err;
502d1c6b4feSMark Brown 	}
503d1c6b4feSMark Brown 
504d1c6b4feSMark Brown 	platform_set_drvdata(pdev, ldo);
505d1c6b4feSMark Brown 
506d1c6b4feSMark Brown 	return 0;
507d1c6b4feSMark Brown 
508d1c6b4feSMark Brown err:
509d1c6b4feSMark Brown 	return ret;
510d1c6b4feSMark Brown }
511d1c6b4feSMark Brown 
512d1c6b4feSMark Brown static struct platform_driver wm831x_aldo_driver = {
513d1c6b4feSMark Brown 	.probe = wm831x_aldo_probe,
514d1c6b4feSMark Brown 	.driver		= {
515d1c6b4feSMark Brown 		.name	= "wm831x-aldo",
516*259b93b2SDouglas Anderson 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
517d1c6b4feSMark Brown 	},
518d1c6b4feSMark Brown };
519d1c6b4feSMark Brown 
520d1c6b4feSMark Brown /*
521d1c6b4feSMark Brown  * Alive LDO
522d1c6b4feSMark Brown  */
523d1c6b4feSMark Brown 
524d1c6b4feSMark Brown #define WM831X_ALIVE_LDO_MAX_SELECTOR 0xf
525d1c6b4feSMark Brown 
wm831x_alive_ldo_set_suspend_voltage(struct regulator_dev * rdev,int uV)526d1c6b4feSMark Brown static int wm831x_alive_ldo_set_suspend_voltage(struct regulator_dev *rdev,
527d1c6b4feSMark Brown 					     int uV)
528d1c6b4feSMark Brown {
529d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
5300a479689SAxel Lin 	struct wm831x *wm831x = ldo->wm831x;
5310a479689SAxel Lin 	int sel, reg = ldo->base + WM831X_ALIVE_LDO_SLEEP_CONTROL;
532d1c6b4feSMark Brown 
5330a479689SAxel Lin 	sel = regulator_map_voltage_linear(rdev, uV, uV);
5340a479689SAxel Lin 	if (sel < 0)
5350a479689SAxel Lin 		return sel;
5360a479689SAxel Lin 
5370a479689SAxel Lin 	return wm831x_set_bits(wm831x, reg, WM831X_LDO11_ON_VSEL_MASK, sel);
538d1c6b4feSMark Brown }
539d1c6b4feSMark Brown 
wm831x_alive_ldo_get_status(struct regulator_dev * rdev)540d1c6b4feSMark Brown static int wm831x_alive_ldo_get_status(struct regulator_dev *rdev)
541d1c6b4feSMark Brown {
542d1c6b4feSMark Brown 	struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
543d1c6b4feSMark Brown 	struct wm831x *wm831x = ldo->wm831x;
544d1c6b4feSMark Brown 	int mask = 1 << rdev_get_id(rdev);
545d1c6b4feSMark Brown 	int ret;
546d1c6b4feSMark Brown 
547d1c6b4feSMark Brown 	/* Is the regulator on? */
548d1c6b4feSMark Brown 	ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS);
549d1c6b4feSMark Brown 	if (ret < 0)
550d1c6b4feSMark Brown 		return ret;
551d1c6b4feSMark Brown 	if (ret & mask)
552d1c6b4feSMark Brown 		return REGULATOR_STATUS_ON;
553d1c6b4feSMark Brown 	else
554d1c6b4feSMark Brown 		return REGULATOR_STATUS_OFF;
555d1c6b4feSMark Brown }
556d1c6b4feSMark Brown 
557b0d6dd3bSJulia Lawall static const struct regulator_ops wm831x_alive_ldo_ops = {
558d31e954eSMark Brown 	.list_voltage = regulator_list_voltage_linear,
559c2543b5fSAxel Lin 	.map_voltage = regulator_map_voltage_linear,
560ac663b47SMark Brown 	.get_voltage_sel = regulator_get_voltage_sel_regmap,
5610a479689SAxel Lin 	.set_voltage_sel = regulator_set_voltage_sel_regmap,
562d1c6b4feSMark Brown 	.set_suspend_voltage = wm831x_alive_ldo_set_suspend_voltage,
563d1c6b4feSMark Brown 	.get_status = wm831x_alive_ldo_get_status,
564d1c6b4feSMark Brown 
565ca8c361bSMark Brown 	.is_enabled = regulator_is_enabled_regmap,
566ca8c361bSMark Brown 	.enable = regulator_enable_regmap,
567ca8c361bSMark Brown 	.disable = regulator_disable_regmap,
568d1c6b4feSMark Brown };
569d1c6b4feSMark Brown 
wm831x_alive_ldo_probe(struct platform_device * pdev)570a5023574SBill Pemberton static int wm831x_alive_ldo_probe(struct platform_device *pdev)
571d1c6b4feSMark Brown {
572d1c6b4feSMark Brown 	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
573dff91d0bSJingoo Han 	struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev);
574c172708dSMark Brown 	struct regulator_config config = { };
575137a6354SMark Brown 	int id;
576d1c6b4feSMark Brown 	struct wm831x_ldo *ldo;
577d1c6b4feSMark Brown 	struct resource *res;
578d1c6b4feSMark Brown 	int ret;
579d1c6b4feSMark Brown 
580137a6354SMark Brown 	if (pdata && pdata->wm831x_num)
581137a6354SMark Brown 		id = (pdata->wm831x_num * 10) + 1;
582137a6354SMark Brown 	else
583137a6354SMark Brown 		id = 0;
584137a6354SMark Brown 	id = pdev->id - id;
585137a6354SMark Brown 
586137a6354SMark Brown 
587d1c6b4feSMark Brown 	dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
588d1c6b4feSMark Brown 
589fded2f4fSMark Brown 	ldo = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_ldo), GFP_KERNEL);
590fae3b836SSachin Kamat 	if (!ldo)
591d1c6b4feSMark Brown 		return -ENOMEM;
592d1c6b4feSMark Brown 
593d1c6b4feSMark Brown 	ldo->wm831x = wm831x;
594d1c6b4feSMark Brown 
5955656098eSMark Brown 	res = platform_get_resource(pdev, IORESOURCE_REG, 0);
596d1c6b4feSMark Brown 	if (res == NULL) {
5975656098eSMark Brown 		dev_err(&pdev->dev, "No REG resource\n");
598d1c6b4feSMark Brown 		ret = -EINVAL;
599d1c6b4feSMark Brown 		goto err;
600d1c6b4feSMark Brown 	}
601d1c6b4feSMark Brown 	ldo->base = res->start;
602d1c6b4feSMark Brown 
603d1c6b4feSMark Brown 	snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1);
604d1c6b4feSMark Brown 	ldo->desc.name = ldo->name;
605f1aba13fSMark Brown 
606f1aba13fSMark Brown 	snprintf(ldo->supply_name, sizeof(ldo->supply_name),
607f1aba13fSMark Brown 		 "LDO%dVDD", id + 1);
608f1aba13fSMark Brown 	ldo->desc.supply_name = ldo->supply_name;
609f1aba13fSMark Brown 
610d1c6b4feSMark Brown 	ldo->desc.id = id;
611d1c6b4feSMark Brown 	ldo->desc.type = REGULATOR_VOLTAGE;
612d1c6b4feSMark Brown 	ldo->desc.n_voltages = WM831X_ALIVE_LDO_MAX_SELECTOR + 1;
613d1c6b4feSMark Brown 	ldo->desc.ops = &wm831x_alive_ldo_ops;
614d1c6b4feSMark Brown 	ldo->desc.owner = THIS_MODULE;
615ac663b47SMark Brown 	ldo->desc.vsel_reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL;
616ac663b47SMark Brown 	ldo->desc.vsel_mask = WM831X_LDO11_ON_VSEL_MASK;
617ca8c361bSMark Brown 	ldo->desc.enable_reg = WM831X_LDO_ENABLE;
618ca8c361bSMark Brown 	ldo->desc.enable_mask = 1 << id;
619d31e954eSMark Brown 	ldo->desc.min_uV = 800000;
620d31e954eSMark Brown 	ldo->desc.uV_step = 50000;
621eefaa3c6SMark Brown 	ldo->desc.enable_time = 1000;
622d1c6b4feSMark Brown 
623c172708dSMark Brown 	config.dev = pdev->dev.parent;
624b7ca8788SMark Brown 	if (pdata)
625c172708dSMark Brown 		config.init_data = pdata->ldo[id];
626c172708dSMark Brown 	config.driver_data = ldo;
627ac663b47SMark Brown 	config.regmap = wm831x->regmap;
628c172708dSMark Brown 
629fc7c60e3SMark Brown 	ldo->regulator = devm_regulator_register(&pdev->dev, &ldo->desc,
630fc7c60e3SMark Brown 						 &config);
631d1c6b4feSMark Brown 	if (IS_ERR(ldo->regulator)) {
632d1c6b4feSMark Brown 		ret = PTR_ERR(ldo->regulator);
633d1c6b4feSMark Brown 		dev_err(wm831x->dev, "Failed to register LDO%d: %d\n",
634d1c6b4feSMark Brown 			id + 1, ret);
635d1c6b4feSMark Brown 		goto err;
636d1c6b4feSMark Brown 	}
637d1c6b4feSMark Brown 
638d1c6b4feSMark Brown 	platform_set_drvdata(pdev, ldo);
639d1c6b4feSMark Brown 
640d1c6b4feSMark Brown 	return 0;
641d1c6b4feSMark Brown 
642d1c6b4feSMark Brown err:
643d1c6b4feSMark Brown 	return ret;
644d1c6b4feSMark Brown }
645d1c6b4feSMark Brown 
646d1c6b4feSMark Brown static struct platform_driver wm831x_alive_ldo_driver = {
647d1c6b4feSMark Brown 	.probe = wm831x_alive_ldo_probe,
648d1c6b4feSMark Brown 	.driver		= {
649d1c6b4feSMark Brown 		.name	= "wm831x-alive-ldo",
650*259b93b2SDouglas Anderson 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
651d1c6b4feSMark Brown 	},
652d1c6b4feSMark Brown };
653d1c6b4feSMark Brown 
65492a513b7SThierry Reding static struct platform_driver * const drivers[] = {
65592a513b7SThierry Reding 	&wm831x_gp_ldo_driver,
65692a513b7SThierry Reding 	&wm831x_aldo_driver,
65792a513b7SThierry Reding 	&wm831x_alive_ldo_driver,
65892a513b7SThierry Reding };
65992a513b7SThierry Reding 
wm831x_ldo_init(void)660d1c6b4feSMark Brown static int __init wm831x_ldo_init(void)
661d1c6b4feSMark Brown {
66292a513b7SThierry Reding 	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
663d1c6b4feSMark Brown }
664d1c6b4feSMark Brown subsys_initcall(wm831x_ldo_init);
665d1c6b4feSMark Brown 
wm831x_ldo_exit(void)666d1c6b4feSMark Brown static void __exit wm831x_ldo_exit(void)
667d1c6b4feSMark Brown {
66892a513b7SThierry Reding 	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
669d1c6b4feSMark Brown }
670d1c6b4feSMark Brown module_exit(wm831x_ldo_exit);
671d1c6b4feSMark Brown 
672d1c6b4feSMark Brown /* Module information */
673d1c6b4feSMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
674d1c6b4feSMark Brown MODULE_DESCRIPTION("WM831x LDO driver");
675d1c6b4feSMark Brown MODULE_LICENSE("GPL");
676d1c6b4feSMark Brown MODULE_ALIAS("platform:wm831x-ldo");
677d1c6b4feSMark Brown MODULE_ALIAS("platform:wm831x-aldo");
678d1c6b4feSMark Brown MODULE_ALIAS("platform:wm831x-aliveldo");
679