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[] = { | |
21f1fc38 HZ |
77 | PM8606_LED_RESOURCE(PM8606_LED1_RED, RGB1B), |
78 | PM8606_LED_RESOURCE(PM8606_LED1_GREEN, RGB1C), | |
79 | PM8606_LED_RESOURCE(PM8606_LED1_BLUE, RGB1D), | |
80 | PM8606_LED_RESOURCE(PM8606_LED2_RED, RGB2B), | |
81 | PM8606_LED_RESOURCE(PM8606_LED2_GREEN, RGB2C), | |
82 | PM8606_LED_RESOURCE(PM8606_LED2_BLUE, RGB2D), | |
a16122bc HZ |
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 | ||
2c36af7b HZ |
161 | static struct resource codec_resources[] = { |
162 | { | |
163 | /* Headset microphone insertion or removal */ | |
164 | .name = "micin", | |
165 | .start = PM8607_IRQ_MICIN, | |
166 | .end = PM8607_IRQ_MICIN, | |
167 | .flags = IORESOURCE_IRQ, | |
168 | }, { | |
169 | /* Hook-switch press or release */ | |
170 | .name = "hook", | |
171 | .start = PM8607_IRQ_HOOK, | |
172 | .end = PM8607_IRQ_HOOK, | |
173 | .flags = IORESOURCE_IRQ, | |
174 | }, { | |
175 | /* Headset insertion or removal */ | |
176 | .name = "headset", | |
177 | .start = PM8607_IRQ_HEADSET, | |
178 | .end = PM8607_IRQ_HEADSET, | |
179 | .flags = IORESOURCE_IRQ, | |
180 | }, { | |
181 | /* Audio short */ | |
182 | .name = "audio-short", | |
183 | .start = PM8607_IRQ_AUDIO_SHORT, | |
184 | .end = PM8607_IRQ_AUDIO_SHORT, | |
185 | .flags = IORESOURCE_IRQ, | |
186 | }, | |
187 | }; | |
188 | ||
189 | static struct mfd_cell codec_devs[] = { | |
190 | { | |
191 | .name = "88pm860x-codec", | |
192 | .num_resources = ARRAY_SIZE(codec_resources), | |
193 | .resources = &codec_resources[0], | |
194 | .id = -1, | |
195 | }, | |
196 | }; | |
197 | ||
a16122bc | 198 | static struct resource regulator_resources[] = { |
bbd51b1f HZ |
199 | PM8607_REG_RESOURCE(BUCK1, BUCK1), |
200 | PM8607_REG_RESOURCE(BUCK2, BUCK2), | |
201 | PM8607_REG_RESOURCE(BUCK3, BUCK3), | |
202 | PM8607_REG_RESOURCE(LDO1, LDO1), | |
203 | PM8607_REG_RESOURCE(LDO2, LDO2), | |
204 | PM8607_REG_RESOURCE(LDO3, LDO3), | |
205 | PM8607_REG_RESOURCE(LDO4, LDO4), | |
206 | PM8607_REG_RESOURCE(LDO5, LDO5), | |
207 | PM8607_REG_RESOURCE(LDO6, LDO6), | |
208 | PM8607_REG_RESOURCE(LDO7, LDO7), | |
209 | PM8607_REG_RESOURCE(LDO8, LDO8), | |
210 | PM8607_REG_RESOURCE(LDO9, LDO9), | |
211 | PM8607_REG_RESOURCE(LDO10, LDO10), | |
212 | PM8607_REG_RESOURCE(LDO12, LDO12), | |
9f79e9db | 213 | PM8607_REG_RESOURCE(VIBRATOR_SET, VIBRATOR_SET), |
bbd51b1f HZ |
214 | PM8607_REG_RESOURCE(LDO14, LDO14), |
215 | }; | |
216 | ||
192bbb95 | 217 | #define PM8607_REG_DEVS(_id) \ |
bbd51b1f | 218 | { \ |
192bbb95 | 219 | .name = "88pm860x-regulator", \ |
bbd51b1f | 220 | .num_resources = 1, \ |
a16122bc HZ |
221 | .resources = ®ulator_resources[PM8607_ID_##_id], \ |
222 | .id = PM8607_ID_##_id, \ | |
bbd51b1f HZ |
223 | } |
224 | ||
a16122bc | 225 | static struct mfd_cell regulator_devs[] = { |
192bbb95 HZ |
226 | PM8607_REG_DEVS(BUCK1), |
227 | PM8607_REG_DEVS(BUCK2), | |
228 | PM8607_REG_DEVS(BUCK3), | |
229 | PM8607_REG_DEVS(LDO1), | |
230 | PM8607_REG_DEVS(LDO2), | |
231 | PM8607_REG_DEVS(LDO3), | |
232 | PM8607_REG_DEVS(LDO4), | |
233 | PM8607_REG_DEVS(LDO5), | |
234 | PM8607_REG_DEVS(LDO6), | |
235 | PM8607_REG_DEVS(LDO7), | |
236 | PM8607_REG_DEVS(LDO8), | |
237 | PM8607_REG_DEVS(LDO9), | |
238 | PM8607_REG_DEVS(LDO10), | |
239 | PM8607_REG_DEVS(LDO12), | |
9f79e9db | 240 | PM8607_REG_DEVS(LDO13), |
192bbb95 | 241 | PM8607_REG_DEVS(LDO14), |
bbd51b1f HZ |
242 | }; |
243 | ||
2afa62ea HZ |
244 | struct pm860x_irq_data { |
245 | int reg; | |
246 | int mask_reg; | |
247 | int enable; /* enable or not */ | |
248 | int offs; /* bit offset in mask register */ | |
249 | }; | |
5c42e8c4 | 250 | |
2afa62ea HZ |
251 | static struct pm860x_irq_data pm860x_irqs[] = { |
252 | [PM8607_IRQ_ONKEY] = { | |
253 | .reg = PM8607_INT_STATUS1, | |
254 | .mask_reg = PM8607_INT_MASK_1, | |
255 | .offs = 1 << 0, | |
256 | }, | |
257 | [PM8607_IRQ_EXTON] = { | |
258 | .reg = PM8607_INT_STATUS1, | |
259 | .mask_reg = PM8607_INT_MASK_1, | |
260 | .offs = 1 << 1, | |
261 | }, | |
262 | [PM8607_IRQ_CHG] = { | |
263 | .reg = PM8607_INT_STATUS1, | |
264 | .mask_reg = PM8607_INT_MASK_1, | |
265 | .offs = 1 << 2, | |
266 | }, | |
267 | [PM8607_IRQ_BAT] = { | |
268 | .reg = PM8607_INT_STATUS1, | |
269 | .mask_reg = PM8607_INT_MASK_1, | |
270 | .offs = 1 << 3, | |
271 | }, | |
272 | [PM8607_IRQ_RTC] = { | |
273 | .reg = PM8607_INT_STATUS1, | |
274 | .mask_reg = PM8607_INT_MASK_1, | |
275 | .offs = 1 << 4, | |
276 | }, | |
277 | [PM8607_IRQ_CC] = { | |
278 | .reg = PM8607_INT_STATUS1, | |
279 | .mask_reg = PM8607_INT_MASK_1, | |
280 | .offs = 1 << 5, | |
281 | }, | |
282 | [PM8607_IRQ_VBAT] = { | |
283 | .reg = PM8607_INT_STATUS2, | |
284 | .mask_reg = PM8607_INT_MASK_2, | |
285 | .offs = 1 << 0, | |
286 | }, | |
287 | [PM8607_IRQ_VCHG] = { | |
288 | .reg = PM8607_INT_STATUS2, | |
289 | .mask_reg = PM8607_INT_MASK_2, | |
290 | .offs = 1 << 1, | |
291 | }, | |
292 | [PM8607_IRQ_VSYS] = { | |
293 | .reg = PM8607_INT_STATUS2, | |
294 | .mask_reg = PM8607_INT_MASK_2, | |
295 | .offs = 1 << 2, | |
296 | }, | |
297 | [PM8607_IRQ_TINT] = { | |
298 | .reg = PM8607_INT_STATUS2, | |
299 | .mask_reg = PM8607_INT_MASK_2, | |
300 | .offs = 1 << 3, | |
301 | }, | |
302 | [PM8607_IRQ_GPADC0] = { | |
303 | .reg = PM8607_INT_STATUS2, | |
304 | .mask_reg = PM8607_INT_MASK_2, | |
305 | .offs = 1 << 4, | |
306 | }, | |
307 | [PM8607_IRQ_GPADC1] = { | |
308 | .reg = PM8607_INT_STATUS2, | |
309 | .mask_reg = PM8607_INT_MASK_2, | |
310 | .offs = 1 << 5, | |
311 | }, | |
312 | [PM8607_IRQ_GPADC2] = { | |
313 | .reg = PM8607_INT_STATUS2, | |
314 | .mask_reg = PM8607_INT_MASK_2, | |
315 | .offs = 1 << 6, | |
316 | }, | |
317 | [PM8607_IRQ_GPADC3] = { | |
318 | .reg = PM8607_INT_STATUS2, | |
319 | .mask_reg = PM8607_INT_MASK_2, | |
320 | .offs = 1 << 7, | |
321 | }, | |
322 | [PM8607_IRQ_AUDIO_SHORT] = { | |
323 | .reg = PM8607_INT_STATUS3, | |
324 | .mask_reg = PM8607_INT_MASK_3, | |
325 | .offs = 1 << 0, | |
326 | }, | |
327 | [PM8607_IRQ_PEN] = { | |
328 | .reg = PM8607_INT_STATUS3, | |
329 | .mask_reg = PM8607_INT_MASK_3, | |
330 | .offs = 1 << 1, | |
331 | }, | |
332 | [PM8607_IRQ_HEADSET] = { | |
333 | .reg = PM8607_INT_STATUS3, | |
334 | .mask_reg = PM8607_INT_MASK_3, | |
335 | .offs = 1 << 2, | |
336 | }, | |
337 | [PM8607_IRQ_HOOK] = { | |
338 | .reg = PM8607_INT_STATUS3, | |
339 | .mask_reg = PM8607_INT_MASK_3, | |
340 | .offs = 1 << 3, | |
341 | }, | |
342 | [PM8607_IRQ_MICIN] = { | |
343 | .reg = PM8607_INT_STATUS3, | |
344 | .mask_reg = PM8607_INT_MASK_3, | |
345 | .offs = 1 << 4, | |
346 | }, | |
347 | [PM8607_IRQ_CHG_FAIL] = { | |
348 | .reg = PM8607_INT_STATUS3, | |
349 | .mask_reg = PM8607_INT_MASK_3, | |
350 | .offs = 1 << 5, | |
351 | }, | |
352 | [PM8607_IRQ_CHG_DONE] = { | |
353 | .reg = PM8607_INT_STATUS3, | |
354 | .mask_reg = PM8607_INT_MASK_3, | |
355 | .offs = 1 << 6, | |
356 | }, | |
357 | [PM8607_IRQ_CHG_FAULT] = { | |
358 | .reg = PM8607_INT_STATUS3, | |
359 | .mask_reg = PM8607_INT_MASK_3, | |
360 | .offs = 1 << 7, | |
361 | }, | |
362 | }; | |
5c42e8c4 | 363 | |
2afa62ea HZ |
364 | static inline struct pm860x_irq_data *irq_to_pm860x(struct pm860x_chip *chip, |
365 | int irq) | |
5c42e8c4 | 366 | { |
2afa62ea | 367 | return &pm860x_irqs[irq - chip->irq_base]; |
5c42e8c4 | 368 | } |
5c42e8c4 | 369 | |
2afa62ea | 370 | static irqreturn_t pm860x_irq(int irq, void *data) |
5c42e8c4 | 371 | { |
5c42e8c4 | 372 | struct pm860x_chip *chip = data; |
2afa62ea HZ |
373 | struct pm860x_irq_data *irq_data; |
374 | struct i2c_client *i2c; | |
375 | int read_reg = -1, value = 0; | |
376 | int i; | |
377 | ||
378 | i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | |
379 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
380 | irq_data = &pm860x_irqs[i]; | |
381 | if (read_reg != irq_data->reg) { | |
382 | read_reg = irq_data->reg; | |
383 | value = pm860x_reg_read(i2c, irq_data->reg); | |
5c42e8c4 | 384 | } |
2afa62ea HZ |
385 | if (value & irq_data->enable) |
386 | handle_nested_irq(chip->irq_base + i); | |
5c42e8c4 | 387 | } |
5c42e8c4 HZ |
388 | return IRQ_HANDLED; |
389 | } | |
390 | ||
2afa62ea | 391 | static void pm860x_irq_lock(unsigned int irq) |
53dbab7a | 392 | { |
2afa62ea | 393 | struct pm860x_chip *chip = get_irq_chip_data(irq); |
5c42e8c4 HZ |
394 | |
395 | mutex_lock(&chip->irq_lock); | |
53dbab7a HZ |
396 | } |
397 | ||
2afa62ea | 398 | static void pm860x_irq_sync_unlock(unsigned int irq) |
bbd51b1f | 399 | { |
2afa62ea HZ |
400 | struct pm860x_chip *chip = get_irq_chip_data(irq); |
401 | struct pm860x_irq_data *irq_data; | |
402 | struct i2c_client *i2c; | |
403 | static unsigned char cached[3] = {0x0, 0x0, 0x0}; | |
404 | unsigned char mask[3]; | |
405 | int i; | |
406 | ||
407 | i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | |
408 | /* Load cached value. In initial, all IRQs are masked */ | |
409 | for (i = 0; i < 3; i++) | |
410 | mask[i] = cached[i]; | |
411 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
412 | irq_data = &pm860x_irqs[i]; | |
413 | switch (irq_data->mask_reg) { | |
414 | case PM8607_INT_MASK_1: | |
415 | mask[0] &= ~irq_data->offs; | |
416 | mask[0] |= irq_data->enable; | |
417 | break; | |
418 | case PM8607_INT_MASK_2: | |
419 | mask[1] &= ~irq_data->offs; | |
420 | mask[1] |= irq_data->enable; | |
421 | break; | |
422 | case PM8607_INT_MASK_3: | |
423 | mask[2] &= ~irq_data->offs; | |
424 | mask[2] |= irq_data->enable; | |
425 | break; | |
426 | default: | |
427 | dev_err(chip->dev, "wrong IRQ\n"); | |
428 | break; | |
429 | } | |
430 | } | |
431 | /* update mask into registers */ | |
432 | for (i = 0; i < 3; i++) { | |
433 | if (mask[i] != cached[i]) { | |
434 | cached[i] = mask[i]; | |
435 | pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]); | |
436 | } | |
437 | } | |
5c42e8c4 | 438 | |
5c42e8c4 | 439 | mutex_unlock(&chip->irq_lock); |
2afa62ea | 440 | } |
5c42e8c4 | 441 | |
2afa62ea HZ |
442 | static void pm860x_irq_enable(unsigned int irq) |
443 | { | |
444 | struct pm860x_chip *chip = get_irq_chip_data(irq); | |
445 | pm860x_irqs[irq - chip->irq_base].enable | |
446 | = pm860x_irqs[irq - chip->irq_base].offs; | |
5c42e8c4 | 447 | } |
2afa62ea HZ |
448 | |
449 | static void pm860x_irq_disable(unsigned int irq) | |
450 | { | |
451 | struct pm860x_chip *chip = get_irq_chip_data(irq); | |
452 | pm860x_irqs[irq - chip->irq_base].enable = 0; | |
453 | } | |
454 | ||
455 | static struct irq_chip pm860x_irq_chip = { | |
456 | .name = "88pm860x", | |
457 | .bus_lock = pm860x_irq_lock, | |
458 | .bus_sync_unlock = pm860x_irq_sync_unlock, | |
459 | .enable = pm860x_irq_enable, | |
460 | .disable = pm860x_irq_disable, | |
461 | }; | |
5c42e8c4 | 462 | |
a16122bc HZ |
463 | static int __devinit device_gpadc_init(struct pm860x_chip *chip, |
464 | struct pm860x_platform_data *pdata) | |
465 | { | |
466 | struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ | |
467 | : chip->companion; | |
eb6e8ddf DC |
468 | int data; |
469 | int ret; | |
a16122bc HZ |
470 | |
471 | /* initialize GPADC without activating it */ | |
472 | ||
eb6e8ddf DC |
473 | if (!pdata || !pdata->touch) |
474 | return -EINVAL; | |
475 | ||
476 | /* set GPADC MISC1 register */ | |
477 | data = 0; | |
478 | data |= (pdata->touch->gpadc_prebias << 1) & PM8607_GPADC_PREBIAS_MASK; | |
479 | data |= (pdata->touch->slot_cycle << 3) & PM8607_GPADC_SLOT_CYCLE_MASK; | |
480 | data |= (pdata->touch->off_scale << 5) & PM8607_GPADC_OFF_SCALE_MASK; | |
481 | data |= (pdata->touch->sw_cal << 7) & PM8607_GPADC_SW_CAL_MASK; | |
482 | if (data) { | |
483 | ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data); | |
484 | if (ret < 0) | |
485 | goto out; | |
a16122bc | 486 | } |
eb6e8ddf DC |
487 | /* set tsi prebias time */ |
488 | if (pdata->touch->tsi_prebias) { | |
489 | data = pdata->touch->tsi_prebias; | |
490 | ret = pm860x_reg_write(i2c, PM8607_TSI_PREBIAS, data); | |
491 | if (ret < 0) | |
492 | goto out; | |
493 | } | |
494 | /* set prebias & prechg time of pen detect */ | |
495 | data = 0; | |
496 | data |= pdata->touch->pen_prebias & PM8607_PD_PREBIAS_MASK; | |
497 | data |= (pdata->touch->pen_prechg << 5) & PM8607_PD_PRECHG_MASK; | |
498 | if (data) { | |
499 | ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data); | |
500 | if (ret < 0) | |
501 | goto out; | |
a16122bc | 502 | } |
eb6e8ddf DC |
503 | |
504 | ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, | |
505 | PM8607_GPADC_EN, PM8607_GPADC_EN); | |
a16122bc HZ |
506 | out: |
507 | return ret; | |
508 | } | |
509 | ||
5c42e8c4 HZ |
510 | static int __devinit device_irq_init(struct pm860x_chip *chip, |
511 | struct pm860x_platform_data *pdata) | |
512 | { | |
513 | struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ | |
514 | : chip->companion; | |
515 | unsigned char status_buf[INT_STATUS_NUM]; | |
2afa62ea HZ |
516 | unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; |
517 | struct irq_desc *desc; | |
518 | int i, data, mask, ret = -EINVAL; | |
519 | int __irq; | |
5c42e8c4 | 520 | |
2afa62ea HZ |
521 | if (!pdata || !pdata->irq_base) { |
522 | dev_warn(chip->dev, "No interrupt support on IRQ base\n"); | |
523 | return -EINVAL; | |
524 | } | |
5c42e8c4 HZ |
525 | |
526 | mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR | |
527 | | PM8607_B0_MISC1_INT_MASK; | |
528 | data = 0; | |
529 | chip->irq_mode = 0; | |
530 | if (pdata && pdata->irq_mode) { | |
531 | /* | |
532 | * irq_mode defines the way of clearing interrupt. If it's 1, | |
533 | * clear IRQ by write. Otherwise, clear it by read. | |
534 | * This control bit is valid from 88PM8607 B0 steping. | |
535 | */ | |
536 | data |= PM8607_B0_MISC1_INT_CLEAR; | |
537 | chip->irq_mode = 1; | |
538 | } | |
539 | ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data); | |
540 | if (ret < 0) | |
541 | goto out; | |
542 | ||
543 | /* mask all IRQs */ | |
544 | memset(status_buf, 0, INT_STATUS_NUM); | |
545 | ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1, | |
546 | INT_STATUS_NUM, status_buf); | |
547 | if (ret < 0) | |
548 | goto out; | |
549 | ||
550 | if (chip->irq_mode) { | |
551 | /* clear interrupt status by write */ | |
552 | memset(status_buf, 0xFF, INT_STATUS_NUM); | |
553 | ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, | |
554 | INT_STATUS_NUM, status_buf); | |
555 | } else { | |
556 | /* clear interrupt status by read */ | |
557 | ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, | |
558 | INT_STATUS_NUM, status_buf); | |
559 | } | |
560 | if (ret < 0) | |
561 | goto out; | |
562 | ||
2afa62ea HZ |
563 | mutex_init(&chip->irq_lock); |
564 | chip->irq_base = pdata->irq_base; | |
565 | chip->core_irq = i2c->irq; | |
566 | if (!chip->core_irq) | |
5c42e8c4 | 567 | goto out; |
2afa62ea HZ |
568 | |
569 | desc = irq_to_desc(chip->core_irq); | |
570 | ||
571 | /* register IRQ by genirq */ | |
572 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
573 | __irq = i + chip->irq_base; | |
574 | set_irq_chip_data(__irq, chip); | |
575 | set_irq_chip_and_handler(__irq, &pm860x_irq_chip, | |
576 | handle_edge_irq); | |
577 | set_irq_nested_thread(__irq, 1); | |
578 | #ifdef CONFIG_ARM | |
579 | set_irq_flags(__irq, IRQF_VALID); | |
580 | #else | |
581 | set_irq_noprobe(__irq); | |
582 | #endif | |
5c42e8c4 | 583 | } |
2afa62ea HZ |
584 | |
585 | ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, flags, | |
586 | "88pm860x", chip); | |
587 | if (ret) { | |
588 | dev_err(chip->dev, "Failed to request IRQ: %d\n", ret); | |
589 | chip->core_irq = 0; | |
590 | } | |
591 | ||
5c42e8c4 HZ |
592 | return 0; |
593 | out: | |
2afa62ea | 594 | chip->core_irq = 0; |
5c42e8c4 HZ |
595 | return ret; |
596 | } | |
597 | ||
872c1b14 | 598 | static void device_irq_exit(struct pm860x_chip *chip) |
5c42e8c4 | 599 | { |
2afa62ea HZ |
600 | if (chip->core_irq) |
601 | free_irq(chip->core_irq, chip); | |
5c42e8c4 HZ |
602 | } |
603 | ||
604 | static void __devinit device_8606_init(struct pm860x_chip *chip, | |
605 | struct i2c_client *i2c, | |
606 | struct pm860x_platform_data *pdata) | |
607 | { | |
a16122bc HZ |
608 | int ret; |
609 | ||
610 | if (pdata && pdata->backlight) { | |
611 | ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0], | |
612 | ARRAY_SIZE(backlight_devs), | |
613 | &backlight_resources[0], 0); | |
614 | if (ret < 0) { | |
615 | dev_err(chip->dev, "Failed to add backlight " | |
616 | "subdev\n"); | |
617 | goto out_dev; | |
618 | } | |
619 | } | |
620 | ||
621 | if (pdata && pdata->led) { | |
622 | ret = mfd_add_devices(chip->dev, 0, &led_devs[0], | |
623 | ARRAY_SIZE(led_devs), | |
624 | &led_resources[0], 0); | |
625 | if (ret < 0) { | |
626 | dev_err(chip->dev, "Failed to add led " | |
627 | "subdev\n"); | |
628 | goto out_dev; | |
629 | } | |
630 | } | |
631 | return; | |
632 | out_dev: | |
633 | mfd_remove_devices(chip->dev); | |
634 | device_irq_exit(chip); | |
5c42e8c4 HZ |
635 | } |
636 | ||
637 | static void __devinit device_8607_init(struct pm860x_chip *chip, | |
638 | struct i2c_client *i2c, | |
639 | struct pm860x_platform_data *pdata) | |
640 | { | |
a16122bc | 641 | int data, ret; |
bbd51b1f | 642 | |
53dbab7a | 643 | ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); |
bbd51b1f HZ |
644 | if (ret < 0) { |
645 | dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); | |
646 | goto out; | |
647 | } | |
53dbab7a | 648 | if ((ret & PM8607_VERSION_MASK) == PM8607_VERSION) |
bbd51b1f HZ |
649 | dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", |
650 | ret); | |
651 | else { | |
652 | dev_err(chip->dev, "Failed to detect Marvell 88PM8607. " | |
653 | "Chip ID: %02x\n", ret); | |
654 | goto out; | |
655 | } | |
bbd51b1f | 656 | |
53dbab7a | 657 | ret = pm860x_reg_read(i2c, PM8607_BUCK3); |
bbd51b1f HZ |
658 | if (ret < 0) { |
659 | dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); | |
660 | goto out; | |
661 | } | |
662 | if (ret & PM8607_BUCK3_DOUBLE) | |
663 | chip->buck3_double = 1; | |
664 | ||
5c42e8c4 | 665 | ret = pm860x_reg_read(i2c, PM8607_B0_MISC1); |
bbd51b1f HZ |
666 | if (ret < 0) { |
667 | dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); | |
668 | goto out; | |
669 | } | |
bbd51b1f | 670 | |
5c42e8c4 HZ |
671 | if (pdata && (pdata->i2c_port == PI2C_PORT)) |
672 | data = PM8607_B0_MISC1_PI2C; | |
673 | else | |
674 | data = 0; | |
675 | ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data); | |
676 | if (ret < 0) { | |
677 | dev_err(chip->dev, "Failed to access MISC1:%d\n", ret); | |
678 | goto out; | |
679 | } | |
680 | ||
a16122bc HZ |
681 | ret = device_gpadc_init(chip, pdata); |
682 | if (ret < 0) | |
683 | goto out; | |
684 | ||
5c42e8c4 HZ |
685 | ret = device_irq_init(chip, pdata); |
686 | if (ret < 0) | |
687 | goto out; | |
688 | ||
a16122bc HZ |
689 | ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], |
690 | ARRAY_SIZE(regulator_devs), | |
691 | ®ulator_resources[0], 0); | |
692 | if (ret < 0) { | |
693 | dev_err(chip->dev, "Failed to add regulator subdev\n"); | |
694 | goto out_dev; | |
695 | } | |
696 | ||
697 | if (pdata && pdata->touch) { | |
698 | ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], | |
699 | ARRAY_SIZE(touch_devs), | |
700 | &touch_resources[0], 0); | |
701 | if (ret < 0) { | |
702 | dev_err(chip->dev, "Failed to add touch " | |
703 | "subdev\n"); | |
704 | goto out_dev; | |
bbd51b1f HZ |
705 | } |
706 | } | |
2afa62ea HZ |
707 | |
708 | if (pdata && pdata->power) { | |
709 | ret = mfd_add_devices(chip->dev, 0, &power_devs[0], | |
710 | ARRAY_SIZE(power_devs), | |
711 | &power_supply_resources[0], 0); | |
712 | if (ret < 0) { | |
713 | dev_err(chip->dev, "Failed to add power supply " | |
714 | "subdev\n"); | |
715 | goto out_dev; | |
716 | } | |
717 | } | |
718 | ||
719 | ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], | |
720 | ARRAY_SIZE(onkey_devs), | |
721 | &onkey_resources[0], 0); | |
722 | if (ret < 0) { | |
723 | dev_err(chip->dev, "Failed to add onkey subdev\n"); | |
724 | goto out_dev; | |
725 | } | |
726 | ||
2c36af7b HZ |
727 | ret = mfd_add_devices(chip->dev, 0, &codec_devs[0], |
728 | ARRAY_SIZE(codec_devs), | |
729 | &codec_resources[0], 0); | |
730 | if (ret < 0) { | |
731 | dev_err(chip->dev, "Failed to add codec subdev\n"); | |
732 | goto out_dev; | |
733 | } | |
a16122bc HZ |
734 | return; |
735 | out_dev: | |
736 | mfd_remove_devices(chip->dev); | |
737 | device_irq_exit(chip); | |
bbd51b1f | 738 | out: |
53dbab7a HZ |
739 | return; |
740 | } | |
741 | ||
872c1b14 | 742 | int __devinit pm860x_device_init(struct pm860x_chip *chip, |
53dbab7a HZ |
743 | struct pm860x_platform_data *pdata) |
744 | { | |
2afa62ea | 745 | chip->core_irq = 0; |
5c42e8c4 | 746 | |
53dbab7a HZ |
747 | switch (chip->id) { |
748 | case CHIP_PM8606: | |
749 | device_8606_init(chip, chip->client, pdata); | |
750 | break; | |
751 | case CHIP_PM8607: | |
752 | device_8607_init(chip, chip->client, pdata); | |
753 | break; | |
754 | } | |
755 | ||
756 | if (chip->companion) { | |
757 | switch (chip->id) { | |
758 | case CHIP_PM8607: | |
759 | device_8606_init(chip, chip->companion, pdata); | |
760 | break; | |
761 | case CHIP_PM8606: | |
762 | device_8607_init(chip, chip->companion, pdata); | |
763 | break; | |
764 | } | |
765 | } | |
5c42e8c4 | 766 | |
53dbab7a | 767 | return 0; |
bbd51b1f HZ |
768 | } |
769 | ||
872c1b14 | 770 | void __devexit pm860x_device_exit(struct pm860x_chip *chip) |
bbd51b1f | 771 | { |
5c42e8c4 | 772 | device_irq_exit(chip); |
bbd51b1f HZ |
773 | mfd_remove_devices(chip->dev); |
774 | } | |
775 | ||
53dbab7a | 776 | MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x"); |
bbd51b1f HZ |
777 | MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); |
778 | MODULE_LICENSE("GPL"); |