Commit | Line | Data |
---|---|---|
bbd51b1f HZ |
1 | /* |
2 | * Base driver for Marvell 88PM8607 | |
3 | * | |
4 | * Copyright (C) 2009 Marvell International Ltd. | |
5 | * Haojian Zhuang <haojian.zhuang@marvell.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | ||
12 | #include <linux/kernel.h> | |
13 | #include <linux/module.h> | |
5c42e8c4 | 14 | #include <linux/i2c.h> |
2afa62ea | 15 | #include <linux/irq.h> |
bbd51b1f HZ |
16 | #include <linux/interrupt.h> |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/mfd/core.h> | |
53dbab7a | 19 | #include <linux/mfd/88pm860x.h> |
bbd51b1f | 20 | |
2afa62ea HZ |
21 | #define INT_STATUS_NUM 3 |
22 | ||
a16122bc HZ |
23 | char pm860x_backlight_name[][MFD_NAME_SIZE] = { |
24 | "backlight-0", | |
25 | "backlight-1", | |
26 | "backlight-2", | |
27 | }; | |
28 | EXPORT_SYMBOL(pm860x_backlight_name); | |
29 | ||
30 | char pm860x_led_name[][MFD_NAME_SIZE] = { | |
31 | "led0-red", | |
32 | "led0-green", | |
33 | "led0-blue", | |
34 | "led1-red", | |
35 | "led1-green", | |
36 | "led1-blue", | |
37 | }; | |
38 | EXPORT_SYMBOL(pm860x_led_name); | |
39 | ||
40 | #define PM8606_BACKLIGHT_RESOURCE(_i, _x) \ | |
41 | { \ | |
42 | .name = pm860x_backlight_name[_i], \ | |
43 | .start = PM8606_##_x, \ | |
44 | .end = PM8606_##_x, \ | |
45 | .flags = IORESOURCE_IO, \ | |
46 | } | |
47 | ||
48 | static struct resource backlight_resources[] = { | |
49 | PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT1, WLED1A), | |
50 | PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT2, WLED2A), | |
51 | PM8606_BACKLIGHT_RESOURCE(PM8606_BACKLIGHT3, WLED3A), | |
52 | }; | |
53 | ||
54 | #define PM8606_BACKLIGHT_DEVS(_i) \ | |
55 | { \ | |
56 | .name = "88pm860x-backlight", \ | |
57 | .num_resources = 1, \ | |
58 | .resources = &backlight_resources[_i], \ | |
59 | .id = _i, \ | |
60 | } | |
61 | ||
62 | static struct mfd_cell backlight_devs[] = { | |
63 | PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT1), | |
64 | PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT2), | |
65 | PM8606_BACKLIGHT_DEVS(PM8606_BACKLIGHT3), | |
66 | }; | |
67 | ||
68 | #define PM8606_LED_RESOURCE(_i, _x) \ | |
69 | { \ | |
70 | .name = pm860x_led_name[_i], \ | |
71 | .start = PM8606_##_x, \ | |
72 | .end = PM8606_##_x, \ | |
73 | .flags = IORESOURCE_IO, \ | |
74 | } | |
75 | ||
76 | static struct resource led_resources[] = { | |
77 | PM8606_LED_RESOURCE(PM8606_LED1_RED, RGB2B), | |
78 | PM8606_LED_RESOURCE(PM8606_LED1_GREEN, RGB2C), | |
79 | PM8606_LED_RESOURCE(PM8606_LED1_BLUE, RGB2D), | |
80 | PM8606_LED_RESOURCE(PM8606_LED2_RED, RGB1B), | |
81 | PM8606_LED_RESOURCE(PM8606_LED2_GREEN, RGB1C), | |
82 | PM8606_LED_RESOURCE(PM8606_LED2_BLUE, RGB1D), | |
83 | }; | |
84 | ||
85 | #define PM8606_LED_DEVS(_i) \ | |
86 | { \ | |
87 | .name = "88pm860x-led", \ | |
88 | .num_resources = 1, \ | |
89 | .resources = &led_resources[_i], \ | |
90 | .id = _i, \ | |
91 | } | |
92 | ||
93 | static struct mfd_cell led_devs[] = { | |
94 | PM8606_LED_DEVS(PM8606_LED1_RED), | |
95 | PM8606_LED_DEVS(PM8606_LED1_GREEN), | |
96 | PM8606_LED_DEVS(PM8606_LED1_BLUE), | |
97 | PM8606_LED_DEVS(PM8606_LED2_RED), | |
98 | PM8606_LED_DEVS(PM8606_LED2_GREEN), | |
99 | PM8606_LED_DEVS(PM8606_LED2_BLUE), | |
100 | }; | |
101 | ||
102 | static struct resource touch_resources[] = { | |
103 | { | |
104 | .start = PM8607_IRQ_PEN, | |
105 | .end = PM8607_IRQ_PEN, | |
106 | .flags = IORESOURCE_IRQ, | |
107 | }, | |
108 | }; | |
109 | ||
110 | static struct mfd_cell touch_devs[] = { | |
111 | { | |
112 | .name = "88pm860x-touch", | |
113 | .num_resources = 1, | |
114 | .resources = &touch_resources[0], | |
115 | }, | |
116 | }; | |
bbd51b1f HZ |
117 | |
118 | #define PM8607_REG_RESOURCE(_start, _end) \ | |
119 | { \ | |
120 | .start = PM8607_##_start, \ | |
121 | .end = PM8607_##_end, \ | |
122 | .flags = IORESOURCE_IO, \ | |
123 | } | |
124 | ||
2afa62ea HZ |
125 | static struct resource power_supply_resources[] = { |
126 | { | |
127 | .name = "88pm860x-power", | |
128 | .start = PM8607_IRQ_CHG, | |
129 | .end = PM8607_IRQ_CHG, | |
130 | .flags = IORESOURCE_IRQ, | |
131 | }, | |
132 | }; | |
133 | ||
134 | static struct mfd_cell power_devs[] = { | |
135 | { | |
136 | .name = "88pm860x-power", | |
137 | .num_resources = 1, | |
138 | .resources = &power_supply_resources[0], | |
139 | .id = -1, | |
140 | }, | |
141 | }; | |
142 | ||
143 | static struct resource onkey_resources[] = { | |
144 | { | |
145 | .name = "88pm860x-onkey", | |
146 | .start = PM8607_IRQ_ONKEY, | |
147 | .end = PM8607_IRQ_ONKEY, | |
148 | .flags = IORESOURCE_IRQ, | |
149 | }, | |
150 | }; | |
151 | ||
152 | static struct mfd_cell onkey_devs[] = { | |
153 | { | |
154 | .name = "88pm860x-onkey", | |
155 | .num_resources = 1, | |
156 | .resources = &onkey_resources[0], | |
157 | .id = -1, | |
158 | }, | |
159 | }; | |
160 | ||
a16122bc | 161 | static struct resource regulator_resources[] = { |
bbd51b1f HZ |
162 | PM8607_REG_RESOURCE(BUCK1, BUCK1), |
163 | PM8607_REG_RESOURCE(BUCK2, BUCK2), | |
164 | PM8607_REG_RESOURCE(BUCK3, BUCK3), | |
165 | PM8607_REG_RESOURCE(LDO1, LDO1), | |
166 | PM8607_REG_RESOURCE(LDO2, LDO2), | |
167 | PM8607_REG_RESOURCE(LDO3, LDO3), | |
168 | PM8607_REG_RESOURCE(LDO4, LDO4), | |
169 | PM8607_REG_RESOURCE(LDO5, LDO5), | |
170 | PM8607_REG_RESOURCE(LDO6, LDO6), | |
171 | PM8607_REG_RESOURCE(LDO7, LDO7), | |
172 | PM8607_REG_RESOURCE(LDO8, LDO8), | |
173 | PM8607_REG_RESOURCE(LDO9, LDO9), | |
174 | PM8607_REG_RESOURCE(LDO10, LDO10), | |
175 | PM8607_REG_RESOURCE(LDO12, LDO12), | |
176 | PM8607_REG_RESOURCE(LDO14, LDO14), | |
177 | }; | |
178 | ||
179 | #define PM8607_REG_DEVS(_name, _id) \ | |
180 | { \ | |
181 | .name = "88pm8607-" #_name, \ | |
182 | .num_resources = 1, \ | |
a16122bc HZ |
183 | .resources = ®ulator_resources[PM8607_ID_##_id], \ |
184 | .id = PM8607_ID_##_id, \ | |
bbd51b1f HZ |
185 | } |
186 | ||
a16122bc | 187 | static struct mfd_cell regulator_devs[] = { |
bbd51b1f HZ |
188 | PM8607_REG_DEVS(buck1, BUCK1), |
189 | PM8607_REG_DEVS(buck2, BUCK2), | |
190 | PM8607_REG_DEVS(buck3, BUCK3), | |
191 | PM8607_REG_DEVS(ldo1, LDO1), | |
192 | PM8607_REG_DEVS(ldo2, LDO2), | |
193 | PM8607_REG_DEVS(ldo3, LDO3), | |
194 | PM8607_REG_DEVS(ldo4, LDO4), | |
195 | PM8607_REG_DEVS(ldo5, LDO5), | |
196 | PM8607_REG_DEVS(ldo6, LDO6), | |
197 | PM8607_REG_DEVS(ldo7, LDO7), | |
198 | PM8607_REG_DEVS(ldo8, LDO8), | |
199 | PM8607_REG_DEVS(ldo9, LDO9), | |
200 | PM8607_REG_DEVS(ldo10, LDO10), | |
201 | PM8607_REG_DEVS(ldo12, LDO12), | |
202 | PM8607_REG_DEVS(ldo14, LDO14), | |
203 | }; | |
204 | ||
2afa62ea HZ |
205 | struct pm860x_irq_data { |
206 | int reg; | |
207 | int mask_reg; | |
208 | int enable; /* enable or not */ | |
209 | int offs; /* bit offset in mask register */ | |
210 | }; | |
5c42e8c4 | 211 | |
2afa62ea HZ |
212 | static struct pm860x_irq_data pm860x_irqs[] = { |
213 | [PM8607_IRQ_ONKEY] = { | |
214 | .reg = PM8607_INT_STATUS1, | |
215 | .mask_reg = PM8607_INT_MASK_1, | |
216 | .offs = 1 << 0, | |
217 | }, | |
218 | [PM8607_IRQ_EXTON] = { | |
219 | .reg = PM8607_INT_STATUS1, | |
220 | .mask_reg = PM8607_INT_MASK_1, | |
221 | .offs = 1 << 1, | |
222 | }, | |
223 | [PM8607_IRQ_CHG] = { | |
224 | .reg = PM8607_INT_STATUS1, | |
225 | .mask_reg = PM8607_INT_MASK_1, | |
226 | .offs = 1 << 2, | |
227 | }, | |
228 | [PM8607_IRQ_BAT] = { | |
229 | .reg = PM8607_INT_STATUS1, | |
230 | .mask_reg = PM8607_INT_MASK_1, | |
231 | .offs = 1 << 3, | |
232 | }, | |
233 | [PM8607_IRQ_RTC] = { | |
234 | .reg = PM8607_INT_STATUS1, | |
235 | .mask_reg = PM8607_INT_MASK_1, | |
236 | .offs = 1 << 4, | |
237 | }, | |
238 | [PM8607_IRQ_CC] = { | |
239 | .reg = PM8607_INT_STATUS1, | |
240 | .mask_reg = PM8607_INT_MASK_1, | |
241 | .offs = 1 << 5, | |
242 | }, | |
243 | [PM8607_IRQ_VBAT] = { | |
244 | .reg = PM8607_INT_STATUS2, | |
245 | .mask_reg = PM8607_INT_MASK_2, | |
246 | .offs = 1 << 0, | |
247 | }, | |
248 | [PM8607_IRQ_VCHG] = { | |
249 | .reg = PM8607_INT_STATUS2, | |
250 | .mask_reg = PM8607_INT_MASK_2, | |
251 | .offs = 1 << 1, | |
252 | }, | |
253 | [PM8607_IRQ_VSYS] = { | |
254 | .reg = PM8607_INT_STATUS2, | |
255 | .mask_reg = PM8607_INT_MASK_2, | |
256 | .offs = 1 << 2, | |
257 | }, | |
258 | [PM8607_IRQ_TINT] = { | |
259 | .reg = PM8607_INT_STATUS2, | |
260 | .mask_reg = PM8607_INT_MASK_2, | |
261 | .offs = 1 << 3, | |
262 | }, | |
263 | [PM8607_IRQ_GPADC0] = { | |
264 | .reg = PM8607_INT_STATUS2, | |
265 | .mask_reg = PM8607_INT_MASK_2, | |
266 | .offs = 1 << 4, | |
267 | }, | |
268 | [PM8607_IRQ_GPADC1] = { | |
269 | .reg = PM8607_INT_STATUS2, | |
270 | .mask_reg = PM8607_INT_MASK_2, | |
271 | .offs = 1 << 5, | |
272 | }, | |
273 | [PM8607_IRQ_GPADC2] = { | |
274 | .reg = PM8607_INT_STATUS2, | |
275 | .mask_reg = PM8607_INT_MASK_2, | |
276 | .offs = 1 << 6, | |
277 | }, | |
278 | [PM8607_IRQ_GPADC3] = { | |
279 | .reg = PM8607_INT_STATUS2, | |
280 | .mask_reg = PM8607_INT_MASK_2, | |
281 | .offs = 1 << 7, | |
282 | }, | |
283 | [PM8607_IRQ_AUDIO_SHORT] = { | |
284 | .reg = PM8607_INT_STATUS3, | |
285 | .mask_reg = PM8607_INT_MASK_3, | |
286 | .offs = 1 << 0, | |
287 | }, | |
288 | [PM8607_IRQ_PEN] = { | |
289 | .reg = PM8607_INT_STATUS3, | |
290 | .mask_reg = PM8607_INT_MASK_3, | |
291 | .offs = 1 << 1, | |
292 | }, | |
293 | [PM8607_IRQ_HEADSET] = { | |
294 | .reg = PM8607_INT_STATUS3, | |
295 | .mask_reg = PM8607_INT_MASK_3, | |
296 | .offs = 1 << 2, | |
297 | }, | |
298 | [PM8607_IRQ_HOOK] = { | |
299 | .reg = PM8607_INT_STATUS3, | |
300 | .mask_reg = PM8607_INT_MASK_3, | |
301 | .offs = 1 << 3, | |
302 | }, | |
303 | [PM8607_IRQ_MICIN] = { | |
304 | .reg = PM8607_INT_STATUS3, | |
305 | .mask_reg = PM8607_INT_MASK_3, | |
306 | .offs = 1 << 4, | |
307 | }, | |
308 | [PM8607_IRQ_CHG_FAIL] = { | |
309 | .reg = PM8607_INT_STATUS3, | |
310 | .mask_reg = PM8607_INT_MASK_3, | |
311 | .offs = 1 << 5, | |
312 | }, | |
313 | [PM8607_IRQ_CHG_DONE] = { | |
314 | .reg = PM8607_INT_STATUS3, | |
315 | .mask_reg = PM8607_INT_MASK_3, | |
316 | .offs = 1 << 6, | |
317 | }, | |
318 | [PM8607_IRQ_CHG_FAULT] = { | |
319 | .reg = PM8607_INT_STATUS3, | |
320 | .mask_reg = PM8607_INT_MASK_3, | |
321 | .offs = 1 << 7, | |
322 | }, | |
323 | }; | |
5c42e8c4 | 324 | |
2afa62ea HZ |
325 | static inline struct pm860x_irq_data *irq_to_pm860x(struct pm860x_chip *chip, |
326 | int irq) | |
5c42e8c4 | 327 | { |
2afa62ea | 328 | return &pm860x_irqs[irq - chip->irq_base]; |
5c42e8c4 | 329 | } |
5c42e8c4 | 330 | |
2afa62ea | 331 | static irqreturn_t pm860x_irq(int irq, void *data) |
5c42e8c4 | 332 | { |
5c42e8c4 | 333 | struct pm860x_chip *chip = data; |
2afa62ea HZ |
334 | struct pm860x_irq_data *irq_data; |
335 | struct i2c_client *i2c; | |
336 | int read_reg = -1, value = 0; | |
337 | int i; | |
338 | ||
339 | i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | |
340 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
341 | irq_data = &pm860x_irqs[i]; | |
342 | if (read_reg != irq_data->reg) { | |
343 | read_reg = irq_data->reg; | |
344 | value = pm860x_reg_read(i2c, irq_data->reg); | |
5c42e8c4 | 345 | } |
2afa62ea HZ |
346 | if (value & irq_data->enable) |
347 | handle_nested_irq(chip->irq_base + i); | |
5c42e8c4 | 348 | } |
5c42e8c4 HZ |
349 | return IRQ_HANDLED; |
350 | } | |
351 | ||
2afa62ea | 352 | static void pm860x_irq_lock(unsigned int irq) |
53dbab7a | 353 | { |
2afa62ea | 354 | struct pm860x_chip *chip = get_irq_chip_data(irq); |
5c42e8c4 HZ |
355 | |
356 | mutex_lock(&chip->irq_lock); | |
53dbab7a HZ |
357 | } |
358 | ||
2afa62ea | 359 | static void pm860x_irq_sync_unlock(unsigned int irq) |
bbd51b1f | 360 | { |
2afa62ea HZ |
361 | struct pm860x_chip *chip = get_irq_chip_data(irq); |
362 | struct pm860x_irq_data *irq_data; | |
363 | struct i2c_client *i2c; | |
364 | static unsigned char cached[3] = {0x0, 0x0, 0x0}; | |
365 | unsigned char mask[3]; | |
366 | int i; | |
367 | ||
368 | i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | |
369 | /* Load cached value. In initial, all IRQs are masked */ | |
370 | for (i = 0; i < 3; i++) | |
371 | mask[i] = cached[i]; | |
372 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
373 | irq_data = &pm860x_irqs[i]; | |
374 | switch (irq_data->mask_reg) { | |
375 | case PM8607_INT_MASK_1: | |
376 | mask[0] &= ~irq_data->offs; | |
377 | mask[0] |= irq_data->enable; | |
378 | break; | |
379 | case PM8607_INT_MASK_2: | |
380 | mask[1] &= ~irq_data->offs; | |
381 | mask[1] |= irq_data->enable; | |
382 | break; | |
383 | case PM8607_INT_MASK_3: | |
384 | mask[2] &= ~irq_data->offs; | |
385 | mask[2] |= irq_data->enable; | |
386 | break; | |
387 | default: | |
388 | dev_err(chip->dev, "wrong IRQ\n"); | |
389 | break; | |
390 | } | |
391 | } | |
392 | /* update mask into registers */ | |
393 | for (i = 0; i < 3; i++) { | |
394 | if (mask[i] != cached[i]) { | |
395 | cached[i] = mask[i]; | |
396 | pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]); | |
397 | } | |
398 | } | |
5c42e8c4 | 399 | |
5c42e8c4 | 400 | mutex_unlock(&chip->irq_lock); |
2afa62ea | 401 | } |
5c42e8c4 | 402 | |
2afa62ea HZ |
403 | static void pm860x_irq_enable(unsigned int irq) |
404 | { | |
405 | struct pm860x_chip *chip = get_irq_chip_data(irq); | |
406 | pm860x_irqs[irq - chip->irq_base].enable | |
407 | = pm860x_irqs[irq - chip->irq_base].offs; | |
5c42e8c4 | 408 | } |
2afa62ea HZ |
409 | |
410 | static void pm860x_irq_disable(unsigned int irq) | |
411 | { | |
412 | struct pm860x_chip *chip = get_irq_chip_data(irq); | |
413 | pm860x_irqs[irq - chip->irq_base].enable = 0; | |
414 | } | |
415 | ||
416 | static struct irq_chip pm860x_irq_chip = { | |
417 | .name = "88pm860x", | |
418 | .bus_lock = pm860x_irq_lock, | |
419 | .bus_sync_unlock = pm860x_irq_sync_unlock, | |
420 | .enable = pm860x_irq_enable, | |
421 | .disable = pm860x_irq_disable, | |
422 | }; | |
5c42e8c4 | 423 | |
a16122bc HZ |
424 | static int __devinit device_gpadc_init(struct pm860x_chip *chip, |
425 | struct pm860x_platform_data *pdata) | |
426 | { | |
427 | struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ | |
428 | : chip->companion; | |
429 | int use_gpadc = 0, data, ret; | |
430 | ||
431 | /* initialize GPADC without activating it */ | |
432 | ||
433 | if (pdata && pdata->touch) { | |
434 | /* set GPADC MISC1 register */ | |
435 | data = 0; | |
436 | data |= (pdata->touch->gpadc_prebias << 1) | |
437 | & PM8607_GPADC_PREBIAS_MASK; | |
438 | data |= (pdata->touch->slot_cycle << 3) | |
439 | & PM8607_GPADC_SLOT_CYCLE_MASK; | |
440 | data |= (pdata->touch->off_scale << 5) | |
441 | & PM8607_GPADC_OFF_SCALE_MASK; | |
442 | data |= (pdata->touch->sw_cal << 7) | |
443 | & PM8607_GPADC_SW_CAL_MASK; | |
444 | if (data) { | |
445 | ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data); | |
446 | if (ret < 0) | |
447 | goto out; | |
448 | } | |
449 | /* set tsi prebias time */ | |
450 | if (pdata->touch->tsi_prebias) { | |
451 | data = pdata->touch->tsi_prebias; | |
452 | ret = pm860x_reg_write(i2c, PM8607_TSI_PREBIAS, data); | |
453 | if (ret < 0) | |
454 | goto out; | |
455 | } | |
456 | /* set prebias & prechg time of pen detect */ | |
457 | data = 0; | |
458 | data |= pdata->touch->pen_prebias & PM8607_PD_PREBIAS_MASK; | |
459 | data |= (pdata->touch->pen_prechg << 5) | |
460 | & PM8607_PD_PRECHG_MASK; | |
461 | if (data) { | |
462 | ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data); | |
463 | if (ret < 0) | |
464 | goto out; | |
465 | } | |
466 | ||
467 | use_gpadc = 1; | |
468 | } | |
469 | ||
470 | /* turn on GPADC */ | |
471 | if (use_gpadc) { | |
472 | ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, | |
473 | PM8607_GPADC_EN, PM8607_GPADC_EN); | |
474 | } | |
475 | out: | |
476 | return ret; | |
477 | } | |
478 | ||
5c42e8c4 HZ |
479 | static int __devinit device_irq_init(struct pm860x_chip *chip, |
480 | struct pm860x_platform_data *pdata) | |
481 | { | |
482 | struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ | |
483 | : chip->companion; | |
484 | unsigned char status_buf[INT_STATUS_NUM]; | |
2afa62ea HZ |
485 | unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; |
486 | struct irq_desc *desc; | |
487 | int i, data, mask, ret = -EINVAL; | |
488 | int __irq; | |
5c42e8c4 | 489 | |
2afa62ea HZ |
490 | if (!pdata || !pdata->irq_base) { |
491 | dev_warn(chip->dev, "No interrupt support on IRQ base\n"); | |
492 | return -EINVAL; | |
493 | } | |
5c42e8c4 HZ |
494 | |
495 | mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR | |
496 | | PM8607_B0_MISC1_INT_MASK; | |
497 | data = 0; | |
498 | chip->irq_mode = 0; | |
499 | if (pdata && pdata->irq_mode) { | |
500 | /* | |
501 | * irq_mode defines the way of clearing interrupt. If it's 1, | |
502 | * clear IRQ by write. Otherwise, clear it by read. | |
503 | * This control bit is valid from 88PM8607 B0 steping. | |
504 | */ | |
505 | data |= PM8607_B0_MISC1_INT_CLEAR; | |
506 | chip->irq_mode = 1; | |
507 | } | |
508 | ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data); | |
509 | if (ret < 0) | |
510 | goto out; | |
511 | ||
512 | /* mask all IRQs */ | |
513 | memset(status_buf, 0, INT_STATUS_NUM); | |
514 | ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1, | |
515 | INT_STATUS_NUM, status_buf); | |
516 | if (ret < 0) | |
517 | goto out; | |
518 | ||
519 | if (chip->irq_mode) { | |
520 | /* clear interrupt status by write */ | |
521 | memset(status_buf, 0xFF, INT_STATUS_NUM); | |
522 | ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, | |
523 | INT_STATUS_NUM, status_buf); | |
524 | } else { | |
525 | /* clear interrupt status by read */ | |
526 | ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, | |
527 | INT_STATUS_NUM, status_buf); | |
528 | } | |
529 | if (ret < 0) | |
530 | goto out; | |
531 | ||
2afa62ea HZ |
532 | mutex_init(&chip->irq_lock); |
533 | chip->irq_base = pdata->irq_base; | |
534 | chip->core_irq = i2c->irq; | |
535 | if (!chip->core_irq) | |
5c42e8c4 | 536 | goto out; |
2afa62ea HZ |
537 | |
538 | desc = irq_to_desc(chip->core_irq); | |
539 | ||
540 | /* register IRQ by genirq */ | |
541 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
542 | __irq = i + chip->irq_base; | |
543 | set_irq_chip_data(__irq, chip); | |
544 | set_irq_chip_and_handler(__irq, &pm860x_irq_chip, | |
545 | handle_edge_irq); | |
546 | set_irq_nested_thread(__irq, 1); | |
547 | #ifdef CONFIG_ARM | |
548 | set_irq_flags(__irq, IRQF_VALID); | |
549 | #else | |
550 | set_irq_noprobe(__irq); | |
551 | #endif | |
5c42e8c4 | 552 | } |
2afa62ea HZ |
553 | |
554 | ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, flags, | |
555 | "88pm860x", chip); | |
556 | if (ret) { | |
557 | dev_err(chip->dev, "Failed to request IRQ: %d\n", ret); | |
558 | chip->core_irq = 0; | |
559 | } | |
560 | ||
5c42e8c4 HZ |
561 | return 0; |
562 | out: | |
2afa62ea | 563 | chip->core_irq = 0; |
5c42e8c4 HZ |
564 | return ret; |
565 | } | |
566 | ||
567 | static void __devexit device_irq_exit(struct pm860x_chip *chip) | |
568 | { | |
2afa62ea HZ |
569 | if (chip->core_irq) |
570 | free_irq(chip->core_irq, chip); | |
5c42e8c4 HZ |
571 | } |
572 | ||
573 | static void __devinit device_8606_init(struct pm860x_chip *chip, | |
574 | struct i2c_client *i2c, | |
575 | struct pm860x_platform_data *pdata) | |
576 | { | |
a16122bc HZ |
577 | int ret; |
578 | ||
579 | if (pdata && pdata->backlight) { | |
580 | ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0], | |
581 | ARRAY_SIZE(backlight_devs), | |
582 | &backlight_resources[0], 0); | |
583 | if (ret < 0) { | |
584 | dev_err(chip->dev, "Failed to add backlight " | |
585 | "subdev\n"); | |
586 | goto out_dev; | |
587 | } | |
588 | } | |
589 | ||
590 | if (pdata && pdata->led) { | |
591 | ret = mfd_add_devices(chip->dev, 0, &led_devs[0], | |
592 | ARRAY_SIZE(led_devs), | |
593 | &led_resources[0], 0); | |
594 | if (ret < 0) { | |
595 | dev_err(chip->dev, "Failed to add led " | |
596 | "subdev\n"); | |
597 | goto out_dev; | |
598 | } | |
599 | } | |
600 | return; | |
601 | out_dev: | |
602 | mfd_remove_devices(chip->dev); | |
603 | device_irq_exit(chip); | |
5c42e8c4 HZ |
604 | } |
605 | ||
606 | static void __devinit device_8607_init(struct pm860x_chip *chip, | |
607 | struct i2c_client *i2c, | |
608 | struct pm860x_platform_data *pdata) | |
609 | { | |
a16122bc | 610 | int data, ret; |
bbd51b1f | 611 | |
53dbab7a | 612 | ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); |
bbd51b1f HZ |
613 | if (ret < 0) { |
614 | dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); | |
615 | goto out; | |
616 | } | |
53dbab7a | 617 | if ((ret & PM8607_VERSION_MASK) == PM8607_VERSION) |
bbd51b1f HZ |
618 | dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", |
619 | ret); | |
620 | else { | |
621 | dev_err(chip->dev, "Failed to detect Marvell 88PM8607. " | |
622 | "Chip ID: %02x\n", ret); | |
623 | goto out; | |
624 | } | |
bbd51b1f | 625 | |
53dbab7a | 626 | ret = pm860x_reg_read(i2c, PM8607_BUCK3); |
bbd51b1f HZ |
627 | if (ret < 0) { |
628 | dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); | |
629 | goto out; | |
630 | } | |
631 | if (ret & PM8607_BUCK3_DOUBLE) | |
632 | chip->buck3_double = 1; | |
633 | ||
5c42e8c4 | 634 | ret = pm860x_reg_read(i2c, PM8607_B0_MISC1); |
bbd51b1f HZ |
635 | if (ret < 0) { |
636 | dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); | |
637 | goto out; | |
638 | } | |
bbd51b1f | 639 | |
5c42e8c4 HZ |
640 | if (pdata && (pdata->i2c_port == PI2C_PORT)) |
641 | data = PM8607_B0_MISC1_PI2C; | |
642 | else | |
643 | data = 0; | |
644 | ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data); | |
645 | if (ret < 0) { | |
646 | dev_err(chip->dev, "Failed to access MISC1:%d\n", ret); | |
647 | goto out; | |
648 | } | |
649 | ||
a16122bc HZ |
650 | ret = device_gpadc_init(chip, pdata); |
651 | if (ret < 0) | |
652 | goto out; | |
653 | ||
5c42e8c4 HZ |
654 | ret = device_irq_init(chip, pdata); |
655 | if (ret < 0) | |
656 | goto out; | |
657 | ||
a16122bc HZ |
658 | ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], |
659 | ARRAY_SIZE(regulator_devs), | |
660 | ®ulator_resources[0], 0); | |
661 | if (ret < 0) { | |
662 | dev_err(chip->dev, "Failed to add regulator subdev\n"); | |
663 | goto out_dev; | |
664 | } | |
665 | ||
666 | if (pdata && pdata->touch) { | |
667 | ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], | |
668 | ARRAY_SIZE(touch_devs), | |
669 | &touch_resources[0], 0); | |
670 | if (ret < 0) { | |
671 | dev_err(chip->dev, "Failed to add touch " | |
672 | "subdev\n"); | |
673 | goto out_dev; | |
bbd51b1f HZ |
674 | } |
675 | } | |
2afa62ea HZ |
676 | |
677 | if (pdata && pdata->power) { | |
678 | ret = mfd_add_devices(chip->dev, 0, &power_devs[0], | |
679 | ARRAY_SIZE(power_devs), | |
680 | &power_supply_resources[0], 0); | |
681 | if (ret < 0) { | |
682 | dev_err(chip->dev, "Failed to add power supply " | |
683 | "subdev\n"); | |
684 | goto out_dev; | |
685 | } | |
686 | } | |
687 | ||
688 | ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], | |
689 | ARRAY_SIZE(onkey_devs), | |
690 | &onkey_resources[0], 0); | |
691 | if (ret < 0) { | |
692 | dev_err(chip->dev, "Failed to add onkey subdev\n"); | |
693 | goto out_dev; | |
694 | } | |
695 | ||
a16122bc HZ |
696 | return; |
697 | out_dev: | |
698 | mfd_remove_devices(chip->dev); | |
699 | device_irq_exit(chip); | |
bbd51b1f | 700 | out: |
53dbab7a HZ |
701 | return; |
702 | } | |
703 | ||
704 | int pm860x_device_init(struct pm860x_chip *chip, | |
705 | struct pm860x_platform_data *pdata) | |
706 | { | |
2afa62ea | 707 | chip->core_irq = 0; |
5c42e8c4 | 708 | |
53dbab7a HZ |
709 | switch (chip->id) { |
710 | case CHIP_PM8606: | |
711 | device_8606_init(chip, chip->client, pdata); | |
712 | break; | |
713 | case CHIP_PM8607: | |
714 | device_8607_init(chip, chip->client, pdata); | |
715 | break; | |
716 | } | |
717 | ||
718 | if (chip->companion) { | |
719 | switch (chip->id) { | |
720 | case CHIP_PM8607: | |
721 | device_8606_init(chip, chip->companion, pdata); | |
722 | break; | |
723 | case CHIP_PM8606: | |
724 | device_8607_init(chip, chip->companion, pdata); | |
725 | break; | |
726 | } | |
727 | } | |
5c42e8c4 | 728 | |
53dbab7a | 729 | return 0; |
bbd51b1f HZ |
730 | } |
731 | ||
53dbab7a | 732 | void pm860x_device_exit(struct pm860x_chip *chip) |
bbd51b1f | 733 | { |
5c42e8c4 | 734 | device_irq_exit(chip); |
bbd51b1f HZ |
735 | mfd_remove_devices(chip->dev); |
736 | } | |
737 | ||
53dbab7a | 738 | MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x"); |
bbd51b1f HZ |
739 | MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); |
740 | MODULE_LICENSE("GPL"); |