aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/mfd
diff options
context:
space:
mode:
authorJin Park <jinyoungp@nvidia.com>2011-07-04 13:48:12 -0400
committerSamuel Ortiz <sameo@linux.intel.com>2011-07-31 17:28:26 -0400
commit09d6292befba8c6319d9471803149573ea6ed170 (patch)
tree1de1bda81b4ca3844e80a6041e0ccefbc5db1628 /drivers/mfd
parent7785bf11f312624101462d7dc840e27344899873 (diff)
mfd: Add AAT2870 mfd driver
Add mfd core driver for AnalogicTech AAT2870. The AAT2870 is communication through I2C and contains backlight and regulator components. Signed-off-by: Jin Park <jinyoungp@nvidia.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Diffstat (limited to 'drivers/mfd')
-rw-r--r--drivers/mfd/Kconfig10
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/aat2870-core.c535
3 files changed, 546 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c26136443c05..21574bdf485f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -760,6 +760,16 @@ config MFD_PM8XXX_IRQ
760config TPS65911_COMPARATOR 760config TPS65911_COMPARATOR
761 tristate 761 tristate
762 762
763config MFD_AAT2870_CORE
764 bool "Support for the AnalogicTech AAT2870"
765 select MFD_CORE
766 depends on I2C=y && GPIOLIB
767 help
768 If you say yes here you get support for the AAT2870.
769 This driver provides common support for accessing the device,
770 additional drivers must be enabled in order to use the
771 functionality of the device.
772
763endif # MFD_SUPPORT 773endif # MFD_SUPPORT
764 774
765menu "Multimedia Capabilities Port drivers" 775menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index ca1867db3db8..c58020303d18 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -101,3 +101,4 @@ obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o
101obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o 101obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o
102obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o 102obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o
103obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o 103obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o
104obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o
diff --git a/drivers/mfd/aat2870-core.c b/drivers/mfd/aat2870-core.c
new file mode 100644
index 000000000000..345dc658ef06
--- /dev/null
+++ b/drivers/mfd/aat2870-core.c
@@ -0,0 +1,535 @@
1/*
2 * linux/drivers/mfd/aat2870-core.c
3 *
4 * Copyright (c) 2011, NVIDIA Corporation.
5 * Author: Jin Park <jinyoungp@nvidia.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * version 2 as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA
20 */
21
22#include <linux/kernel.h>
23#include <linux/module.h>
24#include <linux/init.h>
25#include <linux/debugfs.h>
26#include <linux/slab.h>
27#include <linux/uaccess.h>
28#include <linux/i2c.h>
29#include <linux/delay.h>
30#include <linux/gpio.h>
31#include <linux/mfd/core.h>
32#include <linux/mfd/aat2870.h>
33#include <linux/regulator/machine.h>
34
35static struct aat2870_register aat2870_regs[AAT2870_REG_NUM] = {
36 /* readable, writeable, value */
37 { 0, 1, 0x00 }, /* 0x00 AAT2870_BL_CH_EN */
38 { 0, 1, 0x16 }, /* 0x01 AAT2870_BLM */
39 { 0, 1, 0x16 }, /* 0x02 AAT2870_BLS */
40 { 0, 1, 0x56 }, /* 0x03 AAT2870_BL1 */
41 { 0, 1, 0x56 }, /* 0x04 AAT2870_BL2 */
42 { 0, 1, 0x56 }, /* 0x05 AAT2870_BL3 */
43 { 0, 1, 0x56 }, /* 0x06 AAT2870_BL4 */
44 { 0, 1, 0x56 }, /* 0x07 AAT2870_BL5 */
45 { 0, 1, 0x56 }, /* 0x08 AAT2870_BL6 */
46 { 0, 1, 0x56 }, /* 0x09 AAT2870_BL7 */
47 { 0, 1, 0x56 }, /* 0x0A AAT2870_BL8 */
48 { 0, 1, 0x00 }, /* 0x0B AAT2870_FLR */
49 { 0, 1, 0x03 }, /* 0x0C AAT2870_FM */
50 { 0, 1, 0x03 }, /* 0x0D AAT2870_FS */
51 { 0, 1, 0x10 }, /* 0x0E AAT2870_ALS_CFG0 */
52 { 0, 1, 0x06 }, /* 0x0F AAT2870_ALS_CFG1 */
53 { 0, 1, 0x00 }, /* 0x10 AAT2870_ALS_CFG2 */
54 { 1, 0, 0x00 }, /* 0x11 AAT2870_AMB */
55 { 0, 1, 0x00 }, /* 0x12 AAT2870_ALS0 */
56 { 0, 1, 0x00 }, /* 0x13 AAT2870_ALS1 */
57 { 0, 1, 0x00 }, /* 0x14 AAT2870_ALS2 */
58 { 0, 1, 0x00 }, /* 0x15 AAT2870_ALS3 */
59 { 0, 1, 0x00 }, /* 0x16 AAT2870_ALS4 */
60 { 0, 1, 0x00 }, /* 0x17 AAT2870_ALS5 */
61 { 0, 1, 0x00 }, /* 0x18 AAT2870_ALS6 */
62 { 0, 1, 0x00 }, /* 0x19 AAT2870_ALS7 */
63 { 0, 1, 0x00 }, /* 0x1A AAT2870_ALS8 */
64 { 0, 1, 0x00 }, /* 0x1B AAT2870_ALS9 */
65 { 0, 1, 0x00 }, /* 0x1C AAT2870_ALSA */
66 { 0, 1, 0x00 }, /* 0x1D AAT2870_ALSB */
67 { 0, 1, 0x00 }, /* 0x1E AAT2870_ALSC */
68 { 0, 1, 0x00 }, /* 0x1F AAT2870_ALSD */
69 { 0, 1, 0x00 }, /* 0x20 AAT2870_ALSE */
70 { 0, 1, 0x00 }, /* 0x21 AAT2870_ALSF */
71 { 0, 1, 0x00 }, /* 0x22 AAT2870_SUB_SET */
72 { 0, 1, 0x00 }, /* 0x23 AAT2870_SUB_CTRL */
73 { 0, 1, 0x00 }, /* 0x24 AAT2870_LDO_AB */
74 { 0, 1, 0x00 }, /* 0x25 AAT2870_LDO_CD */
75 { 0, 1, 0x00 }, /* 0x26 AAT2870_LDO_EN */
76};
77
78static struct mfd_cell aat2870_devs[] = {
79 {
80 .name = "aat2870-backlight",
81 .id = AAT2870_ID_BL,
82 .pdata_size = sizeof(struct aat2870_bl_platform_data),
83 },
84 {
85 .name = "aat2870-regulator",
86 .id = AAT2870_ID_LDOA,
87 .pdata_size = sizeof(struct regulator_init_data),
88 },
89 {
90 .name = "aat2870-regulator",
91 .id = AAT2870_ID_LDOB,
92 .pdata_size = sizeof(struct regulator_init_data),
93 },
94 {
95 .name = "aat2870-regulator",
96 .id = AAT2870_ID_LDOC,
97 .pdata_size = sizeof(struct regulator_init_data),
98 },
99 {
100 .name = "aat2870-regulator",
101 .id = AAT2870_ID_LDOD,
102 .pdata_size = sizeof(struct regulator_init_data),
103 },
104};
105
106static int __aat2870_read(struct aat2870_data *aat2870, u8 addr, u8 *val)
107{
108 int ret;
109
110 if (addr >= AAT2870_REG_NUM) {
111 dev_err(aat2870->dev, "Invalid address, 0x%02x\n", addr);
112 return -EINVAL;
113 }
114
115 if (!aat2870->reg_cache[addr].readable) {
116 *val = aat2870->reg_cache[addr].value;
117 goto out;
118 }
119
120 ret = i2c_master_send(aat2870->client, &addr, 1);
121 if (ret < 0)
122 return ret;
123 if (ret != 1)
124 return -EIO;
125
126 ret = i2c_master_recv(aat2870->client, val, 1);
127 if (ret < 0)
128 return ret;
129 if (ret != 1)
130 return -EIO;
131
132out:
133 dev_dbg(aat2870->dev, "read: addr=0x%02x, val=0x%02x\n", addr, *val);
134 return 0;
135}
136
137static int __aat2870_write(struct aat2870_data *aat2870, u8 addr, u8 val)
138{
139 u8 msg[2];
140 int ret;
141
142 if (addr >= AAT2870_REG_NUM) {
143 dev_err(aat2870->dev, "Invalid address, 0x%02x\n", addr);
144 return -EINVAL;
145 }
146
147 if (!aat2870->reg_cache[addr].writeable) {
148 dev_err(aat2870->dev, "Address 0x%02x is not writeable\n",
149 addr);
150 return -EINVAL;
151 }
152
153 msg[0] = addr;
154 msg[1] = val;
155 ret = i2c_master_send(aat2870->client, msg, 2);
156 if (ret < 0)
157 return ret;
158 if (ret != 2)
159 return -EIO;
160
161 aat2870->reg_cache[addr].value = val;
162
163 dev_dbg(aat2870->dev, "write: addr=0x%02x, val=0x%02x\n", addr, val);
164 return 0;
165}
166
167static int aat2870_read(struct aat2870_data *aat2870, u8 addr, u8 *val)
168{
169 int ret;
170
171 mutex_lock(&aat2870->io_lock);
172 ret = __aat2870_read(aat2870, addr, val);
173 mutex_unlock(&aat2870->io_lock);
174
175 return ret;
176}
177
178static int aat2870_write(struct aat2870_data *aat2870, u8 addr, u8 val)
179{
180 int ret;
181
182 mutex_lock(&aat2870->io_lock);
183 ret = __aat2870_write(aat2870, addr, val);
184 mutex_unlock(&aat2870->io_lock);
185
186 return ret;
187}
188
189static int aat2870_update(struct aat2870_data *aat2870, u8 addr, u8 mask,
190 u8 val)
191{
192 int change;
193 u8 old_val, new_val;
194 int ret;
195
196 mutex_lock(&aat2870->io_lock);
197
198 ret = __aat2870_read(aat2870, addr, &old_val);
199 if (ret)
200 goto out_unlock;
201
202 new_val = (old_val & ~mask) | (val & mask);
203 change = old_val != new_val;
204 if (change)
205 ret = __aat2870_write(aat2870, addr, new_val);
206
207out_unlock:
208 mutex_unlock(&aat2870->io_lock);
209
210 return ret;
211}
212
213static inline void aat2870_enable(struct aat2870_data *aat2870)
214{
215 if (aat2870->en_pin >= 0)
216 gpio_set_value(aat2870->en_pin, 1);
217
218 aat2870->is_enable = 1;
219}
220
221static inline void aat2870_disable(struct aat2870_data *aat2870)
222{
223 if (aat2870->en_pin >= 0)
224 gpio_set_value(aat2870->en_pin, 0);
225
226 aat2870->is_enable = 0;
227}
228
229#ifdef CONFIG_DEBUG_FS
230static ssize_t aat2870_dump_reg(struct aat2870_data *aat2870, char *buf)
231{
232 u8 addr, val;
233 ssize_t count = 0;
234 int ret;
235
236 count += sprintf(buf, "aat2870 registers\n");
237 for (addr = 0; addr < AAT2870_REG_NUM; addr++) {
238 count += sprintf(buf + count, "0x%02x: ", addr);
239 if (count >= PAGE_SIZE - 1)
240 break;
241
242 ret = aat2870->read(aat2870, addr, &val);
243 if (ret == 0)
244 count += snprintf(buf + count, PAGE_SIZE - count,
245 "0x%02x", val);
246 else
247 count += snprintf(buf + count, PAGE_SIZE - count,
248 "<read fail: %d>", ret);
249
250 if (count >= PAGE_SIZE - 1)
251 break;
252
253 count += snprintf(buf + count, PAGE_SIZE - count, "\n");
254 if (count >= PAGE_SIZE - 1)
255 break;
256 }
257
258 /* Truncate count; min() would cause a warning */
259 if (count >= PAGE_SIZE)
260 count = PAGE_SIZE - 1;
261
262 return count;
263}
264
265static int aat2870_reg_open_file(struct inode *inode, struct file *file)
266{
267 file->private_data = inode->i_private;
268
269 return 0;
270}
271
272static ssize_t aat2870_reg_read_file(struct file *file, char __user *user_buf,
273 size_t count, loff_t *ppos)
274{
275 struct aat2870_data *aat2870 = file->private_data;
276 char *buf;
277 ssize_t ret;
278
279 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
280 if (!buf)
281 return -ENOMEM;
282
283 ret = aat2870_dump_reg(aat2870, buf);
284 if (ret >= 0)
285 ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
286
287 kfree(buf);
288
289 return ret;
290}
291
292static ssize_t aat2870_reg_write_file(struct file *file,
293 const char __user *user_buf, size_t count,
294 loff_t *ppos)
295{
296 struct aat2870_data *aat2870 = file->private_data;
297 char buf[32];
298 int buf_size;
299 char *start = buf;
300 unsigned long addr, val;
301 int ret;
302
303 buf_size = min(count, (sizeof(buf)-1));
304 if (copy_from_user(buf, user_buf, buf_size)) {
305 dev_err(aat2870->dev, "Failed to copy from user\n");
306 return -EFAULT;
307 }
308 buf[buf_size] = 0;
309
310 while (*start == ' ')
311 start++;
312
313 addr = simple_strtoul(start, &start, 16);
314 if (addr >= AAT2870_REG_NUM) {
315 dev_err(aat2870->dev, "Invalid address, 0x%lx\n", addr);
316 return -EINVAL;
317 }
318
319 while (*start == ' ')
320 start++;
321
322 if (strict_strtoul(start, 16, &val))
323 return -EINVAL;
324
325 ret = aat2870->write(aat2870, (u8)addr, (u8)val);
326 if (ret)
327 return ret;
328
329 return buf_size;
330}
331
332static const struct file_operations aat2870_reg_fops = {
333 .open = aat2870_reg_open_file,
334 .read = aat2870_reg_read_file,
335 .write = aat2870_reg_write_file,
336};
337
338static void aat2870_init_debugfs(struct aat2870_data *aat2870)
339{
340 aat2870->dentry_root = debugfs_create_dir("aat2870", NULL);
341 if (!aat2870->dentry_root) {
342 dev_warn(aat2870->dev,
343 "Failed to create debugfs root directory\n");
344 return;
345 }
346
347 aat2870->dentry_reg = debugfs_create_file("regs", 0644,
348 aat2870->dentry_root,
349 aat2870, &aat2870_reg_fops);
350 if (!aat2870->dentry_reg)
351 dev_warn(aat2870->dev,
352 "Failed to create debugfs register file\n");
353}
354
355static void aat2870_uninit_debugfs(struct aat2870_data *aat2870)
356{
357 debugfs_remove_recursive(aat2870->dentry_root);
358}
359#else
360static inline void aat2870_init_debugfs(struct aat2870_data *aat2870)
361{
362}
363
364static inline void aat2870_uninit_debugfs(struct aat2870_data *aat2870)
365{
366}
367#endif /* CONFIG_DEBUG_FS */
368
369static int aat2870_i2c_probe(struct i2c_client *client,
370 const struct i2c_device_id *id)
371{
372 struct aat2870_platform_data *pdata = client->dev.platform_data;
373 struct aat2870_data *aat2870;
374 int i, j;
375 int ret = 0;
376
377 aat2870 = kzalloc(sizeof(struct aat2870_data), GFP_KERNEL);
378 if (!aat2870) {
379 dev_err(&client->dev,
380 "Failed to allocate memory for aat2870\n");
381 ret = -ENOMEM;
382 goto out;
383 }
384
385 aat2870->dev = &client->dev;
386 dev_set_drvdata(aat2870->dev, aat2870);
387
388 aat2870->client = client;
389 i2c_set_clientdata(client, aat2870);
390
391 aat2870->reg_cache = aat2870_regs;
392
393 if (pdata->en_pin < 0)
394 aat2870->en_pin = -1;
395 else
396 aat2870->en_pin = pdata->en_pin;
397
398 aat2870->init = pdata->init;
399 aat2870->uninit = pdata->uninit;
400 aat2870->read = aat2870_read;
401 aat2870->write = aat2870_write;
402 aat2870->update = aat2870_update;
403
404 mutex_init(&aat2870->io_lock);
405
406 if (aat2870->init)
407 aat2870->init(aat2870);
408
409 if (aat2870->en_pin >= 0) {
410 ret = gpio_request(aat2870->en_pin, "aat2870-en");
411 if (ret < 0) {
412 dev_err(&client->dev,
413 "Failed to request GPIO %d\n", aat2870->en_pin);
414 goto out_kfree;
415 }
416 gpio_direction_output(aat2870->en_pin, 1);
417 }
418
419 aat2870_enable(aat2870);
420
421 for (i = 0; i < pdata->num_subdevs; i++) {
422 for (j = 0; j < ARRAY_SIZE(aat2870_devs); j++) {
423 if ((pdata->subdevs[i].id == aat2870_devs[j].id) &&
424 !strcmp(pdata->subdevs[i].name,
425 aat2870_devs[j].name)) {
426 aat2870_devs[j].platform_data =
427 pdata->subdevs[i].platform_data;
428 break;
429 }
430 }
431 }
432
433 ret = mfd_add_devices(aat2870->dev, 0, aat2870_devs,
434 ARRAY_SIZE(aat2870_devs), NULL, 0);
435 if (ret != 0) {
436 dev_err(aat2870->dev, "Failed to add subdev: %d\n", ret);
437 goto out_disable;
438 }
439
440 aat2870_init_debugfs(aat2870);
441
442 return 0;
443
444out_disable:
445 aat2870_disable(aat2870);
446 if (aat2870->en_pin >= 0)
447 gpio_free(aat2870->en_pin);
448out_kfree:
449 kfree(aat2870);
450out:
451 return ret;
452}
453
454static int aat2870_i2c_remove(struct i2c_client *client)
455{
456 struct aat2870_data *aat2870 = i2c_get_clientdata(client);
457
458 aat2870_uninit_debugfs(aat2870);
459
460 mfd_remove_devices(aat2870->dev);
461 aat2870_disable(aat2870);
462 if (aat2870->en_pin >= 0)
463 gpio_free(aat2870->en_pin);
464 if (aat2870->uninit)
465 aat2870->uninit(aat2870);
466 kfree(aat2870);
467
468 return 0;
469}
470
471#ifdef CONFIG_PM
472static int aat2870_i2c_suspend(struct i2c_client *client, pm_message_t state)
473{
474 struct aat2870_data *aat2870 = i2c_get_clientdata(client);
475
476 aat2870_disable(aat2870);
477
478 return 0;
479}
480
481static int aat2870_i2c_resume(struct i2c_client *client)
482{
483 struct aat2870_data *aat2870 = i2c_get_clientdata(client);
484 struct aat2870_register *reg = NULL;
485 int i;
486
487 aat2870_enable(aat2870);
488
489 /* restore registers */
490 for (i = 0; i < AAT2870_REG_NUM; i++) {
491 reg = &aat2870->reg_cache[i];
492 if (reg->writeable)
493 aat2870->write(aat2870, i, reg->value);
494 }
495
496 return 0;
497}
498#else
499#define aat2870_i2c_suspend NULL
500#define aat2870_i2c_resume NULL
501#endif /* CONFIG_PM */
502
503static struct i2c_device_id aat2870_i2c_id_table[] = {
504 { "aat2870", 0 },
505 { }
506};
507MODULE_DEVICE_TABLE(i2c, aat2870_i2c_id_table);
508
509static struct i2c_driver aat2870_i2c_driver = {
510 .driver = {
511 .name = "aat2870",
512 .owner = THIS_MODULE,
513 },
514 .probe = aat2870_i2c_probe,
515 .remove = aat2870_i2c_remove,
516 .suspend = aat2870_i2c_suspend,
517 .resume = aat2870_i2c_resume,
518 .id_table = aat2870_i2c_id_table,
519};
520
521static int __init aat2870_init(void)
522{
523 return i2c_add_driver(&aat2870_i2c_driver);
524}
525subsys_initcall(aat2870_init);
526
527static void __exit aat2870_exit(void)
528{
529 i2c_del_driver(&aat2870_i2c_driver);
530}
531module_exit(aat2870_exit);
532
533MODULE_DESCRIPTION("Core support for the AnalogicTech AAT2870");
534MODULE_LICENSE("GPL");
535MODULE_AUTHOR("Jin Park <jinyoungp@nvidia.com>");