All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/3 V1] add 88pm80x mfd driver
@ 2012-06-28  3:13 Qiao Zhou
  2012-06-28  3:13 ` [PATCH 1/3] mfd: support 88pm80x in 80x driver Qiao Zhou
                   ` (2 more replies)
  0 siblings, 3 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-06-28  3:13 UTC (permalink / raw)
  To: linux-arm-kernel

change log [v1]:
1, pm800 and pm805 are decoupled and probed separately;
2, re-used the most of API for pm800 and pm805 per Arnd's comments;
3, use regmap_irq, instead of previous 88pm80x_irq_data per Mark's comments.
use regmap_add_irq_chip, and remove previous 88pm80x irq handling.
4, remove callback function in rtc pdata per Arnd's comments.
5, updated some coding style issue.

Qiao Zhou (3):
  mfd: support 88pm80x in 80x driver
  rtc: add rtc support to 88PM80X PMIC
  input: add onkey support to 88PM80X PMIC

 drivers/input/misc/88pm80x_onkey.c |  194 ++++++++++
 drivers/input/misc/Kconfig         |   10 +
 drivers/input/misc/Makefile        |    1 +
 drivers/mfd/88pm80x-core.c         |  545 +++++++++++++++++++++++++++++
 drivers/mfd/88pm80x-i2c.c          |  274 +++++++++++++++
 drivers/mfd/Kconfig                |   12 +
 drivers/mfd/Makefile               |    2 +
 drivers/rtc/Kconfig                |   10 +
 drivers/rtc/Makefile               |    1 +
 drivers/rtc/rtc-88pm80x.c          |  370 ++++++++++++++++++++
 include/linux/mfd/88pm80x.h        |  678 ++++++++++++++++++++++++++++++++++++
 11 files changed, 2097 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/88pm80x_onkey.c
 create mode 100644 drivers/mfd/88pm80x-core.c
 create mode 100644 drivers/mfd/88pm80x-i2c.c
 create mode 100644 drivers/rtc/rtc-88pm80x.c
 create mode 100644 include/linux/mfd/88pm80x.h

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28  3:13 [PATCH 0/3 V1] add 88pm80x mfd driver Qiao Zhou
@ 2012-06-28  3:13 ` Qiao Zhou
  2012-06-28 11:21   ` Arnd Bergmann
  2012-06-29 14:21   ` Arnd Bergmann
  2012-06-28  3:13 ` [PATCH 2/3] rtc: add rtc support to 88PM80X PMIC Qiao Zhou
  2012-06-28  3:13 ` [PATCH 3/3] input: add onkey " Qiao Zhou
  2 siblings, 2 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-06-28  3:13 UTC (permalink / raw)
  To: linux-arm-kernel

88PM800 and 88PM805 are two discrete chips used for power management.
Hardware designer can use them together or only one of them according to
requirement.

due to hardware design, there are some registers used by pm805, but actually
defined in pm800 chip. so pm805 needs to access pm800 register in some
occasion. A workaround is used in driver to solve such issue and would be
removed when it's fixed in latter new chip version.

All I2C operations are accessed by 80x-i2c driver, and register access is via
regmap interface.

The benefit is that client drivers only need one kind of read/write API. I2C
and MFD driver can be shared in both 800 and 805.

Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
 drivers/mfd/88pm80x-core.c  |  545 ++++++++++++++++++++++++++++++++++
 drivers/mfd/88pm80x-i2c.c   |  274 +++++++++++++++++
 drivers/mfd/Kconfig         |   12 +
 drivers/mfd/Makefile        |    2 +
 include/linux/mfd/88pm80x.h |  678 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1511 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/88pm80x-core.c
 create mode 100644 drivers/mfd/88pm80x-i2c.c
 create mode 100644 include/linux/mfd/88pm80x.h

diff --git a/drivers/mfd/88pm80x-core.c b/drivers/mfd/88pm80x-core.c
new file mode 100644
index 0000000..b836c0c
--- /dev/null
+++ b/drivers/mfd/88pm80x-core.c
@@ -0,0 +1,545 @@
+/*
+ * Base driver for Marvell 88PM80X
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Joseph(Yossi) Hanin <yhanin@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+static struct resource rtc_resources[] = {
+	{
+	 .name = "88pm80x-rtc",
+	 .start = PM800_IRQ_RTC,
+	 .end = PM800_IRQ_RTC,
+	 .flags = IORESOURCE_IRQ,
+	 },
+};
+
+static struct mfd_cell rtc_devs[] = {
+	{
+	 .name = "88pm80x-rtc",
+	 .num_resources = ARRAY_SIZE(rtc_resources),
+	 .resources = &rtc_resources[0],
+	 .id = -1,
+	 },
+};
+
+static struct resource onkey_resources[] = {
+	{
+	 .name = "88pm80x-onkey",
+	 .start = PM800_IRQ_ONKEY,
+	 .end = PM800_IRQ_ONKEY,
+	 .flags = IORESOURCE_IRQ,
+	 },
+};
+
+static struct mfd_cell onkey_devs[] = {
+	{
+	 .name = "88pm80x-onkey",
+	 .num_resources = 1,
+	 .resources = &onkey_resources[0],
+	 .id = -1,
+	 },
+};
+
+static struct resource codec_resources[] = {
+	{
+	 /* Headset microphone insertion or removal */
+	 .name = "micin",
+	 .start = PM805_IRQ_MIC_DET,
+	 .end = PM805_IRQ_MIC_DET,
+	 .flags = IORESOURCE_IRQ,
+	 },
+	{
+	 /* Audio short HP1 */
+	 .name = "audio-short1",
+	 .start = PM805_IRQ_HP1_SHRT,
+	 .end = PM805_IRQ_HP1_SHRT,
+	 .flags = IORESOURCE_IRQ,
+	 },
+	{
+	 /* Audio short HP2 */
+	 .name = "audio-short2",
+	 .start = PM805_IRQ_HP2_SHRT,
+	 .end = PM805_IRQ_HP2_SHRT,
+	 .flags = IORESOURCE_IRQ,
+	 },
+};
+
+static struct mfd_cell codec_devs[] = {
+	{
+	 .name = "88pm80x-codec",
+	 .num_resources = ARRAY_SIZE(codec_resources),
+	 .resources = &codec_resources[0],
+	 .id = -1,
+	 },
+};
+
+static const struct regmap_irq pm800_irqs[] = {
+	/* INT0 */
+	[PM800_IRQ_ONKEY] = {
+		.mask = PM800_ONKEY_INT_ENA1,
+	},
+	[PM800_IRQ_EXTON] = {
+		.mask = PM800_EXTON_INT_ENA1,
+	},
+	[PM800_IRQ_CHG] = {
+		.mask = PM800_CHG_INT_ENA1,
+	},
+	[PM800_IRQ_BAT] = {
+		.mask = PM800_BAT_INT_ENA1,
+	},
+	[PM800_IRQ_RTC] = {
+		.mask = PM800_RTC_INT_ENA1,
+	},
+	[PM800_IRQ_CLASSD] = {
+		.mask = PM800_CLASSD_OC_INT_ENA1,
+	},
+	/* INT1 */
+	[PM800_IRQ_VBAT] = {
+		.reg_offset = 1,
+		.mask = PM800_VBAT_INT_ENA2,
+	},
+	[PM800_IRQ_VSYS] = {
+		.reg_offset = 1,
+		.mask = PM800_VSYS_INT_ENA2,
+	},
+	[PM800_IRQ_VCHG] = {
+		.reg_offset = 1,
+		.mask = PM800_VCHG_INT_ENA2,
+	},
+	[PM800_IRQ_TINT] = {
+		.reg_offset = 1,
+		.mask = PM800_TINT_INT_ENA2,
+	},
+	/* INT2 */
+	[PM800_IRQ_GPADC0] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC0_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC1] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC1_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC2] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC2_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC3] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC3_INT_ENA3,
+	},
+	[PM800_IRQ_GPADC4] = {
+		.reg_offset = 2,
+		.mask = PM800_GPADC4_INT_ENA3,
+	},
+	/* INT3 */
+	[PM800_IRQ_GPIO0] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO0_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO1] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO1_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO2] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO2_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO3] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO3_INT_ENA4,
+	},
+	[PM800_IRQ_GPIO4] = {
+		.reg_offset = 3,
+		.mask = PM800_GPIO4_INT_ENA4,
+	},
+};
+
+static struct regmap_irq pm805_irqs[] = {
+	/* INT0 */
+	[PM805_IRQ_LDO_OFF] = {
+		.mask = PM805_INT1_HP1_SHRT,
+	},
+	[PM805_IRQ_SRC_DPLL_LOCK] = {
+		.mask = PM805_INT1_HP2_SHRT,
+	},
+	[PM805_IRQ_CLIP_FAULT] = {
+		.mask = PM805_INT1_MIC_CONFLICT,
+	},
+	[PM805_IRQ_MIC_CONFLICT] = {
+		.mask = PM805_INT1_CLIP_FAULT,
+	},
+	[PM805_IRQ_HP2_SHRT] = {
+		.mask = PM805_INT1_LDO_OFF,
+	},
+	[PM805_IRQ_HP1_SHRT] = {
+		.mask = PM805_INT1_SRC_DPLL_LOCK,
+	},
+	/* INT1 */
+	[PM805_IRQ_FINE_PLL_FAULT] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_MIC_DET,
+	},
+	[PM805_IRQ_RAW_PLL_FAULT] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_SHRT_BTN_DET,
+	},
+	[PM805_IRQ_VOLP_BTN_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_VOLM_BTN_DET,
+	},
+	[PM805_IRQ_VOLM_BTN_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_VOLP_BTN_DET,
+	},
+	[PM805_IRQ_SHRT_BTN_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_RAW_PLL_FAULT,
+	},
+	[PM805_IRQ_MIC_DET] = {
+		.reg_offset = 1,
+		.mask = PM805_INT2_FINE_PLL_FAULT,
+	},
+};
+
+static int __devinit device_gpadc_init(struct pm80x_chip *chip,
+				       struct pm80x_platform_data *pdata)
+{
+	struct pm80x_subchip *subchip = chip->subchip;
+	struct i2c_client *i2c = subchip->gpadc_page;
+	struct regmap *map = dev_get_regmap(&i2c->dev, NULL);
+	int data = 0, mask = 0, ret = 0;
+
+	if (!map) {
+		dev_warn(chip->dev,
+			 "Warning: gpadc regmap is not available!\n");
+		return -EINVAL;
+	}
+	/* initialize GPADC without activating it turn on GPADC
+	 * measurments
+	 */
+	ret = regmap_update_bits(map,
+				 PM800_GPADC_MISC_CONFIG2,
+				 PM800_GPADC_MISC_GPFSM_EN,
+				 PM800_GPADC_MISC_GPFSM_EN);
+	if (ret < 0)
+		goto out;
+	/*
+	 * This function configures the ADC as requires for
+	 * CP implementation.CP does not "own" the ADC configuration
+	 * registers and relies on AP.
+	 * Reason: enable automatic ADC measurements needed
+	 * for CP to get VBAT and RF temperature readings.
+	 */
+	ret = regmap_update_bits(map, PM800_GPADC_MEAS_EN1,
+				 PM800_MEAS_EN1_VBAT, PM800_MEAS_EN1_VBAT);
+	if (ret < 0)
+		goto out;
+	ret = regmap_update_bits(map, PM800_GPADC_MEAS_EN2,
+				 (PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN),
+				 (PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN));
+	if (ret < 0)
+		goto out;
+
+	/* the defult of PM800 is GPADC operates@100Ks/s rate
+	 * and Number of GPADC slots with active current bias prior
+	 * to GPADC sampling = 1 slot for all GPADCs set for
+	 * Temprature mesurmants
+	 */
+	mask = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 |
+		PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3);
+
+	if (pdata && (pdata->batt_det == 0)) {
+		data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 |
+			PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3);
+	} else {
+		data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN2 |
+			PM800_GPADC_GP_BIAS_EN3);
+	}
+	ret = regmap_update_bits(map, PM800_GP_BIAS_ENA1, mask, data);
+	if (ret < 0)
+		goto out;
+
+	dev_info(chip->dev, "pm80x device_gpadc_init: Done\n");
+	return 0;
+
+out:
+	dev_info(chip->dev, "pm80x device_gpadc_init: Failed!\n");
+	return ret;
+}
+
+static int __devinit device_irq_init_80x(struct pm80x_chip *chip)
+{
+	struct i2c_client *i2c = chip->client;
+	struct regmap *map = dev_get_regmap(&i2c->dev, NULL);
+	unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+	int data, mask, ret = -EINVAL;
+
+	if (!map || !chip->irq) {
+		dev_err(chip->dev, "incorrect parameters\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * irq_mode defines the way of clearing interrupt. currently only
+	 * suport write 1 to clear status, which is aligned with regmap
+	 * implementation.
+	 */
+	if (chip->id == CHIP_PM800) {
+		flags |= IRQF_SHARED;
+		mask =
+		    PM800_WAKEUP2_INV_INT | PM800_WAKEUP2_INT_CLEAR |
+		    PM800_WAKEUP2_INT_MASK;
+
+		data = PM800_WAKEUP2_INT_CLEAR;
+		ret = regmap_update_bits(map, PM800_WAKEUP2, mask, data);
+	} else if (chip->id == CHIP_PM805) {
+		mask =
+		    PM805_STATUS0_INT_CLEAR | PM805_STATUS0_INV_INT |
+		    PM800_STATUS0_INT_MASK;
+
+		data = PM805_STATUS0_INT_CLEAR;
+		ret = regmap_update_bits(map, PM805_INT_STATUS0, mask, data);
+		/* PM805_INT_STATUS is under 32K clock domain, so need to
+		 * add proper delay before the next I2C register access.
+		 */
+		msleep(1);
+	}
+
+	if (ret < 0)
+		goto out;
+
+	ret =
+	    regmap_add_irq_chip(chip->regmap, chip->irq, flags, chip->irq_base,
+				chip->regmap_irq_chip, &chip->irq_data);
+
+out:
+	return ret;
+}
+
+static void device_irq_exit_80x(struct pm80x_chip *chip)
+{
+	regmap_del_irq_chip(chip->irq, chip->irq_data);
+}
+
+static struct regmap_irq_chip pm805_irq_chip = {
+	.name = "88pm805",
+	.irqs = pm805_irqs,
+	.num_irqs = ARRAY_SIZE(pm805_irqs),
+
+	.num_regs = 2,
+	.status_base = PM805_INT_STATUS1,
+	.mask_base = PM805_INT_MASK1,
+	.ack_base = PM805_INT_STATUS1,
+};
+
+static struct regmap_irq_chip pm800_irq_chip = {
+	.name = "88pm800",
+	.irqs = pm800_irqs,
+	.num_irqs = ARRAY_SIZE(pm800_irqs),
+
+	.num_regs = 4,
+	.status_base = PM800_INT_STATUS1,
+	.mask_base = PM800_INT_ENA_1,
+	.ack_base = PM800_INT_STATUS1,
+};
+
+static int __devinit device_805_init(struct pm80x_chip *chip,
+				     struct i2c_client *i2c,
+				     struct pm80x_platform_data *pdata)
+{
+	int ret = 0;
+	struct regmap *map = dev_get_regmap(&i2c->dev, NULL);
+
+	if (!map) {
+		dev_err(chip->dev, "regmap is invalid\n");
+		return -EINVAL;
+	}
+
+	dev_info(chip->dev, "pm80x:%s slave addr[0x%x]\n", __func__, i2c->addr);
+
+	regmap_read(map, PM805_CHIP_ID, &ret);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
+		goto out_irq_init;
+	}
+	chip->version = ret;
+
+	chip->regmap_irq_chip = &pm805_irq_chip;
+
+	ret = device_irq_init_80x(chip);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to init pm805 irq!\n");
+		goto out_irq_init;
+	}
+
+	ret = mfd_add_devices(chip->dev, 0, &codec_devs[0],
+			      ARRAY_SIZE(codec_devs), &codec_resources[0], 0);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to add codec subdev\n");
+		goto out_codec;
+	} else
+		dev_info(chip->dev, "[%s]:Added mfd codec_devs\n", __func__);
+
+	if (pdata->pm80x_plat_config)
+		pdata->pm80x_plat_config(chip, pdata);
+
+	return 0;
+
+out_codec:
+	device_irq_exit_80x(chip);
+out_irq_init:
+	return ret;
+}
+
+static int __devinit device_800_init(struct pm80x_chip *chip,
+				     struct i2c_client *i2c,
+				     struct pm80x_platform_data *pdata)
+{
+	struct regmap *map = dev_get_regmap(&i2c->dev, NULL);
+	int ret, data, pmic_id;
+
+	if (!map) {
+		dev_err(chip->dev, "regmap is invalid\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_read(map, PM800_CHIP_ID, &data);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
+		goto out;
+	}
+
+	pmic_id = data & PM80X_VERSION_MASK;
+
+	if ((pmic_id >= PM800_CHIP_A0) && (pmic_id <= PM800_CHIP_END)) {
+		chip->version = data;
+		dev_info(chip->dev,
+			 "88PM80x:Marvell 88PM800 (ID:0x%x) detected\n", ret);
+	} else {
+		dev_err(chip->dev,
+			"Failed to detect Marvell 88PM800:ChipID[0x%x]\n", ret);
+		goto out;
+	}
+
+	/*
+	 * alarm wake up bit will be clear in device_irq_init(),
+	 * read before that
+	 */
+	ret = regmap_read(map, PM800_RTC_CONTROL, &data);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to read RTC register: %d\n", ret);
+		goto out;
+	}
+	if (data & PM800_ALARM_WAKEUP) {
+		if (pdata && pdata->rtc)
+			pdata->rtc->rtc_wakeup = 1;
+	}
+
+	ret = device_gpadc_init(chip, pdata);
+	if (ret < 0) {
+		dev_err(chip->dev, "[%s]Failed to init gpadc\n", __func__);
+		goto out;
+	}
+
+	chip->regmap_irq_chip = &pm800_irq_chip;
+
+	ret = device_irq_init_80x(chip);
+	if (ret < 0) {
+		dev_err(chip->dev, "[%s]Failed to init pm800 irq\n", __func__);
+		goto out;
+	}
+
+	ret = mfd_add_devices(chip->dev, 0, &onkey_devs[0],
+			      ARRAY_SIZE(onkey_devs), &onkey_resources[0],
+			      chip->irq_base);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to add onkey subdev\n");
+		goto out_dev;
+	} else
+		dev_info(chip->dev, "[%s]:Added mfd onkey_devs\n", __func__);
+
+	if (pdata && pdata->rtc) {
+		rtc_devs[0].platform_data = pdata->rtc;
+		rtc_devs[0].pdata_size = sizeof(struct pm80x_rtc_pdata);
+		ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0],
+				      ARRAY_SIZE(rtc_devs), NULL,
+				      chip->irq_base);
+		if (ret < 0) {
+			dev_err(chip->dev, "Failed to add rtc subdev\n");
+			goto out_dev;
+		} else
+			dev_info(chip->dev,
+				 "[%s]:Added mfd rtc_devs\n", __func__);
+	}
+
+	if (pdata->pm80x_plat_config)
+		pdata->pm80x_plat_config(chip, pdata);
+
+	return 0;
+out_dev:
+	mfd_remove_devices(chip->dev);
+	device_irq_exit_80x(chip);
+out:
+	return ret;
+}
+
+int __devinit pm80x_device_init(struct pm80x_chip *chip,
+				struct pm80x_platform_data *pdata)
+{
+	int ret = 0;
+
+	switch (chip->id) {
+	case CHIP_PM800:
+		ret = device_800_init(chip, chip->client, pdata);
+		break;
+	case CHIP_PM805:
+		ret = device_805_init(chip, chip->client, pdata);
+		break;
+	default:
+		dev_err(chip->dev, "%s incorrect chip id!\n", __func__);
+		return -EINVAL;
+	}
+
+	if (ret)
+		dev_err(chip->dev, "%s id 0x%x failed!\n", __func__, chip->id);
+
+	return ret;
+}
+
+void __devexit pm80x_device_exit(struct pm80x_chip *chip)
+{
+	device_irq_exit_80x(chip);
+	mfd_remove_devices(chip->dev);
+}
+
+MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM80x");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/88pm80x-i2c.c b/drivers/mfd/88pm80x-i2c.c
new file mode 100644
index 0000000..71ebcd5
--- /dev/null
+++ b/drivers/mfd/88pm80x-i2c.c
@@ -0,0 +1,274 @@
+/*
+ * I2C driver for Marvell 88PM80x
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Joseph(Yossi) Hanin <yhanin@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <linux/err.h>
+
+/*
+ * workaround: some registers needed by pm805 are defined in pm800, so
+ * need to use this global variable to maintain the relation between
+ * pm800 and pm805. would remove it after HW chip fixes the issue.
+ */
+static struct pm80x_chip *g_pm80x_chip;
+
+static struct regmap_config pm80x_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static const struct i2c_device_id pm80x_id_table[] = {
+	{"88PM800", CHIP_PM800},
+	{"88PM805", CHIP_PM805},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
+
+static int pm80x_pages_init(struct pm80x_chip *chip, struct i2c_client *client)
+{
+	struct pm80x_subchip *subchip;
+
+	subchip = chip->subchip;
+	/* PM800 block power: i2c addr 0x31 */
+	if (subchip->power_page_addr) {
+		subchip->power_page =
+		    i2c_new_dummy(client->adapter, subchip->power_page_addr);
+		subchip->regmap_power =
+		    devm_regmap_init_i2c(subchip->power_page,
+					 &pm80x_regmap_config);
+		i2c_set_clientdata(subchip->power_page, chip);
+	} else
+		dev_info(chip->dev,
+			 "PM800 block power 0x31: No power_page_addr\n");
+
+	/* PM800 block GPADC: i2c addr 0x32 */
+	if (subchip->gpadc_page_addr) {
+		subchip->gpadc_page = i2c_new_dummy(client->adapter,
+						    subchip->gpadc_page_addr);
+		subchip->regmap_gpadc =
+		    devm_regmap_init_i2c(subchip->gpadc_page,
+					 &pm80x_regmap_config);
+		i2c_set_clientdata(subchip->gpadc_page, chip);
+	} else
+		dev_info(chip->dev,
+			 "PM800 block GPADC 0x32: No gpadc_page_addr\n");
+
+	return 0;
+}
+
+static void pm80x_pages_exit(struct pm80x_chip *chip)
+{
+	struct pm80x_subchip *subchip;
+
+	/*
+	 * workaround: clear the dependency between pm800 and pm805.
+	 * would remove it after HW chip fixes the issue.
+	 */
+	if (g_pm80x_chip->companion)
+		g_pm80x_chip->companion = NULL;
+	else
+		g_pm80x_chip = NULL;
+
+	regmap_exit(chip->regmap);
+	i2c_unregister_device(chip->client);
+
+	if (chip->id != CHIP_PM800 || !chip->subchip)
+		return;
+
+	subchip = chip->subchip;
+	if (subchip->power_page) {
+		regmap_exit(subchip->regmap_power);
+		i2c_unregister_device(subchip->power_page);
+	}
+	if (subchip->gpadc_page) {
+		regmap_exit(subchip->regmap_gpadc);
+		i2c_unregister_device(subchip->gpadc_page);
+	}
+}
+
+static int __devinit pm80x_probe(struct i2c_client *client,
+				 const struct i2c_device_id *id)
+{
+	struct pm80x_platform_data *pdata = client->dev.platform_data;
+	struct pm80x_chip *chip;
+	struct pm80x_subchip *subchip;
+	struct regmap *map;
+	int ret = 0;
+
+	if (!pdata) {
+		dev_err(&client->dev, "No platform data in %s!\n", __func__);
+		return -EINVAL;
+	}
+
+	chip =
+	    devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	map = devm_regmap_init_i2c(client, &pm80x_regmap_config);
+	if (IS_ERR(map)) {
+		ret = PTR_ERR(map);
+		dev_err(&client->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto err_regmap_init;
+	}
+
+	chip->id = id->driver_data;
+	if (chip->id < CHIP_PM800 || chip->id > CHIP_PM805) {
+		ret = -EINVAL;
+		goto err_subchip_alloc;
+	}
+
+	chip->client = client;
+	chip->regmap = map;
+
+	chip->irq = client->irq;
+	chip->irq_base = pdata->irq_base;
+
+	i2c_set_clientdata(client, chip);
+	chip->dev = &client->dev;
+	dev_set_drvdata(chip->dev, chip);
+
+	/*
+	 * workaround: set g_pm80x_chip to the first probed chip. if the
+	 * second chip is probed, just point to the companion to each
+	 * other so that pm805 can access those specific register. would
+	 * remove it after HW chip fixes the issue.
+	 */
+	if (!g_pm80x_chip)
+		g_pm80x_chip = chip;
+	else {
+		chip->companion = g_pm80x_chip->client;
+		g_pm80x_chip->companion = chip->client;
+	}
+
+	chip->regmap = devm_regmap_init_i2c(client, &pm80x_regmap_config);
+	i2c_set_clientdata(chip->client, chip);
+
+	device_init_wakeup(&client->dev, 1);
+
+	/* init subchip for PM800 */
+	if (chip->id == CHIP_PM800) {
+		subchip =
+		    devm_kzalloc(&client->dev, sizeof(struct pm80x_subchip),
+				 GFP_KERNEL);
+		if (!subchip) {
+			ret = -ENOMEM;
+			goto err_subchip_alloc;
+		}
+
+		subchip->power_page_addr = pdata->power_page_addr;
+		subchip->gpadc_page_addr = pdata->gpadc_page_addr;
+		chip->subchip = subchip;
+
+		ret = pm80x_pages_init(chip, client);
+		if (ret) {
+			dev_err(&client->dev, "pm80x_pages_init failed!\n");
+			devm_kfree(&client->dev, subchip);
+			goto err_subchip_alloc;
+		}
+	}
+
+	ret = pm80x_device_init(chip, pdata);
+	if (ret) {
+		dev_err(&client->dev,
+			"Chip[0x%x]:pm80x_device_init failed!\n", chip->id);
+		goto err_dev_init;
+	}
+
+	return 0;
+
+err_dev_init:
+	pm80x_device_exit(chip);
+	pm80x_pages_exit(chip);
+err_subchip_alloc:
+	regmap_exit(map);
+err_regmap_init:
+	devm_kfree(&client->dev, chip);
+	return ret;
+}
+
+static int __devexit pm80x_remove(struct i2c_client *client)
+{
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	pm80x_device_exit(chip);
+	pm80x_pages_exit(chip);
+	if (chip->id == CHIP_PM800)
+		devm_kfree(&client->dev, chip->subchip);
+	devm_kfree(&client->dev, chip);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pm80x_suspend(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	if (chip && chip->wu_flag)
+		if (device_may_wakeup(chip->dev))
+			enable_irq_wake(chip->irq);
+
+	return 0;
+}
+
+static int pm80x_resume(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct pm80x_chip *chip = i2c_get_clientdata(client);
+
+	if (chip && chip->wu_flag)
+		if (device_may_wakeup(chip->dev))
+			disable_irq_wake(chip->irq);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);
+
+static struct i2c_driver pm80x_driver = {
+	.driver = {
+		.name = "88PM80x",
+		.owner = THIS_MODULE,
+		.pm = &pm80x_pm_ops,
+		},
+	.probe = pm80x_probe,
+	.remove = __devexit_p(pm80x_remove),
+	.id_table = pm80x_id_table,
+};
+
+static int __init pm80x_i2c_init(void)
+{
+	return i2c_add_driver(&pm80x_driver);
+}
+
+subsys_initcall(pm80x_i2c_init);
+
+static void __exit pm80x_i2c_exit(void)
+{
+	i2c_del_driver(&pm80x_driver);
+}
+
+module_exit(pm80x_i2c_exit);
+
+MODULE_DESCRIPTION("I2C Driver for Marvell 88PM80x");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index e129c82..96dd2d7 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -20,6 +20,18 @@ config MFD_88PM860X
 	  select individual components like voltage regulators, RTC and
 	  battery-charger under the corresponding menus.
 
+config MFD_88PM80X
+	bool "Support Marvell 88PM800/88PM805"
+	depends on I2C=y && GENERIC_HARDIRQS
+	select REGMAP_I2C
+	select REGMAP_IRQ
+	select MFD_CORE
+	help
+	  This supports for Marvell 88PM800/88PM805 Power Management IC.
+	  This includes the I2C driver and the core APIs _only_, you have to
+	  select individual components like voltage regulators, RTC and
+	  battery-charger under the corresponding menus.
+
 config MFD_SM501
 	tristate "Support for Silicon Motion SM501"
 	 ---help---
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 75f6ed6..bf5de13 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -3,7 +3,9 @@
 #
 
 88pm860x-objs			:= 88pm860x-core.o 88pm860x-i2c.o
+88pm80x-objs			:= 88pm80x-core.o 88pm80x-i2c.o
 obj-$(CONFIG_MFD_88PM860X)	+= 88pm860x.o
+obj-$(CONFIG_MFD_88PM80X)	+= 88pm80x.o
 obj-$(CONFIG_MFD_SM501)		+= sm501.o
 obj-$(CONFIG_MFD_ASIC3)		+= asic3.o tmio_core.o
 
diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
new file mode 100644
index 0000000..9e2e11d
--- /dev/null
+++ b/include/linux/mfd/88pm80x.h
@@ -0,0 +1,678 @@
+/*
+ * Marvell 88PM80x Interface
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_MFD_88PM80X_H
+#define __LINUX_MFD_88PM80X_H
+
+#define PM80X_VERSION_MASK		(0xFF)	/* 80X chip ID mask */
+enum {
+	CHIP_INVALID = 0,
+	CHIP_PM800,
+	CHIP_PM805,
+	CHIP_MAX,
+};
+
+enum {
+	/* Procida */
+	PM800_CHIP_A0  = 0x60,
+	PM800_CHIP_A1  = 0x61,
+	PM800_CHIP_B0  = 0x62,
+	PM800_CHIP_C0  = 0x63,
+	PM800_CHIP_END = PM800_CHIP_C0,
+
+	/* Make sure to update this to the last stepping */
+	PM8XXX_CHIP_END = PM800_CHIP_END
+};
+
+enum {
+	PM800_ID_INVALID,
+	PM800_ID_VIBRATOR,
+	PM800_ID_SOUND,
+	PM800_ID_MAX,
+};
+
+enum {
+	PM800_ID_BUCK1 = 0,
+	PM800_ID_BUCK2,
+	PM800_ID_BUCK3,
+	PM800_ID_BUCK4,
+	PM800_ID_BUCK5,
+
+	PM800_ID_LDO1,
+	PM800_ID_LDO2,
+	PM800_ID_LDO3,
+	PM800_ID_LDO4,
+	PM800_ID_LDO5,
+	PM800_ID_LDO6,
+	PM800_ID_LDO7,
+	PM800_ID_LDO8,
+	PM800_ID_LDO9,
+	PM800_ID_LDO10,
+	PM800_ID_LDO11,
+	PM800_ID_LDO12,
+	PM800_ID_LDO13,
+	PM800_ID_LDO14,
+	PM800_ID_LDO15,
+	PM800_ID_LDO16,
+	PM800_ID_LDO17,
+	PM800_ID_LDO18,
+	PM800_ID_LDO19,
+
+	PM800_ID_RG_MAX,
+};
+#define PM800_MAX_REGULATOR	PM800_ID_RG_MAX	/* 5 Bucks, 19 LDOs */
+#define PM800_NUM_BUCK (5)	/*5 Bucks */
+#define PM800_NUM_LDO (19)	/*19 Bucks */
+
+/* 88PM805 Registers */
+#define PM805_CHIP_ID			(0x00)
+
+/*Audio*/
+
+/*88PM800 registers*/
+enum {
+	PM80X_INVALID_PAGE = 0,
+	PM80X_BASE_PAGE,
+	PM80X_POWER_PAGE,
+	PM80X_GPADC_PAGE,
+	PM80X_TEST_PAGE,
+};
+/*********************************/
+/*page 0 basic: slave adder 0x60*/
+/*********************************/
+/* Interrupt Registers */
+#define PM800_CHIP_ID			(0x00)
+
+#define PM800_STATUS_1			(0x01)
+#define PM800_ONKEY_STS1		(1 << 0)
+#define PM800_EXTON_STS1		(1 << 1)
+#define PM800_CHG_STS1			(1 << 2)
+#define PM800_BAT_STS1			(1 << 3)
+#define PM800_VBUS_STS1			(1 << 4)
+#define PM800_LDO_PGOOD_STS1	(1 << 5)
+#define PM800_BUCK_PGOOD_STS1	(1 << 6)
+
+#define PM800_STATUS_2			(0x02)
+#define PM800_RTC_ALARM_STS2	(1 << 0)
+
+#define PM800_INT_STATUS1		(0x05)
+#define PM800_ONKEY_INT_STS1		(1 << 0)
+#define PM800_EXTON_INT_STS1		(1 << 1)
+#define PM800_CHG_INT_STS1			(1 << 2)
+#define PM800_BAT_INT_STS1			(1 << 3)
+#define PM800_RTC_INT_STS1			(1 << 4)
+#define PM800_CLASSD_OC_INT_STS1	(1 << 5)
+
+#define PM800_INT_STATUS2		(0x06)
+#define PM800_VBAT_INT_STS2		(1 << 0)
+#define PM800_VSYS_INT_STS2		(1 << 1)
+#define PM800_VCHG_INT_STS2		(1 << 2)
+#define PM800_TINT_INT_STS2		(1 << 3)
+#define PM800_GPADC0_INT_STS2	(1 << 4)
+#define PM800_TBAT_INT_STS2		(1 << 5)
+#define PM800_GPADC2_INT_STS2	(1 << 6)
+#define PM800_GPADC3_INT_STS2	(1 << 7)
+
+#define PM800_INT_STATUS3		(0x07)
+
+#define PM800_INT_STATUS4		(0x08)
+#define PM800_GPIO0_INT_STS4		(1 << 0)
+#define PM800_GPIO1_INT_STS4		(1 << 1)
+#define PM800_GPIO2_INT_STS4		(1 << 2)
+#define PM800_GPIO3_INT_STS4		(1 << 3)
+#define PM800_GPIO4_INT_STS4		(1 << 4)
+
+#define PM800_INT_ENA_1		(0x09)
+#define PM800_ONKEY_INT_ENA1		(1 << 0)
+#define PM800_EXTON_INT_ENA1		(1 << 1)
+#define PM800_CHG_INT_ENA1			(1 << 2)
+#define PM800_BAT_INT_ENA1			(1 << 3)
+#define PM800_RTC_INT_ENA1			(1 << 4)
+#define PM800_CLASSD_OC_INT_ENA1	(1 << 5)
+
+#define PM800_INT_ENA_2		(0x0A)
+#define PM800_VBAT_INT_ENA2		(1 << 0)
+#define PM800_VSYS_INT_ENA2		(1 << 1)
+#define PM800_VCHG_INT_ENA2		(1 << 2)
+#define PM800_TINT_INT_ENA2		(1 << 3)
+
+#define PM800_INT_ENA_3		(0x0B)
+#define PM800_GPADC0_INT_ENA3		(1 << 0)
+#define PM800_GPADC1_INT_ENA3		(1 << 1)
+#define PM800_GPADC2_INT_ENA3		(1 << 2)
+#define PM800_GPADC3_INT_ENA3		(1 << 3)
+#define PM800_GPADC4_INT_ENA3		(1 << 4)
+
+#define PM800_INT_ENA_4		(0x0C)
+#define PM800_GPIO0_INT_ENA4		(1 << 0)
+#define PM800_GPIO1_INT_ENA4		(1 << 1)
+#define PM800_GPIO2_INT_ENA4		(1 << 2)
+#define PM800_GPIO3_INT_ENA4		(1 << 3)
+#define PM800_GPIO4_INT_ENA4		(1 << 4)
+
+/*number of INT_ENA & INT_STATUS regs*/
+#define PM800_INT_REG_NUM			(4)
+
+/* Wakeup Registers */
+#define PM800_WAKEUP1		(0x0D)
+
+#define PM800_WAKEUP2		(0x0E)
+#define PM800_WAKEUP2_INV_INT		(1 << 0)
+#define PM800_WAKEUP2_INT_CLEAR		(1 << 1)
+#define PM800_WAKEUP2_INT_MASK		(1 << 2)
+
+#define PM800_POWER_UP_LOG	(0x10)
+
+/*test page*/
+#define PM800_TEST_PAGE_ENTRY	(0x1F)
+
+/*Referance and low power registers*/
+#define PM800_LOW_POWER1		(0x20)
+#define PM800_LOW_POWER2		(0x21)
+#define PM800_LOW_POWER_CONFIG3	(0x22)
+#define PM800_LOW_POWER_CONFIG4	(0x23)
+
+/*GPIO register*/
+#define PM800_GPIO_0_1_CNTRL		(0x30)
+#define PM800_GPIO0_VAL				(1 << 0)
+#define PM800_GPIO0_GPIO_MODE(x)	(x << 1)
+#define PM800_GPIO1_VAL				(1 << 4)
+#define PM800_GPIO1_GPIO_MODE(x)	(x << 5)
+
+#define PM800_GPIO_2_3_CNTRL		(0x31)
+#define PM800_GPIO2_VAL				(1 << 0)
+#define PM800_GPIO2_GPIO_MODE(x)	(x << 1)
+#define PM800_GPIO3_VAL				(1 << 4)
+#define PM800_GPIO3_GPIO_MODE(x)	(x << 5)
+#define PM800_GPIO3_MODE_MASK		0x1F
+#define PM800_GPIO3_HEADSET_MODE	PM800_GPIO3_GPIO_MODE(6)
+
+#define PM800_GPIO_4_CNTRL			(0x32)
+#define PM800_GPIO4_VAL				(1 << 0)
+#define PM800_GPIO4_GPIO_MODE(x)	(x << 1)
+
+#define PM800_HEADSET_CNTRL		(0x38)
+#define PM800_HEADSET_DET_EN		(1 << 7)
+#define PM800_HSDET_SLP			(1 << 1)
+/*PWM register*/
+#define PM800_PWM1		(0x40)
+#define PM800_PWM2		(0x41)
+#define PM800_PWM3		(0x42)
+#define PM800_PWM4		(0x43)
+
+/*RTC Registers*/
+#define PM800_RTC_CONTROL		(0xD0)
+#define PM800_RTC_COUNTER1		(0xD1)
+#define PM800_RTC_COUNTER2		(0xD2)
+#define PM800_RTC_COUNTER3		(0xD3)
+#define PM800_RTC_COUNTER4		(0xD4)
+#define PM800_RTC_EXPIRE1_1		(0xD5)
+#define PM800_RTC_EXPIRE1_2		(0xD6)
+#define PM800_RTC_EXPIRE1_3		(0xD7)
+#define PM800_RTC_EXPIRE1_4		(0xD8)
+#define PM800_RTC_TRIM1			(0xD9)
+#define PM800_RTC_TRIM2			(0xDA)
+#define PM800_RTC_TRIM3			(0xDB)
+#define PM800_RTC_TRIM4			(0xDC)
+#define PM800_RTC_EXPIRE2_1		(0xDD)
+#define PM800_RTC_EXPIRE2_2		(0xDE)
+#define PM800_RTC_EXPIRE2_3		(0xDF)
+#define PM800_RTC_EXPIRE2_4		(0xE0)
+#define PM800_RTC_MISC1			(0xE1)
+#define PM800_RTC_MISC2			(0xE2)
+#define PM800_RTC_MISC3			(0xE3)
+#define PM800_RTC_MISC4			(0xE4)
+
+/* bit definitions of RTC Register 1 (0xD0) */
+#define PM800_ALARM1_EN			(1 << 0)
+#define PM800_ALARM_WAKEUP		(1 << 4)
+#define PM800_ALARM			(1 << 5)
+#define PM800_RTC1_USE_XO		(1 << 7)
+
+/*for save RTC offset*/
+#define PM800_USER_DATA1		(0xE8)
+#define PM800_USER_DATA2		(0xE9)
+#define PM800_USER_DATA3		(0xEA)
+#define PM800_USER_DATA4		(0xEB)
+#define PM800_USER_DATA5		(0xEC)
+#define PM800_USER_DATA6		(0xED)
+#define PM800_USER_DATA7		(0xEE)
+#define PM800_USER_DATA8		(0xEF)
+
+#define PM800_POWER_DOWN_LOG1	(0xE5)
+#define PM800_POWER_DOWN_LOG2	(0xE6)
+
+#define PM800_RTC_MISC5			(0xE7)
+
+/*********************************/
+/*page 1 Power: slave adder 0x01*/
+/********************************/
+#define PM800_BUCK_POWER_GOOD_STS	(0x01)
+#define PM800_LDO_POWER_GOOD_STS1	(0x02)
+#define PM800_LDO_POWER_GOOD_STS2	(0x03)
+#define PM800_LDO_POWER_GOOD_STS3	(0x04)
+#define PM800_LDO_LAST_GROUP		(0x05)	/*reg#? not sure */
+
+/* Regulator Control Registers: BUCK1,BUCK5,LDO1 have DVC */
+/* LDO1 with DVC[0..3] */
+#define PM800_LDO1_VOUT		(0x08) /* VOUT1 */
+#define PM800_LDO1_VOUT_2	(0x09)
+#define PM800_LDO1_VOUT_3	(0x0A)
+#define PM800_LDO2_VOUT		(0x0B)
+#define PM800_LDO3_VOUT		(0x0C)
+#define PM800_LDO4_VOUT		(0x0D)
+#define PM800_LDO5_VOUT		(0x0E)
+#define PM800_LDO6_VOUT		(0x0F)
+#define PM800_LDO7_VOUT		(0x10)
+#define PM800_LDO8_VOUT		(0x11)
+#define PM800_LDO9_VOUT		(0x12)
+#define PM800_LDO10_VOUT	(0x13)
+#define PM800_LDO11_VOUT	(0x14)
+#define PM800_LDO12_VOUT	(0x15)
+#define PM800_LDO13_VOUT	(0x16)
+#define PM800_LDO14_VOUT	(0x17)
+#define PM800_LDO15_VOUT	(0x18)
+#define PM800_LDO16_VOUT	(0x19)
+#define PM800_LDO17_VOUT	(0x1A)
+#define PM800_LDO18_VOUT	(0x1B)
+#define PM800_LDO19_VOUT	(0x1C)
+
+/*buck registers*/
+#define PM800_SLEEP_BUCK1	(0x30)
+#define PM800_SLEEP_BUCK2	(0x31)
+#define PM800_SLEEP_BUCK3	(0x32)
+#define PM800_SLEEP_BUCK4	(0x33)
+#define PM800_SLEEP_BUCK5	(0x34)
+/* BUCK1 with DVC[0..3] */
+#define PM800_BUCK1			(0x3C)
+#define PM800_BUCK1_1		(0x3D)
+#define PM800_BUCK1_2		(0x3E)
+#define PM800_BUCK1_3		(0x3F)
+#define PM800_BUCK2			(0x40)
+#define PM800_BUCK3			(0x41)
+#define PM800_BUCK3_DOUBLE	(1 << 6)
+#define PM800_BUCK4			(0x42)
+/* BUCK5 with DVC[0..3] */
+#define PM800_BUCK5			(0x43)
+#define PM800_BUCK5_1		(0x44)
+#define PM800_BUCK5_2		(0x45)
+#define PM800_BUCK5_3		(0x46)
+
+#define PM800_BUCK_ENA		(0x50)
+#define PM800_LDO_ENA1_1	(0x51)
+#define PM800_LDO_ENA1_2	(0x52)
+#define PM800_LDO_ENA1_3	(0x53)
+
+#define PM800_LDO_ENA2_1	(0x56)
+#define PM800_LDO_ENA2_2	(0x57)
+#define PM800_LDO_ENA2_3	(0x58)
+
+/* BUCK Sleep Mode Register 1: BUCK[1..4] */
+#define PM800_BUCK_SLP1		(0x5A)
+#define PM800_BUCK1_SLP1_SHIFT	0
+#define PM800_BUCK1_SLP1_MASK	(0x3 << PM800_BUCK1_SLP1_SHIFT)
+#define PM800_BUCK2_SLP1_SHIFT	2
+#define PM800_BUCK2_SLP1_MASK	(0x3 << PM800_BUCK2_SLP1_SHIFT)
+#define PM800_BUCK3_SLP1_SHIFT	4
+#define PM800_BUCK3_SLP1_MASK	(0x3 << PM800_BUCK3_SLP1_SHIFT)
+#define PM800_BUCK4_SLP1_SHIFT	6
+#define PM800_BUCK4_SLP1_MASK	(0x3 << PM800_BUCK4_SLP1_SHIFT)
+/* BUCK Sleep Mode Register 2: BUCK5 */
+#define PM800_BUCK_SLP2		(0x5B)
+#define PM800_BUCK5_SLP2_SHIFT	0
+#define PM800_BUCK5_SLP2_MASK	(0x3 << PM800_BUCK5_SLP2_SHIFT)
+
+/* LDO Sleep Mode Register 1: LDO[1..4] */
+#define PM800_LDO_SLP1		(0x5C)
+#define PM800_LDO1_SLP1_SHIFT	0
+#define PM800_LDO1_SLP1_MASK	(0x3 << PM800_LDO1_SLP1_SHIFT)
+#define PM800_LDO2_SLP1_SHIFT	2
+#define PM800_LDO2_SLP1_MASK	(0x3 << PM800_LDO2_SLP1_SHIFT)
+#define PM800_LDO3_SLP1_SHIFT	4
+#define PM800_LDO3_SLP1_MASK	(0x3 << PM800_LDO3_SLP1_SHIFT)
+#define PM800_LDO4_SLP1_SHIFT	6
+#define PM800_LDO4_SLP1_MASK	(0x3 << PM800_LDO4_SLP1_SHIFT)
+
+/* LDO Sleep Mode Register 2: LDO[5..8] */
+#define PM800_LDO_SLP2		(0x5D)
+#define PM800_LDO5_SLP2_SHIFT	0
+#define PM800_LDO5_SLP2_MASK	(0x3 << PM800_LDO5_SLP2_SHIFT)
+#define PM800_LDO6_SLP2_SHIFT	2
+#define PM800_LDO6_SLP2_MASK	(0x3 << PM800_LDO6_SLP2_SHIFT)
+#define PM800_LDO7_SLP2_SHIFT	4
+#define PM800_LDO7_SLP2_MASK	(0x3 << PM800_LDO7_SLP2_SHIFT)
+#define PM800_LDO8_SLP2_SHIFT	6
+#define PM800_LDO8_SLP2_MASK	(0x3 << PM800_LDO8_SLP2_SHIFT)
+
+/* LDO Sleep Mode Register 3: LDO[9..12] */
+#define PM800_LDO_SLP3		(0x5E)
+#define PM800_LDO9_SLP3_SHIFT	0
+#define PM800_LDO9_SLP3_MASK	(0x3 << PM800_LDO9_SLP3_SHIFT)
+#define PM800_LDO10_SLP3_SHIFT	2
+#define PM800_LDO10_SLP3_MASK	(0x3 << PM800_LDO10_SLP3_SHIFT)
+#define PM800_LDO11_SLP3_SHIFT	4
+#define PM800_LDO11_SLP3_MASK	(0x3 << PM800_LDO11_SLP3_SHIFT)
+#define PM800_LDO12_SLP3_SHIFT	6
+#define PM800_LDO12_SLP3_MASK	(0x3 << PM800_LDO12_SLP3_SHIFT)
+
+/* LDO Sleep Mode Register 4: LDO[13..16] */
+#define PM800_LDO_SLP4		(0x5F)
+#define PM800_LDO13_SLP4_SHIFT	0
+#define PM800_LDO13_SLP4_MASK	(0x3 << PM800_LDO13_SLP4_SHIFT)
+#define PM800_LDO14_SLP4_SHIFT	2
+#define PM800_LDO14_SLP4_MASK	(0x3 << PM800_LDO14_SLP4_SHIFT)
+#define PM800_LDO15_SLP4_SHIFT	4
+#define PM800_LDO15_SLP4_MASK	(0x3 << PM800_LDO15_SLP4_SHIFT)
+#define PM800_LDO16_SLP4_SHIFT	6
+#define PM800_LDO16_SLP4_MASK	(0x3 << PM800_LDO16_SLP4_SHIFT)
+
+/* LDO Sleep Mode Register 5: LDO[17..19] */
+#define PM800_LDO_SLP5		(0x60)
+#define PM800_LDO17_SLP5_SHIFT	0
+#define PM800_LDO17_SLP5_MASK	(0x3 << PM800_LDO17_SLP5_SHIFT)
+#define PM800_LDO18_SLP5_SHIFT	2
+#define PM800_LDO18_SLP5_MASK	(0x3 << PM800_LDO18_SLP5_SHIFT)
+#define PM800_LDO19_SLP5_SHIFT	4
+#define PM800_LDO19_SLP5_MASK	(0x3 << PM800_LDO19_SLP5_SHIFT)
+
+#define PM800_LDO_GROUP1	(0x68)
+#define PM800_LDO_GROUP2	(0x69)
+#define PM800_LDO_GROUP3	(0x6A)
+#define PM800_LDO_GROUP4	(0x6B)
+#define PM800_LDO_GROUP5	(0x6C)
+#define PM800_LDO_GROUP6	(0x6D)
+#define PM800_LDO_GROUP7	(0x6E)
+#define PM800_LDO_GROUP8	(0x6F)
+#define PM800_LDO_GROUP9	(0x70)
+#define PM800_LDO_GROUP10	(0x71)
+
+#define PM800_LDO_MISC1		(0x90)
+#define PM800_LDO_MISC2		(0x91)
+#define PM800_LDO_MISC3		(0x92)
+#define PM800_LDO_MISC4		(0x93)
+#define PM800_LDO_MISC5		(0x94)
+#define PM800_LDO_MISC6		(0x95)
+#define PM800_LDO_MISC7		(0x96)
+#define PM800_LDO_MISC8		(0x97)
+#define PM800_LDO_MISC9		(0x98)
+#define PM800_LDO_MISC10	(0x99)
+#define PM800_LDO_MISC11	(0x9A)
+
+/*********************************/
+/*page 2 GPADC: slave adder 0x02*/
+/********************************/
+#define PM800_GPADC_MEAS_EN1		(0x01)
+#define PM800_MEAS_EN1_VBAT         (1 << 2)
+#define PM800_GPADC_MEAS_EN2		(0x02)
+#define PM800_MEAS_EN2_RFTMP        (1 << 0)
+#define PM800_MEAS_GP0_EN			(1 << 2)
+#define PM800_MEAS_GP1_EN			(1 << 3)
+#define PM800_MEAS_GP2_EN			(1 << 4)
+#define PM800_MEAS_GP3_EN			(1 << 5)
+#define PM800_MEAS_GP4_EN			(1 << 6)
+
+#define PM800_GPADC_MISC_CONFIG1	(0x05)
+#define PM800_GPADC_MISC_CONFIG2	(0x06)
+#define PM800_GPADC_MISC_GPFSM_EN	(1 << 0)
+#define PM800_GPADC_SLOW_MODE(x)	(x << 3)
+
+#define PM800_GPADC_MEAS_OFF_TIME1	(0x07)
+#define PM800_GPADC_MEAS_OFF_TIME2	(0x08)
+
+#define PM800_GPADC_MISC_CONFIG3		(0x09)
+#define PM800_GPADC_MISC_CONFIG4		(0x0A)
+#define PM800_GPADC_MEAS_OFF_TIME2_1	(0x07)
+#define PM800_GPADC_MEAS_OFF_TIME2_2	(0x08)
+
+#define PM800_GPADC_PREBIAS1			(0x0F)
+#define PM800_GPADC0_GP_PREBIAS_TIME(x)	(x << 0)
+#define PM800_GPADC_PREBIAS2			(0x10)
+
+#define PM800_GP_BIAS_ENA1				(0x14)
+#define PM800_GPADC_GP_BIAS_EN0			(1 << 0)
+#define PM800_GPADC_GP_BIAS_EN1			(1 << 1)
+#define PM800_GPADC_GP_BIAS_EN2			(1 << 2)
+#define PM800_GPADC_GP_BIAS_EN3			(1 << 3)
+
+#define PM800_GP_BIAS_OUT1		(0x15)
+#define PM800_BIAS_OUT_GP0		(1 << 0)
+#define PM800_BIAS_OUT_GP1		(1 << 1)
+#define PM800_BIAS_OUT_GP2		(1 << 2)
+#define PM800_BIAS_OUT_GP3		(1 << 3)
+
+#define PM800_GPADC0_LOW_TH		0x20
+#define PM800_GPADC1_LOW_TH		0x21
+#define PM800_GPADC2_LOW_TH		0x22
+#define PM800_GPADC3_LOW_TH		0x23
+#define PM800_GPADC4_LOW_TH		0x24
+
+#define PM800_GPADC0_UPP_TH		0x30
+#define PM800_GPADC1_UPP_TH		0x31
+#define PM800_GPADC2_UPP_TH		0x32
+#define PM800_GPADC3_UPP_TH		0x33
+#define PM800_GPADC4_UPP_TH		0x34
+
+#define PM800_VBBAT_MEAS1		0x40
+#define PM800_VBBAT_MEAS2		0x41
+#define PM800_VBAT_MEAS1		0x42
+#define PM800_VBAT_MEAS2		0x43
+#define PM800_VSYS_MEAS1		0x44
+#define PM800_VSYS_MEAS2		0x45
+#define PM800_VCHG_MEAS1		0x46
+#define PM800_VCHG_MEAS2		0x47
+#define PM800_TINT_MEAS1		0x50
+#define PM800_TINT_MEAS2		0x51
+#define PM800_PMOD_MEAS1		0x52
+#define PM800_PMOD_MEAS2		0x53
+
+#define PM800_GPADC0_MEAS1		0x54
+#define PM800_GPADC0_MEAS2		0x55
+#define PM800_GPADC1_MEAS1		0x56
+#define PM800_GPADC1_MEAS2		0x57
+#define PM800_GPADC2_MEAS1		0x58
+#define PM800_GPADC2_MEAS2		0x59
+#define PM800_GPADC3_MEAS1		0x5A
+#define PM800_GPADC3_MEAS2		0x5B
+#define PM800_GPADC4_MEAS1		0x5C
+#define PM800_GPADC4_MEAS2		0x5D
+
+#define PM800_GPADC4_AVG1		0xA8
+#define PM800_GPADC4_AVG2		0xA9
+/*********************************/
+/*page 7 TEST PAGE: slave adder 0x07*/
+/********************************/
+
+/*******************************
+ * customer configuration start*
+********************************/
+
+/*****************************
+ * customer configuration end*
+******************************/
+
+/* 88PM805 Registers */
+#define PM805_MAIN_POWERUP		(0x01)
+#define PM805_INT_STATUS0		(0x02)	/*for ena/dis all interrupts */
+
+#define PM805_STATUS0_INT_CLEAR		(1 << 0)
+#define PM805_STATUS0_INV_INT		(1 << 1)
+#define PM800_STATUS0_INT_MASK		(1 << 2)
+
+#define PM805_INT_STATUS1		(0x03)
+
+#define PM805_INT1_HP1_SHRT		(1 << 0)
+#define PM805_INT1_HP2_SHRT		(1 << 1)
+#define PM805_INT1_MIC_CONFLICT		(1 << 2)
+#define PM805_INT1_CLIP_FAULT		(1 << 3)
+#define PM805_INT1_LDO_OFF			(1 << 4)
+#define PM805_INT1_SRC_DPLL_LOCK	(1 << 5)
+
+#define PM805_INT_STATUS2		(0x04)
+
+#define PM805_INT2_MIC_DET			(1 << 0)
+#define PM805_INT2_SHRT_BTN_DET		(1 << 1)
+#define PM805_INT2_VOLM_BTN_DET		(1 << 2)
+#define PM805_INT2_VOLP_BTN_DET		(1 << 3)
+#define PM805_INT2_RAW_PLL_FAULT	(1 << 4)
+#define PM805_INT2_FINE_PLL_FAULT	(1 << 5)
+
+#define PM805_INT_MASK1			(0x05)
+#define PM805_INT_MASK2			(0x06)
+#define PM805_SHRT_BTN_DET		(1 << 1)
+
+/*number of status and int reg in a row*/
+#define PM805_INT_REG_NUM		(2)
+
+#define PM805_MIC_DET1			(0x07)
+#define PM805_MIC_DET_EN_MIC_DET (1 << 0)
+#define PM805_MIC_DET2			(0x08)
+#define PM805_MIC_DET_STATUS1	(0x09)
+/*where is 2?*/
+#define PM805_MIC_DET_STATUS3	(0x0A)
+#define PM805_AUTO_SEQ_STATUS1	(0x0B)
+#define PM805_AUTO_SEQ_STATUS2	(0x0C)
+
+#define PM805_ADC_SETTING1		(0x10)
+#define PM805_ADC_SETTING2		(0x11)
+#define PM805_ADC_SETTING3		(0x11)
+#define PM805_ADC_GAIN1			(0x12)
+#define PM805_ADC_GAIN2			(0x13)
+#define PM805_DMIC_SETTING		(0x15)
+#define PM805_DWS_SETTING		(0x16)
+#define PM805_MIC_CONFLICT_STS	(0x17)
+
+#define PM805_PDM_SETTING1		(0x20)
+#define PM805_PDM_SETTING2		(0x21)
+#define PM805_PDM_SETTING3		(0x22)
+#define PM805_PDM_CONTROL1		(0x23)
+#define PM805_PDM_CONTROL2		(0x24)
+#define PM805_PDM_CONTROL3		(0x25)
+
+#define PM805_HEADPHONE_SETTING			(0x26)
+#define PM805_HEADPHONE_GAIN_A2A		(0x27)
+#define PM805_HEADPHONE_SHORT_STATE		(0x28)
+#define PM805_EARPHONE_SETTING			(0x29)
+#define PM805_AUTO_SEQ_SETTING			(0x2A)
+
+#define get_pmic_version(chip) (*(unsigned char *) chip)
+
+/*******************************
+ * customer configuration start*
+********************************/
+/* for disabling the use of PM805*/
+/*#define NO_PM805_CHIP*/
+/*****************************
+ * customer configuration end*
+******************************/
+
+/* Interrupt Number in 88PM800 */
+enum {
+	PM800_IRQ_ONKEY,	/*EN1b0 *//*0 */
+	PM800_IRQ_EXTON,	/*EN1b1 */
+	PM800_IRQ_CHG,		/*EN1b2 */
+	PM800_IRQ_BAT,		/*EN1b3 */
+	PM800_IRQ_RTC,		/*EN1b4 */
+	PM800_IRQ_CLASSD,	/*EN1b5 *//*5 */
+	PM800_IRQ_VBAT,		/*EN2b0 */
+	PM800_IRQ_VSYS,		/*EN2b1 */
+	PM800_IRQ_VCHG,		/*EN2b2 */
+	PM800_IRQ_TINT,		/*EN2b3 */
+	PM800_IRQ_GPADC0,	/*EN3b0 *//*10 */
+	PM800_IRQ_GPADC1,	/*EN3b1 */
+	PM800_IRQ_GPADC2,	/*EN3b2 */
+	PM800_IRQ_GPADC3,	/*EN3b3 */
+	PM800_IRQ_GPADC4,	/*EN3b4 */
+	PM800_IRQ_GPIO0,	/*EN4b0 *//*15 */
+	PM800_IRQ_GPIO1,	/*EN4b1 */
+	PM800_IRQ_GPIO2,	/*EN4b2 */
+	PM800_IRQ_GPIO3,	/*EN4b3 */
+	PM800_IRQ_GPIO4,	/*EN4b4 *//*19 */
+	PM800_MAX_IRQ,
+};
+
+/* Interrupt Number in 88PM805 */
+enum {
+	PM805_IRQ_LDO_OFF,	/*0 */
+	PM805_IRQ_SRC_DPLL_LOCK,	/*1 */
+	PM805_IRQ_CLIP_FAULT,
+	PM805_IRQ_MIC_CONFLICT,
+	PM805_IRQ_HP2_SHRT,
+	PM805_IRQ_HP1_SHRT,	/*5 */
+	PM805_IRQ_FINE_PLL_FAULT,
+	PM805_IRQ_RAW_PLL_FAULT,
+	PM805_IRQ_VOLP_BTN_DET,
+	PM805_IRQ_VOLM_BTN_DET,
+	PM805_IRQ_SHRT_BTN_DET,	/*10 */
+	PM805_IRQ_MIC_DET,	/*11 */
+
+	PM805_MAX_IRQ,
+};
+
+struct pm80x_subchip {
+	struct i2c_client *power_page;	/* chip client for power page */
+	struct i2c_client *gpadc_page;	/* chip client for gpadc page */
+	struct regmap *regmap_power;
+	struct regmap *regmap_gpadc;
+	unsigned short power_page_addr;	/* power page I2C address */
+	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
+};
+
+struct pm80x_chip {
+	struct pm80x_subchip *subchip;
+	struct device *dev;
+	struct i2c_client *client;
+	struct i2c_client *companion;
+	struct regmap *regmap;
+	struct regmap_irq_chip *regmap_irq_chip;
+	struct regmap_irq_chip_data *irq_data;
+	unsigned char version;
+	int id;
+	int irq;
+	int irq_mode;
+	int irq_base;
+	unsigned int wu_flag;
+};
+
+enum {
+	PM80X_GPIO1_SUPPLY_VBUS = 1,
+	PM80X_GPIO2_SUPPLY_VBUS = 2,
+};
+
+enum {
+	PM80X_IDPIN_NO_USE = 0,
+	PM80X_IDPIN_USE_GPADC0,
+	PM80X_IDPIN_USE_GPADC1,
+	PM80X_IDPIN_USE_GPADC2,
+	PM80X_IDPIN_USE_GPADC3,
+};
+
+struct pm80x_rtc_pdata {
+	int		vrtc;
+	int		rtc_wakeup;
+};
+
+struct pm80x_platform_data {
+	struct pm80x_rtc_pdata *rtc;
+	unsigned short pm800_addr;
+	unsigned short pm805_addr;
+	unsigned short power_page_addr;	/* power page I2C address */
+	unsigned short gpadc_page_addr;	/* gpadc page I2C address */
+	int irq_mode;		/* Clear interrupt by read/write(0/1) */
+	int irq_base;		/* IRQ base of chip */
+	int batt_det;		/* enable/disable */
+	int (*pm80x_plat_config)(struct pm80x_chip *chip,
+				struct pm80x_platform_data *pdata);
+};
+
+extern int pm80x_device_init(struct pm80x_chip *chip,
+			     struct pm80x_platform_data *pdata) __devinit;
+extern void pm80x_device_exit(struct pm80x_chip *chip) __devexit;
+#endif /* __LINUX_MFD_88PM80X_H */
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 2/3] rtc: add rtc support to 88PM80X PMIC
  2012-06-28  3:13 [PATCH 0/3 V1] add 88pm80x mfd driver Qiao Zhou
  2012-06-28  3:13 ` [PATCH 1/3] mfd: support 88pm80x in 80x driver Qiao Zhou
