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