Commit | Line | Data |
---|---|---|
a5736e0b MH |
1 | /* |
2 | * Base driver for Analog Devices ADP5520/ADP5501 MFD PMICs | |
3 | * LCD Backlight: drivers/video/backlight/adp5520_bl | |
4 | * LEDs : drivers/led/leds-adp5520 | |
5 | * GPIO : drivers/gpio/adp5520-gpio (ADP5520 only) | |
6 | * Keys : drivers/input/keyboard/adp5520-keys (ADP5520 only) | |
7 | * | |
8 | * Copyright 2009 Analog Devices Inc. | |
9 | * | |
6b09274b PG |
10 | * Author: Michael Hennerich <michael.hennerich@analog.com> |
11 | * | |
a5736e0b MH |
12 | * Derived from da903x: |
13 | * Copyright (C) 2008 Compulab, Ltd. | |
3103d44e | 14 | * Mike Rapoport <mike@compulab.co.il> |
a5736e0b MH |
15 | * |
16 | * Copyright (C) 2006-2008 Marvell International Ltd. | |
3103d44e | 17 | * Eric Miao <eric.miao@marvell.com> |
a5736e0b MH |
18 | * |
19 | * Licensed under the GPL-2 or later. | |
20 | */ | |
21 | ||
22 | #include <linux/kernel.h> | |
6b09274b | 23 | #include <linux/init.h> |
a5736e0b | 24 | #include <linux/platform_device.h> |
5a0e3ad6 | 25 | #include <linux/slab.h> |
a5736e0b MH |
26 | #include <linux/interrupt.h> |
27 | #include <linux/irq.h> | |
28 | #include <linux/err.h> | |
29 | #include <linux/i2c.h> | |
30 | ||
31 | #include <linux/mfd/adp5520.h> | |
32 | ||
33 | struct adp5520_chip { | |
34 | struct i2c_client *client; | |
35 | struct device *dev; | |
36 | struct mutex lock; | |
37 | struct blocking_notifier_head notifier_list; | |
38 | int irq; | |
39 | unsigned long id; | |
c6cc25fd | 40 | uint8_t mode; |
a5736e0b MH |
41 | }; |
42 | ||
43 | static int __adp5520_read(struct i2c_client *client, | |
44 | int reg, uint8_t *val) | |
45 | { | |
46 | int ret; | |
47 | ||
48 | ret = i2c_smbus_read_byte_data(client, reg); | |
49 | if (ret < 0) { | |
50 | dev_err(&client->dev, "failed reading at 0x%02x\n", reg); | |
51 | return ret; | |
52 | } | |
53 | ||
54 | *val = (uint8_t)ret; | |
55 | return 0; | |
56 | } | |
57 | ||
58 | static int __adp5520_write(struct i2c_client *client, | |
59 | int reg, uint8_t val) | |
60 | { | |
61 | int ret; | |
62 | ||
63 | ret = i2c_smbus_write_byte_data(client, reg, val); | |
64 | if (ret < 0) { | |
65 | dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n", | |
66 | val, reg); | |
67 | return ret; | |
68 | } | |
69 | return 0; | |
70 | } | |
71 | ||
4e8b7026 MB |
72 | static int __adp5520_ack_bits(struct i2c_client *client, int reg, |
73 | uint8_t bit_mask) | |
a5736e0b MH |
74 | { |
75 | struct adp5520_chip *chip = i2c_get_clientdata(client); | |
76 | uint8_t reg_val; | |
77 | int ret; | |
78 | ||
79 | mutex_lock(&chip->lock); | |
80 | ||
81 | ret = __adp5520_read(client, reg, ®_val); | |
82 | ||
83 | if (!ret) { | |
84 | reg_val |= bit_mask; | |
85 | ret = __adp5520_write(client, reg, reg_val); | |
86 | } | |
87 | ||
88 | mutex_unlock(&chip->lock); | |
89 | return ret; | |
90 | } | |
91 | ||
92 | int adp5520_write(struct device *dev, int reg, uint8_t val) | |
93 | { | |
94 | return __adp5520_write(to_i2c_client(dev), reg, val); | |
95 | } | |
96 | EXPORT_SYMBOL_GPL(adp5520_write); | |
97 | ||
98 | int adp5520_read(struct device *dev, int reg, uint8_t *val) | |
99 | { | |
100 | return __adp5520_read(to_i2c_client(dev), reg, val); | |
101 | } | |
102 | EXPORT_SYMBOL_GPL(adp5520_read); | |
103 | ||
104 | int adp5520_set_bits(struct device *dev, int reg, uint8_t bit_mask) | |
105 | { | |
106 | struct adp5520_chip *chip = dev_get_drvdata(dev); | |
107 | uint8_t reg_val; | |
108 | int ret; | |
109 | ||
110 | mutex_lock(&chip->lock); | |
111 | ||
112 | ret = __adp5520_read(chip->client, reg, ®_val); | |
113 | ||
890c98e3 | 114 | if (!ret && ((reg_val & bit_mask) != bit_mask)) { |
a5736e0b MH |
115 | reg_val |= bit_mask; |
116 | ret = __adp5520_write(chip->client, reg, reg_val); | |
117 | } | |
118 | ||
119 | mutex_unlock(&chip->lock); | |
120 | return ret; | |
121 | } | |
122 | EXPORT_SYMBOL_GPL(adp5520_set_bits); | |
123 | ||
124 | int adp5520_clr_bits(struct device *dev, int reg, uint8_t bit_mask) | |
125 | { | |
126 | struct adp5520_chip *chip = dev_get_drvdata(dev); | |
127 | uint8_t reg_val; | |
128 | int ret; | |
129 | ||
130 | mutex_lock(&chip->lock); | |
131 | ||
132 | ret = __adp5520_read(chip->client, reg, ®_val); | |
133 | ||
134 | if (!ret && (reg_val & bit_mask)) { | |
135 | reg_val &= ~bit_mask; | |
136 | ret = __adp5520_write(chip->client, reg, reg_val); | |
137 | } | |
138 | ||
139 | mutex_unlock(&chip->lock); | |
140 | return ret; | |
141 | } | |
142 | EXPORT_SYMBOL_GPL(adp5520_clr_bits); | |
143 | ||
144 | int adp5520_register_notifier(struct device *dev, struct notifier_block *nb, | |
145 | unsigned int events) | |
146 | { | |
147 | struct adp5520_chip *chip = dev_get_drvdata(dev); | |
148 | ||
149 | if (chip->irq) { | |
150 | adp5520_set_bits(chip->dev, ADP5520_INTERRUPT_ENABLE, | |
151 | events & (ADP5520_KP_IEN | ADP5520_KR_IEN | | |
152 | ADP5520_OVP_IEN | ADP5520_CMPR_IEN)); | |
153 | ||
154 | return blocking_notifier_chain_register(&chip->notifier_list, | |
155 | nb); | |
156 | } | |
157 | ||
158 | return -ENODEV; | |
159 | } | |
160 | EXPORT_SYMBOL_GPL(adp5520_register_notifier); | |
161 | ||
162 | int adp5520_unregister_notifier(struct device *dev, struct notifier_block *nb, | |
163 | unsigned int events) | |
164 | { | |
165 | struct adp5520_chip *chip = dev_get_drvdata(dev); | |
166 | ||
167 | adp5520_clr_bits(chip->dev, ADP5520_INTERRUPT_ENABLE, | |
168 | events & (ADP5520_KP_IEN | ADP5520_KR_IEN | | |
169 | ADP5520_OVP_IEN | ADP5520_CMPR_IEN)); | |
170 | ||
171 | return blocking_notifier_chain_unregister(&chip->notifier_list, nb); | |
172 | } | |
173 | EXPORT_SYMBOL_GPL(adp5520_unregister_notifier); | |
174 | ||
175 | static irqreturn_t adp5520_irq_thread(int irq, void *data) | |
176 | { | |
177 | struct adp5520_chip *chip = data; | |
178 | unsigned int events; | |
179 | uint8_t reg_val; | |
180 | int ret; | |
181 | ||
182 | ret = __adp5520_read(chip->client, ADP5520_MODE_STATUS, ®_val); | |
183 | if (ret) | |
184 | goto out; | |
185 | ||
186 | events = reg_val & (ADP5520_OVP_INT | ADP5520_CMPR_INT | | |
187 | ADP5520_GPI_INT | ADP5520_KR_INT | ADP5520_KP_INT); | |
188 | ||
189 | blocking_notifier_call_chain(&chip->notifier_list, events, NULL); | |
190 | /* ACK, Sticky bits are W1C */ | |
191 | __adp5520_ack_bits(chip->client, ADP5520_MODE_STATUS, events); | |
192 | ||
193 | out: | |
194 | return IRQ_HANDLED; | |
195 | } | |
196 | ||
197 | static int __remove_subdev(struct device *dev, void *unused) | |
198 | { | |
199 | platform_device_unregister(to_platform_device(dev)); | |
200 | return 0; | |
201 | } | |
202 | ||
203 | static int adp5520_remove_subdevs(struct adp5520_chip *chip) | |
204 | { | |
205 | return device_for_each_child(chip->dev, NULL, __remove_subdev); | |
206 | } | |
207 | ||
f791be49 | 208 | static int adp5520_probe(struct i2c_client *client, |
a5736e0b MH |
209 | const struct i2c_device_id *id) |
210 | { | |
334a41ce | 211 | struct adp5520_platform_data *pdata = dev_get_platdata(&client->dev); |
a5736e0b MH |
212 | struct platform_device *pdev; |
213 | struct adp5520_chip *chip; | |
214 | int ret; | |
215 | ||
216 | if (!i2c_check_functionality(client->adapter, | |
217 | I2C_FUNC_SMBUS_BYTE_DATA)) { | |
218 | dev_err(&client->dev, "SMBUS Word Data not Supported\n"); | |
219 | return -EIO; | |
220 | } | |
221 | ||
222 | if (pdata == NULL) { | |
223 | dev_err(&client->dev, "missing platform data\n"); | |
224 | return -ENODEV; | |
225 | } | |
226 | ||
76ba0b89 | 227 | chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); |
a5736e0b MH |
228 | if (!chip) |
229 | return -ENOMEM; | |
230 | ||
231 | i2c_set_clientdata(client, chip); | |
232 | chip->client = client; | |
233 | ||
234 | chip->dev = &client->dev; | |
235 | chip->irq = client->irq; | |
236 | chip->id = id->driver_data; | |
237 | mutex_init(&chip->lock); | |
238 | ||
239 | if (chip->irq) { | |
240 | BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list); | |
241 | ||
242 | ret = request_threaded_irq(chip->irq, NULL, adp5520_irq_thread, | |
243 | IRQF_TRIGGER_LOW | IRQF_ONESHOT, | |
244 | "adp5520", chip); | |
245 | if (ret) { | |
246 | dev_err(&client->dev, "failed to request irq %d\n", | |
247 | chip->irq); | |
76ba0b89 | 248 | return ret; |
a5736e0b MH |
249 | } |
250 | } | |
251 | ||
252 | ret = adp5520_write(chip->dev, ADP5520_MODE_STATUS, ADP5520_nSTNBY); | |
253 | if (ret) { | |
254 | dev_err(&client->dev, "failed to write\n"); | |
255 | goto out_free_irq; | |
256 | } | |
257 | ||
258 | if (pdata->keys) { | |
259 | pdev = platform_device_register_data(chip->dev, "adp5520-keys", | |
260 | chip->id, pdata->keys, sizeof(*pdata->keys)); | |
261 | if (IS_ERR(pdev)) { | |
262 | ret = PTR_ERR(pdev); | |
263 | goto out_remove_subdevs; | |
264 | } | |
265 | } | |
266 | ||
267 | if (pdata->gpio) { | |
268 | pdev = platform_device_register_data(chip->dev, "adp5520-gpio", | |
269 | chip->id, pdata->gpio, sizeof(*pdata->gpio)); | |
270 | if (IS_ERR(pdev)) { | |
271 | ret = PTR_ERR(pdev); | |
272 | goto out_remove_subdevs; | |
273 | } | |
274 | } | |
275 | ||
276 | if (pdata->leds) { | |
277 | pdev = platform_device_register_data(chip->dev, "adp5520-led", | |
278 | chip->id, pdata->leds, sizeof(*pdata->leds)); | |
279 | if (IS_ERR(pdev)) { | |
280 | ret = PTR_ERR(pdev); | |
281 | goto out_remove_subdevs; | |
282 | } | |
283 | } | |
284 | ||
285 | if (pdata->backlight) { | |
286 | pdev = platform_device_register_data(chip->dev, | |
287 | "adp5520-backlight", | |
288 | chip->id, | |
289 | pdata->backlight, | |
290 | sizeof(*pdata->backlight)); | |
291 | if (IS_ERR(pdev)) { | |
292 | ret = PTR_ERR(pdev); | |
293 | goto out_remove_subdevs; | |
294 | } | |
295 | } | |
296 | ||
297 | return 0; | |
298 | ||
299 | out_remove_subdevs: | |
300 | adp5520_remove_subdevs(chip); | |
301 | ||
302 | out_free_irq: | |
303 | if (chip->irq) | |
304 | free_irq(chip->irq, chip); | |
305 | ||
a5736e0b MH |
306 | return ret; |
307 | } | |
308 | ||
938848e7 | 309 | #ifdef CONFIG_PM_SLEEP |
dc781454 | 310 | static int adp5520_suspend(struct device *dev) |
a5736e0b | 311 | { |
dc781454 | 312 | struct i2c_client *client = to_i2c_client(dev); |
a5736e0b MH |
313 | struct adp5520_chip *chip = dev_get_drvdata(&client->dev); |
314 | ||
c6cc25fd LPC |
315 | adp5520_read(chip->dev, ADP5520_MODE_STATUS, &chip->mode); |
316 | /* All other bits are W1C */ | |
317 | chip->mode &= ADP5520_BL_EN | ADP5520_DIM_EN | ADP5520_nSTNBY; | |
318 | adp5520_write(chip->dev, ADP5520_MODE_STATUS, 0); | |
a5736e0b MH |
319 | return 0; |
320 | } | |
321 | ||
dc781454 | 322 | static int adp5520_resume(struct device *dev) |
a5736e0b | 323 | { |
dc781454 | 324 | struct i2c_client *client = to_i2c_client(dev); |
a5736e0b MH |
325 | struct adp5520_chip *chip = dev_get_drvdata(&client->dev); |
326 | ||
c6cc25fd | 327 | adp5520_write(chip->dev, ADP5520_MODE_STATUS, chip->mode); |
a5736e0b MH |
328 | return 0; |
329 | } | |
a5736e0b MH |
330 | #endif |
331 | ||
dc781454 MB |
332 | static SIMPLE_DEV_PM_OPS(adp5520_pm, adp5520_suspend, adp5520_resume); |
333 | ||
a5736e0b MH |
334 | static const struct i2c_device_id adp5520_id[] = { |
335 | { "pmic-adp5520", ID_ADP5520 }, | |
336 | { "pmic-adp5501", ID_ADP5501 }, | |
337 | { } | |
338 | }; | |
a5736e0b MH |
339 | |
340 | static struct i2c_driver adp5520_driver = { | |
341 | .driver = { | |
6b09274b PG |
342 | .name = "adp5520", |
343 | .pm = &adp5520_pm, | |
344 | .suppress_bind_attrs = true, | |
a5736e0b MH |
345 | }, |
346 | .probe = adp5520_probe, | |
3103d44e | 347 | .id_table = adp5520_id, |
a5736e0b | 348 | }; |
6b09274b | 349 | builtin_i2c_driver(adp5520_driver); |