@ 2012-06-28  3:13 ` Qiao Zhou
  2012-06-28  3:13 ` [PATCH 3/3] input: add onkey " Qiao Zhou
  2 siblings, 0 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-06-28  3:13 UTC (permalink / raw)
  To: linux-arm-kernel

add rtc driver for MARVELL 88PM80X PMIC and enable rtc function.

Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
 drivers/rtc/Kconfig       |   10 ++
 drivers/rtc/Makefile      |    1 +
 drivers/rtc/rtc-88pm80x.c |  370 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 381 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-88pm80x.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 08cbdb9..f3b49f8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -135,6 +135,16 @@ config RTC_DRV_88PM860X
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-88pm860x.
 
+config RTC_DRV_88PM80X
+	tristate "Marvell 88PM80x"
+	depends on RTC_CLASS && I2C && MFD_88PM80X
+	help
+	  If you say yes here you get support for RTC function in Marvell
+	  88PM80x chips.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-88pm80x.
+
 config RTC_DRV_DS1307
 	tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025"
 	help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2973921..0d5b2b6 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -16,6 +16,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
 # Keep the list ordered.
 
 obj-$(CONFIG_RTC_DRV_88PM860X)  += rtc-88pm860x.o
+obj-$(CONFIG_RTC_DRV_88PM80X)	+= rtc-88pm80x.o
 obj-$(CONFIG_RTC_DRV_AB3100)	+= rtc-ab3100.o
 obj-$(CONFIG_RTC_DRV_AB8500)	+= rtc-ab8500.o
 obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
