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 | 364 | static irqreturn_t pm860x_irq(int irq, void *data) |
5c42e8c4 | 365 | { |
5c42e8c4 | 366 | struct pm860x_chip *chip = data; |
2afa62ea HZ |
367 | struct pm860x_irq_data *irq_data; |
368 | struct i2c_client *i2c; | |
369 | int read_reg = -1, value = 0; | |
370 | int i; | |
371 | ||
372 | i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | |
373 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
374 | irq_data = &pm860x_irqs[i]; | |
375 | if (read_reg != irq_data->reg) { | |
376 | read_reg = irq_data->reg; | |
377 | value = pm860x_reg_read(i2c, irq_data->reg); | |
5c42e8c4 | 378 | } |
2afa62ea HZ |
379 | if (value & irq_data->enable) |
380 | handle_nested_irq(chip->irq_base + i); | |
5c42e8c4 | 381 | } |
5c42e8c4 HZ |
382 | return IRQ_HANDLED; |
383 | } | |
384 | ||
49f89d9a | 385 | static void pm860x_irq_lock(struct irq_data *data) |
53dbab7a | 386 | { |
49f89d9a | 387 | struct pm860x_chip *chip = irq_data_get_irq_chip_data(data); |
5c42e8c4 HZ |
388 | |
389 | mutex_lock(&chip->irq_lock); | |
53dbab7a HZ |
390 | } |
391 | ||
49f89d9a | 392 | static void pm860x_irq_sync_unlock(struct irq_data *data) |
bbd51b1f | 393 | { |
49f89d9a | 394 | struct pm860x_chip *chip = irq_data_get_irq_chip_data(data); |
2afa62ea HZ |
395 | struct pm860x_irq_data *irq_data; |
396 | struct i2c_client *i2c; | |
397 | static unsigned char cached[3] = {0x0, 0x0, 0x0}; | |
398 | unsigned char mask[3]; | |
399 | int i; | |
400 | ||
401 | i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; | |
402 | /* Load cached value. In initial, all IRQs are masked */ | |
403 | for (i = 0; i < 3; i++) | |
404 | mask[i] = cached[i]; | |
405 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
406 | irq_data = &pm860x_irqs[i]; | |
407 | switch (irq_data->mask_reg) { | |
408 | case PM8607_INT_MASK_1: | |
409 | mask[0] &= ~irq_data->offs; | |
410 | mask[0] |= irq_data->enable; | |
411 | break; | |
412 | case PM8607_INT_MASK_2: | |
413 | mask[1] &= ~irq_data->offs; | |
414 | mask[1] |= irq_data->enable; | |
415 | break; | |
416 | case PM8607_INT_MASK_3: | |
417 | mask[2] &= ~irq_data->offs; | |
418 | mask[2] |= irq_data->enable; | |
419 | break; | |
420 | default: | |
421 | dev_err(chip->dev, "wrong IRQ\n"); | |
422 | break; | |
423 | } | |
424 | } | |
425 | /* update mask into registers */ | |
426 | for (i = 0; i < 3; i++) { | |
427 | if (mask[i] != cached[i]) { | |
428 | cached[i] = mask[i]; | |
429 | pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]); | |
430 | } | |
431 | } | |
5c42e8c4 | 432 | |
5c42e8c4 | 433 | mutex_unlock(&chip->irq_lock); |
2afa62ea | 434 | } |
5c42e8c4 | 435 | |
49f89d9a | 436 | static void pm860x_irq_enable(struct irq_data *data) |
2afa62ea | 437 | { |
49f89d9a MB |
438 | struct pm860x_chip *chip = irq_data_get_irq_chip_data(data); |
439 | pm860x_irqs[data->irq - chip->irq_base].enable | |
440 | = pm860x_irqs[data->irq - chip->irq_base].offs; | |
5c42e8c4 | 441 | } |
2afa62ea | 442 | |
49f89d9a | 443 | static void pm860x_irq_disable(struct irq_data *data) |
2afa62ea | 444 | { |
49f89d9a MB |
445 | struct pm860x_chip *chip = irq_data_get_irq_chip_data(data); |
446 | pm860x_irqs[data->irq - chip->irq_base].enable = 0; | |
2afa62ea HZ |
447 | } |
448 | ||
449 | static struct irq_chip pm860x_irq_chip = { | |
450 | .name = "88pm860x", | |
49f89d9a MB |
451 | .irq_bus_lock = pm860x_irq_lock, |
452 | .irq_bus_sync_unlock = pm860x_irq_sync_unlock, | |
453 | .irq_enable = pm860x_irq_enable, | |
454 | .irq_disable = pm860x_irq_disable, | |
2afa62ea | 455 | }; |
5c42e8c4 | 456 | |
a16122bc HZ |
457 | static int __devinit device_gpadc_init(struct pm860x_chip *chip, |
458 | struct pm860x_platform_data *pdata) | |
459 | { | |
460 | struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ | |
461 | : chip->companion; | |
eb6e8ddf DC |
462 | int data; |
463 | int ret; | |
a16122bc HZ |
464 | |
465 | /* initialize GPADC without activating it */ | |
466 | ||
eb6e8ddf DC |
467 | if (!pdata || !pdata->touch) |
468 | return -EINVAL; | |
469 | ||
470 | /* set GPADC MISC1 register */ | |
471 | data = 0; | |
472 | data |= (pdata->touch->gpadc_prebias << 1) & PM8607_GPADC_PREBIAS_MASK; | |
473 | data |= (pdata->touch->slot_cycle << 3) & PM8607_GPADC_SLOT_CYCLE_MASK; | |
474 | data |= (pdata->touch->off_scale << 5) & PM8607_GPADC_OFF_SCALE_MASK; | |
475 | data |= (pdata->touch->sw_cal << 7) & PM8607_GPADC_SW_CAL_MASK; | |
476 | if (data) { | |
477 | ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data); | |
478 | if (ret < 0) | |
479 | goto out; | |
a16122bc | 480 | } |
eb6e8ddf DC |
481 | /* set tsi prebias time */ |
482 | if (pdata->touch->tsi_prebias) { | |
483 | data = pdata->touch->tsi_prebias; | |
484 | ret = pm860x_reg_write(i2c, PM8607_TSI_PREBIAS, data); | |
485 | if (ret < 0) | |
486 | goto out; | |
487 | } | |
488 | /* set prebias & prechg time of pen detect */ | |
489 | data = 0; | |
490 | data |= pdata->touch->pen_prebias & PM8607_PD_PREBIAS_MASK; | |
491 | data |= (pdata->touch->pen_prechg << 5) & PM8607_PD_PRECHG_MASK; | |
492 | if (data) { | |
493 | ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data); | |
494 | if (ret < 0) | |
495 | goto out; | |
a16122bc | 496 | } |
eb6e8ddf DC |
497 | |
498 | ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1, | |
499 | PM8607_GPADC_EN, PM8607_GPADC_EN); | |
a16122bc HZ |
500 | out: |
501 | return ret; | |
502 | } | |
503 | ||
5c42e8c4 HZ |
504 | static int __devinit device_irq_init(struct pm860x_chip *chip, |
505 | struct pm860x_platform_data *pdata) | |
506 | { | |
507 | struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \ | |
508 | : chip->companion; | |
509 | unsigned char status_buf[INT_STATUS_NUM]; | |
2afa62ea HZ |
510 | unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; |
511 | struct irq_desc *desc; | |
512 | int i, data, mask, ret = -EINVAL; | |
513 | int __irq; | |
5c42e8c4 | 514 | |
2afa62ea HZ |
515 | if (!pdata || !pdata->irq_base) { |
516 | dev_warn(chip->dev, "No interrupt support on IRQ base\n"); | |
517 | return -EINVAL; | |
518 | } | |
5c42e8c4 HZ |
519 | |
520 | mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR | |
521 | | PM8607_B0_MISC1_INT_MASK; | |
522 | data = 0; | |
523 | chip->irq_mode = 0; | |
524 | if (pdata && pdata->irq_mode) { | |
525 | /* | |
526 | * irq_mode defines the way of clearing interrupt. If it's 1, | |
527 | * clear IRQ by write. Otherwise, clear it by read. | |
528 | * This control bit is valid from 88PM8607 B0 steping. | |
529 | */ | |
530 | data |= PM8607_B0_MISC1_INT_CLEAR; | |
531 | chip->irq_mode = 1; | |
532 | } | |
533 | ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data); | |
534 | if (ret < 0) | |
535 | goto out; | |
536 | ||
537 | /* mask all IRQs */ | |
538 | memset(status_buf, 0, INT_STATUS_NUM); | |
539 | ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1, | |
540 | INT_STATUS_NUM, status_buf); | |
541 | if (ret < 0) | |
542 | goto out; | |
543 | ||
544 | if (chip->irq_mode) { | |
545 | /* clear interrupt status by write */ | |
546 | memset(status_buf, 0xFF, INT_STATUS_NUM); | |
547 | ret = pm860x_bulk_write(i2c, PM8607_INT_STATUS1, | |
548 | INT_STATUS_NUM, status_buf); | |
549 | } else { | |
550 | /* clear interrupt status by read */ | |
551 | ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1, | |
552 | INT_STATUS_NUM, status_buf); | |
553 | } | |
554 | if (ret < 0) | |
555 | goto out; | |
556 | ||
2afa62ea HZ |
557 | mutex_init(&chip->irq_lock); |
558 | chip->irq_base = pdata->irq_base; | |
559 | chip->core_irq = i2c->irq; | |
560 | if (!chip->core_irq) | |
5c42e8c4 | 561 | goto out; |
2afa62ea HZ |
562 | |
563 | desc = irq_to_desc(chip->core_irq); | |
564 | ||
565 | /* register IRQ by genirq */ | |
566 | for (i = 0; i < ARRAY_SIZE(pm860x_irqs); i++) { | |
567 | __irq = i + chip->irq_base; | |
568 | set_irq_chip_data(__irq, chip); | |
569 | set_irq_chip_and_handler(__irq, &pm860x_irq_chip, | |
570 | handle_edge_irq); | |
571 | set_irq_nested_thread(__irq, 1); | |
572 | #ifdef CONFIG_ARM | |
573 | set_irq_flags(__irq, IRQF_VALID); | |
574 | #else | |
575 | set_irq_noprobe(__irq); | |
576 | #endif | |
5c42e8c4 | 577 | } |
2afa62ea HZ |
578 | |
579 | ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, flags, | |
580 | "88pm860x", chip); | |
581 | if (ret) { | |
582 | dev_err(chip->dev, "Failed to request IRQ: %d\n", ret); | |
583 | chip->core_irq = 0; | |
584 | } | |
585 | ||
5c42e8c4 HZ |
586 | return 0; |
587 | out: | |
2afa62ea | 588 | chip->core_irq = 0; |
5c42e8c4 HZ |
589 | return ret; |
590 | } | |
591 | ||
872c1b14 | 592 | static void device_irq_exit(struct pm860x_chip *chip) |
5c42e8c4 | 593 | { |
2afa62ea HZ |
594 | if (chip->core_irq) |
595 | free_irq(chip->core_irq, chip); | |
5c42e8c4 HZ |
596 | } |
597 | ||
598 | static void __devinit device_8606_init(struct pm860x_chip *chip, | |
599 | struct i2c_client *i2c, | |
600 | struct pm860x_platform_data *pdata) | |
601 | { | |
a16122bc HZ |
602 | int ret; |
603 | ||
604 | if (pdata && pdata->backlight) { | |
605 | ret = mfd_add_devices(chip->dev, 0, &backlight_devs[0], | |
606 | ARRAY_SIZE(backlight_devs), | |
607 | &backlight_resources[0], 0); | |
608 | if (ret < 0) { | |
609 | dev_err(chip->dev, "Failed to add backlight " | |
610 | "subdev\n"); | |
611 | goto out_dev; | |
612 | } | |
613 | } | |
614 | ||
615 | if (pdata && pdata->led) { | |
616 | ret = mfd_add_devices(chip->dev, 0, &led_devs[0], | |
617 | ARRAY_SIZE(led_devs), | |
618 | &led_resources[0], 0); | |
619 | if (ret < 0) { | |
620 | dev_err(chip->dev, "Failed to add led " | |
621 | "subdev\n"); | |
622 | goto out_dev; | |
623 | } | |
624 | } | |
625 | return; | |
626 | out_dev: | |
627 | mfd_remove_devices(chip->dev); | |
628 | device_irq_exit(chip); | |
5c42e8c4 HZ |
629 | } |
630 | ||
631 | static void __devinit device_8607_init(struct pm860x_chip *chip, | |
632 | struct i2c_client *i2c, | |
633 | struct pm860x_platform_data *pdata) | |
634 | { | |
a16122bc | 635 | int data, ret; |
bbd51b1f | 636 | |
53dbab7a | 637 | ret = pm860x_reg_read(i2c, PM8607_CHIP_ID); |
bbd51b1f HZ |
638 | if (ret < 0) { |
639 | dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret); | |
640 | goto out; | |
641 | } | |
38b34052 HZ |
642 | switch (ret & PM8607_VERSION_MASK) { |
643 | case 0x40: | |
644 | case 0x50: | |
bbd51b1f HZ |
645 | dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n", |
646 | ret); | |
38b34052 HZ |
647 | break; |
648 | default: | |
bbd51b1f HZ |
649 | dev_err(chip->dev, "Failed to detect Marvell 88PM8607. " |
650 | "Chip ID: %02x\n", ret); | |
651 | goto out; | |
652 | } | |
bbd51b1f | 653 | |
53dbab7a | 654 | ret = pm860x_reg_read(i2c, PM8607_BUCK3); |
bbd51b1f HZ |
655 | if (ret < 0) { |
656 | dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret); | |
657 | goto out; | |
658 | } | |
659 | if (ret & PM8607_BUCK3_DOUBLE) | |
660 | chip->buck3_double = 1; | |
661 | ||
5c42e8c4 | 662 | ret = pm860x_reg_read(i2c, PM8607_B0_MISC1); |
bbd51b1f HZ |
663 | if (ret < 0) { |
664 | dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret); | |
665 | goto out; | |
666 | } | |
bbd51b1f | 667 | |
5c42e8c4 HZ |
668 | if (pdata && (pdata->i2c_port == PI2C_PORT)) |
669 | data = PM8607_B0_MISC1_PI2C; | |
670 | else | |
671 | data = 0; | |
672 | ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data); | |
673 | if (ret < 0) { | |
674 | dev_err(chip->dev, "Failed to access MISC1:%d\n", ret); | |
675 | goto out; | |
676 | } | |
677 | ||
a16122bc HZ |
678 | ret = device_gpadc_init(chip, pdata); |
679 | if (ret < 0) | |
680 | goto out; | |
681 | ||
5c42e8c4 HZ |
682 | ret = device_irq_init(chip, pdata); |
683 | if (ret < 0) | |
684 | goto out; | |
685 | ||
a16122bc HZ |
686 | ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0], |
687 | ARRAY_SIZE(regulator_devs), | |
688 | ®ulator_resources[0], 0); | |
689 | if (ret < 0) { | |
690 | dev_err(chip->dev, "Failed to add regulator subdev\n"); | |
691 | goto out_dev; | |
692 | } | |
693 | ||
694 | if (pdata && pdata->touch) { | |
695 | ret = mfd_add_devices(chip->dev, 0, &touch_devs[0], | |
696 | ARRAY_SIZE(touch_devs), | |
697 | &touch_resources[0], 0); | |
698 | if (ret < 0) { | |
699 | dev_err(chip->dev, "Failed to add touch " | |
700 | "subdev\n"); | |
701 | goto out_dev; | |
bbd51b1f HZ |
702 | } |
703 | } | |
2afa62ea HZ |
704 | |
705 | if (pdata && pdata->power) { | |
706 | ret = mfd_add_devices(chip->dev, 0, &power_devs[0], | |
707 | ARRAY_SIZE(power_devs), | |
708 | &power_supply_resources[0], 0); | |
709 | if (ret < 0) { | |
710 | dev_err(chip->dev, "Failed to add power supply " | |
711 | "subdev\n"); | |
712 | goto out_dev; | |
713 | } | |
714 | } | |
715 | ||
716 | ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0], | |
717 | ARRAY_SIZE(onkey_devs), | |
718 | &onkey_resources[0], 0); | |
719 | if (ret < 0) { | |
720 | dev_err(chip->dev, "Failed to add onkey subdev\n"); | |
721 | goto out_dev; | |
722 | } | |
723 | ||
2c36af7b HZ |
724 | ret = mfd_add_devices(chip->dev, 0, &codec_devs[0], |
725 | ARRAY_SIZE(codec_devs), | |
726 | &codec_resources[0], 0); | |
727 | if (ret < 0) { | |
728 | dev_err(chip->dev, "Failed to add codec subdev\n"); | |
729 | goto out_dev; | |
730 | } | |
a16122bc HZ |
731 | return; |
732 | out_dev: | |
733 | mfd_remove_devices(chip->dev); | |
734 | device_irq_exit(chip); | |
bbd51b1f | 735 | out: |
53dbab7a HZ |
736 | return; |
737 | } | |
738 | ||
872c1b14 | 739 | int __devinit pm860x_device_init(struct pm860x_chip *chip, |
53dbab7a HZ |
740 | struct pm860x_platform_data *pdata) |
741 | { | |
2afa62ea | 742 | chip->core_irq = 0; |
5c42e8c4 | 743 | |
53dbab7a HZ |
744 | switch (chip->id) { |
745 | case CHIP_PM8606: | |
746 | device_8606_init(chip, chip->client, pdata); | |
747 | break; | |
748 | case CHIP_PM8607: | |
749 | device_8607_init(chip, chip->client, pdata); | |
750 | break; | |
751 | } | |
752 | ||
753 | if (chip->companion) { | |
754 | switch (chip->id) { | |
755 | case CHIP_PM8607: | |
756 | device_8606_init(chip, chip->companion, pdata); | |
757 | break; | |
758 | case CHIP_PM8606: | |
759 | device_8607_init(chip, chip->companion, pdata); | |
760 | break; | |
761 | } | |
762 | } | |
5c42e8c4 | 763 | |
53dbab7a | 764 | return 0; |
bbd51b1f HZ |
765 | } |
766 | ||
872c1b14 | 767 | void __devexit pm860x_device_exit(struct pm860x_chip *chip) |
bbd51b1f | 768 | { |
5c42e8c4 | 769 | device_irq_exit(chip); |
bbd51b1f HZ |
770 | mfd_remove_devices(chip->dev); |
771 | } | |
772 | ||
53dbab7a | 773 | MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x"); |
bbd51b1f HZ |
774 | MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); |
775 | MODULE_LICENSE("GPL"); |