1d4d6b722SMark Brown /*
2d4d6b722SMark Brown  * wm831x-isink.c  --  Current sink driver for the WM831x series
3d4d6b722SMark Brown  *
4d4d6b722SMark Brown  * Copyright 2009 Wolfson Microelectronics PLC.
5d4d6b722SMark Brown  *
6d4d6b722SMark Brown  * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
7d4d6b722SMark Brown  *
8d4d6b722SMark Brown  *  This program is free software; you can redistribute  it and/or modify it
9d4d6b722SMark Brown  *  under  the terms of  the GNU General  Public License as published by the
10d4d6b722SMark Brown  *  Free Software Foundation;  either version 2 of the  License, or (at your
11d4d6b722SMark Brown  *  option) any later version.
12d4d6b722SMark Brown  */
13d4d6b722SMark Brown 
14d4d6b722SMark Brown #include <linux/module.h>
15d4d6b722SMark Brown #include <linux/moduleparam.h>
16d4d6b722SMark Brown #include <linux/init.h>
17d4d6b722SMark Brown #include <linux/bitops.h>
18d4d6b722SMark Brown #include <linux/err.h>
19d4d6b722SMark Brown #include <linux/i2c.h>
20d4d6b722SMark Brown #include <linux/platform_device.h>
21d4d6b722SMark Brown #include <linux/regulator/driver.h>
225a0e3ad6STejun Heo #include <linux/slab.h>
23d4d6b722SMark Brown 
24d4d6b722SMark Brown #include <linux/mfd/wm831x/core.h>
25d4d6b722SMark Brown #include <linux/mfd/wm831x/regulator.h>
26d4d6b722SMark Brown #include <linux/mfd/wm831x/pdata.h>
27d4d6b722SMark Brown 
28d4d6b722SMark Brown #define WM831X_ISINK_MAX_NAME 7
29d4d6b722SMark Brown 
30d4d6b722SMark Brown struct wm831x_isink {
31d4d6b722SMark Brown 	char name[WM831X_ISINK_MAX_NAME];
32d4d6b722SMark Brown 	struct regulator_desc desc;
33d4d6b722SMark Brown 	int reg;
34d4d6b722SMark Brown 	struct wm831x *wm831x;
35d4d6b722SMark Brown 	struct regulator_dev *regulator;
36d4d6b722SMark Brown };
37d4d6b722SMark Brown 
38d4d6b722SMark Brown static int wm831x_isink_enable(struct regulator_dev *rdev)
39d4d6b722SMark Brown {
40d4d6b722SMark Brown 	struct wm831x_isink *isink = rdev_get_drvdata(rdev);
41d4d6b722SMark Brown 	struct wm831x *wm831x = isink->wm831x;
42d4d6b722SMark Brown 	int ret;
43d4d6b722SMark Brown 
44d4d6b722SMark Brown 	/* We have a two stage enable: first start the ISINK... */
45d4d6b722SMark Brown 	ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA,
46d4d6b722SMark Brown 			      WM831X_CS1_ENA);
47d4d6b722SMark Brown 	if (ret != 0)
48d4d6b722SMark Brown 		return ret;
49d4d6b722SMark Brown 
50d4d6b722SMark Brown 	/* ...then enable drive */
51d4d6b722SMark Brown 	ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE,
52d4d6b722SMark Brown 			      WM831X_CS1_DRIVE);
53d4d6b722SMark Brown 	if (ret != 0)
54d4d6b722SMark Brown 		wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0);
55d4d6b722SMark Brown 
56d4d6b722SMark Brown 	return ret;
57d4d6b722SMark Brown 
58d4d6b722SMark Brown }
59d4d6b722SMark Brown 
60d4d6b722SMark Brown static int wm831x_isink_disable(struct regulator_dev *rdev)
61d4d6b722SMark Brown {
62d4d6b722SMark Brown 	struct wm831x_isink *isink = rdev_get_drvdata(rdev);
63d4d6b722SMark Brown 	struct wm831x *wm831x = isink->wm831x;
64d4d6b722SMark Brown 	int ret;
65d4d6b722SMark Brown 
66d4d6b722SMark Brown 	ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, 0);
67d4d6b722SMark Brown 	if (ret < 0)
68d4d6b722SMark Brown 		return ret;
69d4d6b722SMark Brown 
70d4d6b722SMark Brown 	ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0);
71d4d6b722SMark Brown 	if (ret < 0)
72d4d6b722SMark Brown 		return ret;
73d4d6b722SMark Brown 
74d4d6b722SMark Brown 	return ret;
75d4d6b722SMark Brown 
76d4d6b722SMark Brown }
77d4d6b722SMark Brown 
78d4d6b722SMark Brown static int wm831x_isink_is_enabled(struct regulator_dev *rdev)
79d4d6b722SMark Brown {
80d4d6b722SMark Brown 	struct wm831x_isink *isink = rdev_get_drvdata(rdev);
81d4d6b722SMark Brown 	struct wm831x *wm831x = isink->wm831x;
82d4d6b722SMark Brown 	int ret;
83d4d6b722SMark Brown 
84d4d6b722SMark Brown 	ret = wm831x_reg_read(wm831x, isink->reg);
85d4d6b722SMark Brown 	if (ret < 0)
86d4d6b722SMark Brown 		return ret;
87d4d6b722SMark Brown 
88d4d6b722SMark Brown 	if ((ret & (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) ==
89d4d6b722SMark Brown 	    (WM831X_CS1_ENA | WM831X_CS1_DRIVE))
90d4d6b722SMark Brown 		return 1;
91d4d6b722SMark Brown 	else
92d4d6b722SMark Brown 		return 0;
93d4d6b722SMark Brown }
94d4d6b722SMark Brown 
95b0d6dd3bSJulia Lawall static const struct regulator_ops wm831x_isink_ops = {
96d4d6b722SMark Brown 	.is_enabled = wm831x_isink_is_enabled,
97d4d6b722SMark Brown 	.enable = wm831x_isink_enable,
98d4d6b722SMark Brown 	.disable = wm831x_isink_disable,
99d48acfd0SAxel Lin 	.set_current_limit = regulator_set_current_limit_regmap,
100d48acfd0SAxel Lin 	.get_current_limit = regulator_get_current_limit_regmap,
101d4d6b722SMark Brown };
102d4d6b722SMark Brown 
103d4d6b722SMark Brown static irqreturn_t wm831x_isink_irq(int irq, void *data)
104d4d6b722SMark Brown {
105d4d6b722SMark Brown 	struct wm831x_isink *isink = data;
106d4d6b722SMark Brown 
107f7a62172SSteve Twiss 	regulator_lock(isink->regulator);
108d4d6b722SMark Brown 	regulator_notifier_call_chain(isink->regulator,
109d4d6b722SMark Brown 				      REGULATOR_EVENT_OVER_CURRENT,
110d4d6b722SMark Brown 				      NULL);
111f7a62172SSteve Twiss 	regulator_unlock(isink->regulator);
112d4d6b722SMark Brown 
113d4d6b722SMark Brown 	return IRQ_HANDLED;
114d4d6b722SMark Brown }
115d4d6b722SMark Brown 
116d4d6b722SMark Brown 
117a5023574SBill Pemberton static int wm831x_isink_probe(struct platform_device *pdev)
118d4d6b722SMark Brown {
119d4d6b722SMark Brown 	struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
120dff91d0bSJingoo Han 	struct wm831x_pdata *pdata = dev_get_platdata(wm831x->dev);
121d4d6b722SMark Brown 	struct wm831x_isink *isink;
122d4d6b722SMark Brown 	int id = pdev->id % ARRAY_SIZE(pdata->isink);
123c172708dSMark Brown 	struct regulator_config config = { };
124d4d6b722SMark Brown 	struct resource *res;
125d4d6b722SMark Brown 	int ret, irq;
126d4d6b722SMark Brown 
127d4d6b722SMark Brown 	dev_dbg(&pdev->dev, "Probing ISINK%d\n", id + 1);
128d4d6b722SMark Brown 
129d4d6b722SMark Brown 	if (pdata == NULL || pdata->isink[id] == NULL)
130d4d6b722SMark Brown 		return -ENODEV;
131d4d6b722SMark Brown 
132fded2f4fSMark Brown 	isink = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_isink),
133fded2f4fSMark Brown 			     GFP_KERNEL);
134d718debcSSachin Kamat 	if (!isink)
135d4d6b722SMark Brown 		return -ENOMEM;
136d4d6b722SMark Brown 
137e8092da9SMark Brown 	isink->wm831x = wm831x;
138e8092da9SMark Brown 
1395656098eSMark Brown 	res = platform_get_resource(pdev, IORESOURCE_REG, 0);
140d4d6b722SMark Brown 	if (res == NULL) {
1415656098eSMark Brown 		dev_err(&pdev->dev, "No REG resource\n");
142d4d6b722SMark Brown 		ret = -EINVAL;
143d4d6b722SMark Brown 		goto err;
144d4d6b722SMark Brown 	}
145d4d6b722SMark Brown 	isink->reg = res->start;
146d4d6b722SMark Brown 
147d4d6b722SMark Brown 	/* For current parts this is correct; probably need to revisit
148d4d6b722SMark Brown 	 * in future.
149d4d6b722SMark Brown 	 */
150d4d6b722SMark Brown 	snprintf(isink->name, sizeof(isink->name), "ISINK%d", id + 1);
151d4d6b722SMark Brown 	isink->desc.name = isink->name;
152d4d6b722SMark Brown 	isink->desc.id = id;
153d4d6b722SMark Brown 	isink->desc.ops = &wm831x_isink_ops;
154d4d6b722SMark Brown 	isink->desc.type = REGULATOR_CURRENT;
155d4d6b722SMark Brown 	isink->desc.owner = THIS_MODULE;
156d48acfd0SAxel Lin 	isink->desc.curr_table = wm831x_isinkv_values,
157d48acfd0SAxel Lin 	isink->desc.n_current_limits = ARRAY_SIZE(wm831x_isinkv_values),
158d48acfd0SAxel Lin 	isink->desc.csel_reg = isink->reg,
159d48acfd0SAxel Lin 	isink->desc.csel_mask = WM831X_CS1_ISEL_MASK,
160d4d6b722SMark Brown 
161c172708dSMark Brown 	config.dev = pdev->dev.parent;
162c172708dSMark Brown 	config.init_data = pdata->isink[id];
163c172708dSMark Brown 	config.driver_data = isink;
164d48acfd0SAxel Lin 	config.regmap = wm831x->regmap;
165c172708dSMark Brown 
166af151dedSMark Brown 	isink->regulator = devm_regulator_register(&pdev->dev, &isink->desc,
167af151dedSMark Brown 						   &config);
168d4d6b722SMark Brown 	if (IS_ERR(isink->regulator)) {
169d4d6b722SMark Brown 		ret = PTR_ERR(isink->regulator);
170d4d6b722SMark Brown 		dev_err(wm831x->dev, "Failed to register ISINK%d: %d\n",
171d4d6b722SMark Brown 			id + 1, ret);
172d4d6b722SMark Brown 		goto err;
173d4d6b722SMark Brown 	}
174d4d6b722SMark Brown 
175cd99758bSMark Brown 	irq = wm831x_irq(wm831x, platform_get_irq(pdev, 0));
17663fb3149SMark Brown 	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
17763fb3149SMark Brown 					wm831x_isink_irq,
17829454738SFabio Estevam 					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
17929454738SFabio Estevam 					isink->name,
18063fb3149SMark Brown 					isink);
181d4d6b722SMark Brown 	if (ret != 0) {
182d4d6b722SMark Brown 		dev_err(&pdev->dev, "Failed to request ISINK IRQ %d: %d\n",
183d4d6b722SMark Brown 			irq, ret);
184af151dedSMark Brown 		goto err;
185d4d6b722SMark Brown 	}
186d4d6b722SMark Brown 
187d4d6b722SMark Brown 	platform_set_drvdata(pdev, isink);
188d4d6b722SMark Brown 
189d4d6b722SMark Brown 	return 0;
190d4d6b722SMark Brown 
191d4d6b722SMark Brown err:
192d4d6b722SMark Brown 	return ret;
193d4d6b722SMark Brown }
194d4d6b722SMark Brown 
195d4d6b722SMark Brown static struct platform_driver wm831x_isink_driver = {
196d4d6b722SMark Brown 	.probe = wm831x_isink_probe,
197d4d6b722SMark Brown 	.driver		= {
198d4d6b722SMark Brown 		.name	= "wm831x-isink",
199d4d6b722SMark Brown 	},
200d4d6b722SMark Brown };
201d4d6b722SMark Brown 
202d4d6b722SMark Brown static int __init wm831x_isink_init(void)
203d4d6b722SMark Brown {
204d4d6b722SMark Brown 	int ret;
205d4d6b722SMark Brown 	ret = platform_driver_register(&wm831x_isink_driver);
206d4d6b722SMark Brown 	if (ret != 0)
207d4d6b722SMark Brown 		pr_err("Failed to register WM831x ISINK driver: %d\n", ret);
208d4d6b722SMark Brown 
209d4d6b722SMark Brown 	return ret;
210d4d6b722SMark Brown }
211d4d6b722SMark Brown subsys_initcall(wm831x_isink_init);
212d4d6b722SMark Brown 
213d4d6b722SMark Brown static void __exit wm831x_isink_exit(void)
214d4d6b722SMark Brown {
215d4d6b722SMark Brown 	platform_driver_unregister(&wm831x_isink_driver);
216d4d6b722SMark Brown }
217d4d6b722SMark Brown module_exit(wm831x_isink_exit);
218d4d6b722SMark Brown 
219d4d6b722SMark Brown /* Module information */
220d4d6b722SMark Brown MODULE_AUTHOR("Mark Brown");
221d4d6b722SMark Brown MODULE_DESCRIPTION("WM831x current sink driver");
222d4d6b722SMark Brown MODULE_LICENSE("GPL");
223d4d6b722SMark Brown MODULE_ALIAS("platform:wm831x-isink");
224