[PATCH] mfd: add MAX8907 core driver

Stephen Warren swarren at wwwdotorg.org
Fri Jul 27 05:40:30 EST 2012


From: Gyungoh Yoo <jack.yoo at maxim-ic.com>

The MAX8907 is an I2C-based power-management IC containing voltage
regulators, a reset controller, a real-time clock, and a touch-screen
controller.

The original driver was written by:
* Gyungoh Yoo <jack.yoo at maxim-ic.com>

Various fixes and enhancements by:
* Jin Park <jinyoungp at nvidia.com>
* Tom Cherry <tcherry at nvidia.com>
* Prashant Gaikwad <pgaikwad at nvidia.com>
* Dan Willemsen <dwillemsen at nvidia.com>
* Laxman Dewangan <ldewangan at nvidia.com>

During upstreaming, I (swarren):
* Converted to regmap.
* Converted to irq domain, and stopped storing state in globals.
* Allowed probing from device tree.
* Renamed from max8907c->max8907, since the driver covers at least the
C and B revisions.
* General cleanup.

Signed-off-by: Gyungoh Yoo <jack.yoo at maxim-ic.com>
Signed-off-by: Stephen Warren <swarren at nvidia.com>
---
Note that I also have a regulator driver for this part, which I'll post
in the near future. That driver will depend on this patch (at least the
header file). I'm not sure how dependencies between the mfd and regulator
trees are usually managed.

 .../devicetree/bindings/regulator/max8907.txt      |   49 +++
 drivers/mfd/Kconfig                                |   11 +
 drivers/mfd/Makefile                               |    1 +
 drivers/mfd/max8907-irq.c                          |  436 ++++++++++++++++++++
 drivers/mfd/max8907.c                              |  213 ++++++++++
 include/linux/mfd/max8907.h                        |  248 +++++++++++
 6 files changed, 958 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/regulator/max8907.txt
 create mode 100644 drivers/mfd/max8907-irq.c
 create mode 100644 drivers/mfd/max8907.c
 create mode 100644 include/linux/mfd/max8907.h

diff --git a/Documentation/devicetree/bindings/regulator/max8907.txt b/Documentation/devicetree/bindings/regulator/max8907.txt
new file mode 100644
index 0000000..dd48036
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/max8907.txt
@@ -0,0 +1,49 @@
+MAX8907 regulator
+
+Required properties:
+- compatible: "maxim,max8907"
+- reg: I2C slave address
+- interrupts: The interrupt output of the controller
+- regulators: A node that houses a sub-node for each regulator within the
+  device. Each sub-node is identified using the regulator-compatible
+  property, with valid values listed below. The content of each sub-node
+  is defined by the standard binding for regulators; see regulator.txt.
+
+Valid regulator-compatible values are:
+
+  sd1, sd2, sd3, ldo1, ldo2, ldo3, ldo4, ldo5, ldo6, ldo7, ldo8, ldo9, ldo10,
+  ldo11, ldo12, ldo13, ldo14, ldo15, ldo16, ldo17, ldo18, ldo19, ldo20, out5v,
+  out33v, bbat, sdby, vrtc, wled.
+
+Example:
+
+		max8907 at 3c {
+			compatible = "maxim,max8907";
+			reg = <0x3c>;
+			interrupts = <0 86 0x4>;
+
+			regulators {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				regulator at 0 {
+					reg = <0>;
+					regulator-compatible = "sd1";
+					regulator-name = "nvvdd_sv1,vdd_cpu_pmu";
+					regulator-min-microvolt = <1000000>;
+					regulator-max-microvolt = <1000000>;
+					regulator-always-on;
+				};
+
+				regulator at 1 {
+					reg = <1>;
+					regulator-compatible = "sd2";
+					regulator-name = "nvvdd_sv2,vdd_core";
+					regulator-min-microvolt = <1200000>;
+					regulator-max-microvolt = <1200000>;
+					regulator-always-on;
+				};
+...
+			};
+		};
+	};
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 66fd378..1ef2814 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -476,6 +476,17 @@ config MFD_MAX77693
 	  additional drivers must be enabled in order to use the functionality
 	  of the device.
 
+config MFD_MAX8907
+	tristate "Maxim Semiconductor MAX8907 PMIC Support"
+	select MFD_CORE
+	depends on I2C=y && GENERIC_HARDIRQS
+	select REGMAP_I2C
+	help
+	  Say yes here to support for Maxim Semiconductor MAX8907. This is
+	  a Power Management IC. This driver provides common support for
+	  accessing the device; additional drivers must be enabled in order
+	  to use the functionality of the device.
+
 config MFD_MAX8925
 	bool "Maxim Semiconductor MAX8925 PMIC Support"
 	depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 79dd22d..3cc47ee 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_MFD_DA9052_I2C)	+= da9052-i2c.o
 
 obj-$(CONFIG_MFD_MAX77686)	+= max77686.o max77686-irq.o
 obj-$(CONFIG_MFD_MAX77693)	+= max77693.o max77693-irq.o
