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