diff --git a/drivers/rtc/rtc-88pm80x.c b/drivers/rtc/rtc-88pm80x.c
new file mode 100644
index 0000000..ee6519f
--- /dev/null
+++ b/drivers/rtc/rtc-88pm80x.c
@@ -0,0 +1,370 @@
+/*
+ * Real Time Clock driver for Marvell 88PM80x PMIC
+ *
+ * Copyright (c) 2012 Marvell International Ltd.
+ *  Wenzeng Chen<wzch@marvell.com>
+ *  Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/rtc.h>
+
+struct pm80x_rtc_info {
+	struct pm80x_chip *chip;
+	struct i2c_client *i2c;
+	struct regmap *map;
+	struct rtc_device *rtc_dev;
+	struct device *dev;
+	struct delayed_work calib_work;
+
+	int irq;
+	int vrtc;
+};
+
+static int pm80x_rtc_read_time(struct device *dev, struct rtc_time *tm);
+
+static irqreturn_t rtc_update_handler(int irq, void *data)
+{
+	struct pm80x_rtc_info *info = (struct pm80x_rtc_info *)data;
+	int mask;
+
+	mask = PM800_ALARM | PM800_ALARM_WAKEUP;
+	regmap_update_bits(info->map, PM800_RTC_CONTROL, mask | PM800_ALARM1_EN,
+			   mask);
+	rtc_update_irq(info->rtc_dev, 1, RTC_AF);
+	return IRQ_HANDLED;
+}
+
+static int pm80x_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+
+	if (enabled)
+		regmap_update_bits(info->map, PM800_RTC_CONTROL,
+				   PM800_ALARM1_EN, PM800_ALARM1_EN);
+	else
+		regmap_update_bits(info->map, PM800_RTC_CONTROL,
+				   PM800_ALARM1_EN, 0);
+	return 0;
+}
+
+/*
+ * Calculate the next alarm time given the requested alarm time mask
+ * and the current time.
+ */
+static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now,
+				struct rtc_time *alrm)
+{
+	unsigned long next_time;
+	unsigned long now_time;
+
+	next->tm_year = now->tm_year;
+	next->tm_mon = now->tm_mon;
+	next->tm_mday = now->tm_mday;
+	next->tm_hour = alrm->tm_hour;
+	next->tm_min = alrm->tm_min;
+	next->tm_sec = alrm->tm_sec;
+
+	rtc_tm_to_time(now, &now_time);
+	rtc_tm_to_time(next, &next_time);
+
+	if (next_time < now_time) {
+		/* Advance one day */
+		next_time += 60 * 60 * 24;
+		rtc_time_to_tm(next_time, next);
+	}
+}
+
+static int pm80x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	unsigned char buf[4];
+	unsigned long ticks, base, data;
+	regmap_raw_read(info->map, PM800_RTC_EXPIRE2_1, buf, 4);
+	base = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	dev_dbg(info->dev, "%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]);
+
+	/* load 32-bit read-only counter */
+	regmap_raw_read(info->map, PM800_RTC_COUNTER1, buf, 4);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	ticks = base + data;
+	dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+	rtc_time_to_tm(ticks, tm);
+	return 0;
+}
+
+static int pm80x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	unsigned char buf[4];
+	unsigned long ticks, base, data;
+	if ((tm->tm_year < 70) || (tm->tm_year > 138)) {
+		dev_dbg(info->dev,
+			"Set time %d out of range. Please set time between 1970 to 2038.\n",
+			1900 + tm->tm_year);
+		return -EINVAL;
+	}
+	rtc_tm_to_time(tm, &ticks);
+
+	/* load 32-bit read-only counter */
+	regmap_raw_read(info->map, PM800_RTC_COUNTER1, buf, 4);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	base = ticks - data;
+	dev_dbg(info->dev, "set base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+	buf[0] = base & 0xFF;
+	buf[1] = (base >> 8) & 0xFF;
+	buf[2] = (base >> 16) & 0xFF;
+	buf[3] = (base >> 24) & 0xFF;
+	regmap_raw_write(info->map, PM800_RTC_EXPIRE2_1, buf, 4);
+
+	return 0;
+}
+
+static int pm80x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	unsigned char buf[4];
+	unsigned long ticks, base, data;
+	int ret;
+
+	regmap_raw_read(info->map, PM800_RTC_EXPIRE2_1, buf, 4);
+	base = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	dev_dbg(info->dev, "%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]);
+
+	regmap_raw_read(info->map, PM800_RTC_EXPIRE1_1, buf, 4);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	ticks = base + data;
+	dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+
+	rtc_time_to_tm(ticks, &alrm->time);
+	regmap_read(info->map, PM800_RTC_CONTROL, &ret);
+	alrm->enabled = (ret & PM800_ALARM1_EN) ? 1 : 0;
+	alrm->pending = (ret & (PM800_ALARM | PM800_ALARM_WAKEUP)) ? 1 : 0;
+	return 0;
+}
+
+static int pm80x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	struct rtc_time now_tm, alarm_tm;
+	unsigned long ticks, base, data;
+	unsigned char buf[4];
+	int mask;
+
+	regmap_update_bits(info->map, PM800_RTC_CONTROL, PM800_ALARM1_EN, 0);
+
+	regmap_raw_read(info->map, PM800_RTC_EXPIRE2_1, buf, 4);
+	base = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	dev_dbg(info->dev, "%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]);
+
+	/* load 32-bit read-only counter */
+	regmap_raw_read(info->map, PM800_RTC_COUNTER1, buf, 4);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	ticks = base + data;
+	dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+
+	rtc_time_to_tm(ticks, &now_tm);
+	dev_dbg(info->dev, "%s, now time : %lu\n", __func__, ticks);
+	rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time);
+	/* get new ticks for alarm in 24 hours */
+	rtc_tm_to_time(&alarm_tm, &ticks);
+	dev_dbg(info->dev, "%s, alarm time: %lu\n", __func__, ticks);
+	data = ticks - base;
+
+	buf[0] = data & 0xff;
+	buf[1] = (data >> 8) & 0xff;
+	buf[2] = (data >> 16) & 0xff;
+	buf[3] = (data >> 24) & 0xff;
+	regmap_raw_write(info->map, PM800_RTC_EXPIRE1_1, buf, 4);
+	if (alrm->enabled) {
+		mask = PM800_ALARM | PM800_ALARM_WAKEUP | PM800_ALARM1_EN;
+		regmap_update_bits(info->map, PM800_RTC_CONTROL, mask, mask);
+	} else {
+		mask = PM800_ALARM | PM800_ALARM_WAKEUP | PM800_ALARM1_EN;
+		regmap_update_bits(info->map, PM800_RTC_CONTROL, mask,
+				   PM800_ALARM | PM800_ALARM_WAKEUP);
+	}
+	return 0;
+}
+
+static const struct rtc_class_ops pm80x_rtc_ops = {
+	.read_time = pm80x_rtc_read_time,
+	.set_time = pm80x_rtc_set_time,
+	.read_alarm = pm80x_rtc_read_alarm,
+	.set_alarm = pm80x_rtc_set_alarm,
+	.alarm_irq_enable = pm80x_rtc_alarm_irq_enable,
+};
+
+#ifdef CONFIG_PM
+static int pm80x_rtc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+	if (device_may_wakeup(dev))
+		chip->wu_flag |= (1 << PM800_IRQ_RTC);
+
+	return 0;
+}
+
+static int pm80x_rtc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+	if (device_may_wakeup(dev))
+		chip->wu_flag &= ~(1 << PM800_IRQ_RTC);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(pm80x_rtc_pm_ops, pm80x_rtc_suspend, pm80x_rtc_resume);
+
+static int __devinit pm80x_rtc_probe(struct platform_device *pdev)
+{
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+	struct pm80x_platform_data *pm80x_pdata;
+	struct pm80x_rtc_pdata *pdata = NULL;
+	struct pm80x_rtc_info *info;
+	struct rtc_time tm;
+	unsigned long ticks = 0;
+	int ret;
+
+	pdata = pdev->dev.platform_data;
+	if (pdata == NULL)
+		dev_warn(&pdev->dev, "No platform data!\n");
+
+	info =
+	    devm_kzalloc(&pdev->dev, sizeof(struct pm80x_rtc_info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+	info->irq = platform_get_irq(pdev, 0);
+	if (info->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ resource!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	info->chip = chip;
+	info->i2c = chip->client;
+
+	info->map = dev_get_regmap(&info->i2c->dev, NULL);
+	if (!info->map) {
+		dev_err(&pdev->dev, "no regmap!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	info->dev = &pdev->dev;
+	dev_set_drvdata(&pdev->dev, info);
+
+	ret = request_threaded_irq(info->irq, NULL, rtc_update_handler,
+				   IRQF_ONESHOT, "rtc", info);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+			info->irq, ret);
+		goto out;
+	}
+
+	ret = pm80x_rtc_read_time(&pdev->dev, &tm);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to read initial time.\n");
+		goto out_rtc;
+	}
+	if ((tm.tm_year < 70) || (tm.tm_year > 138)) {
+		tm.tm_year = 70;
+		tm.tm_mon = 0;
+		tm.tm_mday = 1;
+		tm.tm_hour = 0;
+		tm.tm_min = 0;
+		tm.tm_sec = 0;
+		ret = pm80x_rtc_set_time(&pdev->dev, &tm);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "Failed to set initial time.\n");
+			goto out_rtc;
+		}
+	}
+	rtc_tm_to_time(&tm, &ticks);
+
+	info->rtc_dev = rtc_device_register("88pm80x-rtc", &pdev->dev,
+					    &pm80x_rtc_ops, THIS_MODULE);
+	ret = PTR_ERR(info->rtc_dev);
+	if (IS_ERR(info->rtc_dev)) {
+		dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+		goto out_rtc;
+	}
+	/*
+	 * enable internal XO instead of internal 3.25MHz clock since it can
+	 * free running in PMIC power-down state.
+	 */
+	regmap_update_bits(info->map, PM800_RTC_CONTROL, PM800_RTC1_USE_XO,
+			   PM800_RTC1_USE_XO);
+
+	if (pdev->dev.parent->platform_data) {
+		pm80x_pdata = pdev->dev.parent->platform_data;
+		pdata = pm80x_pdata->rtc;
+		if (pdata)
+			info->rtc_dev->dev.platform_data = &pdata->rtc_wakeup;
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	return 0;
+out_rtc:
+	free_irq(info->irq, info);
+out:
+	devm_kfree(&pdev->dev, info);
+	return ret;
+}
+
+static int __devexit pm80x_rtc_remove(struct platform_device *pdev)
+{
+	struct pm80x_rtc_info *info = platform_get_drvdata(pdev);
+	platform_set_drvdata(pdev, NULL);
+	rtc_device_unregister(info->rtc_dev);
+	free_irq(info->irq, info);
+	devm_kfree(&pdev->dev, info);
+	return 0;
+}
+
+static struct platform_driver pm80x_rtc_driver = {
+	.driver = {
+		   .name = "88pm80x-rtc",
+		   .owner = THIS_MODULE,
+		   .pm = &pm80x_rtc_pm_ops,
+		   },
+	.probe = pm80x_rtc_probe,
+	.remove = __devexit_p(pm80x_rtc_remove),
+};
+
+module_platform_driver(pm80x_rtc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell 88PM80x RTC driver");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_ALIAS("platform:88pm80x-rtc");
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 3/3] input: add onkey support to 88PM80X PMIC
  2012-06-28  3:13 [PATCH 0/3 V1] add 88pm80x mfd driver Qiao Zhou
  2012-06-28  3:13 ` [PATCH 1/3] mfd: support 88pm80x in 80x driver Qiao Zhou
  2012-06-28  3:13 ` [PATCH 2/3] rtc: add rtc support to 88PM80X PMIC Qiao Zhou
@ 2012-06-28  3:13 ` Qiao Zhou
  2 siblings, 0 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-06-28  3:13 UTC (permalink / raw)
  To: linux-arm-kernel

add onkey support to MARVELL 88PM80X PMIC.

Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
 drivers/input/misc/88pm80x_onkey.c |  194 ++++++++++++++++++++++++++++++++++++
 drivers/input/misc/Kconfig         |   10 ++
 drivers/input/misc/Makefile        |    1 +
 3 files changed, 205 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/misc/88pm80x_onkey.c

diff --git a/drivers/input/misc/88pm80x_onkey.c b/drivers/input/misc/88pm80x_onkey.c
new file mode 100644
index 0000000..091bcf9
--- /dev/null
+++ b/drivers/input/misc/88pm80x_onkey.c
@@ -0,0 +1,194 @@
+/*
+ * 88pm80x_onkey.c - Marvell 88PM80x ONKEY driver
+ *
+ * Copyright (C) 2012 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ * Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define PM800_LONG_ONKEY_EN		(1 << 0)
+#define PM800_LONG_KEY_DELAY		(8)	/* 1 .. 16 seconds */
+#define PM800_LONKEY_PRESS_TIME		((PM800_LONG_KEY_DELAY-1) << 4)
+#define PM800_LONKEY_PRESS_TIME_MASK	(0xF0)
+#define PM800_SW_PDOWN			(1 << 5)
+
+struct pm80x_onkey_info {
+	struct input_dev *idev;
+	struct pm80x_chip *pm80x;
+	struct i2c_client *i2c;
+	struct regmap *map;
+	int irq;
+};
+
+/* 88PM80x gives us an interrupt when ONKEY is held */
+static irqreturn_t pm80x_onkey_handler(int irq, void *data)
+{
+	struct pm80x_onkey_info *info = data;
+	int ret = 0;
+
+	regmap_read(info->map, PM800_STATUS_1, &ret);
+	ret &= PM800_ONKEY_STS1;
+
+	input_report_key(info->idev, KEY_POWER, ret);
+	input_sync(info->idev);
+
+	return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int pm80x_onkey_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+	if (device_may_wakeup(dev))
+		chip->wu_flag |= (1 << PM800_IRQ_ONKEY);
+
+	return 0;
+}
+
+static int pm80x_onkey_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+	if (device_may_wakeup(dev))
+		chip->wu_flag &= ~(1 << PM800_IRQ_ONKEY);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(pm80x_onkey_pm_ops, pm80x_onkey_suspend,
+			 pm80x_onkey_resume);
+
+static int __devinit pm80x_onkey_probe(struct platform_device *pdev)
+{
+
+	void *chip = dev_get_drvdata(pdev->dev.parent);
+	struct pm80x_onkey_info *info;
+	int ret;
+
+	info =
+	    devm_kzalloc(&pdev->dev, sizeof(struct pm80x_onkey_info),
+			 GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	info->pm80x = (struct pm80x_chip *)chip;
+	info->i2c = info->pm80x->client;
+
+	info->irq = platform_get_irq(pdev, 0);
+	if (info->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ resource!\n");
+		return -EINVAL;
+	}
+
+
+	info->map = dev_get_regmap(&info->i2c->dev, NULL);
+	if (!info->map) {
+		dev_err(&pdev->dev, "no regmap!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	info->idev = input_allocate_device();
+	if (!info->idev) {
+		dev_err(&pdev->dev, "Failed to allocate input dev\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	info->idev->name = "88pm80x_on";
+	info->idev->phys = "88pm80x_on/input0";
+	info->idev->id.bustype = BUS_I2C;
+	info->idev->dev.parent = &pdev->dev;
+	info->idev->evbit[0] = BIT_MASK(EV_KEY);
+	info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
+
+	ret = input_register_device(info->idev);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't register input device: %d\n", ret);
+		goto out_reg;
+	}
+
+	ret = request_threaded_irq(info->irq, NULL, pm80x_onkey_handler,
+				   IRQF_ONESHOT, "onkey", info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n",
+			info->irq, ret);
+		goto out_irq;
+	}
+
+	platform_set_drvdata(pdev, info);
+
+	/* Enable long onkey detection */
+	regmap_update_bits(info->map, PM800_RTC_MISC4, PM800_LONG_ONKEY_EN,
+			   PM800_LONG_ONKEY_EN);
+	/* Set 8-second interval */
+	regmap_update_bits(info->map, PM800_RTC_MISC3,
+			   PM800_LONKEY_PRESS_TIME_MASK,
+			   PM800_LONKEY_PRESS_TIME);
+
+	device_init_wakeup(&pdev->dev, 1);
+	return 0;
+
+out_irq:
+	input_unregister_device(info->idev);
+out_reg:
+	input_free_device(info->idev);
+out:
+	devm_kfree(&pdev->dev, info);
+	return ret;
+}
+
+static int __devexit pm80x_onkey_remove(struct platform_device *pdev)
+{
+	struct pm80x_onkey_info *info = platform_get_drvdata(pdev);
+
+	free_irq(info->irq, info);
+	input_unregister_device(info->idev);
+	input_free_device(info->idev);
+	devm_kfree(&pdev->dev, info);
+	return 0;
+}
+
+static struct platform_driver pm80x_onkey_driver = {
+	.driver = {
+		   .name = "88pm80x-onkey",
+		   .owner = THIS_MODULE,
+		   .pm = &pm80x_onkey_pm_ops,
+		   },
+	.probe = pm80x_onkey_probe,
+	.remove = __devexit_p(pm80x_onkey_remove),
+};
+
+module_platform_driver(pm80x_onkey_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell 88PM80x ONKEY driver");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_ALIAS("platform:88pm80x-onkey");
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7faf4a7..8205a66 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -22,6 +22,16 @@ config INPUT_88PM860X_ONKEY
 	  To compile this driver as a module, choose M here: the module
 	  will be called 88pm860x_onkey.
 
+config INPUT_88PM80X_ONKEY
+	tristate "88PM80x ONKEY support"
+	depends on MFD_88PM80X
+	help
+	  Support the ONKEY of Marvell 88PM80x PMICs as an input device
+	  reporting power button status.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called 88pm80x_onkey.
+
 config INPUT_AB8500_PONKEY
 	tristate "AB8500 Pon (PowerOn) Key"
 	depends on AB8500_CORE
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index f55cdf4..83fe6f5 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -5,6 +5,7 @@
 # Each configuration option enables a list of files.
 
 obj-$(CONFIG_INPUT_88PM860X_ONKEY)	+= 88pm860x_onkey.o
+obj-$(CONFIG_INPUT_88PM80X_ONKEY)	+= 88pm80x_onkey.o
 obj-$(CONFIG_INPUT_AB8500_PONKEY)	+= ab8500-ponkey.o
 obj-$(CONFIG_INPUT_AD714X)		+= ad714x.o
 obj-$(CONFIG_INPUT_AD714X_I2C)		+= ad714x-i2c.o
-- 
1.7.0.4

^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28  3:13 ` [PATCH 1/3] mfd: support 88pm80x in 80x driver Qiao Zhou
@ 2012-06-28 11:21   ` Arnd Bergmann
  2012-06-28 11:46     ` Mark Brown
  2012-06-29  2:56     ` Qiao Zhou
  2012-06-29 14:21   ` Arnd Bergmann
  1 sibling, 2 replies; 25+ messages in thread
From: Arnd Bergmann @ 2012-06-28 11:21 UTC (permalink / raw)
  To: linux-arm-kernel

On Thursday 28 June 2012, Qiao Zhou wrote:
> 88PM800 and 88PM805 are two discrete chips used for power management.
> Hardware designer can use them together or only one of them according to
> requirement.
> 

This looks much better than the first version, great work!

However, there are a few things that you have not addressed from my review
and that you did not explain either. Most importantly, you said that you'd
add support for DT based booting, which is not in this version.

What platform is this driver for?
 - If it's for an existing platform that does not have DT support, please
   include the platform changes for that platform so we can see what gets
   done in there.
 - If it's for a new platform, you should not have any platform data in
   this patch, just device tree based probing because that platform will
   also have to use DT probing in order to get merged. The tree that
   carries such a platform out of the kernel can add back the platform
   data parsing, just don't submit it upstream with the main driver.

> due to hardware design, there are some registers used by pm805, but actually
> defined in pm800 chip. so pm805 needs to access pm800 register in some
> occasion. A workaround is used in driver to solve such issue and would be
> removed when it's fixed in latter new chip version.

I don't see from the code what this is referring to. Can you be a little
more specific? Ideally you would have one patch that adds the driver
without this workaround, followed by a patch that just adds that workaround
with an explanation why it's needed, and then you can later just revert
that patch once it's no longer required.

> All I2C operations are accessed by 80x-i2c driver, and register access is via
> regmap interface.
> 
> The benefit is that client drivers only need one kind of read/write API. I2C
> and MFD driver can be shared in both 800 and 805.

I'm not sure what the purpose of this split is. Usually you only separate out
the i2c parts if the same driver has multiple host-side interfaces, e.g. i2c
and spi. If your driver only has one of them, it's easier to just have one
file. Using regmap however seems to be a good idea nonetheless.

I would also suggest you consider splitting the driver into three separate
parts after moving the i2c file into the core file:

a) one "library" that contains the common code and exports symbols
b) one driver for 805 that contains just the 805 specific parts and
   registers the i2c driver for 805
