2 * Base driver for Marvell 88PM8607
4 * Copyright (C) 2009 Marvell International Ltd.
5 * Haojian Zhuang <haojian.zhuang@marvell.com>
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.
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/i2c.h>
15 #include <linux/irq.h>
16 #include <linux/interrupt.h>
17 #include <linux/platform_device.h>
18 #include <linux/mfd/core.h>
19 #include <linux/mfd/88pm860x.h>
21 #define INT_STATUS_NUM 3
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,},
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,},
38 static struct mfd_cell bk_devs[] __initdata = {
39 {"88pm860x-backlight", 0,},
40 {"88pm860x-backlight", 1,},
41 {"88pm860x-backlight", 2,},
44 static struct mfd_cell led_devs[] __initdata = {
53 static struct pm860x_backlight_pdata bk_pdata[ARRAY_SIZE(bk_devs)];
54 static struct pm860x_led_pdata led_pdata[ARRAY_SIZE(led_devs)];
56 static struct resource touch_resources[] = {
58 .start = PM8607_IRQ_PEN,
59 .end = PM8607_IRQ_PEN,
60 .flags = IORESOURCE_IRQ,
64 static struct mfd_cell touch_devs[] = {
66 .name = "88pm860x-touch",
68 .resources = &touch_resources[0],
72 #define PM8607_REG_RESOURCE(_start, _end) \
74 .start = PM8607_##_start, \
75 .end = PM8607_##_end, \
76 .flags = IORESOURCE_IO, \
79 static struct resource power_supply_resources[] = {
81 .name = "88pm860x-power",
82 .start = PM8607_IRQ_CHG,
83 .end = PM8607_IRQ_CHG,
84 .flags = IORESOURCE_IRQ,
88 static struct mfd_cell power_devs[] = {
90 .name = "88pm860x-power",
92 .resources = &power_supply_resources[0],
97 static struct resource onkey_resources[] = {
99 .name = "88pm860x-onkey",
100 .start = PM8607_IRQ_ONKEY,
101 .end = PM8607_IRQ_ONKEY,
102 .flags = IORESOURCE_IRQ,
106 static struct mfd_cell onkey_devs[] = {
108 .name = "88pm860x-onkey",
110 .resources = &onkey_resources[0],
115 static struct resource codec_resources[] = {
117 /* Headset microphone insertion or removal */
119 .start = PM8607_IRQ_MICIN,
120 .end = PM8607_IRQ_MICIN,
121 .flags = IORESOURCE_IRQ,
123 /* Hook-switch press or release */
125 .start = PM8607_IRQ_HOOK,
126 .end = PM8607_IRQ_HOOK,
127 .flags = IORESOURCE_IRQ,
129 /* Headset insertion or removal */
131 .start = PM8607_IRQ_HEADSET,
132 .end = PM8607_IRQ_HEADSET,
133 .flags = IORESOURCE_IRQ,
136 .name = "audio-short",
137 .start = PM8607_IRQ_AUDIO_SHORT,
138 .end = PM8607_IRQ_AUDIO_SHORT,
139 .flags = IORESOURCE_IRQ,
143 static struct mfd_cell codec_devs[] = {
145 .name = "88pm860x-codec",
146 .num_resources = ARRAY_SIZE(codec_resources),
147 .resources = &codec_resources[0],
152 static struct resource regulator_resources[] = {
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),
167 PM8607_REG_RESOURCE(VIBRATOR_SET, VIBRATOR_SET),
168 PM8607_REG_RESOURCE(LDO14, LDO14),
171 #define PM8607_REG_DEVS(_id) \
173 .name = "88pm860x-regulator", \
174 .num_resources = 1, \
175 .resources = ®ulator_resources[PM8607_ID_##_id], \
176 .id = PM8607_ID_##_id, \
179 static struct mfd_cell regulator_devs[] = {
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),
194 PM8607_REG_DEVS(LDO13),
195 PM8607_REG_DEVS(LDO14),
198 struct pm860x_irq_data {
201 int enable; /* enable or not */
202 int offs; /* bit offset in mask register */
205 static struct pm860x_irq_data pm860x_irqs[] = {
206 [PM8607_IRQ_ONKEY] = {
207 .reg = PM8607_INT_STATUS1,
208 .mask_reg = PM8607_INT_MASK_1,
211 [PM8607_IRQ_EXTON] = {
212 .reg = PM8607_INT_STATUS1,
213 .mask_reg = PM8607_INT_MASK_1,
217 .reg = PM8607_INT_STATUS1,
218 .mask_reg = PM8607_INT_MASK_1,
222 .reg = PM8607_INT_STATUS1,
223 .mask_reg = PM8607_INT_MASK_1,
227 .reg = PM8607_INT_STATUS1,
228 .mask_reg = PM8607_INT_MASK_1,
232 .reg = PM8607_INT_STATUS1,
233 .mask_reg = PM8607_INT_MASK_1,
236 [PM8607_IRQ_VBAT] = {
237 .reg = PM8607_INT_STATUS2,
238 .mask_reg = PM8607_INT_MASK_2,
241 [PM8607_IRQ_VCHG] = {
242 .reg = PM8607_INT_STATUS2,
243 .mask_reg = PM8607_INT_MASK_2,
246 [PM8607_IRQ_VSYS] = {
247 .reg = PM8607_INT_STATUS2,
248 .mask_reg = PM8607_INT_MASK_2,
251 [PM8607_IRQ_TINT] = {
252 .reg = PM8607_INT_STATUS2,
253 .mask_reg = PM8607_INT_MASK_2,
256 [PM8607_IRQ_GPADC0] = {
257 .reg = PM8607_INT_STATUS2,
258 .mask_reg = PM8607_INT_MASK_2,
261 [PM8607_IRQ_GPADC1] = {
262 .reg = PM8607_INT_STATUS2,
263 .mask_reg = PM8607_INT_MASK_2,
266 [PM8607_IRQ_GPADC2] = {
267 .reg = PM8607_INT_STATUS2,
268 .mask_reg = PM8607_INT_MASK_2,
271 [PM8607_IRQ_GPADC3] = {
272 .reg = PM8607_INT_STATUS2,
273 .mask_reg = PM8607_INT_MASK_2,
276 [PM8607_IRQ_AUDIO_SHORT] = {
277 .reg = PM8607_INT_STATUS3,
278 .mask_reg = PM8607_INT_MASK_3,
282 .reg = PM8607_INT_STATUS3,
283 .mask_reg = PM8607_INT_MASK_3,
286 [PM8607_IRQ_HEADSET] = {
287 .reg = PM8607_INT_STATUS3,
288 .mask_reg = PM8607_INT_MASK_3,
291 [PM8607_IRQ_HOOK] = {
292 .reg = PM8607_INT_STATUS3,
293 .mask_reg = PM8607_INT_MASK_3,
296 [PM8607_IRQ_MICIN] = {
297 .reg = PM8607_INT_STATUS3,
298 .mask_reg = PM8607_INT_MASK_3,
301 [PM8607_IRQ_CHG_FAIL] = {
302 .reg = PM8607_INT_STATUS3,
303 .mask_reg = PM8607_INT_MASK_3,
306 [PM8607_IRQ_CHG_DONE] = {
307 .reg = PM8607_INT_STATUS3,
308 .mask_reg = PM8607_INT_MASK_3,
311 [PM8607_IRQ_CHG_FAULT] = {
312 .reg = PM8607_INT_STATUS3,
313 .mask_reg = PM8607_INT_MASK_3,
318 static irqreturn_t pm860x_irq(int irq, void *data)
320 struct pm860x_chip *chip = data;
321 struct pm860x_irq_data *irq_data;
322 struct i2c_client *i2c;
323 int read_reg = -1, value = 0;
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);
333 if (value & irq_data->enable)
334 handle_nested_irq(chip->irq_base + i);
339 static void pm860x_irq_lock(struct irq_data *data)
341 struct pm860x_chip *chip = irq_data_get_irq_chip_data(data);
343 mutex_lock(&chip->irq_lock);
346 static void pm860x_irq_sync_unlock(struct irq_data *data)
348 struct pm860x_chip *chip = irq_data_get_irq_chip_data(data);
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];
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++)
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;
366 case PM8607_INT_MASK_2:
367 mask[1] &= ~irq_data->offs;
368 mask[1] |= irq_data->enable;
370 case PM8607_INT_MASK_3:
371 mask[2] &= ~irq_data->offs;
372 mask[2] |= irq_data->enable;
375 dev_err(chip->dev, "wrong IRQ\n");
379 /* update mask into registers */
380 for (i = 0; i < 3; i++) {
381 if (mask[i] != cached[i]) {
383 pm860x_reg_write(i2c, PM8607_INT_MASK_1 + i, mask[i]);
387 mutex_unlock(&chip->irq_lock);
390 static void pm860x_irq_enable(struct irq_data *data)
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;
397 static void pm860x_irq_disable(struct irq_data *data)
399 struct pm860x_chip *chip = irq_data_get_irq_chip_data(data);
400 pm860x_irqs[data->irq - chip->irq_base].enable = 0;
403 static struct irq_chip pm860x_irq_chip = {
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,
411 static int __devinit device_gpadc_init(struct pm860x_chip *chip,
412 struct pm860x_platform_data *pdata)
414 struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
419 /* initialize GPADC without activating it */
421 if (!pdata || !pdata->touch)
424 /* set GPADC MISC1 register */
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;
431 ret = pm860x_reg_write(i2c, PM8607_GPADC_MISC1, data);
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);
442 /* set prebias & prechg time of pen detect */
444 data |= pdata->touch->pen_prebias & PM8607_PD_PREBIAS_MASK;
445 data |= (pdata->touch->pen_prechg << 5) & PM8607_PD_PRECHG_MASK;
447 ret = pm860x_reg_write(i2c, PM8607_PD_PREBIAS, data);
452 ret = pm860x_set_bits(i2c, PM8607_GPADC_MISC1,
453 PM8607_GPADC_EN, PM8607_GPADC_EN);
458 static int __devinit device_irq_init(struct pm860x_chip *chip,
459 struct pm860x_platform_data *pdata)
461 struct i2c_client *i2c = (chip->id == CHIP_PM8607) ? chip->client \
463 unsigned char status_buf[INT_STATUS_NUM];
464 unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
465 struct irq_desc *desc;
466 int i, data, mask, ret = -EINVAL;
469 if (!pdata || !pdata->irq_base) {
470 dev_warn(chip->dev, "No interrupt support on IRQ base\n");
474 mask = PM8607_B0_MISC1_INV_INT | PM8607_B0_MISC1_INT_CLEAR
475 | PM8607_B0_MISC1_INT_MASK;
478 if (pdata && pdata->irq_mode) {
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.
484 data |= PM8607_B0_MISC1_INT_CLEAR;
487 ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, mask, data);
492 memset(status_buf, 0, INT_STATUS_NUM);
493 ret = pm860x_bulk_write(i2c, PM8607_INT_MASK_1,
494 INT_STATUS_NUM, status_buf);
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);
504 /* clear interrupt status by read */
505 ret = pm860x_bulk_read(i2c, PM8607_INT_STATUS1,
506 INT_STATUS_NUM, status_buf);
511 mutex_init(&chip->irq_lock);
512 chip->irq_base = pdata->irq_base;
513 chip->core_irq = i2c->irq;
517 desc = irq_to_desc(chip->core_irq);
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,
525 set_irq_nested_thread(__irq, 1);
527 set_irq_flags(__irq, IRQF_VALID);
529 set_irq_noprobe(__irq);
533 ret = request_threaded_irq(chip->core_irq, NULL, pm860x_irq, flags,
536 dev_err(chip->dev, "Failed to request IRQ: %d\n", ret);
546 static void device_irq_exit(struct pm860x_chip *chip)
549 free_irq(chip->core_irq, chip);
552 static void __devinit device_bk_init(struct pm860x_chip *chip,
553 struct i2c_client *i2c,
554 struct pm860x_platform_data *pdata)
559 if ((pdata == NULL) || (pdata->backlight == NULL))
562 if (pdata->num_backlights > ARRAY_SIZE(bk_devs))
563 pdata->num_backlights = ARRAY_SIZE(bk_devs);
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];
570 for (j = 0; j < ARRAY_SIZE(bk_devs); j++) {
571 id = bk_resources[j].start;
572 if (bk_pdata[i].flags != id)
575 bk_devs[i].num_resources = 1;
576 bk_devs[i].resources = &bk_resources[j];
577 ret = mfd_add_devices(chip->dev, 0,
579 &bk_resources[j], 0);
581 dev_err(chip->dev, "Failed to add "
582 "backlight subdev\n");
589 static void __devinit device_led_init(struct pm860x_chip *chip,
590 struct i2c_client *i2c,
591 struct pm860x_platform_data *pdata)
596 if ((pdata == NULL) || (pdata->led == NULL))
599 if (pdata->num_leds > ARRAY_SIZE(led_devs))
600 pdata->num_leds = ARRAY_SIZE(led_devs);
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];
607 for (j = 0; j < ARRAY_SIZE(led_devs); j++) {
608 id = led_resources[j].start;
609 if (led_pdata[i].flags != id)
612 led_devs[i].num_resources = 1;
613 led_devs[i].resources = &led_resources[j],
614 ret = mfd_add_devices(chip->dev, 0,
616 &led_resources[j], 0);
618 dev_err(chip->dev, "Failed to add "
626 static void __devinit device_8607_init(struct pm860x_chip *chip,
627 struct i2c_client *i2c,
628 struct pm860x_platform_data *pdata)
632 ret = pm860x_reg_read(i2c, PM8607_CHIP_ID);
634 dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
637 switch (ret & PM8607_VERSION_MASK) {
640 dev_info(chip->dev, "Marvell 88PM8607 (ID: %02x) detected\n",
644 dev_err(chip->dev, "Failed to detect Marvell 88PM8607. "
645 "Chip ID: %02x\n", ret);
649 ret = pm860x_reg_read(i2c, PM8607_BUCK3);
651 dev_err(chip->dev, "Failed to read BUCK3 register: %d\n", ret);
654 if (ret & PM8607_BUCK3_DOUBLE)
655 chip->buck3_double = 1;
657 ret = pm860x_reg_read(i2c, PM8607_B0_MISC1);
659 dev_err(chip->dev, "Failed to read MISC1 register: %d\n", ret);
663 if (pdata && (pdata->i2c_port == PI2C_PORT))
664 data = PM8607_B0_MISC1_PI2C;
667 ret = pm860x_set_bits(i2c, PM8607_B0_MISC1, PM8607_B0_MISC1_PI2C, data);
669 dev_err(chip->dev, "Failed to access MISC1:%d\n", ret);
673 ret = device_gpadc_init(chip, pdata);
677 ret = device_irq_init(chip, pdata);
681 ret = mfd_add_devices(chip->dev, 0, ®ulator_devs[0],
682 ARRAY_SIZE(regulator_devs),
683 ®ulator_resources[0], 0);
685 dev_err(chip->dev, "Failed to add regulator subdev\n");
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);
694 dev_err(chip->dev, "Failed to add touch "
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);
705 dev_err(chip->dev, "Failed to add power supply "
711 ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
712 ARRAY_SIZE(onkey_devs),
713 &onkey_resources[0], 0);
715 dev_err(chip->dev, "Failed to add onkey subdev\n");
719 ret = mfd_add_devices(chip->dev, 0, &codec_devs[0],
720 ARRAY_SIZE(codec_devs),
721 &codec_resources[0], 0);
723 dev_err(chip->dev, "Failed to add codec subdev\n");
728 mfd_remove_devices(chip->dev);
729 device_irq_exit(chip);
734 int __devinit pm860x_device_init(struct pm860x_chip *chip,
735 struct pm860x_platform_data *pdata)
741 device_bk_init(chip, chip->client, pdata);
742 device_led_init(chip, chip->client, pdata);
745 device_8607_init(chip, chip->client, pdata);
749 if (chip->companion) {
752 device_bk_init(chip, chip->companion, pdata);
753 device_led_init(chip, chip->companion, pdata);
756 device_8607_init(chip, chip->companion, pdata);
764 void __devexit pm860x_device_exit(struct pm860x_chip *chip)
766 device_irq_exit(chip);
767 mfd_remove_devices(chip->dev);
770 MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM860x");
771 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
772 MODULE_LICENSE("GPL");