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