c) one driver for 800 that contains just the 800 specific parts and
   registers the i2c driver for 800

Does that sound reasonable?

There is very little common that is actually part of the 88pm80x_common
file, everything else is specific to just one of the variants. The only common
parts I found are device_irq_init_80x and device_irq_exit_80x, and
even for those I'm not sure if it wouldn't be easier to just
have separate functions.

The common parts seem to be mostly in your current _i2c.c file, so that could
instead become the common file when you split 805 from 800. You basically
just export the pm_ops and the probe/remove functions and let that be called
from the individual drivers.

> +	if (pdata->pm80x_plat_config)
> +		pdata->pm80x_plat_config(chip, pdata);

You should definitely remove these callbacks, as I pointed out in the
initial review.

> +int __devinit pm80x_device_init(struct pm80x_chip *chip,
> +				struct pm80x_platform_data *pdata)
> +{
> +	int ret = 0;
> +
> +	switch (chip->id) {
> +	case CHIP_PM800:
> +		ret = device_800_init(chip, chip->client, pdata);
> +		break;
> +	case CHIP_PM805:
> +		ret = device_805_init(chip, chip->client, pdata);
> +		break;
> +	default:
> +		dev_err(chip->dev, "%s incorrect chip id!\n", __func__);
> +		return -EINVAL;
> +	}

It seems strange that you access the i2c_client in the common file,
rather than just working with the regmap.


> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
> new file mode 100644
> index 0000000..9e2e11d
> --- /dev/null

A lot of the contents of this file should not really be globally visible.
Most of the register definitions are just used by the common mfd driver,
so I would put them in a header file in the mfd directory or (even better)
just into the .c file that uses them.


	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28 11:21   ` Arnd Bergmann
@ 2012-06-28 11:46     ` Mark Brown
  2012-06-28 14:32       ` Arnd Bergmann
  2012-06-29  2:56     ` Qiao Zhou
  1 sibling, 1 reply; 25+ messages in thread
From: Mark Brown @ 2012-06-28 11:46 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 28, 2012 at 11:21:56AM +0000, Arnd Bergmann wrote:

> What platform is this driver for?
>  - If it's for an existing platform that does not have DT support, please
>    include the platform changes for that platform so we can see what gets
>    done in there.

This is really not normal practice for device drivers, it'd be a serious
obstacle to getting drivers integrated if we have to have a board merged
with every driver.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120628/85bd8638/attachment.sig>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28 11:46     ` Mark Brown
@ 2012-06-28 14:32       ` Arnd Bergmann
  2012-06-29  1:18         ` Haojian Zhuang
  0 siblings, 1 reply; 25+ messages in thread
From: Arnd Bergmann @ 2012-06-28 14:32 UTC (permalink / raw)
  To: linux-arm-kernel

On Thursday 28 June 2012, Mark Brown wrote:
> Show Details
>   On Thu, Jun 28, 2012 at 11:21:56AM +0000, Arnd Bergmann wrote:
> 
> > What platform is this driver for?
> >  - If it's for an existing platform that does not have DT support, please
> >    include the platform changes for that platform so we can see what gets
> >    done in there.
> 
> This is really not normal practice for device drivers, it'd be a serious
> obstacle to getting drivers integrated if we have to have a board merged
> with every driver.

But it would be very helpful to see how the platform data is set, especially
with the callback. If the callback is just there to set up a regulator
or clock, then it should be changed to a more generic way.

	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28 14:32       ` Arnd Bergmann
@ 2012-06-29  1:18         ` Haojian Zhuang
  2012-06-29  1:29           ` Mark Brown
  2012-06-29 14:18           ` Arnd Bergmann
  0 siblings, 2 replies; 25+ messages in thread
From: Haojian Zhuang @ 2012-06-29  1:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jun 28, 2012 at 10:32 PM, Arnd Bergmann <arnd@arndb.de> wrote:
> On Thursday 28 June 2012, Mark Brown wrote:
>> Show Details
>> ? On Thu, Jun 28, 2012 at 11:21:56AM +0000, Arnd Bergmann wrote:
>>
>> > What platform is this driver for?
>> > ?- If it's for an existing platform that does not have DT support, please
>> > ? ?include the platform changes for that platform so we can see what gets
>> > ? ?done in there.
>>
>> This is really not normal practice for device drivers, it'd be a serious
>> obstacle to getting drivers integrated if we have to have a board merged
>> with every driver.
>
> But it would be very helpful to see how the platform data is set, especially
> with the callback. If the callback is just there to set up a regulator
> or clock, then it should be changed to a more generic way.
>
> ? ? ? ?Arnd

No, the callbacks is not used to set up a regulator or clock. They're used to
configure the logic that are not integrated into drivers yet. For example, one
special regulator needs active in sleep mode; some power saving configuration
with board; accessing special register for fuse or OTP, .... Could we set up
milestones for this? I agree that we need to move to DT support. But we have
the interface of OF_DEV_AUXDATA in machine driver. We can use them to
deliver platform data even with callback into drivers.

By the way, this PMIC is installed on new platforms that are not
submitted patches
into mainline yet. They're used and verified in internal code without
DT support. The
plan is submitting PMIC pathces before the new platform. If we only
support DT, we
can't verify these code.

Regards
Haojian

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-29  1:18         ` Haojian Zhuang
@ 2012-06-29  1:29           ` Mark Brown
  2012-06-29 14:18           ` Arnd Bergmann
  1 sibling, 0 replies; 25+ messages in thread
From: Mark Brown @ 2012-06-29  1:29 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 29, 2012 at 09:18:07AM +0800, Haojian Zhuang wrote:
> On Thu, Jun 28, 2012 at 10:32 PM, Arnd Bergmann <arnd@arndb.de> wrote:

> > But it would be very helpful to see how the platform data is set, especially
> > with the callback. If the callback is just there to set up a regulator
> > or clock, then it should be changed to a more generic way.

> No, the callbacks is not used to set up a regulator or clock. They're used to
> configure the logic that are not integrated into drivers yet. For example, one
> special regulator needs active in sleep mode; some power saving configuration

This is a *totally* normal pattern for PMICs, normally the things that
are being configured by Linux would be configured prior to Linux
starting and the callback is basically there for overriding things so we
can fix up mistakes.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120629/bd351af7/attachment.sig>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28 11:21   ` Arnd Bergmann
  2012-06-28 11:46     ` Mark Brown
@ 2012-06-29  2:56     ` Qiao Zhou
  2012-06-29 13:58       ` Arnd Bergmann
  1 sibling, 1 reply; 25+ messages in thread
From: Qiao Zhou @ 2012-06-29  2:56 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/28/2012 07:21 PM, Arnd Bergmann wrote:
> On Thursday 28 June 2012, Qiao Zhou wrote:
>> 88PM800 and 88PM805 are two discrete chips used for power management.
>> Hardware designer can use them together or only one of them according to
>> requirement.
>>
>
> This looks much better than the first version, great work!
>
> However, there are a few things that you have not addressed from my review
> and that you did not explain either. Most importantly, you said that you'd
> add support for DT based booting, which is not in this version.
>
> What platform is this driver for?
>   - If it's for an existing platform that does not have DT support, please
>     include the platform changes for that platform so we can see what gets
>     done in there.
>   - If it's for a new platform, you should not have any platform data in
>     this patch, just device tree based probing because that platform will
>     also have to use DT probing in order to get merged. The tree that
>     carries such a platform out of the kernel can add back the platform
>     data parsing, just don't submit it upstream with the main driver.
it's for a new platform as Haojian has commented. currently we can't 
only support DT for verifying issue.
>
>> due to hardware design, there are some registers used by pm805, but actually
>> defined in pm800 chip. so pm805 needs to access pm800 register in some
>> occasion. A workaround is used in driver to solve such issue and would be
>> removed when it's fixed in latter new chip version.
>
> I don't see from the code what this is referring to. Can you be a little
> more specific? Ideally you would have one patch that adds the driver
> without this workaround, followed by a patch that just adds that workaround
> with an explanation why it's needed, and then you can later just revert
> that patch once it's no longer required.
would update and give details to explain.
>
>> All I2C operations are accessed by 80x-i2c driver, and register access is via
>> regmap interface.
>>
>> The benefit is that client drivers only need one kind of read/write API. I2C
>> and MFD driver can be shared in both 800 and 805.
>
> I'm not sure what the purpose of this split is. Usually you only separate out
> the i2c parts if the same driver has multiple host-side interfaces, e.g. i2c
> and spi. If your driver only has one of them, it's easier to just have one
> file. Using regmap however seems to be a good idea nonetheless.
>
> I would also suggest you consider splitting the driver into three separate
> parts after moving the i2c file into the core file:
>
> a) one "library" that contains the common code and exports symbols
> b) one driver for 805 that contains just the 805 specific parts and
>     registers the i2c driver for 805
> c) one driver for 800 that contains just the 800 specific parts and
>     registers the i2c driver for 800
>
> Does that sound reasonable?
>
> There is very little common that is actually part of the 88pm80x_common
> file, everything else is specific to just one of the variants. The only common
> parts I found are device_irq_init_80x and device_irq_exit_80x, and
> even for those I'm not sure if it wouldn't be easier to just
> have separate functions.
>
> The common parts seem to be mostly in your current _i2c.c file, so that could
> instead become the common file when you split 805 from 800. You basically
> just export the pm_ops and the probe/remove functions and let that be called
> from the individual drivers.
Arnd, the comments is inaccurate for I2C operations. actually all I2C 
operations are handled by regmap in the new driver. and no more API 
needs to be exported by 88pm80x-i2c driver. driver which needs to access 
registers only uses uses regmap handle via dev_get_regmap, only i2c 
client needed. actually there is no split in I2C operations. please let 
me know if I miss your meaning.

