aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mfd/Kconfig10
-rw-r--r--drivers/mfd/Makefile2
-rw-r--r--drivers/mfd/lp8788-irq.c198
-rw-r--r--drivers/mfd/lp8788.c245
4 files changed, 455 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index b9282cafa978..84d6ea2d74f4 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -440,6 +440,16 @@ config PMIC_ADP5520
440 individual components like LCD backlight, LEDs, GPIOs and Kepad 440 individual components like LCD backlight, LEDs, GPIOs and Kepad
441 under the corresponding menus. 441 under the corresponding menus.
442 442
443config MFD_LP8788
444 bool "Texas Instruments LP8788 Power Management Unit Driver"
445 depends on I2C=y
446 select MFD_CORE
447 select REGMAP_I2C
448 select IRQ_DOMAIN
449 help
450 TI LP8788 PMU supports regulators, battery charger, RTC,
451 ADC, backlight driver and current sinks.
452
443config MFD_MAX77686 453config MFD_MAX77686
444 bool "Maxim Semiconductor MAX77686 PMIC Support" 454 bool "Maxim Semiconductor MAX77686 PMIC Support"
445 depends on I2C=y && GENERIC_HARDIRQS 455 depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index d16bc0220e1f..083acf97af26 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -89,6 +89,8 @@ obj-$(CONFIG_PMIC_DA9052) += da9052-core.o
89obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o 89obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o
90obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o 90obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
91 91
92obj-$(CONFIG_MFD_LP8788) += lp8788.o lp8788-irq.o
93
92obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o 94obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o
93obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o 95obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o
94obj-$(CONFIG_MFD_MAX8907) += max8907.o 96obj-$(CONFIG_MFD_MAX8907) += max8907.o
diff --git a/drivers/mfd/lp8788-irq.c b/drivers/mfd/lp8788-irq.c
new file mode 100644
index 000000000000..c84ded5f8ece
--- /dev/null
+++ b/drivers/mfd/lp8788-irq.c
@@ -0,0 +1,198 @@
1/*
2 * TI LP8788 MFD - interrupt handler
3 *
4 * Copyright 2012 Texas Instruments
5 *
6 * Author: Milo(Woogyom) Kim <milo.kim@ti.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 */
13
14#include <linux/delay.h>
15#include <linux/err.h>
16#include <linux/interrupt.h>
17#include <linux/irq.h>
18#include <linux/irqdomain.h>
19#include <linux/device.h>
20#include <linux/mfd/lp8788.h>
21#include <linux/module.h>
22#include <linux/slab.h>
23
24/* register address */
25#define LP8788_INT_1 0x00
26#define LP8788_INTEN_1 0x03
27
28#define BASE_INTEN_ADDR LP8788_INTEN_1
29#define SIZE_REG 8
30#define NUM_REGS 3
31
32/*
33 * struct lp8788_irq_data
34 * @lp : used for accessing to lp8788 registers
35 * @irq_lock : mutex for enabling/disabling the interrupt
36 * @domain : IRQ domain for handling nested interrupt
37 * @enabled : status of enabled interrupt
38 */
39struct lp8788_irq_data {
40 struct lp8788 *lp;
41 struct mutex irq_lock;
42 struct irq_domain *domain;
43 int enabled[LP8788_INT_MAX];
44};
45
46static inline u8 _irq_to_addr(enum lp8788_int_id id)
47{
48 return id / SIZE_REG;
49}
50
51static inline u8 _irq_to_enable_addr(enum lp8788_int_id id)
52{
53 return _irq_to_addr(id) + BASE_INTEN_ADDR;
54}
55
56static inline u8 _irq_to_mask(enum lp8788_int_id id)
57{
58 return 1 << (id % SIZE_REG);
59}
60
61static inline u8 _irq_to_val(enum lp8788_int_id id, int enable)
62{
63 return enable << (id % SIZE_REG);
64}
65
66static void lp8788_irq_enable(struct irq_data *data)
67{
68 struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
69 irqd->enabled[data->hwirq] = 1;
70}
71
72static void lp8788_irq_disable(struct irq_data *data)
73{
74 struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
75 irqd->enabled[data->hwirq] = 0;
76}
77
78static void lp8788_irq_bus_lock(struct irq_data *data)
79{
80 struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
81
82 mutex_lock(&irqd->irq_lock);
83}
84
85static void lp8788_irq_bus_sync_unlock(struct irq_data *data)
86{
87 struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
88 enum lp8788_int_id irq = data->hwirq;
89 u8 addr, mask, val;
90
91 addr = _irq_to_enable_addr(irq);
92 mask = _irq_to_mask(irq);
93 val = _irq_to_val(irq, irqd->enabled[irq]);
94
95 lp8788_update_bits(irqd->lp, addr, mask, val);
96
97 mutex_unlock(&irqd->irq_lock);
98}
99
100static struct irq_chip lp8788_irq_chip = {
101 .name = "lp8788",
102 .irq_enable = lp8788_irq_enable,
103 .irq_disable = lp8788_irq_disable,
104 .irq_bus_lock = lp8788_irq_bus_lock,
105 .irq_bus_sync_unlock = lp8788_irq_bus_sync_unlock,
106};
107
108static irqreturn_t lp8788_irq_handler(int irq, void *ptr)
109{
110 struct lp8788_irq_data *irqd = ptr;
111 struct lp8788 *lp = irqd->lp;
112 u8 status[NUM_REGS], addr, mask;
113 bool handled;
114 int i;
115
116 if (lp8788_read_multi_bytes(lp, LP8788_INT_1, status, NUM_REGS))
117 return IRQ_NONE;
118
119 for (i = 0 ; i < LP8788_INT_MAX ; i++) {
120 addr = _irq_to_addr(i);
121 mask = _irq_to_mask(i);
122
123 /* reporting only if the irq is enabled */
124 if (status[addr] & mask) {
125 handle_nested_irq(irq_find_mapping(irqd->domain, i));
126 handled = true;
127 }
128 }
129
130 return handled ? IRQ_HANDLED : IRQ_NONE;
131}
132
133static int lp8788_irq_map(struct irq_domain *d, unsigned int virq,
134 irq_hw_number_t hwirq)
135{
136 struct lp8788_irq_data *irqd = d->host_data;
137 struct irq_chip *chip = &lp8788_irq_chip;
138
139 irq_set_chip_data(virq, irqd);
140 irq_set_chip_and_handler(virq, chip, handle_edge_irq);
141 irq_set_nested_thread(virq, 1);
142
143#ifdef CONFIG_ARM
144 set_irq_flags(virq, IRQF_VALID);
145#else
146 irq_set_noprobe(virq);
147#endif
148
149 return 0;
150}
151
152static struct irq_domain_ops lp8788_domain_ops = {
153 .map = lp8788_irq_map,
154};
155
156int lp8788_irq_init(struct lp8788 *lp, int irq)
157{
158 struct lp8788_irq_data *irqd;
159 int ret;
160
161 if (irq <= 0) {
162 dev_warn(lp->dev, "invalid irq number: %d\n", irq);
163 return 0;
164 }
165
166 irqd = devm_kzalloc(lp->dev, sizeof(*irqd), GFP_KERNEL);
167 if (!irqd)
168 return -ENOMEM;
169
170 irqd->lp = lp;
171 irqd->domain = irq_domain_add_linear(lp->dev->of_node, LP8788_INT_MAX,
172 &lp8788_domain_ops, irqd);
173 if (!irqd->domain) {
174 dev_err(lp->dev, "failed to add irq domain err\n");
175 return -EINVAL;
176 }
177
178 lp->irqdm = irqd->domain;
179 mutex_init(&irqd->irq_lock);
180
181 ret = request_threaded_irq(irq, NULL, lp8788_irq_handler,
182 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
183 "lp8788-irq", irqd);
184 if (ret) {
185 dev_err(lp->dev, "failed to create a thread for IRQ_N\n");
186 return ret;
187 }
188
189 lp->irq = irq;
190
191 return 0;
192}
193
194void lp8788_irq_exit(struct lp8788 *lp)
195{
196 if (lp->irq)
197 free_irq(lp->irq, lp->irqdm);
198}
diff --git a/drivers/mfd/lp8788.c b/drivers/mfd/lp8788.c
new file mode 100644
index 000000000000..3e94a699833c
--- /dev/null
+++ b/drivers/mfd/lp8788.c
@@ -0,0 +1,245 @@
1/*
2 * TI LP8788 MFD - core interface
3 *
4 * Copyright 2012 Texas Instruments
5 *
6 * Author: Milo(Woogyom) Kim <milo.kim@ti.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 */
13
14#include <linux/err.h>
15#include <linux/i2c.h>
16#include <linux/mfd/core.h>
17#include <linux/mfd/lp8788.h>
18#include <linux/module.h>
19#include <linux/slab.h>
20
21#define MAX_LP8788_REGISTERS 0xA2
22
23#define MFD_DEV_SIMPLE(_name) \
24{ \
25 .name = LP8788_DEV_##_name, \
26}
27
28#define MFD_DEV_WITH_ID(_name, _id) \
29{ \
30 .name = LP8788_DEV_##_name, \
31 .id = _id, \
32}
33
34#define MFD_DEV_WITH_RESOURCE(_name, _resource, num_resource) \
35{ \
36 .name = LP8788_DEV_##_name, \
37 .resources = _resource, \
38 .num_resources = num_resource, \
39}
40
41static struct resource chg_irqs[] = {
42 /* Charger Interrupts */
43 {
44 .start = LP8788_INT_CHG_INPUT_STATE,
45 .end = LP8788_INT_PRECHG_TIMEOUT,
46 .name = LP8788_CHG_IRQ,
47 .flags = IORESOURCE_IRQ,
48 },
49 /* Power Routing Switch Interrupts */
50 {
51 .start = LP8788_INT_ENTER_SYS_SUPPORT,
52 .end = LP8788_INT_EXIT_SYS_SUPPORT,
53 .name = LP8788_PRSW_IRQ,
54 .flags = IORESOURCE_IRQ,
55 },
56 /* Battery Interrupts */
57 {
58 .start = LP8788_INT_BATT_LOW,
59 .end = LP8788_INT_NO_BATT,
60 .name = LP8788_BATT_IRQ,
61 .flags = IORESOURCE_IRQ,
62 },
63};
64
65static struct resource rtc_irqs[] = {
66 {
67 .start = LP8788_INT_RTC_ALARM1,
68 .end = LP8788_INT_RTC_ALARM2,
69 .name = LP8788_ALM_IRQ,
70 .flags = IORESOURCE_IRQ,
71 },
72};
73
74static struct mfd_cell lp8788_devs[] = {
75 /* 4 bucks */
76 MFD_DEV_WITH_ID(BUCK, 1),
77 MFD_DEV_WITH_ID(BUCK, 2),
78 MFD_DEV_WITH_ID(BUCK, 3),
79 MFD_DEV_WITH_ID(BUCK, 4),
80
81 /* 12 digital ldos */
82 MFD_DEV_WITH_ID(DLDO, 1),
83 MFD_DEV_WITH_ID(DLDO, 2),
84 MFD_DEV_WITH_ID(DLDO, 3),
85 MFD_DEV_WITH_ID(DLDO, 4),
86 MFD_DEV_WITH_ID(DLDO, 5),
87 MFD_DEV_WITH_ID(DLDO, 6),
88 MFD_DEV_WITH_ID(DLDO, 7),
89 MFD_DEV_WITH_ID(DLDO, 8),
90 MFD_DEV_WITH_ID(DLDO, 9),
91 MFD_DEV_WITH_ID(DLDO, 10),
92 MFD_DEV_WITH_ID(DLDO, 11),
93 MFD_DEV_WITH_ID(DLDO, 12),
94
95 /* 10 analog ldos */
96 MFD_DEV_WITH_ID(ALDO, 1),
97 MFD_DEV_WITH_ID(ALDO, 2),
98 MFD_DEV_WITH_ID(ALDO, 3),
99 MFD_DEV_WITH_ID(ALDO, 4),
100 MFD_DEV_WITH_ID(ALDO, 5),
101 MFD_DEV_WITH_ID(ALDO, 6),
102 MFD_DEV_WITH_ID(ALDO, 7),
103 MFD_DEV_WITH_ID(ALDO, 8),
104 MFD_DEV_WITH_ID(ALDO, 9),
105 MFD_DEV_WITH_ID(ALDO, 10),
106
107 /* ADC */
108 MFD_DEV_SIMPLE(ADC),
109
110 /* battery charger */
111 MFD_DEV_WITH_RESOURCE(CHARGER, chg_irqs, ARRAY_SIZE(chg_irqs)),
112
113 /* rtc */
114 MFD_DEV_WITH_RESOURCE(RTC, rtc_irqs, ARRAY_SIZE(rtc_irqs)),
115
116 /* backlight */
117 MFD_DEV_SIMPLE(BACKLIGHT),
118
119 /* current sink for vibrator */
120 MFD_DEV_SIMPLE(VIBRATOR),
121
122 /* current sink for keypad LED */
123 MFD_DEV_SIMPLE(KEYLED),
124};
125
126int lp8788_read_byte(struct lp8788 *lp, u8 reg, u8 *data)
127{
128 int ret;
129 unsigned int val;
130
131 ret = regmap_read(lp->regmap, reg, &val);
132 if (ret < 0) {
133 dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
134 return ret;
135 }
136
137 *data = (u8)val;
138 return 0;
139}
140EXPORT_SYMBOL_GPL(lp8788_read_byte);
141
142int lp8788_read_multi_bytes(struct lp8788 *lp, u8 reg, u8 *data, size_t count)
143{
144 return regmap_bulk_read(lp->regmap, reg, data, count);
145}
146EXPORT_SYMBOL_GPL(lp8788_read_multi_bytes);
147
148int lp8788_write_byte(struct lp8788 *lp, u8 reg, u8 data)
149{
150 return regmap_write(lp->regmap, reg, data);
151}
152EXPORT_SYMBOL_GPL(lp8788_write_byte);
153
154int lp8788_update_bits(struct lp8788 *lp, u8 reg, u8 mask, u8 data)
155{
156 return regmap_update_bits(lp->regmap, reg, mask, data);
157}
158EXPORT_SYMBOL_GPL(lp8788_update_bits);
159
160static int lp8788_platform_init(struct lp8788 *lp)
161{
162 struct lp8788_platform_data *pdata = lp->pdata;
163
164 return (pdata && pdata->init_func) ? pdata->init_func(lp) : 0;
165}
166
167static const struct regmap_config lp8788_regmap_config = {
168 .reg_bits = 8,
169 .val_bits = 8,
170 .max_register = MAX_LP8788_REGISTERS,
171};
172
173static int lp8788_probe(struct i2c_client *cl, const struct i2c_device_id *id)
174{
175 struct lp8788 *lp;
176 struct lp8788_platform_data *pdata = cl->dev.platform_data;
177 int ret;
178
179 lp = devm_kzalloc(&cl->dev, sizeof(struct lp8788), GFP_KERNEL);
180 if (!lp)
181 return -ENOMEM;
182
183 lp->regmap = devm_regmap_init_i2c(cl, &lp8788_regmap_config);
184 if (IS_ERR(lp->regmap)) {
185 ret = PTR_ERR(lp->regmap);
186 dev_err(&cl->dev, "regmap init i2c err: %d\n", ret);
187 return ret;
188 }
189
190 lp->pdata = pdata;
191 lp->dev = &cl->dev;
192 i2c_set_clientdata(cl, lp);
193
194 ret = lp8788_platform_init(lp);
195 if (ret)
196 return ret;
197
198 ret = lp8788_irq_init(lp, cl->irq);
199 if (ret)
200 return ret;
201
202 return mfd_add_devices(lp->dev, -1, lp8788_devs,
203 ARRAY_SIZE(lp8788_devs), NULL, 0, NULL);
204}
205
206static int __devexit lp8788_remove(struct i2c_client *cl)
207{
208 struct lp8788 *lp = i2c_get_clientdata(cl);
209
210 mfd_remove_devices(lp->dev);
211 lp8788_irq_exit(lp);
212 return 0;
213}
214
215static const struct i2c_device_id lp8788_ids[] = {
216 {"lp8788", 0},
217 { }
218};
219MODULE_DEVICE_TABLE(i2c, lp8788_ids);
220
221static struct i2c_driver lp8788_driver = {
222 .driver = {
223 .name = "lp8788",
224 .owner = THIS_MODULE,
225 },
226 .probe = lp8788_probe,
227 .remove = __devexit_p(lp8788_remove),
228 .id_table = lp8788_ids,
229};
230
231static int __init lp8788_init(void)
232{
233 return i2c_add_driver(&lp8788_driver);
234}
235subsys_initcall(lp8788_init);
236
237static void __exit lp8788_exit(void)
238{
239 i2c_del_driver(&lp8788_driver);
240}
241module_exit(lp8788_exit);
242
243MODULE_DESCRIPTION("TI LP8788 MFD Driver");
244MODULE_AUTHOR("Milo Kim");
245MODULE_LICENSE("GPL");