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