in the 88pm80x_core.c, actually it only has five functions, 
irq_init_80x, irq_exit_80x, dev_init_800, dev_init_805, dev_exit_80x. 
there is indeed few common part. so I assume your meaning is that: 
creating 88pm800_core.c and 88pm805_core.c separtely, which implement 
irq_init, irq_exit, dev_init, dev_exit for its own chip. is that correct?
>
>> +	if (pdata->pm80x_plat_config)
>> +		pdata->pm80x_plat_config(chip, pdata);
>
> You should definitely remove these callbacks, as I pointed out in the
> initial review.
would remove it.
>
>> +int __devinit pm80x_device_init(struct pm80x_chip *chip,
>> +				struct pm80x_platform_data *pdata)
>> +{
>> +	int ret = 0;
>> +
>> +	switch (chip->id) {
>> +	case CHIP_PM800:
>> +		ret = device_800_init(chip, chip->client, pdata);
>> +		break;
>> +	case CHIP_PM805:
>> +		ret = device_805_init(chip, chip->client, pdata);
>> +		break;
>> +	default:
>> +		dev_err(chip->dev, "%s incorrect chip id!\n", __func__);
>> +		return -EINVAL;
>> +	}
>
> It seems strange that you access the i2c_client in the common file,
> rather than just working with the regmap.
would remove it.
>
>
>> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
>> new file mode 100644
>> index 0000000..9e2e11d
>> --- /dev/null
>
> A lot of the contents of this file should not really be globally visible.
> Most of the register definitions are just used by the common mfd driver,
> so I would put them in a header file in the mfd directory or (even better)
> just into the .c file that uses them.
yes, would category these registers. some registers which is used by 
regulator/rtc/onkey/codec/headset det/MISC would be kept in this header 
file, while others would be removed from globally visibility.
>
>
> 	Arnd
>
Arnd,

thanks for your review & good suggestions!

-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-29  2:56     ` Qiao Zhou
@ 2012-06-29 13:58       ` Arnd Bergmann
  2012-07-02  7:50         ` Qiao Zhou
  0 siblings, 1 reply; 25+ messages in thread
From: Arnd Bergmann @ 2012-06-29 13:58 UTC (permalink / raw)
  To: linux-arm-kernel

On Friday 29 June 2012, Qiao Zhou wrote:
> On 06/28/2012 07:21 PM, Arnd Bergmann wrote:

> >> All I2C operations are accessed by 80x-i2c driver, and register access is via
> >> regmap interface.
> >>
> >> The benefit is that client drivers only need one kind of read/write API. I2C
> >> and MFD driver can be shared in both 800 and 805.
> >
> > I'm not sure what the purpose of this split is. Usually you only separate out
> > the i2c parts if the same driver has multiple host-side interfaces, e.g. i2c
> > and spi. If your driver only has one of them, it's easier to just have one
> > file. Using regmap however seems to be a good idea nonetheless.
> >
> > I would also suggest you consider splitting the driver into three separate
> > parts after moving the i2c file into the core file:
> >
> > a) one "library" that contains the common code and exports symbols
> > b) one driver for 805 that contains just the 805 specific parts and
> >     registers the i2c driver for 805
> > c) one driver for 800 that contains just the 800 specific parts and
> >     registers the i2c driver for 800
> >
> > Does that sound reasonable?
> >
> > There is very little common that is actually part of the 88pm80x_common
> > file, everything else is specific to just one of the variants. The only common
> > parts I found are device_irq_init_80x and device_irq_exit_80x, and
> > even for those I'm not sure if it wouldn't be easier to just
> > have separate functions.
> >
> > The common parts seem to be mostly in your current _i2c.c file, so that could
> > instead become the common file when you split 805 from 800. You basically
> > just export the pm_ops and the probe/remove functions and let that be called
> > from the individual drivers.
>
> Arnd, the comments is inaccurate for I2C operations. actually all I2C 
> operations are handled by regmap in the new driver. and no more API 
> needs to be exported by 88pm80x-i2c driver. driver which needs to access 
> registers only uses uses regmap handle via dev_get_regmap, only i2c 
> client needed. actually there is no split in I2C operations. please let 
> me know if I miss your meaning.

The point was something else: The regmap always uses i2c behind the covers,
unlike drivers that have a common file using regmap to abstract the
interface so that they can be used with either i2c or spi.

> in the 88pm80x_core.c, actually it only has five functions, 
> irq_init_80x, irq_exit_80x, dev_init_800, dev_init_805, dev_exit_80x. 
> there is indeed few common part. so I assume your meaning is that: 
> creating 88pm800_core.c and 88pm805_core.c separtely, which implement 
> irq_init, irq_exit, dev_init, dev_exit for its own chip. is that correct?

Right. However, I would go further and swap the layering of the driver
parts: Right now, a common pm08x_probe from the common i2c_driver structure
calls the specific device_800_init and device_805_init through
a global pm80x_device_init. When you do the split, I would move the
i2c_driver structure to each of the two 88pm80?_core.c files and have
an individual probe function in them that calls the common pm08x_probe
and then its own device_init.

This is the layering that most other subsystems use when you have drivers
sharing some common code.

> >> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
> >> new file mode 100644
> >> index 0000000..9e2e11d
> >> --- /dev/null
> >
> > A lot of the contents of this file should not really be globally visible.
> > Most of the register definitions are just used by the common mfd driver,
> > so I would put them in a header file in the mfd directory or (even better)
> > just into the .c file that uses them.
> yes, would category these registers. some registers which is used by 
> regulator/rtc/onkey/codec/headset det/MISC would be kept in this header 
> file, while others would be removed from globally visibility.

Ok, sounds good.

	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-29  1:18         ` Haojian Zhuang
  2012-06-29  1:29           ` Mark Brown
@ 2012-06-29 14:18           ` Arnd Bergmann
  1 sibling, 0 replies; 25+ messages in thread
From: Arnd Bergmann @ 2012-06-29 14:18 UTC (permalink / raw)
  To: linux-arm-kernel

On Friday 29 June 2012, Haojian Zhuang wrote:
> On Thu, Jun 28, 2012 at 10:32 PM, Arnd Bergmann <arnd@arndb.de> wrote:
> > On Thursday 28 June 2012, Mark Brown wrote:
> >> Show Details
> >>   On Thu, Jun 28, 2012 at 11:21:56AM +0000, Arnd Bergmann wrote:
> >>
> >> > What platform is this driver for?
> >> >  - If it's for an existing platform that does not have DT support, please
> >> >    include the platform changes for that platform so we can see what gets
> >> >    done in there.
> >>
> >> This is really not normal practice for device drivers, it'd be a serious
> >> obstacle to getting drivers integrated if we have to have a board merged
> >> with every driver.
> >
> > But it would be very helpful to see how the platform data is set, especially
> > with the callback. If the callback is just there to set up a regulator
> > or clock, then it should be changed to a more generic way.
> 
> No, the callbacks is not used to set up a regulator or clock. They're used to
> configure the logic that are not integrated into drivers yet. For example, one
> special regulator needs active in sleep mode; some power saving configuration
> with board; accessing special register for fuse or OTP, .... Could we set up
> milestones for this? I agree that we need to move to DT support. But we have
> the interface of OF_DEV_AUXDATA in machine driver. We can use them to
> deliver platform data even with callback into drivers.

Yes, this is complex enough that using auxdata sounds like the right approach
in this case for now, thanks for the explanation.

> By the way, this PMIC is installed on new platforms that are not
> submitted patches
> into mainline yet. They're used and verified in internal code without
> DT support. The
> plan is submitting PMIC pathces before the new platform. If we only
> support DT, we
> can't verify these code.

Well, as long as no platform in the mainline kernel supports this driver,
it does not matter the upstream maintainers whether the code has been
verified. The more important question is whether it is done the right way.

My usual recommendation for cases like this is to submit the driver in the
way it would be written if all the other code using it was already perfect.
In your internal verification, you already carry patches to add the new
platform support, so you can add more patches to work around missing parts
of the driver, like adding back the platform_data. What is more important
for you is that you get as much as possible upstream in a version that has
been reviewed and acked by the relevant maintainers.

I guess it's ok here to leave the platform data in for now, as it sounds
that it can take a longer time (if ever) to fully eliminate it. However,
I think it would be nice to also add preliminary DT bindings for the
things that can be DT properties.


 	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-28  3:13 ` [PATCH 1/3] mfd: support 88pm80x in 80x driver Qiao Zhou
  2012-06-28 11:21   ` Arnd Bergmann
@ 2012-06-29 14:21   ` Arnd Bergmann
  2012-06-30 12:02     ` Mark Brown
  1 sibling, 1 reply; 25+ messages in thread
From: Arnd Bergmann @ 2012-06-29 14:21 UTC (permalink / raw)
  To: linux-arm-kernel

On Thursday 28 June 2012, Qiao Zhou wrote:
> +       ret =
> +           regmap_add_irq_chip(chip->regmap, chip->irq, flags, chip->irq_base,
> +                               chip->regmap_irq_chip, &chip->irq_data);

One more question I have about this, mainly for Mark:

I understand that by passing -1 into regmap_add_irq_chip, it should allocate
its own linear irq domain rather using a legacy domain. Does that require
any other changes? Should the drivers that use the irq number just use
regmap_irq_get_virq() to get the linux irq number, or is there more to do
here?

	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-29 14:21   ` Arnd Bergmann
@ 2012-06-30 12:02     ` Mark Brown
  0 siblings, 0 replies; 25+ messages in thread
From: Mark Brown @ 2012-06-30 12:02 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jun 29, 2012 at 02:21:40PM +0000, Arnd Bergmann wrote:

> One more question I have about this, mainly for Mark:

> I understand that by passing -1 into regmap_add_irq_chip, it should allocate
> its own linear irq domain rather using a legacy domain. Does that require
> any other changes? Should the drivers that use the irq number just use
> regmap_irq_get_virq() to get the linux irq number, or is there more to do
> here?

That's it - the users don't need to worry about where the Linux IRQ
number came from, they can just call that function and it'll give them
something they can use regardless if it's a linear, legacy or something
else domain.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120630/315a6610/attachment.sig>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-06-29 13:58       ` Arnd Bergmann
@ 2012-07-02  7:50         ` Qiao Zhou
  2012-07-02  9:22           ` Qiao Zhou
  0 siblings, 1 reply; 25+ messages in thread
From: Qiao Zhou @ 2012-07-02  7:50 UTC (permalink / raw)
  To: linux-arm-kernel

On 06/29/2012 09:58 PM, Arnd Bergmann wrote:
> On Friday 29 June 2012, Qiao Zhou wrote:
>> On 06/28/2012 07:21 PM, Arnd Bergmann wrote:
>
>>>> All I2C operations are accessed by 80x-i2c driver, and register access is via
>>>> regmap interface.
>>>>
>>>> The benefit is that client drivers only need one kind of read/write API. I2C
>>>> and MFD driver can be shared in both 800 and 805.
>>>
>>> I'm not sure what the purpose of this split is. Usually you only separate out
>>> the i2c parts if the same driver has multiple host-side interfaces, e.g. i2c
>>> and spi. If your driver only has one of them, it's easier to just have one
>>> file. Using regmap however seems to be a good idea nonetheless.
>>>
>>> I would also suggest you consider splitting the driver into three separate
>>> parts after moving the i2c file into the core file:
>>>
>>> a) one "library" that contains the common code and exports symbols
>>> b) one driver for 805 that contains just the 805 specific parts and
>>>      registers the i2c driver for 805
>>> c) one driver for 800 that contains just the 800 specific parts and
>>>      registers the i2c driver for 800
>>>
>>> Does that sound reasonable?
>>>
>>> There is very little common that is actually part of the 88pm80x_common
>>> file, everything else is specific to just one of the variants. The only common
>>> parts I found are device_irq_init_80x and device_irq_exit_80x, and
>>> even for those I'm not sure if it wouldn't be easier to just
>>> have separate functions.
>>>
>>> The common parts seem to be mostly in your current _i2c.c file, so that could
>>> instead become the common file when you split 805 from 800. You basically
>>> just export the pm_ops and the probe/remove functions and let that be called
>>> from the individual drivers.
>>
>> Arnd, the comments is inaccurate for I2C operations. actually all I2C
>> operations are handled by regmap in the new driver. and no more API
>> needs to be exported by 88pm80x-i2c driver. driver which needs to access
>> registers only uses uses regmap handle via dev_get_regmap, only i2c
>> client needed. actually there is no split in I2C operations. please let
>> me know if I miss your meaning.
>
> The point was something else: The regmap always uses i2c behind the covers,
> unlike drivers that have a common file using regmap to abstract the
> interface so that they can be used with either i2c or spi.
Arnd, understand your meaning. driver just exports the API while callers 
doesn't need to know whether it's via i2c or spi in lower driver. It 
makes sense. thanks!

Here I have a question about the implementation specifically with 
88pm800. this chip has 3 register pages, and 3 i2c address corresponding 
to each page. so it requires an additional parameter to point out the 
page(i2c) to be accessed. currently I didn't think of a good API to 
export for such purpose. seems to me, the 88pm800 chip is already bound 
to i2c interface, and using regmap directly is a better solution. could 
you give some suggestions?
>
>> in the 88pm80x_core.c, actually it only has five functions,
>> irq_init_80x, irq_exit_80x, dev_init_800, dev_init_805, dev_exit_80x.
>> there is indeed few common part. so I assume your meaning is that:
>> creating 88pm800_core.c and 88pm805_core.c separtely, which implement
>> irq_init, irq_exit, dev_init, dev_exit for its own chip. is that correct?
>
> Right. However, I would go further and swap the layering of the driver
> parts: Right now, a common pm08x_probe from the common i2c_driver structure
> calls the specific device_800_init and device_805_init through
> a global pm80x_device_init. When you do the split, I would move the
> i2c_driver structure to each of the two 88pm80?_core.c files and have
> an individual probe function in them that calls the common pm08x_probe
> and then its own device_init.
>
> This is the layering that most other subsystems use when you have drivers
> sharing some common code.
would update according to suggestion. thanks!
>
>>>> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
>>>> new file mode 100644
>>>> index 0000000..9e2e11d
>>>> --- /dev/null
>>>
>>> A lot of the contents of this file should not really be globally visible.
>>> Most of the register definitions are just used by the common mfd driver,
>>> so I would put them in a header file in the mfd directory or (even better)
>>> just into the .c file that uses them.
>> yes, would category these registers. some registers which is used by
>> regulator/rtc/onkey/codec/headset det/MISC would be kept in this header
>> file, while others would be removed from globally visibility.
>
> Ok, sounds good.
>
> 	Arnd
>
Arnd, thanks for your suggestions!

-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02  7:50         ` Qiao Zhou
@ 2012-07-02  9:22           ` Qiao Zhou
  2012-07-02 10:03             ` Mark Brown
  0 siblings, 1 reply; 25+ messages in thread
From: Qiao Zhou @ 2012-07-02  9:22 UTC (permalink / raw)
  To: linux-arm-kernel

On 07/02/2012 03:50 PM, Qiao Zhou wrote:
> On 06/29/2012 09:58 PM, Arnd Bergmann wrote:
>> On Friday 29 June 2012, Qiao Zhou wrote:
>>> On 06/28/2012 07:21 PM, Arnd Bergmann wrote:
>>
>>>>> All I2C operations are accessed by 80x-i2c driver, and register
>>>>> access is via
>>>>> regmap interface.
>>>>>
>>>>> The benefit is that client drivers only need one kind of read/write
>>>>> API. I2C
>>>>> and MFD driver can be shared in both 800 and 805.
>>>>
>>>> I'm not sure what the purpose of this split is. Usually you only
>>>> separate out
>>>> the i2c parts if the same driver has multiple host-side interfaces,
>>>> e.g. i2c
>>>> and spi. If your driver only has one of them, it's easier to just
>>>> have one
>>>> file. Using regmap however seems to be a good idea nonetheless.
>>>>
>>>> I would also suggest you consider splitting the driver into three
>>>> separate
>>>> parts after moving the i2c file into the core file:
>>>>
>>>> a) one "library" that contains the common code and exports symbols
>>>> b) one driver for 805 that contains just the 805 specific parts and
>>>>      registers the i2c driver for 805
>>>> c) one driver for 800 that contains just the 800 specific parts and
>>>>      registers the i2c driver for 800
>>>>
>>>> Does that sound reasonable?
>>>>
>>>> There is very little common that is actually part of the 88pm80x_common
>>>> file, everything else is specific to just one of the variants. The
>>>> only common
>>>> parts I found are device_irq_init_80x and device_irq_exit_80x, and
>>>> even for those I'm not sure if it wouldn't be easier to just
>>>> have separate functions.
>>>>
>>>> The common parts seem to be mostly in your current _i2c.c file, so
>>>> that could
>>>> instead become the common file when you split 805 from 800. You
>>>> basically
>>>> just export the pm_ops and the probe/remove functions and let that
>>>> be called
>>>> from the individual drivers.
>>>
>>> Arnd, the comments is inaccurate for I2C operations. actually all I2C
>>> operations are handled by regmap in the new driver. and no more API
>>> needs to be exported by 88pm80x-i2c driver. driver which needs to access
>>> registers only uses uses regmap handle via dev_get_regmap, only i2c
>>> client needed. actually there is no split in I2C operations. please let
>>> me know if I miss your meaning.
>>
>> The point was something else: The regmap always uses i2c behind the
>> covers,
>> unlike drivers that have a common file using regmap to abstract the
>> interface so that they can be used with either i2c or spi.
> Arnd, understand your meaning. driver just exports the API while callers
> doesn't need to know whether it's via i2c or spi in lower driver. It
> makes sense. thanks!
>
> Here I have a question about the implementation specifically with
> 88pm800. this chip has 3 register pages, and 3 i2c address corresponding
> to each page. so it requires an additional parameter to point out the
> page(i2c) to be accessed. currently I didn't think of a good API to
> export for such purpose. seems to me, the 88pm800 chip is already bound
> to i2c interface, and using regmap directly is a better solution. could
> you give some suggestions?
is it OK to export another two groups of r/w interface for gpadc and 
power page separately in 88pm800? such as pm800_gpadc_read_reg() / 
pm800_power_read_reg() etc?  Appreciate any comments.
>>
>>> in the 88pm80x_core.c, actually it only has five functions,
>>> irq_init_80x, irq_exit_80x, dev_init_800, dev_init_805, dev_exit_80x.
>>> there is indeed few common part. so I assume your meaning is that:
>>> creating 88pm800_core.c and 88pm805_core.c separtely, which implement
>>> irq_init, irq_exit, dev_init, dev_exit for its own chip. is that
>>> correct?
>>
>> Right. However, I would go further and swap the layering of the driver
>> parts: Right now, a common pm08x_probe from the common i2c_driver
>> structure
>> calls the specific device_800_init and device_805_init through
>> a global pm80x_device_init. When you do the split, I would move the
>> i2c_driver structure to each of the two 88pm80?_core.c files and have
>> an individual probe function in them that calls the common pm08x_probe
>> and then its own device_init.
>>
>> This is the layering that most other subsystems use when you have drivers
>> sharing some common code.
> would update according to suggestion. thanks!
>>
>>>>> diff --git a/include/linux/mfd/88pm80x.h b/include/linux/mfd/88pm80x.h
>>>>> new file mode 100644
>>>>> index 0000000..9e2e11d
>>>>> --- /dev/null
>>>>
>>>> A lot of the contents of this file should not really be globally
>>>> visible.
>>>> Most of the register definitions are just used by the common mfd
>>>> driver,
>>>> so I would put them in a header file in the mfd directory or (even
>>>> better)
>>>> just into the .c file that uses them.
>>> yes, would category these registers. some registers which is used by
>>> regulator/rtc/onkey/codec/headset det/MISC would be kept in this header
>>> file, while others would be removed from globally visibility.
>>
>> Ok, sounds good.
>>
>>     Arnd
>>
> Arnd, thanks for your suggestions!
>