+obj-$(CONFIG_MFD_MAX8907)	+= max8907.o max8907-irq.o
 max8925-objs			:= max8925-core.o max8925-i2c.o
 obj-$(CONFIG_MFD_MAX8925)	+= max8925.o
 obj-$(CONFIG_MFD_MAX8997)	+= max8997.o max8997-irq.o
diff --git a/drivers/mfd/max8907-irq.c b/drivers/mfd/max8907-irq.c
new file mode 100644
index 0000000..d459a6a
--- /dev/null
+++ b/drivers/mfd/max8907-irq.c
@@ -0,0 +1,436 @@
+/*
+ * Battery driver for Maxim MAX8907
+ *
+ * Copyright (C) 2010-2012, NVIDIA CORPORATION. All rights reserved.
+ * Based on driver/mfd/max8925-core.c,
+ *     Copyright (C) 2009-2010 Marvell International Ltd.
+ * Portions based on drives/mfd/tps65910-irq.c,
+ *     Copyright 2010 Texas Instruments Inc.
+ *     Author: Graeme Gregory <gg at slimlogic.co.uk>
+ *     Author: Jorge Eduardo Candelaria <jedu at slimlogic.co.uk>
+ *
+ * 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/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max8907.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+struct max8907_irq_data {
+	int	reg;
+	int	mask_reg;
+	int	offs;		/* bit offset in mask register */
+	bool	is_rtc;
+};
+
+static struct max8907_irq_data max8907_irqs[] = {
+	[MAX8907_IRQ_VCHG_DC_OVP] = {
+		.reg		= MAX8907_REG_CHG_IRQ1,
+		.mask_reg	= MAX8907_REG_CHG_IRQ1_MASK,
+		.offs		= 1 << 0,
+	},
+	[MAX8907_IRQ_VCHG_DC_F] = {
+		.reg		= MAX8907_REG_CHG_IRQ1,
+		.mask_reg	= MAX8907_REG_CHG_IRQ1_MASK,
+		.offs		= 1 << 1,
+	},
+	[MAX8907_IRQ_VCHG_DC_R] = {
+		.reg		= MAX8907_REG_CHG_IRQ1,
+		.mask_reg	= MAX8907_REG_CHG_IRQ1_MASK,
+		.offs		= 1 << 2,
+	},
+	[MAX8907_IRQ_VCHG_THM_OK_R] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 0,
+	},
+	[MAX8907_IRQ_VCHG_THM_OK_F] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 1,
+	},
+	[MAX8907_IRQ_VCHG_MBATTLOW_F] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 2,
+	},
+	[MAX8907_IRQ_VCHG_MBATTLOW_R] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 3,
+	},
+	[MAX8907_IRQ_VCHG_RST] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 4,
+	},
+	[MAX8907_IRQ_VCHG_DONE] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 5,
+	},
+	[MAX8907_IRQ_VCHG_TOPOFF] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 6,
+	},
+	[MAX8907_IRQ_VCHG_TMR_FAULT] = {
+		.reg		= MAX8907_REG_CHG_IRQ2,
+		.mask_reg	= MAX8907_REG_CHG_IRQ2_MASK,
+		.offs		= 1 << 7,
+	},
+	[MAX8907_IRQ_GPM_RSTIN] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 0,
+	},
+	[MAX8907_IRQ_GPM_MPL] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 1,
+	},
+	[MAX8907_IRQ_GPM_SW_3SEC] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 2,
+	},
+	[MAX8907_IRQ_GPM_EXTON_F] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 3,
+	},
+	[MAX8907_IRQ_GPM_EXTON_R] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 4,
+	},
+	[MAX8907_IRQ_GPM_SW_1SEC] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 5,
+	},
+	[MAX8907_IRQ_GPM_SW_F] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 6,
+	},
+	[MAX8907_IRQ_GPM_SW_R] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ1,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ1_MASK,
+		.offs		= 1 << 7,
+	},
+	[MAX8907_IRQ_GPM_SYSCKEN_F] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ2,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ2_MASK,
+		.offs		= 1 << 0,
+	},
+	[MAX8907_IRQ_GPM_SYSCKEN_R] = {
+		.reg		= MAX8907_REG_ON_OFF_IRQ2,
+		.mask_reg	= MAX8907_REG_ON_OFF_IRQ2_MASK,
+		.offs		= 1 << 1,
+	},
+	[MAX8907_IRQ_RTC_ALARM1] = {
+		.reg		= MAX8907_REG_RTC_IRQ,
+		.mask_reg	= MAX8907_REG_RTC_IRQ_MASK,
+		.offs		= 1 << 2,
+		.is_rtc		= true,
+	},
+	[MAX8907_IRQ_RTC_ALARM0] = {
+		.reg		= MAX8907_REG_RTC_IRQ,
+		.mask_reg	= MAX8907_REG_RTC_IRQ_MASK,
+		.offs		= 1 << 3,
+		.is_rtc		= true,
+	},
+};
+
+static irqreturn_t max8907_irq(int irq, void *data)
+{
+	struct max8907 *chip = data;
+	int i;
+	struct regmap *rm;
+	unsigned int value;
+	struct max8907_irq_data *irq_data;
+	struct irq_data *d;
+	irqreturn_t handled = IRQ_NONE;
+
+	for (i = 0; i < ARRAY_SIZE(max8907_irqs); i++) {
+		irq_data = &max8907_irqs[i];
+		d = irq_get_irq_data(irq_find_mapping(chip->domain, i));
+
+		if (irq_data->is_rtc)
+			rm = chip->regmap_rtc;
+		else
+			rm = chip->regmap_gen;
+
+		regmap_read(rm, irq_data->reg, &value);
+		if (!irqd_irq_disabled(d) && (value & irq_data->offs)) {
+			handle_nested_irq(irq_find_mapping(chip->domain, i));
+			handled = IRQ_HANDLED;
+		}
+	}
+
+	return handled;
+}
+
+static void max8907_irq_lock(struct irq_data *data)
+{
+	struct max8907 *chip = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&chip->irq_lock);
+}
+
+static void max8907_irq_set_masks(struct max8907 *chip)
+{
+	unsigned char irq_chg[2], irq_on[2], irq_rtc;
+	int i;
+	struct max8907_irq_data *irq_data;
+	struct irq_data *d;
+
+	irq_chg[0] = irq_chg[1] = irq_on[0] = irq_on[1] = irq_rtc = 0xFF;
+
+	for (i = 0; i < ARRAY_SIZE(max8907_irqs); i++) {
+		irq_data = &max8907_irqs[i];
+		d = irq_get_irq_data(irq_find_mapping(chip->domain, i));
+
+		if (!irqd_irq_disabled(d)) {
+			/* 1 -- disable, 0 -- enable */
+			switch (irq_data->mask_reg) {
+			case MAX8907_REG_CHG_IRQ1_MASK:
+				irq_chg[0] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_CHG_IRQ2_MASK:
+				irq_chg[1] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_ON_OFF_IRQ1_MASK:
+				irq_on[0] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_ON_OFF_IRQ2_MASK:
+				irq_on[1] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_RTC_IRQ_MASK:
+				irq_rtc &= ~irq_data->offs;
+				break;
+			default:
+				dev_err(chip->dev, "invalid mask_reg\n");
+				break;
+			}
+		}
+	}
+
+	regmap_write(chip->regmap_gen, MAX8907_REG_CHG_IRQ1_MASK, irq_chg[0]);
+	regmap_write(chip->regmap_gen, MAX8907_REG_CHG_IRQ2_MASK, irq_chg[1]);
+	regmap_write(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ1_MASK,
+		     irq_on[0]);
+	regmap_write(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ2_MASK,
+		     irq_on[1]);
+	regmap_write(chip->regmap_rtc, MAX8907_REG_RTC_IRQ_MASK, irq_rtc);
+}
+
+static void max8907_irq_sync_unlock(struct irq_data *data)
+{
+	struct max8907 *chip = irq_data_get_irq_chip_data(data);
+
+	max8907_irq_set_masks(chip);
+	mutex_unlock(&chip->irq_lock);
+}
+
+static void max8907_irq_enable(struct irq_data *data)
+{
+	/* Everything happens in max8907_irq_sync_unlock */
+}
+
+static void max8907_irq_disable(struct irq_data *data)
+{
+	/* Everything happens in max8907_irq_sync_unlock */
+}
+
+static int max8907_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+	/* Everything happens in max8907_irq_sync_unlock */
+
+	return 0;
+}
+
+static struct irq_chip max8907_irq_chip = {
+	.name			= "max8907",
+	.irq_bus_lock		= max8907_irq_lock,
+	.irq_bus_sync_unlock	= max8907_irq_sync_unlock,
+	.irq_enable		= max8907_irq_enable,
+	.irq_disable		= max8907_irq_disable,
+	.irq_set_wake		= max8907_irq_set_wake,
+};
+
+static int max8907_irq_map(struct irq_domain *h, unsigned int virq,
+			   irq_hw_number_t hw)
+{
+	struct max8907 *chip = h->host_data;
+
+	irq_set_chip_data(virq, chip);
+	irq_set_chip_and_handler(virq, &max8907_irq_chip, handle_edge_irq);
+	irq_set_nested_thread(virq, 1);
+
+	/*
+	 * ARM needs us to explicitly flag the IRQ as valid
+	 * and will set them noprobe when we do so.
+	 */
+#ifdef CONFIG_ARM
+	set_irq_flags(virq, IRQF_VALID);
+#else
+	irq_set_noprobe(virq);
+#endif
+
+	return 0;
+}
+
+static struct irq_domain_ops max8907_domain_ops = {
+	.map = max8907_irq_map,
+	.xlate = irq_domain_xlate_twocell,
+};
+
+int __devinit max8907_irq_init(struct max8907 *chip)
+{
+	struct max8907_platform_data *pdata = dev_get_platdata(chip->dev);
+	unsigned int val;
+	int ret;
+
+	/* clear all interrupts */
+	regmap_read(chip->regmap_gen, MAX8907_REG_CHG_IRQ1, &val);
+	regmap_read(chip->regmap_gen, MAX8907_REG_CHG_IRQ2, &val);
+	regmap_read(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ1, &val);
+	regmap_read(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ2, &val);
+	regmap_read(chip->regmap_rtc, MAX8907_REG_RTC_IRQ, &val);
+
+	/* mask all interrupts */
+	regmap_write(chip->regmap_rtc, MAX8907_REG_ALARM0_CNTL, 0);
+	regmap_write(chip->regmap_rtc, MAX8907_REG_ALARM1_CNTL, 0);
+	regmap_write(chip->regmap_gen, MAX8907_REG_CHG_IRQ1_MASK, 0xff);
+	regmap_write(chip->regmap_gen, MAX8907_REG_CHG_IRQ2_MASK, 0xff);
+	regmap_write(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ1_MASK, 0xff);
+	regmap_write(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ2_MASK, 0xff);
+	regmap_write(chip->regmap_rtc, MAX8907_REG_RTC_IRQ_MASK, 0xff);
+
+	mutex_init(&chip->irq_lock);
+
+	if (pdata)
+		chip->domain = irq_domain_add_legacy(chip->dev->of_node,
+						     chip->i2c_gen->irq,
+						     pdata->irq_base,
+						     0,
+						     &max8907_domain_ops,
+						     chip);
+	else
+		chip->domain = irq_domain_add_linear(chip->dev->of_node,
+						     chip->i2c_gen->irq,
+						     &max8907_domain_ops,
+						     chip);
+
+	if (!chip->domain) {
+		dev_err(chip->dev, "Failed to create IRQ domain\n");
+		return -ENOMEM;
+	}
+
+	ret = request_threaded_irq(chip->i2c_gen->irq, NULL, max8907_irq,
+				   IRQF_ONESHOT, "max8907", chip);
+	if (ret) {
+		dev_err(chip->dev, "Failed to request core IRQ: %d\n", ret);
+		goto err_free_domain;
+	}
+
+	ret = device_init_wakeup(chip->dev, 1);
+	if (ret) {
+		dev_err(chip->dev, "device_init_wakeup() failed: %d\n", ret);
+		goto err_free_irq;
+	}
+
+	return 0;
+
+err_free_domain:
+	irq_domain_remove(chip->domain);
+err_free_irq:
+	free_irq(chip->i2c_gen->irq, chip);
+	return ret;
+}
+
+void max8907_irq_free(struct max8907 *chip)
+{
+	irq_domain_remove(chip->domain);
+	free_irq(chip->i2c_gen->irq, chip);
+}
+
+int max8907_irq_suspend(struct i2c_client *i2c, pm_message_t state)
+{
+	struct max8907 *chip = i2c_get_clientdata(i2c);
+	unsigned char irq_chg[2], irq_on[2], irq_rtc;
+	int i;
+	struct max8907_irq_data *irq_data;
+	struct irq_data *d;
+
+	irq_chg[0] = irq_chg[1] = irq_on[0] = irq_on[1] = irq_rtc = 0xFF;
+
+	for (i = 0; i < ARRAY_SIZE(max8907_irqs); i++) {
+		irq_data = &max8907_irqs[i];
+		d = irq_get_irq_data(irq_find_mapping(chip->domain, i));
+
+		if (irqd_is_wakeup_set(d)) {
+			/* 1 -- disable, 0 -- enable */
+			switch (irq_data->mask_reg) {
+			case MAX8907_REG_CHG_IRQ1_MASK:
+				irq_chg[0] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_CHG_IRQ2_MASK:
+				irq_chg[1] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_ON_OFF_IRQ1_MASK:
+				irq_on[0] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_ON_OFF_IRQ2_MASK:
+				irq_on[1] &= ~irq_data->offs;
+				break;
+			case MAX8907_REG_RTC_IRQ_MASK:
+				irq_rtc &= ~irq_data->offs;
+				break;
+			default:
+				dev_err(chip->dev, "invalid mask_reg\n");
+				break;
+			}
+		}
+	}
+
+	regmap_write(chip->regmap_gen, MAX8907_REG_CHG_IRQ1_MASK, irq_chg[0]);
+	regmap_write(chip->regmap_gen, MAX8907_REG_CHG_IRQ2_MASK, irq_chg[1]);
+	regmap_write(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ1_MASK,
+		     irq_on[0]);
+	regmap_write(chip->regmap_gen, MAX8907_REG_ON_OFF_IRQ2_MASK,
+		     irq_on[1]);
+	regmap_write(chip->regmap_rtc, MAX8907_REG_RTC_IRQ_MASK, irq_rtc);
+
+	if (device_may_wakeup(chip->dev))
+		enable_irq_wake(i2c->irq);
+	else
+		disable_irq(i2c->irq);
+
+	return 0;
+}
+
+int max8907_irq_resume(struct i2c_client *i2c)
+{
+	struct max8907 *chip = i2c_get_clientdata(i2c);
+
+	if (device_may_wakeup(chip->dev))
+		disable_irq_wake(i2c->irq);
+	else
+		enable_irq(i2c->irq);
+
+	max8907_irq_set_masks(chip);
+
+	return 0;
+}
diff --git a/drivers/mfd/max8907.c b/drivers/mfd/max8907.c
new file mode 100644
index 0000000..dce17e1
--- /dev/null
+++ b/drivers/mfd/max8907.c
@@ -0,0 +1,213 @@
+/*
+ * max8907.c - mfd driver for MAX8907
+ *
+ * Copyright (C) 2010 Gyungoh Yoo <jack.yoo at maxim-ic.com>
+ * Copyright (C) 2010-2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * 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/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max8907.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+static struct mfd_cell max8907_cells[] = {
+	{ .name = "max8907-regulator", },
+	{ .name = "max8907-rtc", },
+};
+
+static bool max8907_gen_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX8907_REG_ON_OFF_IRQ1:
+	case MAX8907_REG_ON_OFF_STAT:
+	case MAX8907_REG_ON_OFF_IRQ2:
+	case MAX8907_REG_CHG_IRQ1:
+	case MAX8907_REG_CHG_IRQ2:
+	case MAX8907_REG_CHG_STAT:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool max8907_gen_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+	return !max8907_gen_is_volatile_reg(dev, reg);
+}
+
+static const struct regmap_config max8907_regmap_gen_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_reg = max8907_gen_is_volatile_reg,
+	.writeable_reg = max8907_gen_is_writeable_reg,
+	.max_register = MAX8907_REG_LDO20VOUT,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static bool max8907_rtc_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	if (reg <= MAX8907_REG_RTC_YEAR2)
+		return true;
+
+	switch (reg) {
+	case MAX8907_REG_RTC_STATUS:
+	case MAX8907_REG_RTC_IRQ:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool max8907_rtc_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MAX8907_REG_RTC_STATUS:
+	case MAX8907_REG_RTC_IRQ:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config max8907_regmap_rtc_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.volatile_reg = max8907_rtc_is_volatile_reg,
+	.writeable_reg = max8907_rtc_is_writeable_reg,
+	.max_register = MAX8907_REG_MPL_CNTL,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static __devinit int max8907_i2c_probe(struct i2c_client *i2c,
+				       const struct i2c_device_id *id)
+{
+	struct max8907 *max8907;
+	int ret;
+
+	max8907 = devm_kzalloc(&i2c->dev, sizeof(struct max8907), GFP_KERNEL);
+	if (!max8907) {
+		ret = -ENOMEM;
+		goto err_alloc_drvdata;
+	}
+
+	max8907->dev = &i2c->dev;
+	dev_set_drvdata(max8907->dev, max8907);
+
+	max8907->i2c_gen = i2c;
+	i2c_set_clientdata(i2c, max8907);
+	max8907->regmap_gen = devm_regmap_init_i2c(i2c,
+						&max8907_regmap_gen_config);
+	if (IS_ERR(max8907->regmap_gen)) {
+		ret = PTR_ERR(max8907->regmap_gen);
+		dev_err(&i2c->dev, "gen regmap init failed: %d\n", ret);
+		goto err_regmap_gen;
+	}
+
+	max8907->i2c_rtc = i2c_new_dummy(i2c->adapter, MAX8907_RTC_I2C_ADDR);
+	if (!max8907->i2c_rtc) {
+		ret = -ENOMEM;
+		goto err_dummy_rtc;
+	}
+	i2c_set_clientdata(max8907->i2c_rtc, max8907);
+	max8907->regmap_rtc = devm_regmap_init_i2c(i2c,
+						&max8907_regmap_rtc_config);
+	if (IS_ERR(max8907->regmap_rtc)) {
+		ret = PTR_ERR(max8907->regmap_rtc);
+		dev_err(&i2c->dev, "rtc regmap init failed: %d\n", ret);
+		goto err_regmap_rtc;
+	}
+
+	ret = max8907_irq_init(max8907);
+	if (ret != 0)
+		goto err_irq_init;
+
+	ret = mfd_add_devices(max8907->dev, -1, max8907_cells,
+			      ARRAY_SIZE(max8907_cells), NULL, 0);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "failed to add MFD devices %d\n", ret);
+		goto err_add_devices;
+	}
+
+	return 0;
+
+err_add_devices:
+	max8907_irq_free(max8907);
+err_irq_init:
+err_regmap_rtc:
+	i2c_unregister_device(max8907->i2c_rtc);
+err_dummy_rtc:
+err_regmap_gen:
+err_alloc_drvdata:
+	return ret;
+}
+
+static __devexit int max8907_i2c_remove(struct i2c_client *i2c)
+{
+	struct max8907 *max8907 = i2c_get_clientdata(i2c);
+
+	mfd_remove_devices(max8907->dev);
+
+	max8907_irq_free(max8907);
+
+	i2c_unregister_device(max8907->i2c_rtc);
+
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id max8907_of_match[] = {
+	{ .compatible = "maxim,max8907" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, max8907_of_match);
+#endif
+
+static const struct i2c_device_id max8907_i2c_id[] = {
+	{"max8907", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, max8907_i2c_id);
+
+static struct i2c_driver max8907_i2c_driver = {
+	.driver = {
+		.name = "max8907",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(max8907_of_match),
+	},
+	.probe = max8907_i2c_probe,
+	.remove = max8907_i2c_remove,
+	.suspend = max8907_irq_suspend,
+	.resume = max8907_irq_resume,
+	.id_table = max8907_i2c_id,
+};
+
+static int __init max8907_i2c_init(void)
+{
+	int ret = -ENODEV;
+
+	ret = i2c_add_driver(&max8907_i2c_driver);
+	if (ret != 0)
+		pr_err("Failed to register I2C driver: %d\n", ret);
+
+	return ret;
+}
+subsys_initcall(max8907_i2c_init);
+
+static void __exit max8907_i2c_exit(void)
+{
+	i2c_del_driver(&max8907_i2c_driver);
+}
+module_exit(max8907_i2c_exit);
+
+MODULE_DESCRIPTION("MAX8907 multi-function core driver");
+MODULE_AUTHOR("Gyungoh Yoo <jack.yoo at maxim-ic.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/max8907.h b/include/linux/mfd/max8907.h
new file mode 100644
index 0000000..3985904
--- /dev/null
+++ b/include/linux/mfd/max8907.h
@@ -0,0 +1,248 @@
+/*
+ * Functions to access MAX8907 power management chip.
+ *
+ * Copyright (C) 2010 Gyungoh Yoo <jack.yoo at maxim-ic.com>
+ * Copyright (C) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * 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_MAX8907_H
+#define __LINUX_MFD_MAX8907_H
+
+#define MAX8907_GEN_I2C_ADDR		(0x78 >> 1)
+#define MAX8907_ADC_I2C_ADDR		(0x8e >> 1)
+#define MAX8907_RTC_I2C_ADDR		(0xd0 >> 1)
+
+/* MAX8907 register map */
+#define MAX8907_REG_SYSENSEL		0x00
+#define MAX8907_REG_ON_OFF_IRQ1		0x01
+#define MAX8907_REG_ON_OFF_IRQ1_MASK	0x02
+#define MAX8907_REG_ON_OFF_STAT		0x03
+#define MAX8907_REG_SDCTL1		0x04
+#define MAX8907_REG_SDSEQCNT1		0x05
+#define MAX8907_REG_SDV1		0x06
+#define MAX8907_REG_SDCTL2		0x07
+#define MAX8907_REG_SDSEQCNT2		0x08
+#define MAX8907_REG_SDV2		0x09
+#define MAX8907_REG_SDCTL3		0x0A
+#define MAX8907_REG_SDSEQCNT3		0x0B
+#define MAX8907_REG_SDV3		0x0C
+#define MAX8907_REG_ON_OFF_IRQ2		0x0D
+#define MAX8907_REG_ON_OFF_IRQ2_MASK	0x0E
+#define MAX8907_REG_RESET_CNFG		0x0F
+#define MAX8907_REG_LDOCTL16		0x10
+#define MAX8907_REG_LDOSEQCNT16		0x11
+#define MAX8907_REG_LDO16VOUT		0x12
+#define MAX8907_REG_SDBYSEQCNT		0x13
+#define MAX8907_REG_LDOCTL17		0x14
+#define MAX8907_REG_LDOSEQCNT17		0x15
+#define MAX8907_REG_LDO17VOUT		0x16
+#define MAX8907_REG_LDOCTL1		0x18
+#define MAX8907_REG_LDOSEQCNT1		0x19
+#define MAX8907_REG_LDO1VOUT		0x1A
+#define MAX8907_REG_LDOCTL2		0x1C
+#define MAX8907_REG_LDOSEQCNT2		0x1D
+#define MAX8907_REG_LDO2VOUT		0x1E
+#define MAX8907_REG_LDOCTL3		0x20
+#define MAX8907_REG_LDOSEQCNT3		0x21
+#define MAX8907_REG_LDO3VOUT		0x22
+#define MAX8907_REG_LDOCTL4		0x24
+#define MAX8907_REG_LDOSEQCNT4		0x25
+#define MAX8907_REG_LDO4VOUT		0x26
+#define MAX8907_REG_LDOCTL5		0x28
+#define MAX8907_REG_LDOSEQCNT5		0x29
+#define MAX8907_REG_LDO5VOUT		0x2A
+#define MAX8907_REG_LDOCTL6		0x2C
+#define MAX8907_REG_LDOSEQCNT6		0x2D
+#define MAX8907_REG_LDO6VOUT		0x2E
+#define MAX8907_REG_LDOCTL7		0x30
+#define MAX8907_REG_LDOSEQCNT7		0x31
+#define MAX8907_REG_LDO7VOUT		0x32
+#define MAX8907_REG_LDOCTL8		0x34
+#define MAX8907_REG_LDOSEQCNT8		0x35
+#define MAX8907_REG_LDO8VOUT		0x36
+#define MAX8907_REG_LDOCTL9		0x38
+#define MAX8907_REG_LDOSEQCNT9		0x39
+#define MAX8907_REG_LDO9VOUT		0x3A
+#define MAX8907_REG_LDOCTL10		0x3C
+#define MAX8907_REG_LDOSEQCNT10		0x3D
+#define MAX8907_REG_LDO10VOUT		0x3E
+#define MAX8907_REG_LDOCTL11		0x40
+#define MAX8907_REG_LDOSEQCNT11		0x41
+#define MAX8907_REG_LDO11VOUT		0x42
+#define MAX8907_REG_LDOCTL12		0x44
+#define MAX8907_REG_LDOSEQCNT12		0x45
+#define MAX8907_REG_LDO12VOUT		0x46
+#define MAX8907_REG_LDOCTL13		0x48
+#define MAX8907_REG_LDOSEQCNT13		0x49
+#define MAX8907_REG_LDO13VOUT		0x4A
+#define MAX8907_REG_LDOCTL14		0x4C
+#define MAX8907_REG_LDOSEQCNT14		0x4D
+#define MAX8907_REG_LDO14VOUT		0x4E
+#define MAX8907_REG_LDOCTL15		0x50
+#define MAX8907_REG_LDOSEQCNT15		0x51
+#define MAX8907_REG_LDO15VOUT		0x52
+#define MAX8907_REG_OUT5VEN		0x54
+#define MAX8907_REG_OUT5VSEQ		0x55
+#define MAX8907_REG_OUT33VEN		0x58
+#define MAX8907_REG_OUT33VSEQ		0x59
+#define MAX8907_REG_LDOCTL19		0x5C
+#define MAX8907_REG_LDOSEQCNT19		0x5D
+#define MAX8907_REG_LDO19VOUT		0x5E
+#define MAX8907_REG_LBCNFG		0x60
+#define MAX8907_REG_SEQ1CNFG		0x64
+#define MAX8907_REG_SEQ2CNFG		0x65
+#define MAX8907_REG_SEQ3CNFG		0x66
+#define MAX8907_REG_SEQ4CNFG		0x67
+#define MAX8907_REG_SEQ5CNFG		0x68
+#define MAX8907_REG_SEQ6CNFG		0x69
+#define MAX8907_REG_SEQ7CNFG		0x6A
+#define MAX8907_REG_LDOCTL18		0x72
+#define MAX8907_REG_LDOSEQCNT18		0x73
+#define MAX8907_REG_LDO18VOUT		0x74
+#define MAX8907_REG_BBAT_CNFG		0x78
+#define MAX8907_REG_CHG_CNTL1		0x7C
+#define MAX8907_REG_CHG_CNTL2		0x7D
+#define MAX8907_REG_CHG_IRQ1		0x7E
+#define MAX8907_REG_CHG_IRQ2		0x7F
+#define MAX8907_REG_CHG_IRQ1_MASK	0x80
+#define MAX8907_REG_CHG_IRQ2_MASK	0x81
+#define MAX8907_REG_CHG_STAT		0x82
+#define MAX8907_REG_WLED_MODE_CNTL	0x84
+#define MAX8907_REG_ILED_CNTL		0x84
+#define MAX8907_REG_II1RR		0x8E
+#define MAX8907_REG_II2RR		0x8F
+#define MAX8907_REG_LDOCTL20		0x9C
+#define MAX8907_REG_LDOSEQCNT20		0x9D
+#define MAX8907_REG_LDO20VOUT		0x9E
+
+/* RTC register map */
+#define MAX8907_REG_RTC_SEC		0x00
+#define MAX8907_REG_RTC_MIN		0x01
+#define MAX8907_REG_RTC_HOURS		0x02
+#define MAX8907_REG_RTC_WEEKDAY		0x03
+#define MAX8907_REG_RTC_DATE		0x04
+#define MAX8907_REG_RTC_MONTH		0x05
+#define MAX8907_REG_RTC_YEAR1		0x06
+#define MAX8907_REG_RTC_YEAR2		0x07
+#define MAX8907_REG_ALARM0_SEC		0x08
+#define MAX8907_REG_ALARM0_MIN		0x09
+#define MAX8907_REG_ALARM0_HOURS	0x0A
+#define MAX8907_REG_ALARM0_WEEKDAY	0x0B
+#define MAX8907_REG_ALARM0_DATE		0x0C
+#define MAX8907_REG_ALARM0_MONTH	0x0D
+#define MAX8907_REG_ALARM0_YEAR1	0x0E
+#define MAX8907_REG_ALARM0_YEAR2	0x0F
+#define MAX8907_REG_ALARM1_SEC		0x10
+#define MAX8907_REG_ALARM1_MIN		0x11
+#define MAX8907_REG_ALARM1_HOURS	0x12
+#define MAX8907_REG_ALARM1_WEEKDAY	0x13
+#define MAX8907_REG_ALARM1_DATE		0x14
+#define MAX8907_REG_ALARM1_MONTH	0x15
+#define MAX8907_REG_ALARM1_YEAR1	0x16
+#define MAX8907_REG_ALARM1_YEAR2	0x17
+#define MAX8907_REG_ALARM0_CNTL		0x18
+#define MAX8907_REG_ALARM1_CNTL		0x19
+#define MAX8907_REG_RTC_STATUS		0x1A
+#define MAX8907_REG_RTC_CNTL		0x1B
+#define MAX8907_REG_RTC_IRQ		0x1C
+#define MAX8907_REG_RTC_IRQ_MASK	0x1D
+#define MAX8907_REG_MPL_CNTL		0x1E
+
+/* ADC and Touch Screen Controller register map */
+#define MAX8907_CTL			0
+#define MAX8907_SEQCNT			1
+#define MAX8907_VOUT			2
+
+/* mask bit fields */
+#define MAX8907_MASK_LDO_SEQ		0x1C
+#define MAX8907_MASK_LDO_EN		0x01
+#define MAX8907_MASK_VBBATTCV		0x03
+#define MAX8907_MASK_OUT5V_VINEN	0x10
+#define MAX8907_MASK_OUT5V_ENSRC	0x0E
+#define MAX8907_MASK_OUT5V_EN		0x01
+
+/* IRQ definitions */
+enum {
+	MAX8907_IRQ_VCHG_DC_OVP,
+	MAX8907_IRQ_VCHG_DC_F,
+	MAX8907_IRQ_VCHG_DC_R,
+	MAX8907_IRQ_VCHG_THM_OK_R,
+	MAX8907_IRQ_VCHG_THM_OK_F,
+	MAX8907_IRQ_VCHG_MBATTLOW_F,
+	MAX8907_IRQ_VCHG_MBATTLOW_R,
+	MAX8907_IRQ_VCHG_RST,
+	MAX8907_IRQ_VCHG_DONE,
+	MAX8907_IRQ_VCHG_TOPOFF,
+	MAX8907_IRQ_VCHG_TMR_FAULT,
+	MAX8907_IRQ_GPM_RSTIN,
+	MAX8907_IRQ_GPM_MPL,
+	MAX8907_IRQ_GPM_SW_3SEC,
+	MAX8907_IRQ_GPM_EXTON_F,
+	MAX8907_IRQ_GPM_EXTON_R,
+	MAX8907_IRQ_GPM_SW_1SEC,
+	MAX8907_IRQ_GPM_SW_F,
+	MAX8907_IRQ_GPM_SW_R,
+	MAX8907_IRQ_GPM_SYSCKEN_F,
+	MAX8907_IRQ_GPM_SYSCKEN_R,
+	MAX8907_IRQ_RTC_ALARM1,
+	MAX8907_IRQ_RTC_ALARM0,
+	MAX8907_NR_IRQS,
+};
+
+/* Regulator IDs */
+#define MAX8907_SD1	0
+#define MAX8907_SD2	1
+#define MAX8907_SD3	2
+#define MAX8907_LDO1	3
+#define MAX8907_LDO2	4
+#define MAX8907_LDO3	5
+#define MAX8907_LDO4	6
+#define MAX8907_LDO5	7
+#define MAX8907_LDO6	8
+#define MAX8907_LDO7	9
+#define MAX8907_LDO8	10
+#define MAX8907_LDO9	11
+#define MAX8907_LDO10	12
+#define MAX8907_LDO11	13
+#define MAX8907_LDO12	14
+#define MAX8907_LDO13	15
+#define MAX8907_LDO14	16
+#define MAX8907_LDO15	17
+#define MAX8907_LDO16	18
+#define MAX8907_LDO17	19
+#define MAX8907_LDO18	20
+#define MAX8907_LDO19	21
+#define MAX8907_LDO20	22
+#define MAX8907_OUT5V	23
+#define MAX8907_OUT33V	24
+#define MAX8907_BBAT	25
+#define MAX8907_SDBY	26
+#define MAX8907_VRTC	27
+#define MAX8907_WLED	28
+#define MAX8907_NUM_REGULATORS (MAX8907_WLED + 1)
+
+struct max8907_platform_data {
+	int irq_base;
+	struct regulator_init_data *init_data[MAX8907_NUM_REGULATORS];
+};
+
+struct max8907 {
+	struct device		*dev;
+	struct mutex		irq_lock;
+	struct i2c_client	*i2c_gen;
+	struct i2c_client	*i2c_rtc;
+	struct regmap		*regmap_gen;
+	struct regmap		*regmap_rtc;
+	struct irq_domain	*domain;
+};
+
+int max8907_irq_init(struct max8907 *chip);
+void max8907_irq_free(struct max8907 *chip);
+int max8907_irq_suspend(struct i2c_client *i2c, pm_message_t state);
+int max8907_irq_resume(struct i2c_client *i2c);
+
+#endif
-- 
1.7.0.4



More information about the devicetree-discuss mailing list