linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/3] Add initial support for MAXIM 77686 mfd chip
@ 2012-05-18  9:32 Jonghwa Lee
  2012-05-18  9:32 ` [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver Jonghwa Lee
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Jonghwa Lee @ 2012-05-18  9:32 UTC (permalink / raw)
  To: linux-kernel
  Cc: Alessandro Zummo, Samuel Oritz, Liam Girdwood, Mark Brown,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Jonghwa Lee

This patchset adds support for MAX77686 which is a multifunction device including
regulator and rtc. It also includes drivers supporting rtc and regulator.
All drivers are based on MAX8997 driver and use regmap to access to registers.
To manage IRQs occured by max77686, It supports IRQ domain.

v3:
Changes in mfd core:
- Remove APIs accessing to register ( e.g. max77686_read_reg() )
  to use regmap's API directly over all related drivers.

Changes in regulator driver:
- Convert get_voltage to get_voltage_sel
- Convert set_voltage to set_voltage_sel
- Implement set_voltage_time_sel
- Register regulators unconditionally
- kzalloc -> devm_kzalloc
- Remove unneccessary printk
- Keep probing whether pdata exists or not
- Use regmap API to access to PMIC register.

Changes in rtc driver:
- Use regmap API to access to RTC register.

v2:
http://thread.gmane.org/gmane.linux.kernel/1295479

This patch set is tested on TRATS boards.

Jonghwa Lee (3):
  mfd: MAX77686: Add Maxim 77686 mfd driver
  regulator: MAX77686: Add Maxim 77686 regulator driver
  rtc: MAX77686: Add Maxim 77686 rtc driver

 drivers/mfd/Kconfig                  |   11 +
 drivers/mfd/Makefile                 |    1 +
 drivers/mfd/max77686-irq.c           |  327 ++++++++++++++
 drivers/mfd/max77686.c               |  156 +++++++
 drivers/regulator/Kconfig            |    8 +
 drivers/regulator/Makefile           |    1 +
 drivers/regulator/max77686.c         |  783 ++++++++++++++++++++++++++++++++++
 drivers/rtc/Kconfig                  |   20 +
 drivers/rtc/Makefile                 |    4 +-
 drivers/rtc/rtc-max77686.c           |  668 +++++++++++++++++++++++++++++
 include/linux/mfd/max77686-private.h |  247 +++++++++++
 include/linux/mfd/max77686.h         |  117 +++++
 12 files changed, 2342 insertions(+), 1 deletions(-)
 create mode 100644 drivers/mfd/max77686-irq.c
 create mode 100644 drivers/mfd/max77686.c
 create mode 100644 drivers/regulator/max77686.c
 create mode 100644 drivers/rtc/rtc-max77686.c
 create mode 100644 include/linux/mfd/max77686-private.h
 create mode 100644 include/linux/mfd/max77686.h

-- 
1.7.4.1


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

* [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver
  2012-05-18  9:32 [PATCH v3 0/3] Add initial support for MAXIM 77686 mfd chip Jonghwa Lee
@ 2012-05-18  9:32 ` Jonghwa Lee
  2012-05-20 15:50   ` Mark Brown
  2012-05-18  9:32 ` [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver Jonghwa Lee
  2012-05-18  9:32 ` [PATCH 3/3] rtc: MAX77686: Add Maxim 77686 rtc driver Jonghwa Lee
  2 siblings, 1 reply; 11+ messages in thread
From: Jonghwa Lee @ 2012-05-18  9:32 UTC (permalink / raw)
  To: linux-kernel
  Cc: Alessandro Zummo, Samuel Oritz, Liam Girdwood, Mark Brown,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Jonghwa Lee,
	Chiwoong Byun

This patch is device driver for MAX77686 chip.
MAX77686 is PMIC and includes regulator and rtc on it.
This driver is core of MAX77686 chip, so provides common support for accessing
on-chip devices. It uses irq_domain to manage irq and regmap to read/write data
to its register with i2c bus.

v3
- Remove APIs accessing to register ( e.g. max77686_read_reg() )
  to use regmap's API directly over all related drivers.

v2
- Change I/O interface register to using regmap.
- Support IRQ domain, and remove irq_base from all referenced.
- Add module_param() macro to debug_mask parameter. It can be set various
  masking value for specific purpose while debugging.
- Remove IRQ_TEST block and some unnecessary points.

Signed-off-by: Chiwoong Byun <woong.byun@samsung.com>
Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
Signed-off-by: Myungjoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/mfd/Kconfig                  |   11 ++
 drivers/mfd/Makefile                 |    1 +
 drivers/mfd/max77686-irq.c           |  309 ++++++++++++++++++++++++++++++++++
 drivers/mfd/max77686.c               |  156 +++++++++++++++++
 include/linux/mfd/max77686-private.h |  247 +++++++++++++++++++++++++++
 include/linux/mfd/max77686.h         |  117 +++++++++++++
 6 files changed, 841 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mfd/max77686-irq.c
 create mode 100644 drivers/mfd/max77686.c
 create mode 100644 include/linux/mfd/max77686-private.h
 create mode 100644 include/linux/mfd/max77686.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a8619eb..3ad34b6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -441,6 +441,17 @@ config MFD_MAX8998
 	  additional drivers must be enabled in order to use the functionality
 	  of the device.
 
+config MFD_MAX77686
+	bool "Maxim Semiconductor MAX77686 PMIC Support"
+	depends on I2C && GENERIC_HARDIRQS
+	select MFD_CORE
+	help
+	  Say yes here to support for Maxim Semiconductor MAX77686.
+	  This is a Power Management IC with RTC on chip.
+	  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_S5M_CORE
 	bool "SAMSUNG S5M Series Support"
 	depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index ddd9fa7..a793ebd 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -79,6 +79,7 @@ max8925-objs			:= max8925-core.o max8925-i2c.o
 obj-$(CONFIG_MFD_MAX8925)	+= max8925.o
 obj-$(CONFIG_MFD_MAX8997)	+= max8997.o max8997-irq.o
 obj-$(CONFIG_MFD_MAX8998)	+= max8998.o max8998-irq.o
+obj-$(CONFIG_MFD_MAX77686)	+= max77686.o max77686-irq.o
 obj-$(CONFIG_MFD_MAX77693)	+= max77693.o max77693-irq.o
 
 pcf50633-objs			:= pcf50633-core.o pcf50633-irq.o
diff --git a/drivers/mfd/max77686-irq.c b/drivers/mfd/max77686-irq.c
new file mode 100644
index 0000000..fc10122
--- /dev/null
+++ b/drivers/mfd/max77686-irq.c
@@ -0,0 +1,309 @@
+/*
+ * max77686-irq.c - Interrupt controller support for MAX77686
+ *
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ * Chiwoong Byun <woong.byun@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8997-irq.c
+ */
+
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+#include <linux/irqdomain.h>
+#include <linux/regmap.h>
+
+enum {
+	MAX77686_DEBUG_IRQ_INFO = 1 << 0,
+	MAX77686_DEBUG_IRQ_MASK = 1 << 1,
+	MAX77686_DEBUG_IRQ_INT = 1 << 2,
+};
+
+static int debug_mask = 0;
+module_param(debug_mask, int, 0);
+MODULE_PARM_DESC(debug_mask, "Set debug_mask : 0x0=off 0x1=IRQ_INFO  0x2=IRQ_MASK 0x4=IRQ_INI)");
+
+static const u8 max77686_mask_reg[] = {
+	[PMIC_INT1] = MAX77686_REG_INT1MSK,
+	[PMIC_INT2] = MAX77686_REG_INT2MSK,
+	[RTC_INT] = MAX77686_RTC_INTM,
+};
+
+static struct regmap *max77686_get_regmap(struct max77686_dev *max77686,
+				enum max77686_irq_source src)
+{
+	switch (src) {
+	case PMIC_INT1 ... PMIC_INT2:
+		return max77686->regmap;
+	case RTC_INT:
+		return max77686->rtc_regmap;
+	default:
+		return ERR_PTR(-EINVAL);
+	}
+}
+
+struct max77686_irq_data {
+	int mask;
+	enum max77686_irq_source group;
+};
+
+#define DECLARE_IRQ(idx, _group, _mask)		\
+	[(idx)] = { .group = (_group), .mask = (_mask) }
+static const struct max77686_irq_data max77686_irqs[] = {
+	DECLARE_IRQ(MAX77686_PMICIRQ_PWRONF,	PMIC_INT1, 1 << 0),
+	DECLARE_IRQ(MAX77686_PMICIRQ_PWRONR,	PMIC_INT1, 1 << 1),
+	DECLARE_IRQ(MAX77686_PMICIRQ_JIGONBF,	PMIC_INT1, 1 << 2),
+	DECLARE_IRQ(MAX77686_PMICIRQ_JIGONBR,	PMIC_INT1, 1 << 3),
+	DECLARE_IRQ(MAX77686_PMICIRQ_ACOKBF,	PMIC_INT1, 1 << 4),
+	DECLARE_IRQ(MAX77686_PMICIRQ_ACOKBR,	PMIC_INT1, 1 << 5),
+	DECLARE_IRQ(MAX77686_PMICIRQ_ONKEY1S,	PMIC_INT1, 1 << 6),
+	DECLARE_IRQ(MAX77686_PMICIRQ_MRSTB,		PMIC_INT1, 1 << 7),
+	DECLARE_IRQ(MAX77686_PMICIRQ_140C,		PMIC_INT2, 1 << 0),
+	DECLARE_IRQ(MAX77686_PMICIRQ_120C,		PMIC_INT2, 1 << 1),
+	DECLARE_IRQ(MAX77686_RTCIRQ_RTC60S,		RTC_INT, 1 << 0),
+	DECLARE_IRQ(MAX77686_RTCIRQ_RTCA1,		RTC_INT, 1 << 1),
+	DECLARE_IRQ(MAX77686_RTCIRQ_RTCA2,		RTC_INT, 1 << 2),
+	DECLARE_IRQ(MAX77686_RTCIRQ_SMPL,		RTC_INT, 1 << 3),
+	DECLARE_IRQ(MAX77686_RTCIRQ_RTC1S,		RTC_INT, 1 << 4),
+	DECLARE_IRQ(MAX77686_RTCIRQ_WTSR,		RTC_INT, 1 << 5),
+};
+
+static void max77686_irq_lock(struct irq_data *data)
+{
+	struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+
+	if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+		pr_info("%s\n", __func__);
+
+	mutex_lock(&max77686->irqlock);
+}
+
+static void max77686_irq_sync_unlock(struct irq_data *data)
+{
+	struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+	int i;
+
+	for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) {
+		u8 mask_reg = max77686_mask_reg[i];
+		struct regmap *map = max77686_get_regmap(max77686, i);
+
+		if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+			pr_debug("%s: mask_reg[%d]=0x%x, cur=0x%x\n",
+			__func__, i, mask_reg, max77686->irq_masks_cur[i]);
+
+		if (mask_reg == MAX77686_REG_INVALID ||
+				IS_ERR_OR_NULL(map))
+			continue;
+
+		max77686->irq_masks_cache[i] = max77686->irq_masks_cur[i];
+
+		regmap_write(map, max77686_mask_reg[i],
+				max77686->irq_masks_cur[i]);
+	}
+
+	mutex_unlock(&max77686->irqlock);
+}
+
+static const inline struct max77686_irq_data *to_max77686_irq(int irq)
+{
+	struct irq_data *data = irq_get_irq_data(irq);
+	return &max77686_irqs[data->hwirq];
+}
+
+static void max77686_irq_mask(struct irq_data *data)
+{
+	struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+	const struct max77686_irq_data *irq_data = to_max77686_irq(data->irq);
+
+	max77686->irq_masks_cur[irq_data->group] |= irq_data->mask;
+
+	if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+		pr_info("%s: group=%d, cur=0x%x\n",
+			__func__, irq_data->group,
+			max77686->irq_masks_cur[irq_data->group]);
+}
+
+static void max77686_irq_unmask(struct irq_data *data)
+{
+	struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
+	const struct max77686_irq_data *irq_data = to_max77686_irq(data->irq);
+
+	max77686->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
+
+	if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
+		pr_info("%s: group=%d, cur=0x%x\n",
+			__func__, irq_data->group,
+			max77686->irq_masks_cur[irq_data->group]);
+}
+
+static struct irq_chip max77686_irq_chip = {
+	.name			= "max77686",
+	.irq_bus_lock		= max77686_irq_lock,
+	.irq_bus_sync_unlock	= max77686_irq_sync_unlock,
+	.irq_mask		= max77686_irq_mask,
+	.irq_unmask		= max77686_irq_unmask,
+};
+
+static irqreturn_t max77686_irq_thread(int irq, void *data)
+{
+	struct max77686_dev *max77686 = data;
+	unsigned int irq_reg[MAX77686_IRQ_GROUP_NR] = {};
+	unsigned int irq_src;
+	int ret;
+	int i, cur_irq;
+
+	ret = regmap_read(max77686->regmap,  MAX77686_REG_INTSRC, &irq_src);
+	if (ret < 0) {
+		dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
+				ret);
+		return IRQ_NONE;
+	}
+
+	if (debug_mask & MAX77686_DEBUG_IRQ_INT)
+		pr_info("%s: irq_src=0x%x\n", __func__, irq_src);
+
+	if (irq_src == MAX77686_IRQSRC_PMIC) {
+		ret = regmap_bulk_read(max77686->rtc_regmap,
+					 MAX77686_REG_INT1, irq_reg, 2);
+		if (ret < 0) {
+			dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
+					ret);
+			return IRQ_NONE;
+		}
+
+		if (debug_mask & MAX77686_DEBUG_IRQ_INT)
+			pr_info("%s: int1=0x%x, int2=0x%x\n", __func__,
+				 irq_reg[PMIC_INT1], irq_reg[PMIC_INT2]);
+	}
+
+	if (irq_src & MAX77686_IRQSRC_RTC) {
+		ret = regmap_read(max77686->rtc_regmap,
+					MAX77686_RTC_INT, &irq_reg[RTC_INT]);
+		if (ret < 0) {
+			dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
+					ret);
+			return IRQ_NONE;
+		}
+
+		if (debug_mask & MAX77686_DEBUG_IRQ_INT)
+			pr_info("%s: rtc int=0x%x\n", __func__,
+							 irq_reg[RTC_INT]);
+
+	}
+
+	for (i = 0; i < MAX77686_IRQ_NR; i++) {
+		if (irq_reg[max77686_irqs[i].group] & max77686_irqs[i].mask) {
+			cur_irq = irq_find_mapping(max77686->irq_domain, i);
+			if (cur_irq)
+				handle_nested_irq(cur_irq);
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int max77686_irq_domain_map(struct irq_domain *d, unsigned int irq,
+					irq_hw_number_t hw)
+{
+	struct max77686_dev *max77686 = d->host_data;
+
+	irq_set_chip_data(irq, max77686);
+	irq_set_chip_and_handler(irq, &max77686_irq_chip, handle_edge_irq);
+	irq_set_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+	set_irq_flags(irq, IRQF_VALID);
+#else
+	irq_set_noprobe(irq);
+#endif
+	return 0;
+}
+
+static struct irq_domain_ops max77686_irq_domain_ops = {
+	.map = max77686_irq_domain_map,
+};
+
+int max77686_irq_init(struct max77686_dev *max77686)
+{
+	struct irq_domain *domain;
+	int i;
+	int ret;
+	int val;
+	struct regmap *map;
+
+	mutex_init(&max77686->irqlock);
+
+	max77686->irq = gpio_to_irq(max77686->irq_gpio);
+
+	if (debug_mask & MAX77686_DEBUG_IRQ_INT) {
+		ret = gpio_request(max77686->irq_gpio, "pmic_irq");
+		if (ret < 0) {
+			dev_err(max77686->dev,
+				"Failed to request gpio %d with ret: %d\n",
+				max77686->irq_gpio, ret);
+			return IRQ_NONE;
+		}
+
+		gpio_direction_input(max77686->irq_gpio);
+		val = gpio_get_value(max77686->irq_gpio);
+		gpio_free(max77686->irq_gpio);
+		pr_info("%s: gpio_irq=%x\n", __func__, val);
+	}
+
+	/* Mask individual interrupt sources */
+	for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) {
+		max77686->irq_masks_cur[i] = 0xff;
+		max77686->irq_masks_cache[i] = 0xff;
+		map = max77686_get_regmap(max77686, i);
+
+		if (IS_ERR_OR_NULL(map))
+			continue;
+		if (max77686_mask_reg[i] == MAX77686_REG_INVALID)
+			continue;
+
+		regmap_write(map, max77686_mask_reg[i], 0xff);
+	}
+	domain = irq_domain_add_linear(NULL, MAX77686_IRQ_NR,
+					&max77686_irq_domain_ops, max77686);
+	if (!domain) {
+		dev_err(max77686->dev, "could not create irq domain\n");
+		return -ENODEV;
+	}
+	max77686->irq_domain = domain;
+
+	ret = request_threaded_irq(max77686->irq, NULL, max77686_irq_thread,
+				   IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+				   "max77686-irq", max77686);
+
+	if (ret)
+		dev_err(max77686->dev, "Failed to request IRQ %d: %d\n",
+			max77686->irq, ret);
+
+
+	if (debug_mask & MAX77686_DEBUG_IRQ_INFO)
+		pr_info("%s-\n", __func__);
+
+	return 0;
+}
+
+void max77686_irq_exit(struct max77686_dev *max77686)
+{
+	if (max77686->irq)
+		free_irq(max77686->irq, max77686);
+}
diff --git a/drivers/mfd/max77686.c b/drivers/mfd/max77686.c
new file mode 100644
index 0000000..11e5601
--- /dev/null
+++ b/drivers/mfd/max77686.c
@@ -0,0 +1,156 @@
+/*
+ * max77686.c - mfd core driver for the Maxim 77686
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Chiwoong Byun <woong.byun@smasung.com>
+ * Jonghwa Lee <jonghwa3.lee@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8997.c
+ */
+
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/pm_runtime.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+#include <linux/err.h>
+
+#define I2C_ADDR_RTC	(0x0C >> 1)
+
+static struct mfd_cell max77686_devs[] = {
+	{ .name = "max77686-pmic", },
+	{ .name = "max77686-rtc", },
+};
+
+static struct regmap_config max77686_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static int max77686_i2c_probe(struct i2c_client *i2c,
+			      const struct i2c_device_id *id)
+{
+	struct max77686_dev *max77686;
+	struct max77686_platform_data *pdata = i2c->dev.platform_data;
+	unsigned int data;
+	int ret = 0;
+
+	max77686 = kzalloc(sizeof(struct max77686_dev), GFP_KERNEL);
+	if (max77686 == NULL)
+		return -ENOMEM;
+
+	max77686->regmap = regmap_init_i2c(i2c, &max77686_regmap_config);
+	if (IS_ERR(max77686->regmap)) {
+		ret = PTR_ERR(max77686->regmap);
+		dev_err(max77686->dev, "Failed to allocate register map: %d\n",
+				ret);
+		kfree(max77686);
+		return ret;
+	}
+
+	i2c_set_clientdata(i2c, max77686);
+	max77686->dev = &i2c->dev;
+	max77686->i2c = i2c;
+	max77686->type = id->driver_data;
+
+	if (!pdata) {
+		ret = -EIO;
+		goto err;
+	}
+
+	max77686->wakeup = pdata->wakeup;
+	max77686->irq_gpio = pdata->irq_gpio;
+
+	mutex_init(&max77686->iolock);
+
+	if (regmap_read(max77686->regmap,
+			 MAX77686_REG_DEVICE_ID, &data) < 0) {
+		dev_err(max77686->dev,
+			"device not found on this channel (this is not an error)\n");
+		ret = -ENODEV;
+		goto err;
+	} else
+		dev_info(max77686->dev, "device found\n");
+
+	max77686->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC);
+	i2c_set_clientdata(max77686->rtc, max77686);
+
+	max77686_irq_init(max77686);
+
+	ret = mfd_add_devices(max77686->dev, -1, max77686_devs,
+			      ARRAY_SIZE(max77686_devs), NULL, 0);
+
+	if (ret < 0)
+		goto err_mfd;
+
+	return ret;
+
+err_mfd:
+	mfd_remove_devices(max77686->dev);
+	i2c_unregister_device(max77686->rtc);
+err:
+	kfree(max77686);
+	return ret;
+}
+
+static int max77686_i2c_remove(struct i2c_client *i2c)
+{
+	struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
+
+	mfd_remove_devices(max77686->dev);
+	i2c_unregister_device(max77686->rtc);
+	kfree(max77686);
+
+	return 0;
+}
+
+static const struct i2c_device_id max77686_i2c_id[] = {
+	{ "max77686", TYPE_MAX77686 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, max77686_i2c_id);
+
+static struct i2c_driver max77686_i2c_driver = {
+	.driver = {
+		   .name = "max77686",
+		   .owner = THIS_MODULE,
+	},
+	.probe = max77686_i2c_probe,
+	.remove = max77686_i2c_remove,
+	.id_table = max77686_i2c_id,
+};
+
+static int __init max77686_i2c_init(void)
+{
+	return i2c_add_driver(&max77686_i2c_driver);
+}
+/* init early so consumer devices can complete system boot */
+subsys_initcall(max77686_i2c_init);
+
+static void __exit max77686_i2c_exit(void)
+{
+	i2c_del_driver(&max77686_i2c_driver);
+}
+module_exit(max77686_i2c_exit);
+
+MODULE_DESCRIPTION("MAXIM 77686 multi-function core driver");
+MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max77686-private.h b/include/linux/mfd/max77686-private.h
new file mode 100644
index 0000000..962f65e
--- /dev/null
+++ b/include/linux/mfd/max77686-private.h
@@ -0,0 +1,247 @@
+/*
+ * max77686.h - Voltage regulator driver for the Maxim 77686
+ *
+ *  Copyright (C) 2012 Samsung Electrnoics
+ *  Chiwoong Byun <woong.byun@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ */
+
+#ifndef __LINUX_MFD_MAX77686_PRIV_H
+#define __LINUX_MFD_MAX77686_PRIV_H
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+
+#define MAX77686_REG_INVALID		(0xff)
+
+enum max77686_pmic_reg {
+	MAX77686_REG_DEVICE_ID		= 0x00,
+	MAX77686_REG_INTSRC		= 0x01,
+	MAX77686_REG_INT1		= 0x02,
+	MAX77686_REG_INT2		= 0x03,
+
+	MAX77686_REG_INT1MSK		= 0x04,
+	MAX77686_REG_INT2MSK		= 0x05,
+
+	MAX77686_REG_STATUS1		= 0x06,
+	MAX77686_REG_STATUS2		= 0x07,
+
+	MAX77686_REG_PWRON		= 0x08,
+	MAX77686_REG_ONOFF_DELAY	= 0x09,
+	MAX77686_REG_MRSTB		= 0x0A,
+	/* Reserved: 0x0B-0x0F */
+
+	MAX77686_REG_BUCK1CTRL		= 0x10,
+	MAX77686_REG_BUCK1OUT		= 0x11,
+	MAX77686_REG_BUCK2CTRL1		= 0x12,
+	MAX77686_REG_BUCK234FREQ	= 0x13,
+	MAX77686_REG_BUCK2DVS1		= 0x14,
+	MAX77686_REG_BUCK2DVS2		= 0x15,
+	MAX77686_REG_BUCK2DVS3		= 0x16,
+	MAX77686_REG_BUCK2DVS4		= 0x17,
+	MAX77686_REG_BUCK2DVS5		= 0x18,
+	MAX77686_REG_BUCK2DVS6		= 0x19,
+	MAX77686_REG_BUCK2DVS7		= 0x1A,
+	MAX77686_REG_BUCK2DVS8		= 0x1B,
+	MAX77686_REG_BUCK3CTRL1		= 0x1C,
+	/* Reserved: 0x1D */
+	MAX77686_REG_BUCK3DVS1		= 0x1E,
+	MAX77686_REG_BUCK3DVS2		= 0x1F,
+	MAX77686_REG_BUCK3DVS3		= 0x20,
+	MAX77686_REG_BUCK3DVS4		= 0x21,
+	MAX77686_REG_BUCK3DVS5		= 0x22,
+	MAX77686_REG_BUCK3DVS6		= 0x23,
+	MAX77686_REG_BUCK3DVS7		= 0x24,
+	MAX77686_REG_BUCK3DVS8		= 0x25,
+	MAX77686_REG_BUCK4CTRL1		= 0x26,
+	/* Reserved: 0x27 */
+	MAX77686_REG_BUCK4DVS1		= 0x28,
+	MAX77686_REG_BUCK4DVS2		= 0x29,
+	MAX77686_REG_BUCK4DVS3		= 0x2A,
+	MAX77686_REG_BUCK4DVS4		= 0x2B,
+	MAX77686_REG_BUCK4DVS5		= 0x2C,
+	MAX77686_REG_BUCK4DVS6		= 0x2D,
+	MAX77686_REG_BUCK4DVS7		= 0x2E,
+	MAX77686_REG_BUCK4DVS8		= 0x2F,
+	MAX77686_REG_BUCK5CTRL		= 0x30,
+	MAX77686_REG_BUCK5OUT		= 0x31,
+	MAX77686_REG_BUCK6CTRL		= 0x32,
+	MAX77686_REG_BUCK6OUT		= 0x33,
+	MAX77686_REG_BUCK7CTRL		= 0x34,
+	MAX77686_REG_BUCK7OUT		= 0x35,
+	MAX77686_REG_BUCK8CTRL		= 0x36,
+	MAX77686_REG_BUCK8OUT		= 0x37,
+	MAX77686_REG_BUCK9CTRL		= 0x38,
+	MAX77686_REG_BUCK9OUT		= 0x39,
+	/* Reserved: 0x3A-0x3F */
+
+	MAX77686_REG_LDO1CTRL1		= 0x40,
+	MAX77686_REG_LDO2CTRL1		= 0x41,
+	MAX77686_REG_LDO3CTRL1		= 0x42,
+	MAX77686_REG_LDO4CTRL1		= 0x43,
+	MAX77686_REG_LDO5CTRL1		= 0x44,
+	MAX77686_REG_LDO6CTRL1		= 0x45,
+	MAX77686_REG_LDO7CTRL1		= 0x46,
+	MAX77686_REG_LDO8CTRL1		= 0x47,
+	MAX77686_REG_LDO9CTRL1		= 0x48,
+	MAX77686_REG_LDO10CTRL1		= 0x49,
+	MAX77686_REG_LDO11CTRL1		= 0x4A,
+	MAX77686_REG_LDO12CTRL1		= 0x4B,
+	MAX77686_REG_LDO13CTRL1		= 0x4C,
+	MAX77686_REG_LDO14CTRL1		= 0x4D,
+	MAX77686_REG_LDO15CTRL1		= 0x4E,
+	MAX77686_REG_LDO16CTRL1		= 0x4F,
+	MAX77686_REG_LDO17CTRL1		= 0x50,
+	MAX77686_REG_LDO18CTRL1		= 0x51,
+	MAX77686_REG_LDO19CTRL1		= 0x52,
+	MAX77686_REG_LDO20CTRL1		= 0x53,
+	MAX77686_REG_LDO21CTRL1		= 0x54,
+	MAX77686_REG_LDO22CTRL1		= 0x55,
+	MAX77686_REG_LDO23CTRL1		= 0x56,
+	MAX77686_REG_LDO24CTRL1		= 0x57,
+	MAX77686_REG_LDO25CTRL1		= 0x58,
+	MAX77686_REG_LDO26CTRL1		= 0x59,
+	/* Reserved: 0x5A-0x5F */
+	MAX77686_REG_LDO1CTRL2		= 0x60,
+	MAX77686_REG_LDO2CTRL2		= 0x61,
+	MAX77686_REG_LDO3CTRL2		= 0x62,
+	MAX77686_REG_LDO4CTRL2		= 0x63,
+	MAX77686_REG_LDO5CTRL2		= 0x64,
+	MAX77686_REG_LDO6CTRL2		= 0x65,
+	MAX77686_REG_LDO7CTRL2		= 0x66,
+	MAX77686_REG_LDO8CTRL2		= 0x67,
+	MAX77686_REG_LDO9CTRL2		= 0x68,
+	MAX77686_REG_LDO10CTRL2		= 0x69,
+	MAX77686_REG_LDO11CTRL2		= 0x6A,
+	MAX77686_REG_LDO12CTRL2		= 0x6B,
+	MAX77686_REG_LDO13CTRL2		= 0x6C,
+	MAX77686_REG_LDO14CTRL2		= 0x6D,
+	MAX77686_REG_LDO15CTRL2		= 0x6E,
+	MAX77686_REG_LDO16CTRL2		= 0x6F,
+	MAX77686_REG_LDO17CTRL2		= 0x70,
+	MAX77686_REG_LDO18CTRL2		= 0x71,
+	MAX77686_REG_LDO19CTRL2		= 0x72,
+	MAX77686_REG_LDO20CTRL2		= 0x73,
+	MAX77686_REG_LDO21CTRL2		= 0x74,
+	MAX77686_REG_LDO22CTRL2		= 0x75,
+	MAX77686_REG_LDO23CTRL2		= 0x76,
+	MAX77686_REG_LDO24CTRL2		= 0x77,
+	MAX77686_REG_LDO25CTRL2		= 0x78,
+	MAX77686_REG_LDO26CTRL2		= 0x79,
+	/* Reserved: 0x7A-0x7D */
+
+	MAX77686_REG_BBAT_CHG		= 0x7E,
+	MAX77686_REG_32KHZ			= 0x7F,
+
+	MAX77686_REG_PMIC_END		= 0x80,
+};
+
+enum max77686_rtc_reg {
+	MAX77686_RTC_INT			= 0x00,
+	MAX77686_RTC_INTM			= 0x01,
+	MAX77686_RTC_CONTROLM		= 0x02,
+	MAX77686_RTC_CONTROL		= 0x03,
+	MAX77686_RTC_UPDATE0		= 0x04,
+	/* Reserved: 0x5 */
+	MAX77686_WTSR_SMPL_CNTL		= 0x06,
+	MAX77686_RTC_SEC			= 0x07,
+	MAX77686_RTC_MIN			= 0x08,
+	MAX77686_RTC_HOUR			= 0x09,
+	MAX77686_RTC_WEEKDAY		= 0x0A,
+	MAX77686_RTC_MONTH			= 0x0B,
+	MAX77686_RTC_YEAR			= 0x0C,
+	MAX77686_RTC_DATE			= 0x0D,
+	MAX77686_ALARM1_SEC			= 0x0E,
+	MAX77686_ALARM1_MIN			= 0x0F,
+	MAX77686_ALARM1_HOUR		= 0x10,
+	MAX77686_ALARM1_WEEKDAY		= 0x11,
+	MAX77686_ALARM1_MONTH		= 0x12,
+	MAX77686_ALARM1_YEAR		= 0x13,
+	MAX77686_ALARM1_DATE		= 0x14,
+	MAX77686_ALARM2_SEC			= 0x15,
+	MAX77686_ALARM2_MIN			= 0x16,
+	MAX77686_ALARM2_HOUR		= 0x17,
+	MAX77686_ALARM2_WEEKDAY		= 0x18,
+	MAX77686_ALARM2_MONTH		= 0x19,
+	MAX77686_ALARM2_YEAR		= 0x1A,
+	MAX77686_ALARM2_DATE		= 0x1B,
+};
+
+#define MAX77686_IRQSRC_PMIC	(0)
+#define MAX77686_IRQSRC_RTC		(1 << 0)
+
+enum max77686_irq_source {
+	PMIC_INT1 = 0,
+	PMIC_INT2,
+	RTC_INT,
+
+	MAX77686_IRQ_GROUP_NR,
+};
+
+enum max77686_irq {
+	MAX77686_PMICIRQ_PWRONF,
+	MAX77686_PMICIRQ_PWRONR,
+	MAX77686_PMICIRQ_JIGONBF,
+	MAX77686_PMICIRQ_JIGONBR,
+	MAX77686_PMICIRQ_ACOKBF,
+	MAX77686_PMICIRQ_ACOKBR,
+	MAX77686_PMICIRQ_ONKEY1S,
+	MAX77686_PMICIRQ_MRSTB,
+
+	MAX77686_PMICIRQ_140C,
+	MAX77686_PMICIRQ_120C,
+
+	MAX77686_RTCIRQ_RTC60S,
+	MAX77686_RTCIRQ_RTCA1,
+	MAX77686_RTCIRQ_RTCA2,
+	MAX77686_RTCIRQ_SMPL,
+	MAX77686_RTCIRQ_RTC1S,
+	MAX77686_RTCIRQ_WTSR,
+
+	MAX77686_IRQ_NR,
+};
+
+struct max77686_dev {
+	struct device *dev;
+	struct i2c_client *i2c; /* 0xcc / PMIC, Battery Control, and FLASH */
+	struct i2c_client *rtc; /* slave addr 0x0c */
+	struct mutex iolock;
+
+	int type;
+
+	struct regmap *regmap;		/* regmap for mfd */
+	struct regmap *rtc_regmap;	/* regmap for rtc */
+
+	struct irq_domain *irq_domain;
+
+	int irq;
+	int irq_gpio;
+	bool wakeup;
+	struct mutex irqlock;
+	int irq_masks_cur[MAX77686_IRQ_GROUP_NR];
+	int irq_masks_cache[MAX77686_IRQ_GROUP_NR];
+};
+
+enum max77686_types {
+	TYPE_MAX77686,
+};
+
+extern int max77686_irq_init(struct max77686_dev *max77686);
+extern void max77686_irq_exit(struct max77686_dev *max77686);
+extern int max77686_irq_resume(struct max77686_dev *max77686);
+
+#endif /*  __LINUX_MFD_MAX77686_PRIV_H */
diff --git a/include/linux/mfd/max77686.h b/include/linux/mfd/max77686.h
new file mode 100644
index 0000000..fcf3126
--- /dev/null
+++ b/include/linux/mfd/max77686.h
@@ -0,0 +1,117 @@
+/*
+ * max77686.h - Driver for the Maxim 77686
+ *
+ *  Copyright (C) 2012 Samsung Electrnoics
+ *  Chiwoong Byun <woong.byun@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8997.h
+ *
+ * MAX77686 has PMIC, RTC devices.
+ * The devices share the same I2C bus and included in
+ * this mfd driver.
+ */
+
+#ifndef __LINUX_MFD_MAX77686_H
+#define __LINUX_MFD_MAX77686_H
+
+#include <linux/regulator/consumer.h>
+
+/* MAX77686 regulator IDs */
+enum max77686_regulators {
+	MAX77686_LDO1 = 0,
+	MAX77686_LDO2,
+	MAX77686_LDO3,
+	MAX77686_LDO4,
+	MAX77686_LDO5,
+	MAX77686_LDO6,
+	MAX77686_LDO7,
+	MAX77686_LDO8,
+	MAX77686_LDO9,
+	MAX77686_LDO10,
+	MAX77686_LDO11,
+	MAX77686_LDO12,
+	MAX77686_LDO13,
+	MAX77686_LDO14,
+	MAX77686_LDO15,
+	MAX77686_LDO16,
+	MAX77686_LDO17,
+	MAX77686_LDO18,
+	MAX77686_LDO19,
+	MAX77686_LDO20,
+	MAX77686_LDO21,
+	MAX77686_LDO22,
+	MAX77686_LDO23,
+	MAX77686_LDO24,
+	MAX77686_LDO25,
+	MAX77686_LDO26,
+	MAX77686_BUCK1,
+	MAX77686_BUCK2,
+	MAX77686_BUCK3,
+	MAX77686_BUCK4,
+	MAX77686_BUCK5,
+	MAX77686_BUCK6,
+	MAX77686_BUCK7,
+	MAX77686_BUCK8,
+	MAX77686_BUCK9,
+	MAX77686_EN32KHZ_AP,
+	MAX77686_EN32KHZ_CP,
+	MAX77686_P32KH,
+
+	MAX77686_REG_MAX,
+};
+
+struct max77686_regulator_data {
+	int id;
+	struct regulator_init_data *initdata;
+};
+
+enum max77686_opmode {
+	MAX77686_OPMODE_NORMAL,
+	MAX77686_OPMODE_LP,
+	MAX77686_OPMODE_STANDBY,
+};
+
+struct max77686_opmode_data {
+	int id;
+	int mode;
+};
+
+struct max77686_platform_data {
+	/* IRQ */
+	int irq_gpio;
+	int ono;
+	int wakeup;
+
+	/* ---- PMIC ---- */
+	struct max77686_regulator_data *regulators;
+	int num_regulators;
+
+	struct max77686_opmode_data *opmode_data;
+
+	/*
+	 * GPIO-DVS feature is not enabled with the current version of
+	 * MAX77686 driver. Buck2/3/4_voltages[0] is used as the default
+	 * voltage at probe. DVS/SELB gpios are set as OUTPUT-LOW.
+	 */
+	int buck234_gpio_dvs[3]; /* GPIO of [0]DVS1, [1]DVS2, [2]DVS3 */
+	int buck234_gpio_selb[3]; /* [0]SELB2, [1]SELB3, [2]SELB4 */
+	unsigned int buck2_voltage[8]; /* buckx_voltage in uV */
+	unsigned int buck3_voltage[8];
+	unsigned int buck4_voltage[8];
+};
+
+#endif /* __LINUX_MFD_MAX77686_H */
-- 
1.7.4.1


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

* [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-18  9:32 [PATCH v3 0/3] Add initial support for MAXIM 77686 mfd chip Jonghwa Lee
  2012-05-18  9:32 ` [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver Jonghwa Lee
@ 2012-05-18  9:32 ` Jonghwa Lee
  2012-05-20 15:56   ` Mark Brown
  2012-05-18  9:32 ` [PATCH 3/3] rtc: MAX77686: Add Maxim 77686 rtc driver Jonghwa Lee
  2 siblings, 1 reply; 11+ messages in thread
From: Jonghwa Lee @ 2012-05-18  9:32 UTC (permalink / raw)
  To: linux-kernel
  Cc: Alessandro Zummo, Samuel Oritz, Liam Girdwood, Mark Brown,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Jonghwa Lee,
	Chiwoong Byun

Add driver for support max77686 regulator.
MAX77686 provides LDOs[1~26] and BUCKs[1~9]. It support to set or get the
volatege of regulator on max77686 chip with using regmap.

v2
- Convert get_voltage to get_voltage_sel
- Convert set_voltage to set_voltage_sel
- Implement set_voltage_time_sel
- Register regulators unconditionally
- kzalloc -> devm_kzalloc
- Remove unneccessary printk
- Keep probing whether pdata exists or not
- Use regmap API to access PMIC register.

Signed-off-by: Chiwoong Byun <woong.byun@samsung.com>
Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
Signed-off-by: Myungjoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/regulator/Kconfig    |    8 +
 drivers/regulator/Makefile   |    1 +
 drivers/regulator/max77686.c |  791 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 800 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/max77686.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 36db5a4..8e2ebad 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -195,6 +195,14 @@ config REGULATOR_MAX8998
 	  via I2C bus. The provided regulator is suitable for S3C6410
 	  and S5PC1XX chips to control VCC_CORE and VCC_USIM voltages.
 
+config REGULATOR_MAX77686
+	tristate "Maxim 77686 regulator"
+	depends on MFD_MAX77686
+	help
+	  This driver controls a Maxim 77686 regulator
+	  via I2C bus. The provided regulator is suitable for
+	  Exynos-4 chips to control VARM and VINT voltages.
+
 config REGULATOR_PCAP
 	tristate "Motorola PCAP2 regulator driver"
 	depends on EZX_PCAP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 94b5274..008931b 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o
 obj-$(CONFIG_REGULATOR_MAX8952) += max8952.o
 obj-$(CONFIG_REGULATOR_MAX8997) += max8997.o
 obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o
+obj-$(CONFIG_REGULATOR_MAX77686) += max77686.o
 obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
 obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
 obj-$(CONFIG_REGULATOR_MC13XXX_CORE) +=  mc13xxx-regulator-core.o
diff --git a/drivers/regulator/max77686.c b/drivers/regulator/max77686.c
new file mode 100644
index 0000000..8bfd061
--- /dev/null
+++ b/drivers/regulator/max77686.c
@@ -0,0 +1,791 @@
+/*
+ * max77686.c - Regulator driver for the Maxim 77686
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Chiwoong Byun <woong.byun@smasung.com>
+ * Jonghwa Lee <jonghwa3.lee@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8997.c
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+
+#define PMIC_DEBUG KERN_INFO
+#define PMIC_REG_DEBUG KERN_DEBUG
+
+#define MAX77686_OPMODE_SHIFT 6
+#define MAX77686_OPMODE_BUCK234_SHIFT 4
+#define MAX77686_OPMODE_MASK 0x3
+
+#define MAX77686_RAMP_RATE 100
+
+#define MAX77686_REGULATORS 38
+
+struct max77686_data {
+	struct device *dev;
+	struct max77686_dev *iodev;
+	int num_regulators;
+	struct regulator_dev **rdev;
+	int ramp_delay; /* in mV/us */
+
+	struct max77686_opmode_data *opmode_data;
+
+	bool buck2_gpiodvs;
+	bool buck3_gpiodvs;
+	bool buck4_gpiodvs;
+	u8 buck2_vol[8];
+	u8 buck3_vol[8];
+	u8 buck4_vol[8];
+	int buck234_gpios_dvs[3];
+	int buck234_gpios_selb[3];
+	int buck234_gpioindex;
+	bool ignore_gpiodvs_side_effect;
+
+	u8 saved_states[MAX77686_REG_MAX];
+};
+
+static inline void max77686_set_gpio(struct max77686_data *max77686)
+{
+	int set3 = (max77686->buck234_gpioindex) & 0x1;
+	int set2 = ((max77686->buck234_gpioindex) >> 1) & 0x1;
+	int set1 = ((max77686->buck234_gpioindex) >> 2) & 0x1;
+
+	if (max77686->buck234_gpios_dvs[0])
+		gpio_set_value(max77686->buck234_gpios_dvs[0], set1);
+	if (max77686->buck234_gpios_dvs[1])
+		gpio_set_value(max77686->buck234_gpios_dvs[1], set2);
+	if (max77686->buck234_gpios_dvs[2])
+		gpio_set_value(max77686->buck234_gpios_dvs[2], set3);
+}
+
+struct voltage_map_desc {
+	int min;
+	int max;
+	int step;
+	unsigned int n_bits;
+};
+
+/* LDO3 ~ 5, 9 ~ 14, 16 ~ 26 (uV) */
+static const struct voltage_map_desc ldo_voltage_map_desc = {
+	.min = 800000,	.max = 3950000,	.step = 50000,	.n_bits = 6,
+};
+
+/* LDO1 ~ 2, 6 ~ 8, 15 (uV) */
+static const struct voltage_map_desc ldo_low_voltage_map_desc = {
+	.min = 800000,	.max = 2375000,	.step = 25000,	.n_bits = 6,
+};
+
+/* Buck2, 3, 4 (uV) */
+static const struct voltage_map_desc buck_dvs_voltage_map_desc = {
+	.min = 600000,	.max = 3787500,	.step = 12500,	.n_bits = 8,
+};
+
+/* Buck1, 5 ~ 9 (uV) */
+static const struct voltage_map_desc buck_voltage_map_desc = {
+	.min = 750000,	.max = 3900000,	.step = 50000,	.n_bits = 6,
+};
+
+static const struct voltage_map_desc *reg_voltage_map[] = {
+	[MAX77686_LDO1] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO2] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO3] = &ldo_voltage_map_desc,
+	[MAX77686_LDO4] = &ldo_voltage_map_desc,
+	[MAX77686_LDO5] = &ldo_voltage_map_desc,
+	[MAX77686_LDO6] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO7] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO8] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO9] = &ldo_voltage_map_desc,
+	[MAX77686_LDO10] = &ldo_voltage_map_desc,
+	[MAX77686_LDO11] = &ldo_voltage_map_desc,
+	[MAX77686_LDO12] = &ldo_voltage_map_desc,
+	[MAX77686_LDO13] = &ldo_voltage_map_desc,
+	[MAX77686_LDO14] = &ldo_voltage_map_desc,
+	[MAX77686_LDO15] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO16] = &ldo_voltage_map_desc,
+	[MAX77686_LDO17] = &ldo_voltage_map_desc,
+	[MAX77686_LDO18] = &ldo_voltage_map_desc,
+	[MAX77686_LDO19] = &ldo_voltage_map_desc,
+	[MAX77686_LDO20] = &ldo_voltage_map_desc,
+	[MAX77686_LDO21] = &ldo_voltage_map_desc,
+	[MAX77686_LDO22] = &ldo_voltage_map_desc,
+	[MAX77686_LDO23] = &ldo_voltage_map_desc,
+	[MAX77686_LDO24] = &ldo_voltage_map_desc,
+	[MAX77686_LDO25] = &ldo_voltage_map_desc,
+	[MAX77686_LDO26] = &ldo_voltage_map_desc,
+	[MAX77686_BUCK1] = &buck_voltage_map_desc,
+	[MAX77686_BUCK2] = &buck_dvs_voltage_map_desc,
+	[MAX77686_BUCK3] = &buck_dvs_voltage_map_desc,
+	[MAX77686_BUCK4] = &buck_dvs_voltage_map_desc,
+	[MAX77686_BUCK5] = &buck_voltage_map_desc,
+	[MAX77686_BUCK6] = &buck_voltage_map_desc,
+	[MAX77686_BUCK7] = &buck_voltage_map_desc,
+	[MAX77686_BUCK8] = &buck_voltage_map_desc,
+	[MAX77686_BUCK9] = &buck_voltage_map_desc,
+	[MAX77686_EN32KHZ_AP] = NULL,
+	[MAX77686_EN32KHZ_CP] = NULL,
+	[MAX77686_P32KH] = NULL,
+};
+
+static int max77686_list_voltage(struct regulator_dev *rdev,
+		unsigned int selector)
+{
+	const struct voltage_map_desc *desc;
+	int rid = rdev_get_id(rdev);
+	int val;
+
+	if (rid >= ARRAY_SIZE(reg_voltage_map) ||
+			rid < 0)
+		return -EINVAL;
+
+	desc = reg_voltage_map[rid];
+	if (desc == NULL)
+		return -EINVAL;
+
+	val = desc->min + desc->step * selector;
+	if (val > desc->max)
+		return -EINVAL;
+
+	return val;
+}
+
+unsigned int max77686_opmode_reg[][3] = {
+	/* LDO1 ... LDO26 */
+	/* {NORMAL, LP, STANDBY} */
+	{0x3, 0x2, 0x2}, /* LDO1 */
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1}, /* LDO11 */
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2}, /* LDO21 */
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	/* BUCK1 ... BUCK9 */
+	{0x3, 0x2, 0x1}, /* BUCK1 */
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	/* 32KHZ */
+	{0x1, 0x1, 0x1},
+	{0x1, 0x1, 0x1},
+	{0x1, 0x1, 0x1},
+};
+
+static int max77686_get_enable_register(struct regulator_dev *rdev,
+		int *reg, int *mask, int *pattern)
+{
+	unsigned int rid = rdev_get_id(rdev);
+	unsigned int mode;
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+
+	if (rid > ARRAY_SIZE(max77686_opmode_reg))
+		return -EINVAL;
+
+	mode = max77686->opmode_data[rid].mode;
+	printk(KERN_INFO "rid=%d, mode=%d, size=%d\n",
+		 rid, mode, ARRAY_SIZE(max77686_opmode_reg));
+
+	switch (rid) {
+	case MAX77686_LDO1 ... MAX77686_LDO26:
+		*reg = MAX77686_REG_LDO1CTRL1 + (rid - MAX77686_LDO1);
+		*mask = MAX77686_OPMODE_MASK << MAX77686_OPMODE_SHIFT;
+		*pattern = max77686_opmode_reg[rid][mode]
+						 << MAX77686_OPMODE_SHIFT;
+		break;
+	case MAX77686_BUCK1:
+		*reg = MAX77686_REG_BUCK1CTRL;
+		*mask = MAX77686_OPMODE_MASK;
+		*pattern = max77686_opmode_reg[rid][mode];
+		break;
+	case MAX77686_BUCK2:
+	case MAX77686_BUCK3:
+	case MAX77686_BUCK4:
+		*reg = MAX77686_REG_BUCK2CTRL1 + (rid - MAX77686_BUCK2)*10;
+		*mask = MAX77686_OPMODE_MASK << MAX77686_OPMODE_BUCK234_SHIFT;
+		*pattern = max77686_opmode_reg[rid][mode]
+					<< MAX77686_OPMODE_BUCK234_SHIFT;
+		break;
+	case MAX77686_BUCK5 ... MAX77686_BUCK9:
+		*reg = MAX77686_REG_BUCK5CTRL + (rid - MAX77686_BUCK5) * 2;
+		*mask = MAX77686_OPMODE_MASK;
+		*pattern = max77686_opmode_reg[rid][mode];
+		break;
+	case MAX77686_EN32KHZ_AP ... MAX77686_P32KH:
+		*reg = MAX77686_REG_32KHZ;
+		*mask = 0x01 << (rid - MAX77686_EN32KHZ_AP);
+		*pattern = 0x01 << (rid - MAX77686_EN32KHZ_AP);
+		break;
+	default:
+		/* Not controllable or not exists */
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int max77686_reg_is_enabled(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int ret, reg, mask, pattern;
+	unsigned int val;
+
+	ret = max77686_get_enable_register(rdev, &reg, &mask, &pattern);
+	if (ret < 0)
+		return ret;
+
+	ret = regmap_read(max77686->iodev->regmap, reg, &val);
+
+	if (ret)
+		return ret;
+
+	printk(PMIC_DEBUG "id=%d, ret=%d, val=%x, mask=%x, pattern=%x\n",
+		 rdev_get_id(rdev), (val & mask) == pattern,
+		 val, mask, pattern);
+
+	return (val & mask) == pattern;
+}
+
+static int max77686_reg_enable(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int ret, reg, mask, pattern;
+
+	ret = max77686_get_enable_register(rdev, &reg, &mask, &pattern);
+	if (ret < 0)
+		return ret;
+
+	printk(PMIC_DEBUG "%s: id=%d, pattern=%x\n",
+		__func__, rdev_get_id(rdev), pattern);
+
+	return regmap_update_bits(max77686->iodev->regmap, reg, mask, pattern);
+}
+
+static int max77686_reg_disable(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int ret, reg, mask, pattern;
+
+	ret = max77686_get_enable_register(rdev, &reg, &mask, &pattern);
+	if (ret < 0)
+		return ret;
+
+	printk(PMIC_DEBUG "%s: id=%d, pattern=%x\n",
+		__func__, rdev_get_id(rdev), pattern);
+
+	return regmap_update_bits(max77686->iodev->regmap, reg, mask, ~mask);
+}
+
+static int max77686_get_voltage_register(struct regulator_dev *rdev,
+		int *_reg, int *_shift, int *_mask)
+{
+	int rid = rdev_get_id(rdev);
+	int reg, shift = 0, mask = 0x3f;
+
+	switch (rid) {
+	case MAX77686_LDO1 ... MAX77686_LDO26:
+		reg = MAX77686_REG_LDO1CTRL1 + (rid - MAX77686_LDO1);
+		break;
+	case MAX77686_BUCK1:
+		reg = MAX77686_REG_BUCK1OUT;
+		break;
+	case MAX77686_BUCK2:
+		reg = MAX77686_REG_BUCK2DVS1;
+		mask = 0xff;
+		break;
+	case MAX77686_BUCK3:
+		reg = MAX77686_REG_BUCK3DVS1;
+		mask = 0xff;
+		break;
+	case MAX77686_BUCK4:
+		reg = MAX77686_REG_BUCK4DVS1;
+		mask = 0xff;
+		break;
+	case MAX77686_BUCK5 ... MAX77686_BUCK9:
+		reg = MAX77686_REG_BUCK5OUT + (rid - MAX77686_BUCK5) * 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*_reg = reg;
+	*_shift = shift;
+	*_mask = mask;
+
+	return 0;
+}
+
+static inline int max77686_get_voltage_proper_val(
+		const struct voltage_map_desc *desc,
+		int min_vol, int max_vol)
+{
+	int i = 0;
+
+	if (desc == NULL)
+		return -EINVAL;
+
+	if (max_vol < desc->min || min_vol > desc->max)
+		return -EINVAL;
+
+	while (desc->min + desc->step * i < min_vol &&
+			desc->min + desc->step * i < desc->max)
+		i++;
+
+	if (desc->min + desc->step * i > max_vol)
+		return -EINVAL;
+
+	if (i >= (1 << desc->n_bits))
+		return -EINVAL;
+
+	return i;
+}
+
+static int max77686_get_voltage_sel(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int reg, shift, mask, ret;
+	int rid = rdev_get_id(rdev);
+	unsigned int selector;
+
+	ret = max77686_get_voltage_register(rdev, &reg, &shift, &mask);
+	if (ret)
+		return ret;
+
+	if ((rid == MAX77686_BUCK2 && max77686->buck2_gpiodvs) ||
+	    (rid == MAX77686_BUCK3 && max77686->buck3_gpiodvs) ||
+	    (rid == MAX77686_BUCK4 && max77686->buck4_gpiodvs))
+		reg += max77686->buck234_gpioindex;
+
+	ret = regmap_read(max77686->iodev->regmap, reg, &selector);
+	if (ret)
+		return ret;
+
+	selector >>= shift;
+	selector &= mask;
+
+	return selector;
+}
+
+static int max77686_set_voltage_sel(struct regulator_dev *rdev,
+				 unsigned int selector)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int reg, shift, mask, ret;
+
+	ret = max77686_get_voltage_register(rdev, &reg, &shift, &mask);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(max77686->iodev->regmap, reg,
+					mask << shift, selector << shift);
+
+	return ret;
+}
+
+static int max77686_set_voltage_time_sel(struct regulator_dev *rdev,
+			unsigned int old_selector, unsigned int new_selector)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	const struct voltage_map_desc *desc;
+	int rid = rdev_get_id(rdev);
+
+	desc = reg_voltage_map[rid];
+
+	switch (rid) {
+	case MAX77686_BUCK2 ... MAX77686_BUCK4:
+		if (old_selector < new_selector)
+			return (DIV_ROUND_UP(desc->step
+				* (new_selector - old_selector),
+					max77686->ramp_delay * 1000));
+	case MAX77686_BUCK1:
+	case MAX77686_BUCK5 ... MAX77686_BUCK9:
+		/* Unconditionally 100 mV/us */
+		if (old_selector < new_selector)
+			return (DIV_ROUND_UP(desc->step
+				 * (new_selector - old_selector), 100000));
+	default:
+		return 0;
+	}
+}
+
+static struct regulator_ops max77686_ldo_ops = {
+	.list_voltage		= max77686_list_voltage,
+	.is_enabled		= max77686_reg_is_enabled,
+	.enable			= max77686_reg_enable,
+	.disable		= max77686_reg_disable,
+	.get_voltage_sel	= max77686_get_voltage_sel,
+	.set_voltage_sel	= max77686_set_voltage_sel,
+	.set_voltage_time_sel	= max77686_set_voltage_time_sel,
+};
+
+static struct regulator_ops max77686_buck_ops = {
+	.list_voltage		= max77686_list_voltage,
+	.is_enabled		= max77686_reg_is_enabled,
+	.enable			= max77686_reg_enable,
+	.disable		= max77686_reg_disable,
+	.get_voltage_sel	= max77686_get_voltage_sel,
+	.set_voltage_sel	= max77686_set_voltage_sel,
+	.set_voltage_time_sel	= max77686_set_voltage_time_sel,
+};
+
+static struct regulator_ops max77686_fixedvolt_ops = {
+	.list_voltage		= max77686_list_voltage,
+	.is_enabled		= max77686_reg_is_enabled,
+	.enable			= max77686_reg_enable,
+	.disable		= max77686_reg_disable,
+};
+
+#define regulator_desc_ldo(num)		{	\
+	.name		= "LDO"#num,		\
+	.id		= MAX77686_LDO##num,	\
+	.ops		= &max77686_ldo_ops,	\
+	.type		= REGULATOR_VOLTAGE,	\
+	.owner		= THIS_MODULE,		\
+}
+#define regulator_desc_buck(num)		{	\
+	.name		= "BUCK"#num,		\
+	.id		= MAX77686_BUCK##num,	\
+	.ops		= &max77686_buck_ops,	\
+	.type		= REGULATOR_VOLTAGE,	\
+	.owner		= THIS_MODULE,		\
+}
+
+static struct regulator_desc regulators[] = {
+	regulator_desc_ldo(1),
+	regulator_desc_ldo(2),
+	regulator_desc_ldo(3),
+	regulator_desc_ldo(4),
+	regulator_desc_ldo(5),
+	regulator_desc_ldo(6),
+	regulator_desc_ldo(7),
+	regulator_desc_ldo(8),
+	regulator_desc_ldo(9),
+	regulator_desc_ldo(10),
+	regulator_desc_ldo(11),
+	regulator_desc_ldo(12),
+	regulator_desc_ldo(13),
+	regulator_desc_ldo(14),
+	regulator_desc_ldo(15),
+	regulator_desc_ldo(16),
+	regulator_desc_ldo(17),
+	regulator_desc_ldo(18),
+	regulator_desc_ldo(19),
+	regulator_desc_ldo(20),
+	regulator_desc_ldo(21),
+	regulator_desc_ldo(22),
+	regulator_desc_ldo(23),
+	regulator_desc_ldo(24),
+	regulator_desc_ldo(25),
+	regulator_desc_ldo(26),
+	regulator_desc_buck(1),
+	regulator_desc_buck(2),
+	regulator_desc_buck(3),
+	regulator_desc_buck(4),
+	regulator_desc_buck(5),
+	regulator_desc_buck(6),
+	regulator_desc_buck(7),
+	regulator_desc_buck(8),
+	regulator_desc_buck(9),
+	{
+		.name	= "EN32KHz AP",
+		.id	= MAX77686_EN32KHZ_AP,
+		.ops	= &max77686_fixedvolt_ops,
+		.type	= REGULATOR_VOLTAGE,
+		.owner	= THIS_MODULE,
+	}, {
+		.name	= "EN32KHz CP",
+		.id	= MAX77686_EN32KHZ_CP,
+		.ops	= &max77686_fixedvolt_ops,
+		.type	= REGULATOR_VOLTAGE,
+		.owner	= THIS_MODULE,
+	}, {
+		.name	= "EN32KHz PMIC",
+		.id	= MAX77686_P32KH,
+		.ops	= &max77686_fixedvolt_ops,
+		.type	= REGULATOR_VOLTAGE,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static __devinit int max77686_pmic_probe(struct platform_device *pdev)
+{
+	struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+	struct max77686_platform_data *pdata = dev_get_platdata(iodev->dev);
+	struct regulator_dev **rdev;
+	struct max77686_data *max77686;
+	struct i2c_client *i2c;
+	struct regulator_init_data **init_data;
+	int i, ret, size;
+	unsigned int data = 0;
+
+	printk(PMIC_DEBUG "%s\n", __func__);
+
+	if (!pdata)
+		dev_err(pdev->dev.parent, "No platform init data supplied.\n");
+
+	max77686 = devm_kzalloc(&pdev->dev, sizeof(struct max77686_data),
+				GFP_KERNEL);
+	if (!max77686)
+		return -ENOMEM;
+
+	size = sizeof(struct regulator_dev *) * MAX77686_REGULATORS;
+	max77686->rdev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	if (!max77686->rdev) {
+		devm_kfree(&pdev->dev, max77686);
+		return -ENOMEM;
+	}
+
+	rdev = max77686->rdev;
+	max77686->dev = &pdev->dev;
+	max77686->iodev = iodev;
+	if (pdata)
+		max77686->num_regulators = pdata->num_regulators;
+	platform_set_drvdata(pdev, max77686);
+	i2c = max77686->iodev->i2c;
+	init_data = devm_kzalloc(&pdev->dev,
+				sizeof(struct regulator_init_data *)
+					 * MAX77686_REGULATORS, GFP_KERNEL);
+	if (pdata)
+		max77686->opmode_data = pdata->opmode_data;
+
+	max77686->ramp_delay = MAX77686_RAMP_RATE; /* Set 0x3 for RAMP */
+	regmap_update_bits(max77686->iodev->regmap,
+					 MAX77686_REG_BUCK2CTRL1, 0xC0, 0xC0);
+	regmap_update_bits(max77686->iodev->regmap,
+					 MAX77686_REG_BUCK3CTRL1, 0xC0, 0xC0);
+	regmap_update_bits(max77686->iodev->regmap,
+					 MAX77686_REG_BUCK4CTRL1, 0xC0, 0xC0);
+
+	regmap_read(max77686->iodev->regmap, MAX77686_REG_DEVICE_ID, &data);
+
+	/*
+	 * TODO
+	 * This disables GPIO-DVS. Later we may need to implement GPIO-DVS..
+	 * or we do not?
+	 */
+	max77686->buck2_gpiodvs = false;
+	max77686->buck3_gpiodvs = false;
+	max77686->buck4_gpiodvs = false;
+	if (pdata) {
+		for (i = 0; i < 3; i++) {
+			char buf[80];
+			sprintf(buf, "MAX77686 DVS%d", i);
+			if (gpio_is_valid(pdata->buck234_gpio_dvs[i])) {
+				max77686->buck234_gpios_dvs[i] =
+					   pdata->buck234_gpio_dvs[i];
+				gpio_request(pdata->buck234_gpio_dvs[i], buf);
+				gpio_direction_output(
+					pdata->buck234_gpio_dvs[i], 0);
+			} else {
+				dev_info(&pdev->dev, "GPIO %s ignored (%d)\n",
+					buf, pdata->buck234_gpio_dvs[i]);
+			}
+
+			sprintf(buf, "MAX77686 SELB%d", i);
+
+			if (gpio_is_valid(pdata->buck234_gpio_selb[i])) {
+				max77686->buck234_gpios_selb[i] =
+					pdata->buck234_gpio_selb[i];
+				gpio_request(pdata->buck234_gpio_selb[i], buf);
+				gpio_direction_output(
+					pdata->buck234_gpio_selb[i], 0);
+			} else {
+				dev_info(&pdev->dev, "GPIO %s ignored (%d)\n",
+				buf, pdata->buck234_gpio_dvs[i]);
+			}
+		}
+	}
+
+	max77686->buck234_gpioindex = 0;
+	for (i = 0; i < 8; i++) {
+		if (pdata)
+			ret = max77686_get_voltage_proper_val(
+					&buck_dvs_voltage_map_desc,
+					pdata->buck2_voltage[i],
+					pdata->buck2_voltage[i]
+					+ buck_dvs_voltage_map_desc.step);
+		/* 1.1V as default for safety */
+		if (!pdata || ret < 0)
+			max77686->buck2_vol[i] = 0x28;
+		else
+			max77686->buck2_vol[i] = ret;
+
+		regmap_write(max77686->iodev->regmap,
+			 MAX77686_REG_BUCK2DVS1 + i, max77686->buck2_vol[i]);
+		if (pdata)
+			ret = max77686_get_voltage_proper_val(
+					&buck_dvs_voltage_map_desc,
+					pdata->buck3_voltage[i],
+					pdata->buck3_voltage[i]
+					+ buck_dvs_voltage_map_desc.step);
+		/* 1.1V as default for safety */
+		if (!pdata || ret < 0)
+			max77686->buck3_vol[i] = 0x28;
+		else
+			max77686->buck3_vol[i] = ret;
+
+		regmap_write(max77686->iodev->regmap,
+			 MAX77686_REG_BUCK3DVS1 + i, max77686->buck3_vol[i]);
+		if (pdata)
+			ret = max77686_get_voltage_proper_val(
+					&buck_dvs_voltage_map_desc,
+					pdata->buck4_voltage[i],
+					pdata->buck4_voltage[i]
+					+ buck_dvs_voltage_map_desc.step);
+		/* 1.1V as default for safety */
+		if (!pdata || ret < 0)
+			max77686->buck4_vol[i] = 0x28;
+		else
+			max77686->buck4_vol[i] = ret;
+
+		regmap_write(max77686->iodev->regmap,
+			 MAX77686_REG_BUCK4DVS1 + i, max77686->buck4_vol[i]);
+	}
+
+	for (i = 0; i < MAX77686_REGULATORS; i++) {
+		const struct voltage_map_desc *desc;
+		if (pdata)
+			init_data[pdata->regulators[i].id] =
+						 pdata->regulators[i].initdata;
+
+		desc = reg_voltage_map[i];
+		if (desc) {
+			regulators[i].n_voltages =
+				(desc->max - desc->min) / desc->step + 1;
+
+			printk(PMIC_DEBUG "%s: desc=%p, id=%d, n_vol=%d, max=%d,
+				 min=%d, step=%d\n", __func__, desc, i,
+				regulators[i].n_voltages, desc->max,
+				desc->min, desc->step);
+		}
+
+		rdev[i] = regulator_register(&regulators[i], max77686->dev,
+					     init_data[i], max77686, NULL);
+		if (IS_ERR(rdev[i])) {
+			ret = PTR_ERR(rdev[i]);
+			dev_err(max77686->dev,
+				"regulator init failed for %d\n", i);
+			rdev[i] = NULL;
+			goto err;
+		}
+	}
+
+	devm_kfree(&pdev->dev, init_data);
+	return 0;
+err:
+	for (i = 0; i < 3; i++) {
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_dvs[i]);
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_selb[i]);
+	}
+
+	for (i = 0; i < MAX77686_REGULATORS; i++)
+		if (rdev[i])
+			regulator_unregister(rdev[i]);
+
+	devm_kfree(&pdev->dev, max77686->rdev);
+	devm_kfree(&pdev->dev, max77686);
+
+	return ret;
+}
+
+static int __devexit max77686_pmic_remove(struct platform_device *pdev)
+{
+	struct max77686_data *max77686 = platform_get_drvdata(pdev);
+	struct regulator_dev **rdev = max77686->rdev;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_dvs[i]);
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_selb[i]);
+	}
+
+	for (i = 0; i < MAX77686_REGULATORS; i++)
+		if (rdev[i])
+			regulator_unregister(rdev[i]);
+
+	devm_kfree(&pdev->dev, max77686->rdev);
+	devm_kfree(&pdev->dev, max77686);
+
+	return 0;
+}
+
+static const struct platform_device_id max77686_pmic_id[] = {
+	{ "max77686-pmic", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, max77686_pmic_id);
+
+static struct platform_driver max77686_pmic_driver = {
+	.driver = {
+		.name = "max77686-pmic",
+		.owner = THIS_MODULE,
+	},
+	.probe = max77686_pmic_probe,
+	.remove = __devexit_p(max77686_pmic_remove),
+	.id_table = max77686_pmic_id,
+};
+
+static int __init max77686_pmic_init(void)
+{
+	return platform_driver_register(&max77686_pmic_driver);
+}
+subsys_initcall(max77686_pmic_init);
+
+static void __exit max77686_pmic_cleanup(void)
+{
+	platform_driver_unregister(&max77686_pmic_driver);
+}
+module_exit(max77686_pmic_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 77686 Regulator Driver");
+MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
-- 
1.7.4.1


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

* [PATCH 3/3] rtc: MAX77686: Add Maxim 77686 rtc driver
  2012-05-18  9:32 [PATCH v3 0/3] Add initial support for MAXIM 77686 mfd chip Jonghwa Lee
  2012-05-18  9:32 ` [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver Jonghwa Lee
  2012-05-18  9:32 ` [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver Jonghwa Lee
@ 2012-05-18  9:32 ` Jonghwa Lee
  2 siblings, 0 replies; 11+ messages in thread
From: Jonghwa Lee @ 2012-05-18  9:32 UTC (permalink / raw)
  To: linux-kernel
  Cc: Alessandro Zummo, Samuel Oritz, Liam Girdwood, Mark Brown,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Jonghwa Lee,
	Chiwoong Byun

Add driver for support max77686 rtc.
MAX77686 rtc support smpl and wtsr mode. It has two alarm register,
and can be used for waking system up. It sharing i2c bus, regmap supplies
the way can get to the its register.

v2
- Use regmap API to access RTC register.

Signed-off-by: Chiwoong Byun <woong.byun@samsung.com>
Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
Signed-off-by: Myunjoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/rtc/Kconfig        |   10 +
 drivers/rtc/Makefile       |    3 +-
 drivers/rtc/rtc-max77686.c |  645 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 657 insertions(+), 1 deletions(-)
 create mode 100644 drivers/rtc/rtc-max77686.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 8c8377d..a6092a6 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -213,6 +213,16 @@ config RTC_DRV_MAX8998
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-max8998.
 
+config RTC_DRV_MAX77686
+	tristate "Maxim MAX77686"
+	depends on MFD_MAX77686
+	help
+	  If you say yes here you will get support for the
+	  RTC of Maxim MAX77686 PMIC.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-max77686.
+
 config RTC_DRV_RS5C372
 	tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A"
 	help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 727ae77..2e5dc82 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -63,9 +63,10 @@ obj-$(CONFIG_RTC_DRV_M48T59)	+= rtc-m48t59.o
 obj-$(CONFIG_RTC_DRV_M48T86)	+= rtc-m48t86.o
 obj-$(CONFIG_RTC_MXC)		+= rtc-mxc.o
 obj-$(CONFIG_RTC_DRV_MAX6900)	+= rtc-max6900.o
+obj-$(CONFIG_RTC_DRV_MAX6902)	+= rtc-max6902.o
 obj-$(CONFIG_RTC_DRV_MAX8925)	+= rtc-max8925.o
 obj-$(CONFIG_RTC_DRV_MAX8998)	+= rtc-max8998.o
-obj-$(CONFIG_RTC_DRV_MAX6902)	+= rtc-max6902.o
+obj-$(CONFIG_RTC_DRV_MAX77686)	+= rtc-max77686.o
 obj-$(CONFIG_RTC_DRV_MC13XXX)	+= rtc-mc13xxx.o
 obj-$(CONFIG_RTC_DRV_MSM6242)	+= rtc-msm6242.o
 obj-$(CONFIG_RTC_DRV_MPC5121)	+= rtc-mpc5121.o
diff --git a/drivers/rtc/rtc-max77686.c b/drivers/rtc/rtc-max77686.c
new file mode 100644
index 0000000..63da55a
--- /dev/null
+++ b/drivers/rtc/rtc-max77686.c
@@ -0,0 +1,645 @@
+/*
+ * RTC driver for Maxim MAX77686
+ *
+ * Copyright (C) 2012 Samsung Electronics Co.Ltd
+ *
+ *  based on rtc-max8997.c
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/rtc.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max77686-private.h>
+#include <linux/irqdomain.h>
+#include <linux/regmap.h>
+
+/* RTC Control Register */
+#define BCD_EN_SHIFT			0
+#define BCD_EN_MASK				(1 << BCD_EN_SHIFT)
+#define MODEL24_SHIFT			1
+#define MODEL24_MASK			(1 << MODEL24_SHIFT)
+/* RTC Update Register1 */
+#define RTC_UDR_SHIFT			0
+#define RTC_UDR_MASK			(1 << RTC_UDR_SHIFT)
+#define RTC_RBUDR_SHIFT			4
+#define RTC_RBUDR_MASK			(1 << RTC_RBUDR_SHIFT)
+/* WTSR and SMPL Register */
+#define WTSRT_SHIFT				0
+#define SMPLT_SHIFT				2
+#define WTSR_EN_SHIFT			6
+#define SMPL_EN_SHIFT			7
+#define WTSRT_MASK				(3 << WTSRT_SHIFT)
+#define SMPLT_MASK				(3 << SMPLT_SHIFT)
+#define WTSR_EN_MASK			(1 << WTSR_EN_SHIFT)
+#define SMPL_EN_MASK			(1 << SMPL_EN_SHIFT)
+/* RTC Hour register */
+#define HOUR_PM_SHIFT			6
+#define HOUR_PM_MASK			(1 << HOUR_PM_SHIFT)
+/* RTC Alarm Enable */
+#define ALARM_ENABLE_SHIFT		7
+#define ALARM_ENABLE_MASK		(1 << ALARM_ENABLE_SHIFT)
+
+#define MAX77686_RTC_UPDATE_DELAY	16
+#undef MAX77686_RTC_WTSR_SMPL
+
+enum {
+	RTC_SEC = 0,
+	RTC_MIN,
+	RTC_HOUR,
+	RTC_WEEKDAY,
+	RTC_MONTH,
+	RTC_YEAR,
+	RTC_DATE,
+	RTC_NR_TIME
+};
+
+struct max77686_rtc_info {
+	struct device		*dev;
+	struct max77686_dev	*max77686;
+	struct i2c_client	*rtc;
+	struct rtc_device	*rtc_dev;
+	struct mutex		lock;
+
+	struct regmap		*regmap;
+
+	int virq;
+	int rtc_24hr_mode;
+};
+
+enum MAX77686_RTC_OP {
+	MAX77686_RTC_WRITE,
+	MAX77686_RTC_READ,
+};
+
+static inline int max77686_rtc_calculate_wday(u8 shifted)
+{
+	int counter = -1;
+	while (shifted) {
+		shifted >>= 1;
+		counter++;
+	}
+	return counter;
+}
+
+static void max77686_rtc_data_to_tm(u8 *data, struct rtc_time *tm,
+				   int rtc_24hr_mode)
+{
+	tm->tm_sec = data[RTC_SEC] & 0x7f;
+	tm->tm_min = data[RTC_MIN] & 0x7f;
+	if (rtc_24hr_mode)
+		tm->tm_hour = data[RTC_HOUR] & 0x1f;
+	else {
+		tm->tm_hour = data[RTC_HOUR] & 0x0f;
+		if (data[RTC_HOUR] & HOUR_PM_MASK)
+			tm->tm_hour += 12;
+	}
+
+	tm->tm_wday = max77686_rtc_calculate_wday(data[RTC_WEEKDAY] & 0x7f);
+	tm->tm_mday = data[RTC_DATE] & 0x1f;
+	tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1;
+	tm->tm_year = (data[RTC_YEAR] & 0x7f) + 100;
+	tm->tm_yday = 0;
+	tm->tm_isdst = 0;
+}
+
+static int max77686_rtc_tm_to_data(struct rtc_time *tm, u8 *data)
+{
+	data[RTC_SEC] = tm->tm_sec;
+	data[RTC_MIN] = tm->tm_min;
+	data[RTC_HOUR] = tm->tm_hour;
+	data[RTC_WEEKDAY] = 1 << tm->tm_wday;
+	data[RTC_DATE] = tm->tm_mday;
+	data[RTC_MONTH] = tm->tm_mon + 1;
+	data[RTC_YEAR] = tm->tm_year > 100 ? (tm->tm_year - 100) : 0 ;
+
+	if (tm->tm_year < 100) {
+		pr_warn("%s: MAX77686 RTC cannot handle the year %d."
+			"Assume it's 2000.\n", __func__, 1900 + tm->tm_year);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static inline int max77686_rtc_update(struct max77686_rtc_info *info,
+	enum MAX77686_RTC_OP op)
+{
+	int ret;
+	unsigned int data;
+
+	switch (op) {
+	case MAX77686_RTC_WRITE:
+		data = 1 << RTC_UDR_SHIFT;
+		break;
+	case MAX77686_RTC_READ:
+		data = 1 << RTC_RBUDR_SHIFT;
+		break;
+	}
+
+	ret = regmap_update_bits(info->max77686->rtc_regmap,
+				 MAX77686_RTC_UPDATE0, data, data);
+	if (ret < 0)
+		dev_err(info->dev, "%s: fail to write update reg(ret=%d, data=0x%x)\n",
+				__func__, ret, data);
+	else {
+		/* Minimum 16ms delay required before RTC update. */
+		msleep(MAX77686_RTC_UPDATE_DELAY);
+	}
+
+	return ret;
+}
+
+static int max77686_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct max77686_rtc_info *info = dev_get_drvdata(dev);
+	u8 data[RTC_NR_TIME];
+	int ret;
+
+	mutex_lock(&info->lock);
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+	if (ret < 0)
+		goto out;
+
+	ret = regmap_bulk_read(info->max77686->rtc_regmap,
+				MAX77686_RTC_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to read time reg(%d)\n", __func__,	ret);
+		goto out;
+	}
+
+	max77686_rtc_data_to_tm(data, tm, info->rtc_24hr_mode);
+
+	ret = rtc_valid_tm(tm);
+
+out:
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int max77686_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct max77686_rtc_info *info = dev_get_drvdata(dev);
+	u8 data[RTC_NR_TIME];
+	int ret;
+
+	ret = max77686_rtc_tm_to_data(tm, data);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&info->lock);
+
+	ret = regmap_bulk_write(info->max77686->rtc_regmap,
+				 MAX77686_RTC_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to write time reg(%d)\n", __func__,
+				ret);
+		goto out;
+	}
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+
+out:
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int max77686_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct max77686_rtc_info *info = dev_get_drvdata(dev);
+	u8 data[RTC_NR_TIME];
+	unsigned int val;
+	int i, ret;
+
+	mutex_lock(&info->lock);
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+	if (ret < 0)
+		goto out;
+
+	ret = regmap_bulk_read(info->max77686->rtc_regmap,
+				 MAX77686_ALARM1_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n",
+				__func__, __LINE__, ret);
+		goto out;
+	}
+
+	max77686_rtc_data_to_tm(data, &alrm->time, info->rtc_24hr_mode);
+
+	alrm->enabled = 0;
+	for (i = 0; i < RTC_NR_TIME; i++) {
+		if (data[i] & ALARM_ENABLE_MASK) {
+			alrm->enabled = 1;
+			break;
+		}
+	}
+
+	alrm->pending = 0;
+	ret = regmap_read(info->max77686->regmap, MAX77686_REG_STATUS1, &val);
+	if (ret < 0) {
+		dev_err(info->dev, "%s:%d fail to read status1 reg(%d)\n",
+				__func__, __LINE__, ret);
+		goto out;
+	}
+
+	if (val & (1 << 4)) /* RTCA1 */
+		alrm->pending = 1;
+
+out:
+	mutex_unlock(&info->lock);
+	return 0;
+}
+
+static int max77686_rtc_stop_alarm(struct max77686_rtc_info *info)
+{
+	u8 data[RTC_NR_TIME];
+	int ret, i;
+	struct rtc_time tm;
+
+	if (!mutex_is_locked(&info->lock))
+		dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+	if (ret < 0)
+		goto out;
+
+	ret = regmap_bulk_read(info->max77686->rtc_regmap,
+				 MAX77686_ALARM1_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+				__func__, ret);
+		goto out;
+	}
+
+	max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode);
+
+	for (i = 0; i < RTC_NR_TIME; i++)
+		data[i] &= ~ALARM_ENABLE_MASK;
+
+	ret = regmap_bulk_write(info->max77686->rtc_regmap,
+				 MAX77686_ALARM1_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+				__func__, ret);
+		goto out;
+	}
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+	return ret;
+}
+
+static int max77686_rtc_start_alarm(struct max77686_rtc_info *info)
+{
+	u8 data[RTC_NR_TIME];
+	int ret;
+	struct rtc_time tm;
+
+	if (!mutex_is_locked(&info->lock))
+		dev_warn(info->dev, "%s: should have mutex locked\n", __func__);
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_READ);
+	if (ret < 0)
+		goto out;
+
+	ret = regmap_bulk_read(info->max77686->rtc_regmap,
+				 MAX77686_ALARM1_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to read alarm reg(%d)\n",
+				__func__, ret);
+		goto out;
+	}
+
+	max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode);
+
+	data[RTC_SEC] |= (1 << ALARM_ENABLE_SHIFT);
+	data[RTC_MIN] |= (1 << ALARM_ENABLE_SHIFT);
+	data[RTC_HOUR] |= (1 << ALARM_ENABLE_SHIFT);
+	data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK;
+	if (data[RTC_MONTH] & 0xf)
+		data[RTC_MONTH] |= (1 << ALARM_ENABLE_SHIFT);
+	if (data[RTC_YEAR] & 0x7f)
+		data[RTC_YEAR] |= (1 << ALARM_ENABLE_SHIFT);
+	if (data[RTC_DATE] & 0x1f)
+		data[RTC_DATE] |= (1 << ALARM_ENABLE_SHIFT);
+
+	ret = regmap_bulk_write(info->max77686->rtc_regmap,
+				 MAX77686_ALARM1_SEC, data, RTC_NR_TIME);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+				__func__, ret);
+		goto out;
+	}
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+out:
+	return ret;
+}
+
+static int max77686_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct max77686_rtc_info *info = dev_get_drvdata(dev);
+	u8 data[RTC_NR_TIME];
+	int ret;
+
+	ret = max77686_rtc_tm_to_data(&alrm->time, data);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&info->lock);
+
+	ret = max77686_rtc_stop_alarm(info);
+	if (ret < 0)
+		goto out;
+
+	ret = regmap_bulk_write(info->max77686->rtc_regmap,
+				 MAX77686_ALARM1_SEC, data, RTC_NR_TIME);
+
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to write alarm reg(%d)\n",
+				__func__, ret);
+		goto out;
+	}
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+	if (ret < 0)
+		goto out;
+
+	if (alrm->enabled)
+		ret = max77686_rtc_start_alarm(info);
+out:
+	mutex_unlock(&info->lock);
+	return ret;
+}
+
+static int max77686_rtc_alarm_irq_enable(struct device *dev,
+					unsigned int enabled)
+{
+	struct max77686_rtc_info *info = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&info->lock);
+	if (enabled)
+		ret = max77686_rtc_start_alarm(info);
+	else
+		ret = max77686_rtc_stop_alarm(info);
+	mutex_unlock(&info->lock);
+
+	return ret;
+}
+
+static irqreturn_t max77686_rtc_alarm_irq(int irq, void *data)
+{
+	struct max77686_rtc_info *info = data;
+
+	dev_info(info->dev, "%s:irq(%d)\n", __func__, irq);
+
+	rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF);
+
+	return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops max77686_rtc_ops = {
+	.read_time = max77686_rtc_read_time,
+	.set_time = max77686_rtc_set_time,
+	.read_alarm = max77686_rtc_read_alarm,
+	.set_alarm = max77686_rtc_set_alarm,
+	.alarm_irq_enable = max77686_rtc_alarm_irq_enable,
+};
+
+#ifdef MAX77686_RTC_WTSR_SMPL
+static void max77686_rtc_enable_wtsr(struct max77686_rtc_info *info, bool enable)
+{
+	int ret;
+	unsigned int val, mask;
+
+	if (enable)
+		val = (1 << WTSR_EN_SHIFT) | (3 << WTSRT_SHIFT);
+	else
+		val = 0;
+
+	mask = WTSR_EN_MASK | WTSRT_MASK;
+
+	dev_info(info->dev, "%s: %s WTSR\n", __func__,
+			enable ? "enable" : "disable");
+
+	ret = regmap_update_bits(info->max77686->rtc_regmap,
+				 MAX77686_WTSR_SMPL_CNTL, mask, val);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n",
+				__func__, ret);
+		return;
+	}
+
+	max77686_rtc_update(info, MAX77686_RTC_WRITE);
+}
+
+static void max77686_rtc_enable_smpl(struct max77686_rtc_info *info, bool enable)
+{
+	int ret;
+	unsigned int val, mask;
+
+	if (enable)
+		val = (1 << SMPL_EN_SHIFT) | (0 << SMPLT_SHIFT);
+	else
+		val = 0;
+
+	mask = SMPL_EN_MASK | SMPLT_MASK;
+
+	dev_info(info->dev, "%s: %s SMPL\n", __func__,
+			enable ? "enable" : "disable");
+
+	ret = regmap_update_bits(info->max77686->rtc_regmap,
+				 MAX77686_WTSR_SMPL_CNTL, mask, val);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to update SMPL reg(%d)\n",
+				__func__, ret);
+		return;
+	}
+
+	max77686_rtc_update(info, MAX77686_RTC_WRITE);
+
+	val = 0;
+	regmap_read(info->max77686->rtc_regmap, MAX77686_WTSR_SMPL_CNTL, &val);
+	pr_info("%s: WTSR_SMPL(0x%02x)\n", __func__, val);
+}
+#endif /* MAX77686_RTC_WTSR_SMPL */
+
+static int max77686_rtc_init_reg(struct max77686_rtc_info *info)
+{
+	u8 data[2];
+	int ret;
+
+	/* Set RTC control register : Binary mode, 24hour mdoe */
+	data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+	data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT);
+
+	info->rtc_24hr_mode = 1;
+
+	ret = regmap_bulk_write(info->max77686->rtc_regmap, MAX77686_RTC_CONTROLM, data, 2);
+	if (ret < 0) {
+		dev_err(info->dev, "%s: fail to write controlm reg(%d)\n",
+				__func__, ret);
+		return ret;
+	}
+
+	ret = max77686_rtc_update(info, MAX77686_RTC_WRITE);
+	return ret;
+}
+
+static struct regmap_config max77686_rtc_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+static int __devinit max77686_rtc_probe(struct platform_device *pdev)
+{
+	struct max77686_dev *max77686 = dev_get_drvdata(pdev->dev.parent);
+	struct max77686_rtc_info *info;
+	int ret, virq;
+
+	printk(KERN_INFO "%s\n", __func__);
+
+	info = kzalloc(sizeof(struct max77686_rtc_info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	mutex_init(&info->lock);
+	info->dev = &pdev->dev;
+	info->max77686 = max77686;
+	info->rtc = max77686->rtc;
+	info->max77686->rtc_regmap = regmap_init_i2c(info->max77686->rtc,
+					 &max77686_rtc_regmap_config);
+	if (IS_ERR(info->max77686->rtc_regmap)) {
+		ret = PTR_ERR(info->max77686->rtc_regmap);
+		dev_err(info->max77686->dev, "Failed to allocate register map: %d\n",
+				ret);
+		kfree(info);
+		return ret;
+	}
+	platform_set_drvdata(pdev, info);
+
+	ret = max77686_rtc_init_reg(info);
+
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret);
+		goto err_rtc;
+	}
+
+#ifdef MAX77686_RTC_WTSR_SMPL
+	max77686_rtc_enable_wtsr(info, true);
+	max77686_rtc_enable_smpl(info, true);
+#endif
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	info->rtc_dev = rtc_device_register("max77686-rtc", &pdev->dev,
+			&max77686_rtc_ops, THIS_MODULE);
+
+	if (IS_ERR(info->rtc_dev)) {
+		printk(KERN_INFO "%s: fail\n", __func__);
+
+		ret = PTR_ERR(info->rtc_dev);
+		dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
+		if (ret == 0)
+			ret = -EINVAL;
+		goto err_rtc;
+	}
+	virq = irq_create_mapping(max77686->irq_domain, MAX77686_RTCIRQ_RTCA1);
+	if (!virq)
+		goto err_rtc;
+	info->virq = virq;
+
+	ret = request_threaded_irq(virq, NULL, max77686_rtc_alarm_irq, 0,
+			"rtc-alarm0", info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n",
+			info->virq, ret);
+		goto err_rtc;
+	}
+
+	goto out;
+err_rtc:
+	kfree(info);
+	return ret;
+out:
+	return ret;
+}
+
+static int __devexit max77686_rtc_remove(struct platform_device *pdev)
+{
+	struct max77686_rtc_info *info = platform_get_drvdata(pdev);
+
+	if (info) {
+		free_irq(info->virq, info);
+		rtc_device_unregister(info->rtc_dev);
+		kfree(info);
+	}
+
+	return 0;
+}
+
+static void max77686_rtc_shutdown(struct platform_device *pdev)
+{
+#ifdef MAX77686_RTC_WTSR_SMPL
+	struct max77686_rtc_info *info = platform_get_drvdata(pdev);
+	int i;
+	u8 val = 0;
+
+	for (i = 0; i < 3; i++) {
+		max77686_rtc_enable_wtsr(info, false);
+		regmap_read(info->max77686->rtc_regmap, MAX77686_WTSR_SMPL_CNTL, &val);
+		pr_info("%s: WTSR_SMPL reg(0x%02x)\n", __func__, val);
+		if (val & WTSR_EN_MASK)
+			pr_emerg("%s: fail to disable WTSR\n", __func__);
+		else {
+			pr_info("%s: success to disable WTSR\n", __func__);
+			break;
+		}
+	}
+
+	/* Disable SMPL when power off */
+	max77686_rtc_enable_smpl(info, false);
+#endif /* MAX77686_RTC_WTSR_SMPL */
+}
+
+static const struct platform_device_id rtc_id[] = {
+	{ "max77686-rtc", 0 },
+	{},
+};
+
+static struct platform_driver max77686_rtc_driver = {
+	.driver		= {
+		.name	= "max77686-rtc",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= max77686_rtc_probe,
+	.remove		= __devexit_p(max77686_rtc_remove),
+	.shutdown	= max77686_rtc_shutdown,
+	.id_table	= rtc_id,
+};
+
+static int __init max77686_rtc_init(void)
+{
+	return platform_driver_register(&max77686_rtc_driver);
+}
+module_init(max77686_rtc_init);
+
+static void __exit max77686_rtc_exit(void)
+{
+	platform_driver_unregister(&max77686_rtc_driver);
+}
+module_exit(max77686_rtc_exit);
+
+MODULE_DESCRIPTION("Maxim MAX77686 RTC driver");
+MODULE_AUTHOR("<woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
-- 
1.7.4.1


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

* Re: [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver
  2012-05-18  9:32 ` [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver Jonghwa Lee
@ 2012-05-20 15:50   ` Mark Brown
  0 siblings, 0 replies; 11+ messages in thread
From: Mark Brown @ 2012-05-20 15:50 UTC (permalink / raw)
  To: Jonghwa Lee
  Cc: linux-kernel, Alessandro Zummo, Samuel Oritz, Liam Girdwood,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Chiwoong Byun

[-- Attachment #1: Type: text/plain, Size: 518 bytes --]

On Fri, May 18, 2012 at 06:32:17PM +0900, Jonghwa Lee wrote:
> This patch is device driver for MAX77686 chip.
> MAX77686 is PMIC and includes regulator and rtc on it.
> This driver is core of MAX77686 chip, so provides common support for accessing
> on-chip devices. It uses irq_domain to manage irq and regmap to read/write data
> to its register with i2c bus.

Reviwed-by: Mark Brown <broonie@opensource.wolfsonmicro.com>

I think I prefer this to the other version of the core driver, if only
because it's smaller.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-18  9:32 ` [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver Jonghwa Lee
@ 2012-05-20 15:56   ` Mark Brown
  2012-05-21  7:33     ` jonghwa3.lee
  0 siblings, 1 reply; 11+ messages in thread
From: Mark Brown @ 2012-05-20 15:56 UTC (permalink / raw)
  To: Jonghwa Lee
  Cc: linux-kernel, Alessandro Zummo, Samuel Oritz, Liam Girdwood,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Chiwoong Byun

[-- Attachment #1: Type: text/plain, Size: 2009 bytes --]

On Fri, May 18, 2012 at 06:32:18PM +0900, Jonghwa Lee wrote:

> Add driver for support max77686 regulator.
> MAX77686 provides LDOs[1~26] and BUCKs[1~9]. It support to set or get the
> volatege of regulator on max77686 chip with using regmap.

Looks mostly good, a few things below but they're all pretty
minor/straghtforward.

> +/* LDO3 ~ 5, 9 ~ 14, 16 ~ 26 (uV) */
> +static const struct voltage_map_desc ldo_voltage_map_desc = {
> +	.min = 800000,	.max = 3950000,	.step = 50000,	.n_bits = 6,
> +};

Should convert all these to use regulator_map_voltage_linear() and
regulator_{get,set}_voltage_vsel().

> +	ret = regmap_read(max77686->iodev->regmap, reg, &val);
> +
> +	if (ret)
> +		return ret;

> +	printk(PMIC_DEBUG "id=%d, ret=%d, val=%x, mask=%x, pattern=%x\n",
> +		 rdev_get_id(rdev), (val & mask) == pattern,
> +		 val, mask, pattern);

dev_dbg() or just remove this (and similarly for the other similar logs).

> +	{
> +		.name	= "EN32KHz AP",
> +		.id	= MAX77686_EN32KHZ_AP,
> +		.ops	= &max77686_fixedvolt_ops,
> +		.type	= REGULATOR_VOLTAGE,
> +		.owner	= THIS_MODULE,
> +	}, {
> +		.name	= "EN32KHz CP",
> +		.id	= MAX77686_EN32KHZ_CP,
> +		.ops	= &max77686_fixedvolt_ops,
> +		.type	= REGULATOR_VOLTAGE,
> +		.owner	= THIS_MODULE,
> +	}, {
> +		.name	= "EN32KHz PMIC",
> +		.id	= MAX77686_P32KH,
> +		.ops	= &max77686_fixedvolt_ops,
> +		.type	= REGULATOR_VOLTAGE,
> +		.owner	= THIS_MODULE,
> +	},

These should be managed via the clock API now we have one.

> +	if (!pdata)
> +		dev_err(pdev->dev.parent, "No platform init data supplied.\n");

Should be able to carry on without this and register the regulators to
allow for diagnostics and powering off unused regulators when we have
full constraints.

> +	size = sizeof(struct regulator_dev *) * MAX77686_REGULATORS;
> +	max77686->rdev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
> +	if (!max77686->rdev) {
> +		devm_kfree(&pdev->dev, max77686);

Shouldn't need to devm_kfree(), the major point is that this will happen
automatically.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-20 15:56   ` Mark Brown
@ 2012-05-21  7:33     ` jonghwa3.lee
  2012-05-21  9:55       ` Mark Brown
  0 siblings, 1 reply; 11+ messages in thread
From: jonghwa3.lee @ 2012-05-21  7:33 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-kernel, Alessandro Zummo, Samuel Oritz, Liam Girdwood,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Chiwoong Byun

On 2012년 05월 21일 00:56, Mark Brown wrote:
Hi, Mark.

> On Fri, May 18, 2012 at 06:32:18PM +0900, Jonghwa Lee wrote:
> 
>> Add driver for support max77686 regulator.
>> MAX77686 provides LDOs[1~26] and BUCKs[1~9]. It support to set or get the
>> volatege of regulator on max77686 chip with using regmap.
> 
> Looks mostly good, a few things below but they're all pretty
> minor/straghtforward.
> 
>> +/* LDO3 ~ 5, 9 ~ 14, 16 ~ 26 (uV) */
>> +static const struct voltage_map_desc ldo_voltage_map_desc = {
>> +	.min = 800000,	.max = 3950000,	.step = 50000,	.n_bits = 6,
>> +};
> 
> Should convert all these to use regulator_map_voltage_linear() and
> regulator_{get,set}_voltage_vsel().
> 
>> +	ret = regmap_read(max77686->iodev->regmap, reg, &val);
>> +
>> +	if (ret)
>> +		return ret;
> 
>> +	printk(PMIC_DEBUG "id=%d, ret=%d, val=%x, mask=%x, pattern=%x\n",
>> +		 rdev_get_id(rdev), (val & mask) == pattern,
>> +		 val, mask, pattern);
> 
> dev_dbg() or just remove this (and similarly for the other similar logs).
> 
>> +	{
>> +		.name	= "EN32KHz AP",
>> +		.id	= MAX77686_EN32KHZ_AP,
>> +		.ops	= &max77686_fixedvolt_ops,
>> +		.type	= REGULATOR_VOLTAGE,
>> +		.owner	= THIS_MODULE,
>> +	}, {
>> +		.name	= "EN32KHz CP",
>> +		.id	= MAX77686_EN32KHZ_CP,
>> +		.ops	= &max77686_fixedvolt_ops,
>> +		.type	= REGULATOR_VOLTAGE,
>> +		.owner	= THIS_MODULE,
>> +	}, {
>> +		.name	= "EN32KHz PMIC",
>> +		.id	= MAX77686_P32KH,
>> +		.ops	= &max77686_fixedvolt_ops,
>> +		.type	= REGULATOR_VOLTAGE,
>> +		.owner	= THIS_MODULE,
>> +	},
> 
> These should be managed via the clock API now we have one.

>

I already updated all your comments except only this one. Could you
explain more details?


>> +	if (!pdata)
>> +		dev_err(pdev->dev.parent, "No platform init data supplied.\n");
> 
> Should be able to carry on without this and register the regulators to
> allow for diagnostics and powering off unused regulators when we have
> full constraints.
> 
>> +	size = sizeof(struct regulator_dev *) * MAX77686_REGULATORS;
>> +	max77686->rdev = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
>> +	if (!max77686->rdev) {
>> +		devm_kfree(&pdev->dev, max77686);
> 
> Shouldn't need to devm_kfree(), the major point is that this will happen
> automatically.


Thanks.

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

* Re: [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-21  7:33     ` jonghwa3.lee
@ 2012-05-21  9:55       ` Mark Brown
  2012-05-23  0:40         ` jonghwa3.lee
  0 siblings, 1 reply; 11+ messages in thread
From: Mark Brown @ 2012-05-21  9:55 UTC (permalink / raw)
  To: jonghwa3.lee
  Cc: linux-kernel, Alessandro Zummo, Samuel Oritz, Liam Girdwood,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Chiwoong Byun

[-- Attachment #1: Type: text/plain, Size: 743 bytes --]

On Mon, May 21, 2012 at 04:33:15PM +0900, jonghwa3.lee@samsung.com wrote:
> On 2012년 05월 21일 00:56, Mark Brown wrote:
> > On Fri, May 18, 2012 at 06:32:18PM +0900, Jonghwa Lee wrote:

> >> +	}, {
> >> +		.name	= "EN32KHz PMIC",
> >> +		.id	= MAX77686_P32KH,
> >> +		.ops	= &max77686_fixedvolt_ops,
> >> +		.type	= REGULATOR_VOLTAGE,
> >> +		.owner	= THIS_MODULE,
> >> +	},

> > These should be managed via the clock API now we have one.

> I already updated all your comments except only this one. Could you
> explain more details?

We now have a generic clock framework in drivers/clk so we should be
using that to provide drivers for clocks rather than putting them into
the regulator API which was always a bodge.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-21  9:55       ` Mark Brown
@ 2012-05-23  0:40         ` jonghwa3.lee
  2012-05-23 10:03           ` Mark Brown
  0 siblings, 1 reply; 11+ messages in thread
From: jonghwa3.lee @ 2012-05-23  0:40 UTC (permalink / raw)
  To: Mark Brown
  Cc: linux-kernel, Alessandro Zummo, Samuel Oritz, Liam Girdwood,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Chiwoong Byun

Hi, Mark,
On 2012년 05월 21일 18:55, Mark Brown wrote:

> On Mon, May 21, 2012 at 04:33:15PM +0900, jonghwa3.lee@samsung.com wrote:
>> On 2012년 05월 21일 00:56, Mark Brown wrote:
>>> On Fri, May 18, 2012 at 06:32:18PM +0900, Jonghwa Lee wrote:
> 
>>>> +	}, {
>>>> +		.name	= "EN32KHz PMIC",
>>>> +		.id	= MAX77686_P32KH,
>>>> +		.ops	= &max77686_fixedvolt_ops,
>>>> +		.type	= REGULATOR_VOLTAGE,
>>>> +		.owner	= THIS_MODULE,
>>>> +	},
> 
>>> These should be managed via the clock API now we have one.
> 
>> I already updated all your comments except only this one. Could you
>> explain more details?
> 
> We now have a generic clock framework in drivers/clk so we should be
> using that to provide drivers for clocks rather than putting them into
> the regulator API which was always a bodge.


I tried to change API to generic version but it got problem because of
overlapping. As you know almost of boards uses private clock API not
generic. So how do you think can i test it without modifying board file?
I'm afraid of changing all clock registering is too expensive.

Thanks.

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

* Re: [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-23  0:40         ` jonghwa3.lee
@ 2012-05-23 10:03           ` Mark Brown
  0 siblings, 0 replies; 11+ messages in thread
From: Mark Brown @ 2012-05-23 10:03 UTC (permalink / raw)
  To: jonghwa3.lee
  Cc: linux-kernel, Alessandro Zummo, Samuel Oritz, Liam Girdwood,
	Kyungmin Park, MyungJoo Ham, Chanwoo Choi, Chiwoong Byun

[-- Attachment #1: Type: text/plain, Size: 668 bytes --]

On Wed, May 23, 2012 at 09:40:44AM +0900, jonghwa3.lee@samsung.com wrote:

> I tried to change API to generic version but it got problem because of
> overlapping. As you know almost of boards uses private clock API not
> generic. So how do you think can i test it without modifying board file?
> I'm afraid of changing all clock registering is too expensive.

I've been using x86 to do build tests, though it's not so useful for
testing and you do need to enable the API since the x86 maintainers have
been ignoring my patches to do so.  The other option would be to just
drop the support for the clock enables until the clock API is available
on a paltform you have.

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver
  2012-05-11  6:36 [PATCH 0/3] mfd: MAX77686: Add initial support for MAXIMG 77686 mfd chip Jonghwa Lee
@ 2012-05-11  6:36 ` Jonghwa Lee
  0 siblings, 0 replies; 11+ messages in thread
From: Jonghwa Lee @ 2012-05-11  6:36 UTC (permalink / raw)
  To: linux-kernel
  Cc: Alessandro Zummo, Samuel Oritz, Liam Girdwood, Mark Brown,
	kyungmin.park, myungjoo.ham, cw00.choi, Jonghwa Lee,
	Chiwoong Byun

Add driver for support max77686 regulator.
MAX77686 provides LDOs[1~26] and BUCKs[1~9]. It support to set or get the
volatege of regulator on max77686 chip with using regmap.

Signed-off-by: Chiwoong Byun <woong.byun@samsung.com>
Signed-off-by: Jonghwa Lee <jonghwa3.lee@samsung.com>
Signed-off-by: Myungjoo Ham <myungjoo.ham@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
 drivers/regulator/Kconfig    |    8 +
 drivers/regulator/Makefile   |    1 +
 drivers/regulator/max77686.c |  809 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 818 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/max77686.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 36db5a4..8e2ebad 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -195,6 +195,14 @@ config REGULATOR_MAX8998
 	  via I2C bus. The provided regulator is suitable for S3C6410
 	  and S5PC1XX chips to control VCC_CORE and VCC_USIM voltages.
 
+config REGULATOR_MAX77686
+	tristate "Maxim 77686 regulator"
+	depends on MFD_MAX77686
+	help
+	  This driver controls a Maxim 77686 regulator
+	  via I2C bus. The provided regulator is suitable for
+	  Exynos-4 chips to control VARM and VINT voltages.
+
 config REGULATOR_PCAP
 	tristate "Motorola PCAP2 regulator driver"
 	depends on EZX_PCAP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 94b5274..008931b 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_REGULATOR_MAX8925) += max8925-regulator.o
 obj-$(CONFIG_REGULATOR_MAX8952) += max8952.o
 obj-$(CONFIG_REGULATOR_MAX8997) += max8997.o
 obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o
+obj-$(CONFIG_REGULATOR_MAX77686) += max77686.o
 obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
 obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
 obj-$(CONFIG_REGULATOR_MC13XXX_CORE) +=  mc13xxx-regulator-core.o
diff --git a/drivers/regulator/max77686.c b/drivers/regulator/max77686.c
new file mode 100644
index 0000000..9ab7a4c
--- /dev/null
+++ b/drivers/regulator/max77686.c
@@ -0,0 +1,809 @@
+/*
+ * max77686.c - Regulator driver for the Maxim 77686
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Chiwoong Byun <woong.byun@smasung.com>
+ * Jonghwa Lee <jonghwa3.lee@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ *
+ * This driver is based on max8997.c
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/max77686.h>
+#include <linux/mfd/max77686-private.h>
+
+#define PMIC_DEBUG KERN_INFO
+#define PMIC_REG_DEBUG KERN_DEBUG
+
+#define MAX77686_OPMODE_SHIFT 6
+#define MAX77686_OPMODE_BUCK234_SHIFT 4
+#define MAX77686_OPMODE_MASK 0x3
+
+struct max77686_data {
+	struct device *dev;
+	struct max77686_dev *iodev;
+	int num_regulators;
+	struct regulator_dev **rdev;
+	int ramp_delay; /* in mV/us */
+
+	struct max77686_opmode_data *opmode_data;
+
+	bool buck2_gpiodvs;
+	bool buck3_gpiodvs;
+	bool buck4_gpiodvs;
+	u8 buck2_vol[8];
+	u8 buck3_vol[8];
+	u8 buck4_vol[8];
+	int buck234_gpios_dvs[3];
+	int buck234_gpios_selb[3];
+	int buck234_gpioindex;
+	bool ignore_gpiodvs_side_effect;
+
+	u8 saved_states[MAX77686_REG_MAX];
+};
+
+static inline void max77686_set_gpio(struct max77686_data *max77686)
+{
+	int set3 = (max77686->buck234_gpioindex) & 0x1;
+	int set2 = ((max77686->buck234_gpioindex) >> 1) & 0x1;
+	int set1 = ((max77686->buck234_gpioindex) >> 2) & 0x1;
+
+	if (max77686->buck234_gpios_dvs[0])
+		gpio_set_value(max77686->buck234_gpios_dvs[0], set1);
+	if (max77686->buck234_gpios_dvs[1])
+		gpio_set_value(max77686->buck234_gpios_dvs[1], set2);
+	if (max77686->buck234_gpios_dvs[2])
+		gpio_set_value(max77686->buck234_gpios_dvs[2], set3);
+}
+
+struct voltage_map_desc {
+	int min;
+	int max;
+	int step;
+	unsigned int n_bits;
+};
+
+/* LDO3 ~ 5, 9 ~ 14, 16 ~ 26 (uV) */
+static const struct voltage_map_desc ldo_voltage_map_desc = {
+	.min = 800000,	.max = 3950000,	.step = 50000,	.n_bits = 6,
+};
+
+/* LDO1 ~ 2, 6 ~ 8, 15 (uV) */
+static const struct voltage_map_desc ldo_low_voltage_map_desc = {
+	.min = 800000,	.max = 2375000,	.step = 25000,	.n_bits = 6,
+};
+
+/* Buck2, 3, 4 (uV) */
+static const struct voltage_map_desc buck_dvs_voltage_map_desc = {
+	.min = 600000,	.max = 3787500,	.step = 12500,	.n_bits = 8,
+};
+
+/* Buck1, 5 ~ 9 (uV) */
+static const struct voltage_map_desc buck_voltage_map_desc = {
+	.min = 750000,	.max = 3900000,	.step = 50000,	.n_bits = 6,
+};
+
+static const struct voltage_map_desc *reg_voltage_map[] = {
+	[MAX77686_LDO1] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO2] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO3] = &ldo_voltage_map_desc,
+	[MAX77686_LDO4] = &ldo_voltage_map_desc,
+	[MAX77686_LDO5] = &ldo_voltage_map_desc,
+	[MAX77686_LDO6] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO7] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO8] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO9] = &ldo_voltage_map_desc,
+	[MAX77686_LDO10] = &ldo_voltage_map_desc,
+	[MAX77686_LDO11] = &ldo_voltage_map_desc,
+	[MAX77686_LDO12] = &ldo_voltage_map_desc,
+	[MAX77686_LDO13] = &ldo_voltage_map_desc,
+	[MAX77686_LDO14] = &ldo_voltage_map_desc,
+	[MAX77686_LDO15] = &ldo_low_voltage_map_desc,
+	[MAX77686_LDO16] = &ldo_voltage_map_desc,
+	[MAX77686_LDO17] = &ldo_voltage_map_desc,
+	[MAX77686_LDO18] = &ldo_voltage_map_desc,
+	[MAX77686_LDO19] = &ldo_voltage_map_desc,
+	[MAX77686_LDO20] = &ldo_voltage_map_desc,
+	[MAX77686_LDO21] = &ldo_voltage_map_desc,
+	[MAX77686_LDO22] = &ldo_voltage_map_desc,
+	[MAX77686_LDO23] = &ldo_voltage_map_desc,
+	[MAX77686_LDO24] = &ldo_voltage_map_desc,
+	[MAX77686_LDO25] = &ldo_voltage_map_desc,
+	[MAX77686_LDO26] = &ldo_voltage_map_desc,
+	[MAX77686_BUCK1] = &buck_voltage_map_desc,
+	[MAX77686_BUCK2] = &buck_dvs_voltage_map_desc,
+	[MAX77686_BUCK3] = &buck_dvs_voltage_map_desc,
+	[MAX77686_BUCK4] = &buck_dvs_voltage_map_desc,
+	[MAX77686_BUCK5] = &buck_voltage_map_desc,
+	[MAX77686_BUCK6] = &buck_voltage_map_desc,
+	[MAX77686_BUCK7] = &buck_voltage_map_desc,
+	[MAX77686_BUCK8] = &buck_voltage_map_desc,
+	[MAX77686_BUCK9] = &buck_voltage_map_desc,
+	[MAX77686_EN32KHZ_AP] = NULL,
+	[MAX77686_EN32KHZ_CP] = NULL,
+	[MAX77686_P32KH] = NULL,
+};
+
+static int max77686_list_voltage(struct regulator_dev *rdev,
+		unsigned int selector)
+{
+	const struct voltage_map_desc *desc;
+	int rid = rdev_get_id(rdev);
+	int val;
+
+	if (rid >= ARRAY_SIZE(reg_voltage_map) ||
+			rid < 0)
+		return -EINVAL;
+
+	desc = reg_voltage_map[rid];
+	if (desc == NULL)
+		return -EINVAL;
+
+	val = desc->min + desc->step * selector;
+	if (val > desc->max)
+		return -EINVAL;
+
+	return val;
+}
+
+/*
+ * TODO
+ * Reaction to the LP/Standby for each regulator should be defined by
+ * each consumer, not by the regulator device driver if it depends
+ * on which device is attached to which regulator. Here we are
+ * creating possible PM-wise regression with board changes.Also,
+ * we can do the same effect without creating issues with board
+ * changes by carefully defining .state_mem at bsp and suspend ops
+ * callbacks.
+ */
+unsigned int max77686_opmode_reg[][3] = {
+	/* LDO1 ... LDO26 */
+	/* {NORMAL, LP, STANDBY} */
+	{0x3, 0x2, 0x2}, /* LDO1 */
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1}, /* LDO11 */
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2}, /* LDO21 */
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	{0x3, 0x2, 0x2},
+	/* BUCK1 ... BUCK9 */
+	{0x3, 0x2, 0x1}, /* BUCK1 */
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x2, 0x1},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	{0x3, 0x3, 0x3},
+	/* 32KHZ */
+	{0x1, 0x1, 0x1},
+	{0x1, 0x1, 0x1},
+	{0x1, 0x1, 0x1},
+};
+
+static int max77686_get_enable_register(struct regulator_dev *rdev,
+		int *reg, int *mask, int *pattern)
+{
+	unsigned int rid = rdev_get_id(rdev);
+	unsigned int mode;
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+
+	if (rid > ARRAY_SIZE(max77686_opmode_reg))
+		return -EINVAL;
+
+	mode = max77686->opmode_data[rid].mode;
+	pr_debug("rid=%d, mode=%d, size=%d\n",
+		 rid, mode, ARRAY_SIZE(max77686_opmode_reg));
+
+	switch (rid) {
+	case MAX77686_LDO1 ... MAX77686_LDO26:
+		*reg = MAX77686_REG_LDO1CTRL1 + (rid - MAX77686_LDO1);
+		*mask = MAX77686_OPMODE_MASK << MAX77686_OPMODE_SHIFT;
+		*pattern = max77686_opmode_reg[rid][mode] << MAX77686_OPMODE_SHIFT;
+		break;
+	case MAX77686_BUCK1:
+		*reg = MAX77686_REG_BUCK1CTRL;
+		*mask = MAX77686_OPMODE_MASK;
+		*pattern = max77686_opmode_reg[rid][mode];
+		break;
+	case MAX77686_BUCK2:
+	case MAX77686_BUCK3:
+	case MAX77686_BUCK4:
+		*reg = MAX77686_REG_BUCK2CTRL1 + (rid - MAX77686_BUCK2)*10;
+		*mask = MAX77686_OPMODE_MASK << MAX77686_OPMODE_BUCK234_SHIFT;
+		*pattern = max77686_opmode_reg[rid][mode] << MAX77686_OPMODE_BUCK234_SHIFT;
+		break;
+	case MAX77686_BUCK5 ... MAX77686_BUCK9:
+		*reg = MAX77686_REG_BUCK5CTRL + (rid - MAX77686_BUCK5) * 2;
+		*mask = MAX77686_OPMODE_MASK;
+		*pattern = max77686_opmode_reg[rid][mode];
+		break;
+	case MAX77686_EN32KHZ_AP ... MAX77686_P32KH:
+		*reg = MAX77686_REG_32KHZ;
+		*mask = 0x01 << (rid - MAX77686_EN32KHZ_AP);
+		*pattern = 0x01 << (rid - MAX77686_EN32KHZ_AP);
+		break;
+	default:
+		/* Not controllable or not exists */
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int max77686_reg_is_enabled(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int ret, reg, mask, pattern;
+	u8 val;
+
+	ret = max77686_get_enable_register(rdev, &reg, &mask, &pattern);
+	if (ret == -EINVAL)
+		return 1; /* "not controllable" */
+	else if (ret)
+		return ret;
+
+
+	ret = max77686_read_reg(max77686->iodev->regmap, reg, &val);
+	
+	if (ret)
+		return ret;
+
+	pr_debug("id=%d, ret=%d, val=%x, mask=%x, pattern=%x\n",
+		 rdev_get_id(rdev), (val & mask) == pattern,
+		 val, mask, pattern);
+
+	return (val & mask) == pattern;
+}
+
+static int max77686_reg_enable(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int ret, reg, mask, pattern;
+
+	ret = max77686_get_enable_register(rdev, &reg, &mask, &pattern);
+	if (ret)
+		return ret;
+
+	printk(PMIC_DEBUG "%s: id=%d, pattern=%x\n",
+		__func__, rdev_get_id(rdev), pattern);
+
+	return max77686_update_reg(max77686->iodev->regmap, reg, pattern, mask);
+}
+
+static int max77686_reg_disable(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int ret, reg, mask, pattern;
+
+	ret = max77686_get_enable_register(rdev, &reg, &mask, &pattern);
+	if (ret)
+		return ret;
+
+	printk(PMIC_DEBUG "%s: id=%d, pattern=%x\n",
+		__func__, rdev_get_id(rdev), pattern);
+
+	return max77686_update_reg(max77686->iodev->regmap, reg, ~mask, mask);
+}
+
+static int max77686_get_voltage_register(struct regulator_dev *rdev,
+		int *_reg, int *_shift, int *_mask)
+{
+	int rid = rdev_get_id(rdev);
+	int reg, shift = 0, mask = 0x3f;
+
+	switch (rid) {
+	case MAX77686_LDO1 ... MAX77686_LDO26:
+		reg = MAX77686_REG_LDO1CTRL1 + (rid - MAX77686_LDO1);
+		break;
+	case MAX77686_BUCK1:
+		reg = MAX77686_REG_BUCK1OUT;
+		break;
+	case MAX77686_BUCK2:
+		reg = MAX77686_REG_BUCK2DVS1;
+		mask = 0xff;
+		break;
+	case MAX77686_BUCK3:
+		reg = MAX77686_REG_BUCK3DVS1;
+		mask = 0xff;
+		break;
+	case MAX77686_BUCK4:
+		reg = MAX77686_REG_BUCK4DVS1;
+		mask = 0xff;
+		break;
+	case MAX77686_BUCK5 ... MAX77686_BUCK9:
+		reg = MAX77686_REG_BUCK5OUT + (rid - MAX77686_BUCK5) * 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*_reg = reg;
+	*_shift = shift;
+	*_mask = mask;
+
+	return 0;
+}
+
+static int max77686_get_voltage(struct regulator_dev *rdev)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	int reg, shift, mask, ret;
+	int rid = rdev_get_id(rdev);
+	u8 val;
+
+	ret = max77686_get_voltage_register(rdev, &reg, &shift, &mask);
+	if (ret)
+		return ret;
+
+	if ((rid == MAX77686_BUCK2 && max77686->buck2_gpiodvs) ||
+	    (rid == MAX77686_BUCK3 && max77686->buck3_gpiodvs) ||
+	    (rid == MAX77686_BUCK4 && max77686->buck4_gpiodvs))
+		reg += max77686->buck234_gpioindex;
+
+	ret = max77686_read_reg(max77686->iodev->regmap, reg, &val);
+	if (ret)
+		return ret;
+
+	val >>= shift;
+	val &= mask;
+
+	printk(PMIC_REG_DEBUG "%s: id=%d, reg=%x, mask=%x, val=%x\n",
+		__func__, rid, reg, mask, val);
+
+	return max77686_list_voltage(rdev, val);
+}
+
+static inline int max77686_get_voltage_proper_val(
+		const struct voltage_map_desc *desc,
+		int min_vol, int max_vol)
+{
+	int i = 0;
+
+	if (desc == NULL)
+		return -EINVAL;
+
+	if (max_vol < desc->min || min_vol > desc->max)
+		return -EINVAL;
+
+	while (desc->min + desc->step * i < min_vol &&
+			desc->min + desc->step * i < desc->max)
+		i++;
+
+	if (desc->min + desc->step * i > max_vol)
+		return -EINVAL;
+
+	if (i >= (1 << desc->n_bits))
+		return -EINVAL;
+
+	return i;
+}
+
+static int max77686_set_voltage(struct regulator_dev *rdev,
+		int min_uV, int max_uV, unsigned *selector)
+{
+	struct max77686_data *max77686 = rdev_get_drvdata(rdev);
+	const struct voltage_map_desc *desc;
+	int rid = rdev_get_id(rdev);
+	int reg, shift = 0, mask, ret;
+	int i;
+	u8 org;
+
+	desc = reg_voltage_map[rid];
+
+	i = max77686_get_voltage_proper_val(desc, min_uV, max_uV);
+	if (i < 0)
+		return i;
+
+	ret = max77686_get_voltage_register(rdev, &reg, &shift, &mask);
+	if (ret)
+		return ret;
+
+	/* TODO: If GPIO-DVS is being used, this won't work. */
+
+	max77686_read_reg(max77686->iodev->regmap, reg, &org);
+	org = (org & mask) >> shift;
+
+	ret = max77686_update_reg(max77686->iodev->regmap, reg, i << shift, mask << shift);
+	*selector = i;
+
+	switch (rid) {
+	case MAX77686_BUCK2 ... MAX77686_BUCK4:
+		if (org < i)
+			udelay(DIV_ROUND_UP(desc->step * (i - org),
+						max77686->ramp_delay * 1000));
+		break;
+	case MAX77686_BUCK1:
+	case MAX77686_BUCK5 ... MAX77686_BUCK9:
+		/* Unconditionally 100 mV/us */
+		if (org < i)
+			udelay(DIV_ROUND_UP(desc->step * (i - org), 100000));
+		break;
+	default:
+		break;
+	}
+
+	printk(PMIC_REG_DEBUG "%s: id=%d, reg=%x, mask=%x, org=%x, val=%x\n",
+		__func__, rdev_get_id(rdev), reg, mask, org, i);
+
+	return ret;
+}
+
+static int max77686_reg_do_nothing(struct regulator_dev *rdev)
+{
+	return 0;
+}
+
+static struct regulator_ops max77686_ldo_ops = {
+	.list_voltage		= max77686_list_voltage,
+	.is_enabled		= max77686_reg_is_enabled,
+	.enable			= max77686_reg_enable,
+	.disable		= max77686_reg_disable,
+	.get_voltage		= max77686_get_voltage,
+	.set_voltage		= max77686_set_voltage,
+	/* TODO: set 0xC0 -> 0x40 with suspend_enable. for 0x0, ->0x0 */
+	.set_suspend_enable	= max77686_reg_do_nothing,
+	/* LDO's ON(0xC0) means "ON at normal, OFF at suspend" */
+	.set_suspend_disable	= max77686_reg_do_nothing,
+};
+
+static struct regulator_ops max77686_buck_ops = {
+	.list_voltage		= max77686_list_voltage,
+	.is_enabled		= max77686_reg_is_enabled,
+	.enable			= max77686_reg_enable,
+	.disable		= max77686_reg_disable,
+	.get_voltage		= max77686_get_voltage,
+	.set_voltage		= max77686_set_voltage,
+	/* Interpret suspend_enable as "keep on if it was enabled." */
+	.set_suspend_enable	= max77686_reg_do_nothing,
+	.set_suspend_disable	= max77686_reg_disable,
+};
+
+static struct regulator_ops max77686_fixedvolt_ops = {
+	.list_voltage		= max77686_list_voltage,
+	.is_enabled		= max77686_reg_is_enabled,
+	.enable			= max77686_reg_enable,
+	.disable		= max77686_reg_disable,
+	/* Interpret suspend_enable as "keep on if it was enabled." */
+	.set_suspend_enable	= max77686_reg_do_nothing,
+	.set_suspend_disable	= max77686_reg_disable,
+};
+
+#define regulator_desc_ldo(num)		{	\
+	.name		= "LDO"#num,		\
+	.id		= MAX77686_LDO##num,	\
+	.ops		= &max77686_ldo_ops,	\
+	.type		= REGULATOR_VOLTAGE,	\
+	.owner		= THIS_MODULE,		\
+}
+#define regulator_desc_buck(num)		{	\
+	.name		= "BUCK"#num,		\
+	.id		= MAX77686_BUCK##num,	\
+	.ops		= &max77686_buck_ops,	\
+	.type		= REGULATOR_VOLTAGE,	\
+	.owner		= THIS_MODULE,		\
+}
+
+static struct regulator_desc regulators[] = {
+	regulator_desc_ldo(1),
+	regulator_desc_ldo(2),
+	regulator_desc_ldo(3),
+	regulator_desc_ldo(4),
+	regulator_desc_ldo(5),
+	regulator_desc_ldo(6),
+	regulator_desc_ldo(7),
+	regulator_desc_ldo(8),
+	regulator_desc_ldo(9),
+	regulator_desc_ldo(10),
+	regulator_desc_ldo(11),
+	regulator_desc_ldo(12),
+	regulator_desc_ldo(13),
+	regulator_desc_ldo(14),
+	regulator_desc_ldo(15),
+	regulator_desc_ldo(16),
+	regulator_desc_ldo(17),
+	regulator_desc_ldo(18),
+	regulator_desc_ldo(19),
+	regulator_desc_ldo(20),
+	regulator_desc_ldo(21),
+	regulator_desc_ldo(22),
+	regulator_desc_ldo(23),
+	regulator_desc_ldo(24),
+	regulator_desc_ldo(25),
+	regulator_desc_ldo(26),
+	regulator_desc_buck(1),
+	regulator_desc_buck(2),
+	regulator_desc_buck(3),
+	regulator_desc_buck(4),
+	regulator_desc_buck(5),
+	regulator_desc_buck(6),
+	regulator_desc_buck(7),
+	regulator_desc_buck(8),
+	regulator_desc_buck(9),
+	{
+		.name	= "EN32KHz AP",
+		.id	= MAX77686_EN32KHZ_AP,
+		.ops	= &max77686_fixedvolt_ops,
+		.type	= REGULATOR_VOLTAGE,
+		.owner	= THIS_MODULE,
+	}, {
+		.name	= "EN32KHz CP",
+		.id	= MAX77686_EN32KHZ_CP,
+		.ops	= &max77686_fixedvolt_ops,
+		.type	= REGULATOR_VOLTAGE,
+		.owner	= THIS_MODULE,
+	}, {
+		.name	= "EN32KHz PMIC",
+		.id	= MAX77686_P32KH,
+		.ops	= &max77686_fixedvolt_ops,
+		.type	= REGULATOR_VOLTAGE,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static __devinit int max77686_pmic_probe(struct platform_device *pdev)
+{
+	struct max77686_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+	struct max77686_platform_data *pdata = dev_get_platdata(iodev->dev);
+	struct regulator_dev **rdev;
+	struct max77686_data *max77686;
+	struct i2c_client *i2c;
+	int i, ret, size;
+	u8 data = 0;
+
+	printk(PMIC_DEBUG "%s\n", __func__);
+
+	if (!pdata) {
+		dev_err(pdev->dev.parent, "No platform init data supplied.\n");
+		return -ENODEV;
+	}
+
+	max77686 = kzalloc(sizeof(struct max77686_data), GFP_KERNEL);
+	if (!max77686)
+		return -ENOMEM;
+
+	size = sizeof(struct regulator_dev *) * pdata->num_regulators;
+	max77686->rdev = kzalloc(size, GFP_KERNEL);
+	if (!max77686->rdev) {
+		kfree(max77686);
+		return -ENOMEM;
+	}
+
+	rdev = max77686->rdev;
+	max77686->dev = &pdev->dev;
+	max77686->iodev = iodev;
+	max77686->num_regulators = pdata->num_regulators;
+	platform_set_drvdata(pdev, max77686);
+	i2c = max77686->iodev->i2c;
+
+	max77686->opmode_data = pdata->opmode_data;
+
+	max77686->ramp_delay = 100; /* Set 0x3 for RAMP */
+	max77686_update_reg(max77686->iodev->regmap, MAX77686_REG_BUCK2CTRL1, 0xC0, 0xC0);
+	max77686_update_reg(max77686->iodev->regmap, MAX77686_REG_BUCK3CTRL1, 0xC0, 0xC0);
+	max77686_update_reg(max77686->iodev->regmap, MAX77686_REG_BUCK4CTRL1, 0xC0, 0xC0);
+
+	max77686_read_reg(max77686->iodev->regmap, MAX77686_REG_DEVICE_ID, &data);
+	printk(PMIC_DEBUG "%s: DEVICE ID=0x%x\n", __func__, data);
+
+	/*
+	 * TODO
+	 * This disables GPIO-DVS. Later we may need to implement GPIO-DVS..
+	 * or we do not?
+	 */
+	max77686->buck2_gpiodvs = false;
+	max77686->buck3_gpiodvs = false;
+	max77686->buck4_gpiodvs = false;
+	for (i = 0; i < 3; i++) {
+		char buf[80];
+
+		sprintf(buf, "MAX77686 DVS%d", i);
+
+		if (gpio_is_valid(pdata->buck234_gpio_dvs[i])) {
+			max77686->buck234_gpios_dvs[i] =
+				pdata->buck234_gpio_dvs[i];
+			gpio_request(pdata->buck234_gpio_dvs[i], buf);
+			gpio_direction_output(pdata->buck234_gpio_dvs[i], 0);
+		} else {
+			dev_info(&pdev->dev, "GPIO %s ignored (%d)\n",
+				 buf, pdata->buck234_gpio_dvs[i]);
+		}
+
+		sprintf(buf, "MAX77686 SELB%d", i);
+
+		if (gpio_is_valid(pdata->buck234_gpio_selb[i])) {
+			max77686->buck234_gpios_selb[i] =
+				pdata->buck234_gpio_selb[i];
+			gpio_request(pdata->buck234_gpio_selb[i], buf);
+			gpio_direction_output(pdata->buck234_gpio_selb[i], 0);
+		} else {
+			dev_info(&pdev->dev, "GPIO %s ignored (%d)\n",
+				 buf, pdata->buck234_gpio_dvs[i]);
+		}
+	}
+	max77686->buck234_gpioindex = 0;
+
+	for (i = 0; i < 8; i++) {
+		ret = max77686_get_voltage_proper_val(
+				&buck_dvs_voltage_map_desc,
+				pdata->buck2_voltage[i],
+				pdata->buck2_voltage[i]
+					+ buck_dvs_voltage_map_desc.step);
+		/* 1.1V as default for safety */
+		if (ret < 0)
+			max77686->buck2_vol[i] = 0x28;
+		else
+			max77686->buck2_vol[i] = ret;
+		max77686_write_reg(max77686->iodev->regmap, MAX77686_REG_BUCK2DVS1 + i,
+				   max77686->buck2_vol[i]);
+
+		ret = max77686_get_voltage_proper_val(
+				&buck_dvs_voltage_map_desc,
+				pdata->buck3_voltage[i],
+				pdata->buck3_voltage[i]
+					+ buck_dvs_voltage_map_desc.step);
+		/* 1.1V as default for safety */
+		if (ret < 0)
+			max77686->buck3_vol[i] = 0x28;
+		else
+			max77686->buck3_vol[i] = ret;
+		max77686_write_reg(max77686->iodev->regmap, MAX77686_REG_BUCK3DVS1 + i,
+				   max77686->buck3_vol[i]);
+		ret = max77686_get_voltage_proper_val(
+				&buck_dvs_voltage_map_desc,
+				pdata->buck4_voltage[i],
+				pdata->buck4_voltage[i]
+					+ buck_dvs_voltage_map_desc.step);
+		/* 1.1V as default for safety */
+		if (ret < 0)
+			max77686->buck4_vol[i] = 0x28;
+		else
+			max77686->buck4_vol[i] = ret;
+		max77686_write_reg(max77686->iodev->regmap, MAX77686_REG_BUCK4DVS1 + i,
+				   max77686->buck4_vol[i]);
+	}
+
+
+	for (i = 0; i < pdata->num_regulators; i++) {
+		const struct voltage_map_desc *desc;
+		int id = pdata->regulators[i].id;
+
+		desc = reg_voltage_map[id];
+		if (desc) {
+			regulators[id].n_voltages =
+				(desc->max - desc->min) / desc->step + 1;
+
+			printk(PMIC_DEBUG "%s: desc=%p, id=%d, n_vol=%d, max=%d, min=%d, step=%d\n",
+					__func__, desc, id, regulators[id].n_voltages,
+					desc->max, desc->min, desc->step);
+		}
+
+		rdev[i] = regulator_register(&regulators[id], max77686->dev,
+					     pdata->regulators[i].initdata,
+					     max77686, NULL);
+		if (IS_ERR(rdev[i])) {
+			ret = PTR_ERR(rdev[i]);
+			dev_err(max77686->dev, "regulator init failed for %d\n",
+					id);
+			rdev[i] = NULL;
+			goto err;
+		}
+	}
+
+	return 0;
+err:
+	for (i = 0; i < 3; i++) {
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_dvs[i]);
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_selb[i]);
+	}
+
+	for (i = 0; i < max77686->num_regulators; i++)
+		if (rdev[i])
+			regulator_unregister(rdev[i]);
+
+	kfree(max77686->rdev);
+	kfree(max77686);
+
+	return ret;
+}
+
+static int __devexit max77686_pmic_remove(struct platform_device *pdev)
+{
+	struct max77686_data *max77686 = platform_get_drvdata(pdev);
+	struct regulator_dev **rdev = max77686->rdev;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_dvs[i]);
+		if (max77686->buck234_gpios_dvs[i])
+			gpio_free(max77686->buck234_gpios_selb[i]);
+	}
+
+	for (i = 0; i < max77686->num_regulators; i++)
+		if (rdev[i])
+			regulator_unregister(rdev[i]);
+
+	kfree(max77686->rdev);
+	kfree(max77686);
+
+	return 0;
+}
+
+static const struct platform_device_id max77686_pmic_id[] = {
+	{ "max77686-pmic", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, max77686_pmic_id);
+
+static struct platform_driver max77686_pmic_driver = {
+	.driver = {
+		.name = "max77686-pmic",
+		.owner = THIS_MODULE,
+	},
+	.probe = max77686_pmic_probe,
+	.remove = __devexit_p(max77686_pmic_remove),
+	.id_table = max77686_pmic_id,
+};
+
+static int __init max77686_pmic_init(void)
+{
+	printk(PMIC_DEBUG "%s\n", __func__);
+
+	return platform_driver_register(&max77686_pmic_driver);
+}
+subsys_initcall(max77686_pmic_init);
+
+static void __exit max77686_pmic_cleanup(void)
+{
+	platform_driver_unregister(&max77686_pmic_driver);
+}
+module_exit(max77686_pmic_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 77686 Regulator Driver");
+MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>");
+MODULE_LICENSE("GPL");
-- 
1.7.4.1


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

end of thread, other threads:[~2012-05-23 10:03 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-05-18  9:32 [PATCH v3 0/3] Add initial support for MAXIM 77686 mfd chip Jonghwa Lee
2012-05-18  9:32 ` [PATCH 1/3] mfd: MAX77686: Add Maxim 77686 mfd driver Jonghwa Lee
2012-05-20 15:50   ` Mark Brown
2012-05-18  9:32 ` [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver Jonghwa Lee
2012-05-20 15:56   ` Mark Brown
2012-05-21  7:33     ` jonghwa3.lee
2012-05-21  9:55       ` Mark Brown
2012-05-23  0:40         ` jonghwa3.lee
2012-05-23 10:03           ` Mark Brown
2012-05-18  9:32 ` [PATCH 3/3] rtc: MAX77686: Add Maxim 77686 rtc driver Jonghwa Lee
  -- strict thread matches above, loose matches on Subject: below --
2012-05-11  6:36 [PATCH 0/3] mfd: MAX77686: Add initial support for MAXIMG 77686 mfd chip Jonghwa Lee
2012-05-11  6:36 ` [PATCH 2/3] regulator: MAX77686: Add Maxim 77686 regulator driver Jonghwa Lee

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).