-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02  9:22           ` Qiao Zhou
@ 2012-07-02 10:03             ` Mark Brown
  2012-07-02 10:09               ` Qiao Zhou
  0 siblings, 1 reply; 25+ messages in thread
From: Mark Brown @ 2012-07-02 10:03 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jul 02, 2012 at 05:22:34PM +0800, Qiao Zhou wrote:
> On 07/02/2012 03:50 PM, Qiao Zhou wrote:

> >Here I have a question about the implementation specifically with
> >88pm800. this chip has 3 register pages, and 3 i2c address corresponding
> >to each page. so it requires an additional parameter to point out the
> >page(i2c) to be accessed. currently I didn't think of a good API to
> >export for such purpose. seems to me, the 88pm800 chip is already bound
> >to i2c interface, and using regmap directly is a better solution. could
> >you give some suggestions?

> is it OK to export another two groups of r/w interface for gpadc and
> power page separately in 88pm800? such as pm800_gpadc_read_reg() /
> pm800_power_read_reg() etc?  Appreciate any comments.

What do you mean by pages?  regmap has paging support which just maps
everything into a single flat register map from the point of view of
callers.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120702/6fd60484/attachment.sig>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02 10:03             ` Mark Brown
@ 2012-07-02 10:09               ` Qiao Zhou
  2012-07-02 10:12                 ` Mark Brown
  0 siblings, 1 reply; 25+ messages in thread
From: Qiao Zhou @ 2012-07-02 10:09 UTC (permalink / raw)
  To: linux-arm-kernel

On 07/02/2012 06:03 PM, Mark Brown wrote:
> On Mon, Jul 02, 2012 at 05:22:34PM +0800, Qiao Zhou wrote:
>> On 07/02/2012 03:50 PM, Qiao Zhou wrote:
>
>>> Here I have a question about the implementation specifically with
>>> 88pm800. this chip has 3 register pages, and 3 i2c address corresponding
>>> to each page. so it requires an additional parameter to point out the
>>> page(i2c) to be accessed. currently I didn't think of a good API to
>>> export for such purpose. seems to me, the 88pm800 chip is already bound
>>> to i2c interface, and using regmap directly is a better solution. could
>>> you give some suggestions?
>
>> is it OK to export another two groups of r/w interface for gpadc and
>> power page separately in 88pm800? such as pm800_gpadc_read_reg() /
>> pm800_power_read_reg() etc?  Appreciate any comments.
>
> What do you mean by pages?  regmap has paging support which just maps
> everything into a single flat register map from the point of view of
> callers.
>
Mark, let me explain: the 88pm800 chip has three i2c address internally, 
which we called different page instead. it confuses you with the 
register page_read/write operation. there are registers in each i2c 
address domain, and we need to use different i2c client to access reg in 
different domain. such as some common regs are in the page of i2c_addr = 
0x30, and power related regs are in the page of i2c_addr = 0x31, and 
gpadc related regs are in the page of 0x32.

-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02 10:09               ` Qiao Zhou
@ 2012-07-02 10:12                 ` Mark Brown
  2012-07-02 10:15                   ` Qiao Zhou
  0 siblings, 1 reply; 25+ messages in thread
From: Mark Brown @ 2012-07-02 10:12 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jul 02, 2012 at 06:09:57PM +0800, Qiao Zhou wrote:
> On 07/02/2012 06:03 PM, Mark Brown wrote:

> >What do you mean by pages?  regmap has paging support which just maps
> >everything into a single flat register map from the point of view of
> >callers.

> Mark, let me explain: the 88pm800 chip has three i2c address
> internally, which we called different page instead. it confuses you
> with the register page_read/write operation. there are registers in
> each i2c address domain, and we need to use different i2c client to
> access reg in different domain. such as some common regs are in the
> page of i2c_addr = 0x30, and power related regs are in the page of
> i2c_addr = 0x31, and gpadc related regs are in the page of 0x32.

These aren't what people normally call pages, those are just separate
I2C devices from a Linux point of view.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120702/7e53ff13/attachment.sig>

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02 10:12                 ` Mark Brown
@ 2012-07-02 10:15                   ` Qiao Zhou
       [not found]                     ` <4FF174AA.3020001-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
  0 siblings, 1 reply; 25+ messages in thread
From: Qiao Zhou @ 2012-07-02 10:15 UTC (permalink / raw)
  To: linux-arm-kernel

On 07/02/2012 06:12 PM, Mark Brown wrote:
> On Mon, Jul 02, 2012 at 06:09:57PM +0800, Qiao Zhou wrote:
>> On 07/02/2012 06:03 PM, Mark Brown wrote:
>
>>> What do you mean by pages?  regmap has paging support which just maps
>>> everything into a single flat register map from the point of view of
>>> callers.
>
>> Mark, let me explain: the 88pm800 chip has three i2c address
>> internally, which we called different page instead. it confuses you
>> with the register page_read/write operation. there are registers in
>> each i2c address domain, and we need to use different i2c client to
>> access reg in different domain. such as some common regs are in the
>> page of i2c_addr = 0x30, and power related regs are in the page of
>> i2c_addr = 0x31, and gpadc related regs are in the page of 0x32.
>
> These aren't what people normally call pages, those are just separate
> I2C devices from a Linux point of view.
>
Mark, surely I'll pay attention to the terms used. thanks!
due to there separate I2C devices, does it make sense to export separate 
r/w interface for them? do you have suggestion in such case?

-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02 10:15                   ` Qiao Zhou
@ 2012-07-02 15:58                         ` Arnd Bergmann
  0 siblings, 0 replies; 25+ messages in thread
From: Arnd Bergmann @ 2012-07-02 15:58 UTC (permalink / raw)
  To: Qiao Zhou
  Cc: Mark Brown, haojian.zhuang-Re5JQEeQqe8AvxtiuMwx3w, Chao Xie,
	rpurdie-Fm38FmjxZ/leoWH0uzbU5w, sameo-VuQAYsv1563Yd54FQh9/CA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Wilbur Wang,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA

On Monday 02 July 2012, Qiao Zhou wrote:
> On 07/02/2012 06:12 PM, Mark Brown wrote:
> > On Mon, Jul 02, 2012 at 06:09:57PM +0800, Qiao Zhou wrote:
> >> On 07/02/2012 06:03 PM, Mark Brown wrote:
> >
> >>> What do you mean by pages?  regmap has paging support which just maps
> >>> everything into a single flat register map from the point of view of
> >>> callers.
> >
> >> Mark, let me explain: the 88pm800 chip has three i2c address
> >> internally, which we called different page instead. it confuses you
> >> with the register page_read/write operation. there are registers in
> >> each i2c address domain, and we need to use different i2c client to
> >> access reg in different domain. such as some common regs are in the
> >> page of i2c_addr = 0x30, and power related regs are in the page of
> >> i2c_addr = 0x31, and gpadc related regs are in the page of 0x32.
> >
> > These aren't what people normally call pages, those are just separate
> > I2C devices from a Linux point of view.
> >
> Mark, surely I'll pay attention to the terms used. thanks!
> due to there separate I2C devices, does it make sense to export separate 
> r/w interface for them? do you have suggestion in such case?

(adding the i2c mailing list to get more insight)

I think in case of device tree based probing, it would be straightforward
to represent 88pm800 as a single device with three addresses in the "reg"
property, while the natural linux representation would be one regular
i2c_client device with two dummies. Do we or should we have any
infrastructure to deal with this?

If this is a common scenario, we could probably let regmap handle it
entirely internally and represent the i2c client with its dummies
as a single regmap.

	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
@ 2012-07-02 15:58                         ` Arnd Bergmann
  0 siblings, 0 replies; 25+ messages in thread
From: Arnd Bergmann @ 2012-07-02 15:58 UTC (permalink / raw)
  To: linux-arm-kernel

On Monday 02 July 2012, Qiao Zhou wrote:
> On 07/02/2012 06:12 PM, Mark Brown wrote:
> > On Mon, Jul 02, 2012 at 06:09:57PM +0800, Qiao Zhou wrote:
> >> On 07/02/2012 06:03 PM, Mark Brown wrote:
> >
> >>> What do you mean by pages?  regmap has paging support which just maps
> >>> everything into a single flat register map from the point of view of
> >>> callers.
> >
> >> Mark, let me explain: the 88pm800 chip has three i2c address
> >> internally, which we called different page instead. it confuses you
> >> with the register page_read/write operation. there are registers in
> >> each i2c address domain, and we need to use different i2c client to
> >> access reg in different domain. such as some common regs are in the
> >> page of i2c_addr = 0x30, and power related regs are in the page of
> >> i2c_addr = 0x31, and gpadc related regs are in the page of 0x32.
> >
> > These aren't what people normally call pages, those are just separate
> > I2C devices from a Linux point of view.
> >
> Mark, surely I'll pay attention to the terms used. thanks!
> due to there separate I2C devices, does it make sense to export separate 
> r/w interface for them? do you have suggestion in such case?

(adding the i2c mailing list to get more insight)

I think in case of device tree based probing, it would be straightforward
to represent 88pm800 as a single device with three addresses in the "reg"
property, while the natural linux representation would be one regular
i2c_client device with two dummies. Do we or should we have any
infrastructure to deal with this?

If this is a common scenario, we could probably let regmap handle it
entirely internally and represent the i2c client with its dummies
as a single regmap.

	Arnd

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/3] mfd: support 88pm80x in 80x driver
  2012-07-02 15:58                         ` Arnd Bergmann
@ 2012-07-03  2:28                             ` Qiao Zhou
  -1 siblings, 0 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-07-03  2:28 UTC (permalink / raw)
  To: Arnd Bergmann
  Cc: Mark Brown, haojian.zhuang-Re5JQEeQqe8AvxtiuMwx3w, Chao Xie,
	rpurdie-Fm38FmjxZ/leoWH0uzbU5w, sameo-VuQAYsv1563Yd54FQh9/CA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r, Wilbur Wang,
	linux-i2c-u79uwXL29TY76Z2rM5mHXA

On 07/02/2012 11:58 PM, Arnd Bergmann wrote:
> On Monday 02 July 2012, Qiao Zhou wrote:
>> On 07/02/2012 06:12 PM, Mark Brown wrote:
>>> On Mon, Jul 02, 2012 at 06:09:57PM +0800, Qiao Zhou wrote:
>>>> On 07/02/2012 06:03 PM, Mark Brown wrote:
>>>
>>>>> What do you mean by pages?  regmap has paging support which just maps
>>>>> everything into a single flat register map from the point of view of
>>>>> callers.
>>>
>>>> Mark, let me explain: the 88pm800 chip has three i2c address
>>>> internally, which we called different page instead. it confuses you
>>>> with the register page_read/write operation. there are registers in
>>>> each i2c address domain, and we need to use different i2c client to
>>>> access reg in different domain. such as some common regs are in the
>>>> page of i2c_addr = 0x30, and power related regs are in the page of
>>>> i2c_addr = 0x31, and gpadc related regs are in the page of 0x32.
>>>
>>> These aren't what people normally call pages, those are just separate
>>> I2C devices from a Linux point of view.
>>>
>> Mark, surely I'll pay attention to the terms used. thanks!
>> due to there separate I2C devices, does it make sense to export separate
>> r/w interface for them? do you have suggestion in such case?
>
> (adding the i2c mailing list to get more insight)
>
> I think in case of device tree based probing, it would be straightforward
> to represent 88pm800 as a single device with three addresses in the "reg"
> property, while the natural linux representation would be one regular
> i2c_client device with two dummies. Do we or should we have any
> infrastructure to deal with this?
>
> If this is a common scenario, we could probably let regmap handle it
> entirely internally and represent the i2c client with its dummies
> as a single regmap.
actually there are many drivers under mfd which have this common issue, 
which has i2c dummy devices, such as max77693.c, max8925-i2c.c, 
ab3100-core.c, max8997.c, max8998.c, s5m-core.c etc. some use regmap 
handle directly as param in exported r/w api, some add extra param to 
differentiate i2c dummy. it seems to be a common scenario. how do we 
handle the API in short term and long term?
>
> 	Arnd
>


-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 1/3] mfd: support 88pm80x in 80x driver
@ 2012-07-03  2:28                             ` Qiao Zhou
  0 siblings, 0 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-07-03  2:28 UTC (permalink / raw)
  To: linux-arm-kernel

On 07/02/2012 11:58 PM, Arnd Bergmann wrote:
> On Monday 02 July 2012, Qiao Zhou wrote:
>> On 07/02/2012 06:12 PM, Mark Brown wrote:
>>> On Mon, Jul 02, 2012 at 06:09:57PM +0800, Qiao Zhou wrote:
>>>> On 07/02/2012 06:03 PM, Mark Brown wrote:
>>>
>>>>> What do you mean by pages?  regmap has paging support which just maps
>>>>> everything into a single flat register map from the point of view of
>>>>> callers.
>>>
>>>> Mark, let me explain: the 88pm800 chip has three i2c address
>>>> internally, which we called different page instead. it confuses you
>>>> with the register page_read/write operation. there are registers in
>>>> each i2c address domain, and we need to use different i2c client to
>>>> access reg in different domain. such as some common regs are in the
>>>> page of i2c_addr = 0x30, and power related regs are in the page of
>>>> i2c_addr = 0x31, and gpadc related regs are in the page of 0x32.
>>>
>>> These aren't what people normally call pages, those are just separate
>>> I2C devices from a Linux point of view.
>>>
>> Mark, surely I'll pay attention to the terms used. thanks!
>> due to there separate I2C devices, does it make sense to export separate
>> r/w interface for them? do you have suggestion in such case?
>
> (adding the i2c mailing list to get more insight)
>
> I think in case of device tree based probing, it would be straightforward
> to represent 88pm800 as a single device with three addresses in the "reg"
> property, while the natural linux representation would be one regular
> i2c_client device with two dummies. Do we or should we have any
> infrastructure to deal with this?
>
> If this is a common scenario, we could probably let regmap handle it
> entirely internally and represent the i2c client with its dummies
> as a single regmap.
actually there are many drivers under mfd which have this common issue, 
which has i2c dummy devices, such as max77693.c, max8925-i2c.c, 
ab3100-core.c, max8997.c, max8998.c, s5m-core.c etc. some use regmap 
handle directly as param in exported r/w api, some add extra param to 
differentiate i2c dummy. it seems to be a common scenario. how do we 
handle the API in short term and long term?
>
> 	Arnd
>


-- 

Best Regards
Qiao

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH 2/3] rtc: add rtc support to 88PM80X PMIC
  2012-06-13  9:04 [PATCH 0/3 V0] add 88pm80x mfd driver Qiao Zhou
@ 2012-06-13  9:04 ` Qiao Zhou
  0 siblings, 0 replies; 25+ messages in thread
From: Qiao Zhou @ 2012-06-13  9:04 UTC (permalink / raw)
  To: linux-arm-kernel

add rtc driver for MARVELL 88PM80X PMIC and enable rtc function.

Signed-off-by: Qiao Zhou <zhouqiao@marvell.com>
---
 drivers/rtc/Kconfig       |   10 ++
 drivers/rtc/Makefile      |    1 +
 drivers/rtc/rtc-88pm80x.c |  370 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 381 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-88pm80x.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 08cbdb9..f3b49f8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -135,6 +135,16 @@ config RTC_DRV_88PM860X
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-88pm860x.
 
+config RTC_DRV_88PM80X
+	tristate "Marvell 88PM80x"
+	depends on RTC_CLASS && I2C && MFD_88PM80X
+	help
+	  If you say yes here you get support for RTC function in Marvell
+	  88PM80x chips.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-88pm80x.
+
 config RTC_DRV_DS1307
 	tristate "Dallas/Maxim DS1307/37/38/39/40, ST M41T00, EPSON RX-8025"
 	help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2973921..0d5b2b6 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -16,6 +16,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
 # Keep the list ordered.
 
 obj-$(CONFIG_RTC_DRV_88PM860X)  += rtc-88pm860x.o
+obj-$(CONFIG_RTC_DRV_88PM80X)	+= rtc-88pm80x.o
 obj-$(CONFIG_RTC_DRV_AB3100)	+= rtc-ab3100.o
 obj-$(CONFIG_RTC_DRV_AB8500)	+= rtc-ab8500.o
 obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
diff --git a/drivers/rtc/rtc-88pm80x.c b/drivers/rtc/rtc-88pm80x.c
new file mode 100644
index 0000000..109f0e5
--- /dev/null
+++ b/drivers/rtc/rtc-88pm80x.c
@@ -0,0 +1,370 @@
+/*
+ * Real Time Clock driver for Marvell 88PM80x PMIC
+ *
+ * Copyright (c) 2012 Marvell International Ltd.
+ *  Wenzeng Chen<wzch@marvell.com>
+ *  Qiao Zhou <zhouqiao@marvell.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/rtc.h>
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/88pm80x.h>
+#include <linux/sched.h>
+
+struct pm80x_rtc_info {
+	struct pm80x_chip *chip;
+	struct i2c_client *i2c;
+	struct rtc_device *rtc_dev;
+	struct device *dev;
+	struct delayed_work calib_work;
+
+	int irq;
+	int vrtc;
+	int (*sync) (unsigned int ticks);
+};
+
+static int pm80x_rtc_read_time(struct device *dev, struct rtc_time *tm);
+
+static irqreturn_t rtc_update_handler(int irq, void *data)
+{
+	struct pm80x_rtc_info *info = (struct pm80x_rtc_info *)data;
+	int mask;
+
+	mask = PM800_ALARM | PM800_ALARM_WAKEUP;
+	pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, mask | PM800_ALARM1_EN,
+		       mask);
+	rtc_update_irq(info->rtc_dev, 1, RTC_AF);
+	return IRQ_HANDLED;
+}
+
+static int pm80x_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+
+	if (enabled)
+		pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, PM800_ALARM1_EN,
+			       PM800_ALARM1_EN);
+	else
+		pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, PM800_ALARM1_EN,
+			       0);
+	return 0;
+}
+
+/*
+ * Calculate the next alarm time given the requested alarm time mask
+ * and the current time.
+ */
+static void rtc_next_alarm_time(struct rtc_time *next, struct rtc_time *now,
+				struct rtc_time *alrm)
+{
+	unsigned long next_time;
+	unsigned long now_time;
+
+	next->tm_year = now->tm_year;
+	next->tm_mon = now->tm_mon;
+	next->tm_mday = now->tm_mday;
+	next->tm_hour = alrm->tm_hour;
+	next->tm_min = alrm->tm_min;
+	next->tm_sec = alrm->tm_sec;
+
+	rtc_tm_to_time(now, &now_time);
+	rtc_tm_to_time(next, &next_time);
+
+	if (next_time < now_time) {
+		/* Advance one day */
+		next_time += 60 * 60 * 24;
+		rtc_time_to_tm(next_time, next);
+	}
+}
+
+static int pm80x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	unsigned char buf[4];
+	unsigned long ticks, base, data;
+	pm80x_bulk_read(info->i2c, PM800_RTC_EXPIRE2_1, 4, buf);
+	base = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	dev_dbg(info->dev, "%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]);
+
+	/* load 32-bit read-only counter */
+	pm80x_bulk_read(info->i2c, PM800_RTC_COUNTER1, 4, buf);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	ticks = base + data;
+	dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+	rtc_time_to_tm(ticks, tm);
+	return 0;
+}
+
+static int pm80x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	unsigned char buf[4];
+	unsigned long ticks, base, data;
+	if ((tm->tm_year < 70) || (tm->tm_year > 138)) {
+		dev_dbg(info->dev,
+			"Set time %d out of range. Please set time between 1970 to 2038.\n",
+			1900 + tm->tm_year);
+		return -EINVAL;
+	}
+	rtc_tm_to_time(tm, &ticks);
+
+	/* load 32-bit read-only counter */
+	pm80x_bulk_read(info->i2c, PM800_RTC_COUNTER1, 4, buf);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	base = ticks - data;
+	dev_dbg(info->dev, "set base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+	buf[0] = base & 0xFF;
+	buf[1] = (base >> 8) & 0xFF;
+	buf[2] = (base >> 16) & 0xFF;
+	buf[3] = (base >> 24) & 0xFF;
+	pm80x_bulk_write(info->i2c, PM800_RTC_EXPIRE2_1, 4, buf);
+
+	if (info->sync)
+		info->sync(ticks);
+
+	return 0;
+}
+
+static int pm80x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	unsigned char buf[4];
+	unsigned long ticks, base, data;
+	int ret;
+
+	pm80x_bulk_read(info->i2c, PM800_RTC_EXPIRE2_1, 4, buf);
+	base = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	dev_dbg(info->dev, "%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]);
+
+	pm80x_bulk_read(info->i2c, PM800_RTC_EXPIRE1_1, 4, buf);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	ticks = base + data;
+	dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+
+	rtc_time_to_tm(ticks, &alrm->time);
+	ret = pm80x_reg_read(info->i2c, PM800_RTC_CONTROL);
+	alrm->enabled = (ret & PM800_ALARM1_EN) ? 1 : 0;
+	alrm->pending = (ret & (PM800_ALARM | PM800_ALARM_WAKEUP)) ? 1 : 0;
+	return 0;
+}
+
+static int pm80x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct pm80x_rtc_info *info = dev_get_drvdata(dev);
+	struct rtc_time now_tm, alarm_tm;
+	unsigned long ticks, base, data;
+	unsigned char buf[4];
+	int mask;
+
+	pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, PM800_ALARM1_EN, 0);
+
+	pm80x_bulk_read(info->i2c, PM800_RTC_EXPIRE2_1, 4, buf);
+	base = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	dev_dbg(info->dev, "%x-%x-%x-%x\n", buf[0], buf[1], buf[2], buf[3]);
+
+	/* load 32-bit read-only counter */
+	pm80x_bulk_read(info->i2c, PM800_RTC_COUNTER1, 4, buf);
+	data = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
+	ticks = base + data;
+	dev_dbg(info->dev, "get base:0x%lx, RO count:0x%lx, ticks:0x%lx\n",
+		base, data, ticks);
+
+	rtc_time_to_tm(ticks, &now_tm);
+	dev_dbg(info->dev, "%s, now time : %lu\n", __func__, ticks);
+	rtc_next_alarm_time(&alarm_tm, &now_tm, &alrm->time);
+	/* get new ticks for alarm in 24 hours */
+	rtc_tm_to_time(&alarm_tm, &ticks);
+	dev_dbg(info->dev, "%s, alarm time: %lu\n", __func__, ticks);
+	data = ticks - base;
+
+	buf[0] = data & 0xff;
+	buf[1] = (data >> 8) & 0xff;
+	buf[2] = (data >> 16) & 0xff;
+	buf[3] = (data >> 24) & 0xff;
+	pm80x_bulk_write(info->i2c, PM800_RTC_EXPIRE1_1, 4, buf);
+	if (alrm->enabled) {
+		mask = PM800_ALARM | PM800_ALARM_WAKEUP | PM800_ALARM1_EN;
+		pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, mask, mask);
+	} else {
+		mask = PM800_ALARM | PM800_ALARM_WAKEUP | PM800_ALARM1_EN;
+		pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, mask,
+			       PM800_ALARM | PM800_ALARM_WAKEUP);
+	}
+	return 0;
+}
+
+static const struct rtc_class_ops pm80x_rtc_ops = {
+	.read_time = pm80x_rtc_read_time,
+	.set_time = pm80x_rtc_set_time,
+	.read_alarm = pm80x_rtc_read_alarm,
+	.set_alarm = pm80x_rtc_set_alarm,
+	.alarm_irq_enable = pm80x_rtc_alarm_irq_enable,
+};
+
+#ifdef CONFIG_PM
+static int pm80x_rtc_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+	if (device_may_wakeup(dev))
+		chip->wu_flag_pm800 |= (1 << PM800_IRQ_RTC);
+
+	return 0;
+}
+
+static int pm80x_rtc_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+	if (device_may_wakeup(dev))
+		chip->wu_flag_pm800 &= ~(1 << PM800_IRQ_RTC);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(pm80x_rtc_pm_ops, pm80x_rtc_suspend, pm80x_rtc_resume);
+
+static int __devinit pm80x_rtc_probe(struct platform_device *pdev)
+{
+	struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+	struct pm80x_platform_data *pm80x_pdata;
+	struct pm80x_rtc_pdata *pdata = NULL;
+	struct pm80x_rtc_info *info;
+	struct rtc_time tm;
+	unsigned long ticks = 0;
+	int ret;
+
+	pdata = pdev->dev.platform_data;
+	if (pdata == NULL)
+		dev_warn(&pdev->dev, "No platform data!\n");
+
+	info =
+	    devm_kzalloc(&pdev->dev, sizeof(struct pm80x_rtc_info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+	info->irq = platform_get_irq(pdev, 0);
+	if (info->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ resource!\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	info->chip = chip;
+	info->i2c = chip->base_page;
+	info->dev = &pdev->dev;
+	dev_set_drvdata(&pdev->dev, info);
+
+	ret = request_threaded_irq(info->irq, NULL, rtc_update_handler,
+				   IRQF_ONESHOT, "rtc", info);
+	if (ret < 0) {
+		dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+			info->irq, ret);
+		goto out;
+	}
+
+	ret = pm80x_rtc_read_time(&pdev->dev, &tm);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to read initial time.\n");
+		goto out_rtc;
+	}
+	if ((tm.tm_year < 70) || (tm.tm_year > 138)) {
+		tm.tm_year = 70;
+		tm.tm_mon = 0;
+		tm.tm_mday = 1;
+		tm.tm_hour = 0;
+		tm.tm_min = 0;
+		tm.tm_sec = 0;
+		ret = pm80x_rtc_set_time(&pdev->dev, &tm);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "Failed to set initial time.\n");
+			goto out_rtc;
+		}
+	}
+	rtc_tm_to_time(&tm, &ticks);
+	if (pdata && pdata->sync) {
+		pdata->sync(ticks);
+		info->sync = pdata->sync;
+	}
+
+	info->rtc_dev = rtc_device_register("88pm80x-rtc", &pdev->dev,
+					    &pm80x_rtc_ops, THIS_MODULE);
+	ret = PTR_ERR(info->rtc_dev);
+	if (IS_ERR(info->rtc_dev)) {
+		dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+		goto out_rtc;
+	}
+	/*
+	 * enable internal XO instead of internal 3.25MHz clock since it can
+	 * free running in PMIC power-down state.
+	 */
+	pm80x_set_bits(info->i2c, PM800_RTC_CONTROL, PM800_RTC1_USE_XO,
+		       PM800_RTC1_USE_XO);
+
+	if (pdev->dev.parent->platform_data) {
+		pm80x_pdata = pdev->dev.parent->platform_data;
+		pdata = pm80x_pdata->rtc;
+		if (pdata)
+			info->rtc_dev->dev.platform_data = &pdata->rtc_wakeup;
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	return 0;
+out_rtc:
+	free_irq(info->irq, info);
+out:
+	devm_kfree(&pdev->dev, info);
+	return ret;
+}
+
+static int __devexit pm80x_rtc_remove(struct platform_device *pdev)
+{
+	struct pm80x_rtc_info *info = platform_get_drvdata(pdev);
+	platform_set_drvdata(pdev, NULL);
+	rtc_device_unregister(info->rtc_dev);
+	free_irq(info->irq, info);
+	devm_kfree(&pdev->dev, info);
+	return 0;
+}
+
+static struct platform_driver pm80x_rtc_driver = {
+	.driver = {
+		   .name = "88pm80x-rtc",
+		   .owner = THIS_MODULE,
+		   .pm = &pm80x_rtc_pm_ops,
+		   },
+	.probe = pm80x_rtc_probe,
+	.remove = __devexit_p(pm80x_rtc_remove),
+};
+
+module_platform_driver(pm80x_rtc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Marvell 88PM80x RTC driver");
+MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>");
+MODULE_ALIAS("platform:88pm80x-rtc");
-- 
1.7.4.1

^ permalink raw reply related	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2012-07-03  2:28 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-06-28  3:13 [PATCH 0/3 V1] add 88pm80x mfd driver Qiao Zhou
2012-06-28  3:13 ` [PATCH 1/3] mfd: support 88pm80x in 80x driver Qiao Zhou
2012-06-28 11:21   ` Arnd Bergmann
2012-06-28 11:46     ` Mark Brown
2012-06-28 14:32       ` Arnd Bergmann
2012-06-29  1:18         ` Haojian Zhuang
2012-06-29  1:29           ` Mark Brown
2012-06-29 14:18           ` Arnd Bergmann
2012-06-29  2:56     ` Qiao Zhou
2012-06-29 13:58       ` Arnd Bergmann
2012-07-02  7:50         ` Qiao Zhou
2012-07-02  9:22           ` Qiao Zhou
2012-07-02 10:03             ` Mark Brown
2012-07-02 10:09               ` Qiao Zhou
2012-07-02 10:12                 ` Mark Brown
2012-07-02 10:15                   ` Qiao Zhou
     [not found]                     ` <4FF174AA.3020001-eYqpPyKDWXRBDgjK7y7TUQ@public.gmane.org>
2012-07-02 15:58                       ` Arnd Bergmann
2012-07-02 15:58                         ` Arnd Bergmann
     [not found]                         ` <201207021558.51246.arnd-r2nGTMty4D4@public.gmane.org>
2012-07-03  2:28                           ` Qiao Zhou
2012-07-03  2:28                             ` Qiao Zhou
2012-06-29 14:21   ` Arnd Bergmann
2012-06-30 12:02     ` Mark Brown
2012-06-28  3:13 ` [PATCH 2/3] rtc: add rtc support to 88PM80X PMIC Qiao Zhou
2012-06-28  3:13 ` [PATCH 3/3] input: add onkey " Qiao Zhou
  -- strict thread matches above, loose matches on Subject: below --
2012-06-13  9:04 [PATCH 0/3 V0] add 88pm80x mfd driver Qiao Zhou
2012-06-13  9:04 ` [PATCH 2/3] rtc: add rtc support to 88PM80X PMIC Qiao Zhou

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.