diff mbox

[3/7] regulator: MT6397: Add support for MT6397 regulator

Message ID 1416210027-5562-4-git-send-email-flora.fu@mediatek.com (mailing list archive)
State New, archived
Headers show

Commit Message

Flora Fu Nov. 17, 2014, 7:40 a.m. UTC
This patch is MT6397 regulator driver.

signed-off-by: Flora Fu <flora.fu@mediatek.com>
---
 drivers/regulator/Kconfig                  |   6 +
 drivers/regulator/Makefile                 |   1 +
 drivers/regulator/mt6397-regulator.c       | 547 +++++++++++++++++++++++++++++
 include/linux/regulator/mt6397-regulator.h |  50 +++
 4 files changed, 604 insertions(+)
 create mode 100644 drivers/regulator/mt6397-regulator.c
 create mode 100644 include/linux/regulator/mt6397-regulator.h

Comments

Mark Brown Nov. 17, 2014, 11:40 p.m. UTC | #1
On Mon, Nov 17, 2014 at 03:40:23PM +0800, Flora Fu wrote:

This looks mostly good but there are a few fairly straightfoward things:

> @@ -725,5 +725,11 @@ config REGULATOR_WM8994
>  	  This driver provides support for the voltage regulators on the
>  	  WM8994 CODEC.
>  
> +config REGULATOR_MT6397
> +	tristate "MediaTek MT6397 PMIC"
> +	depends on MFD_MT6397
> +	help
> +	   This driver provides support for the voltage regulators on the MediaTek MT6397 PMIC.
> +
>  endif

Keep this and the Makefile sorted.

> +static int mt6397_buck_set_voltage_sel(struct regulator_dev *rdev, unsigned sel)
> +{


> +	vosel = info->buck_conf.vosel_reg;
> +	voselon = info->buck_conf.voselon_reg;
> +	vosel_mask = info->buck_conf.vosel_mask;

Please use the standard way of specifying data even if you can't use the
standard function.

> +
> +	ret = regmap_update_bits(rdev->regmap, vosel, vosel_mask, sel);
> +	if (ret != 0) {
> +		dev_err(&rdev->dev, "Failed to update vosel: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_update_bits(rdev->regmap, voselon, vosel_mask, sel);
> +	if (ret != 0) {
> +		dev_err(&rdev->dev, "Failed to update vosel_on: %d\n", ret);
> +		return ret;
> +	}
> +	return 0;

You should add comments here explaining what's going on - it's very
strange to have to write the same value to two different registers and
the names of the registers look suspicously like this is something to do
with a suspend mode...

Missing blank line before the return too.

> +static int mt6397_buck_get_voltage_sel(struct regulator_dev *rdev)
> +{

You could use the regmap based helper for this.

> +static int mt6397_ldo_set_voltage_sel(struct regulator_dev *rdev, unsigned sel)
> +{

The LDO operations appear to be identical to the standard regmap
helpers, please use them.

> +	if (is_fixed)
> +		return 0;

You should use the standard fixed voltage regulator support rather than 

> +static int mt6397_regulator_is_enabled(struct regulator_dev *rdev)
> +{

Again this looks like it should be using helpers.

> +#define MT6397_REGULATOR_OF_MATCH(_name, _id)			\
> +[MT6397_ID_##_id] = {						\
> +	.name = #_name,						\
> +	.driver_data = &mt6397_regulators[MT6397_ID_##_id],	\
> +}

Define regulators_node and of_match in the regulator desc and you can
remove both this table and all your DT matching code in the driver, the
core will handle it for you.

> +	if ((reg_value & 0xFF) == MT6397_REGULATOR_ID91) {
> +		j = MT6397_ID_VCAMIO;
> +		mt6397_regulator_matches[j].init_data->constraints.min_uV =
> +		1000000;
> +		mt6397_regulators[j].desc.volt_table = ldo_volt_table5_v2;
> +	}

Use a switch statement, that way other variants can be added more
easily.
Flora Fu Nov. 21, 2014, 7:09 a.m. UTC | #2
Hi, Mark, 

On Mon, 2014-11-17 at 23:40 +0000, Mark Brown wrote:
> On Mon, Nov 17, 2014 at 03:40:23PM +0800, Flora Fu wrote:

> > +static int mt6397_buck_set_voltage_sel(struct regulator_dev *rdev, unsigned sel)
> > +{
> 
> 
> > +	vosel = info->buck_conf.vosel_reg;
> > +	voselon = info->buck_conf.voselon_reg;
> > +	vosel_mask = info->buck_conf.vosel_mask;
> 
> Please use the standard way of specifying data even if you can't use the
> standard function.
> 

Could you specify the standard way of specification data? Thanks. 

> > +
> > +	ret = regmap_update_bits(rdev->regmap, vosel, vosel_mask, sel);
> > +	if (ret != 0) {
> > +		dev_err(&rdev->dev, "Failed to update vosel: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = regmap_update_bits(rdev->regmap, voselon, vosel_mask, sel);
> > +	if (ret != 0) {
> > +		dev_err(&rdev->dev, "Failed to update vosel_on: %d\n", ret);
> > +		return ret;
> > +	}
> > +	return 0;
> 
> You should add comments here explaining what's going on - it's very
> strange to have to write the same value to two different registers and
> the names of the registers look suspiciously like this is something to do
> with a suspend mode...
> 

Yes, its is for suspend mode control usage.
For registers "vosel", "voselon", they is called register mode or
hardware control mode voltage settings. Register mode is a default mode
on the buck control. For quickly normal/sleep mode switch, hardware
control can be enabled by controlling buck output by a CTRL_PIN. In the
following diagram, there is a static settings on vosel_sleep for suspend
mode output. According to CTRL_PIN's level, Vout can have different
output (voselon or vosel_sleep). 

           +------------------------------------------------------+
           |    MT6397                                            |
           |                              -vosel     -            |
CTRL_PIN   |     +-----------------+                  \ + nivosel-|-Vout
-----------|-----| hardware control|---+  -voselon   -            |
           |     +-----------------+    \ -vosel_sleep -          |
           |                                                      |
           +------------------------------------------------------+

In the design, voltage change on bucks are set both on registers vosel
and voselon. I will add more comments on the function to clarify the
implementation. 

Thanks, 
Flora
Mark Brown Nov. 21, 2014, 10:16 a.m. UTC | #3
On Fri, Nov 21, 2014 at 03:09:31PM +0800, Flora Fu wrote:
> On Mon, 2014-11-17 at 23:40 +0000, Mark Brown wrote:

> > > +	vosel = info->buck_conf.vosel_reg;
> > > +	voselon = info->buck_conf.voselon_reg;
> > > +	vosel_mask = info->buck_conf.vosel_mask;

> > Please use the standard way of specifying data even if you can't use the
> > standard function.

> Could you specify the standard way of specification data? Thanks. 

Using the fields in the regulator_desc as you can see from the standard
helpers.

> > You should add comments here explaining what's going on - it's very
> > strange to have to write the same value to two different registers and
> > the names of the registers look suspiciously like this is something to do
> > with a suspend mode...

> Yes, its is for suspend mode control usage.
> For registers "vosel", "voselon", they is called register mode or
> hardware control mode voltage settings. Register mode is a default mode
> on the buck control. For quickly normal/sleep mode switch, hardware
> control can be enabled by controlling buck output by a CTRL_PIN. In the
> following diagram, there is a static settings on vosel_sleep for suspend
> mode output. According to CTRL_PIN's level, Vout can have different
> output (voselon or vosel_sleep). 

You need to represent this in your driver, the sleep mode controls
should either be controlled using the suspend API or the GPIO control
needs to be visible in the driver.  It's also OK to ignore the GPIO
control for now and do it later if complex work is needed to represent
it in the driver.
diff mbox

Patch

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 55d7b7b..ced47af 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -725,5 +725,11 @@  config REGULATOR_WM8994
 	  This driver provides support for the voltage regulators on the
 	  WM8994 CODEC.
 
+config REGULATOR_MT6397
+	tristate "MediaTek MT6397 PMIC"
+	depends on MFD_MT6397
+	help
+	   This driver provides support for the voltage regulators on the MediaTek MT6397 PMIC.
+
 endif
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 1029ed3..3622ece 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -95,6 +95,7 @@  obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
 obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
 obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
 obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
+obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o
 
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/mt6397-regulator.c b/drivers/regulator/mt6397-regulator.c
new file mode 100644
index 0000000..6ff0c22
--- /dev/null
+++ b/drivers/regulator/mt6397-regulator.c
@@ -0,0 +1,547 @@ 
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Flora.Fu <flora.fu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/mt6397-regulator.h>
+#include <linux/mfd/mt6397/core.h>
+#include <linux/mfd/mt6397/registers.h>
+
+struct mt6397_buck_conf {
+	unsigned int vosel_reg;
+	unsigned int voselon_reg;
+	unsigned int nivosel_reg;
+	unsigned int vosel_mask;
+};
+
+struct mt6397_ldo_conf {
+	unsigned int is_fixed;
+	unsigned int vosel_reg;
+	unsigned int vosel_mask;
+	unsigned int vosel_shift;
+};
+
+struct mt6397_regulator_info {
+	struct regulator_desc desc;
+	struct mt6397_buck_conf buck_conf;
+	struct mt6397_ldo_conf ldo_conf;
+	unsigned int qi;
+	unsigned int qi_mask;
+};
+
+struct mt6397_regulator_data {
+	int id;
+	const char *name;
+	struct regulator_init_data *initdata;
+	struct device_node *reg_node;
+};
+
+struct mt6397_regulator_priv {
+	struct mt6397_regulator_data *regulators;
+	unsigned int num_regulators;
+};
+
+#define MT6397_BUCK(vreg, min, max, step, volt_ranges,		\
+		enreg, vosel, voselon, nivosel)			\
+[MT6397_ID_##vreg] = {						\
+	.desc = {						\
+		.name = #vreg,					\
+		.ops = &mt6397_volt_range_ops,			\
+		.type = REGULATOR_VOLTAGE,			\
+		.id = MT6397_ID_##vreg,				\
+		.owner = THIS_MODULE,				\
+		.n_voltages	= (max - min)/step + 1,		\
+		.linear_ranges = volt_ranges,			\
+		.n_linear_ranges = ARRAY_SIZE(volt_ranges),	\
+		.enable_reg = enreg,				\
+		.enable_mask = BIT(0),				\
+	},							\
+	.qi = enreg,						\
+	.qi_mask = BIT(13),					\
+	.buck_conf = {						\
+		.vosel_reg = vosel,				\
+		.voselon_reg = voselon,				\
+		.nivosel_reg = nivosel,				\
+		.vosel_mask = 0x7f,				\
+	},							\
+}
+
+#define MT6397_BUCK_TABLE(vreg, buck_volt_table,		\
+		enreg, vosel, voselon, nivosel)			\
+[MT6397_ID_##vreg] = {						\
+	.desc = {						\
+		.name = #vreg,					\
+		.ops = &mt6397_volt_table_ops,			\
+		.type = REGULATOR_VOLTAGE,			\
+		.id = MT6397_ID_##vreg,				\
+		.owner = THIS_MODULE,				\
+		.n_voltages = 1,				\
+		.volt_table = buck_volt_table,			\
+		.enable_reg = enreg,				\
+		.enable_mask = BIT(0),				\
+	},							\
+	.qi = enreg,						\
+	.qi_mask = BIT(13),					\
+	.buck_conf = {						\
+		.vosel_reg = vosel,				\
+		.voselon_reg = voselon,				\
+		.nivosel_reg = nivosel,				\
+		.vosel_mask = 0x1f,				\
+	},							\
+}
+
+#define MT6397_LDO(vreg, ldo_volt_table,			\
+		enreg, enbit, fixed, vosel, _vosel_mask,	\
+		_vosel_shift)					\
+[MT6397_ID_##vreg] = {						\
+	.desc = {						\
+		.name = #vreg,					\
+		.ops = &mt6397_volt_table_ops,			\
+		.type = REGULATOR_VOLTAGE,			\
+		.id = MT6397_ID_##vreg,				\
+		.owner = THIS_MODULE,				\
+		.n_voltages = ARRAY_SIZE(ldo_volt_table),	\
+		.volt_table = ldo_volt_table,			\
+		.enable_reg = enreg,				\
+		.enable_mask = BIT(enbit),			\
+	},							\
+	.qi = enreg,						\
+	.qi_mask = BIT(15),					\
+	.ldo_conf = {						\
+		.is_fixed = fixed,				\
+		.vosel_reg = vosel,				\
+		.vosel_mask = _vosel_mask,			\
+		.vosel_shift = _vosel_shift,			\
+	},							\
+}
+
+static const struct regulator_linear_range buck_volt_range1[] = {
+	REGULATOR_LINEAR_RANGE(700000, 0, 0x7f, 6250),
+};
+
+static const struct regulator_linear_range buck_volt_range2[] = {
+	REGULATOR_LINEAR_RANGE(800000, 0, 0x7f, 6250),
+};
+
+static const unsigned int fixed_1800000_voltage[] = {
+	1800000,
+};
+
+static const unsigned int fixed_2800000_voltage[] = {
+	2800000,
+};
+
+static const unsigned int fixed_3300000_voltage[] = {
+	3300000,
+};
+
+static const unsigned int ldo_volt_table1[] = {
+	1500000, 1800000, 2500000, 2800000,
+};
+
+static const unsigned int ldo_volt_table2[] = {
+	1800000, 3300000,
+};
+
+static const unsigned int ldo_volt_table3[] = {
+	3000000, 3300000,
+};
+
+static const unsigned int ldo_volt_table4[] = {
+	1220000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000,
+};
+
+static const unsigned int ldo_volt_table5[] = {
+	1200000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000,
+};
+
+static const unsigned int ldo_volt_table5_v2[] = {
+	1200000, 1000000, 1500000, 1800000, 2500000, 2800000, 3000000, 3300000,
+};
+
+static const unsigned int ldo_volt_table6[] = {
+	1200000, 1300000, 1500000, 1800000, 2500000, 2800000, 3000000, 2000000,
+};
+
+static const unsigned int ldo_volt_table7[] = {
+	1300000, 1500000, 1800000, 2000000, 2500000, 2800000, 3000000, 3300000,
+};
+
+static int mt6397_buck_set_voltage_sel(struct regulator_dev *rdev, unsigned sel)
+{
+	int ret;
+	unsigned int vosel;
+	unsigned int voselon;
+	unsigned int vosel_mask;
+	struct mt6397_regulator_info *info = rdev_get_drvdata(rdev);
+
+	vosel = info->buck_conf.vosel_reg;
+	voselon = info->buck_conf.voselon_reg;
+	vosel_mask = info->buck_conf.vosel_mask;
+
+	ret = regmap_update_bits(rdev->regmap, vosel, vosel_mask, sel);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to update vosel: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(rdev->regmap, voselon, vosel_mask, sel);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to update vosel_on: %d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int mt6397_buck_get_voltage_sel(struct regulator_dev *rdev)
+{
+	int ret;
+	unsigned int nivosel;
+	unsigned int vosel_mask;
+	unsigned int regval;
+	struct mt6397_regulator_info *info = rdev_get_drvdata(rdev);
+
+	nivosel = info->buck_conf.nivosel_reg;
+	vosel_mask = info->buck_conf.vosel_mask;
+
+	ret = regmap_read(rdev->regmap, nivosel, &regval);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to get vosel: %d\n", ret);
+		return ret;
+	}
+
+	regval &= vosel_mask;
+	regval >>= ffs(vosel_mask) - 1;
+
+	return regval;
+}
+
+
+static int mt6397_ldo_set_voltage_sel(struct regulator_dev *rdev, unsigned sel)
+{
+	int ret;
+	unsigned int is_fixed;
+	unsigned int vosel;
+	unsigned int vosel_mask;
+	unsigned int vosel_shift;
+
+	struct mt6397_regulator_info *info = rdev_get_drvdata(rdev);
+
+	is_fixed = info->ldo_conf.is_fixed;
+	vosel = info->ldo_conf.vosel_reg;
+	vosel_mask = info->ldo_conf.vosel_mask;
+	vosel_shift = info->ldo_conf.vosel_shift;
+
+	if (is_fixed)
+		return 0;
+
+	sel <<= vosel_shift;
+	ret = regmap_update_bits(rdev->regmap, vosel, vosel_mask, sel);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to update vosel: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int mt6397_ldo_get_voltage_sel(struct regulator_dev *rdev)
+{
+	int ret;
+	unsigned int vosel;
+	unsigned int vosel_mask;
+	unsigned int regval;
+	struct mt6397_regulator_info *info = rdev_get_drvdata(rdev);
+
+	vosel = info->ldo_conf.vosel_reg;
+	vosel_mask = info->ldo_conf.vosel_mask;
+
+	ret = regmap_read(rdev->regmap, vosel, &regval);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to get vosel: %d\n", ret);
+		return ret;
+	}
+
+	regval &= vosel_mask;
+	regval >>= ffs(vosel_mask) - 1;
+
+	return regval;
+}
+
+static int mt6397_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	int ret;
+	unsigned int regaddr;
+	unsigned int regmask;
+	unsigned int regval;
+	struct mt6397_regulator_info *info = rdev_get_drvdata(rdev);
+
+	regaddr = info->qi;
+	regmask = info->qi_mask;
+
+	ret = regmap_read(rdev->regmap, regaddr, &regval);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to get vosel: %d\n", ret);
+		return ret;
+	}
+
+	return (regval & info->qi_mask) ? 1 : 0;
+}
+
+static struct regulator_ops mt6397_volt_range_ops = {
+	.list_voltage = regulator_list_voltage_linear_range,
+	.map_voltage = regulator_map_voltage_linear_range,
+	.set_voltage_sel = mt6397_buck_set_voltage_sel,
+	.get_voltage_sel = mt6397_buck_get_voltage_sel,
+	.set_voltage_time_sel = regulator_set_voltage_time_sel,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = mt6397_regulator_is_enabled,
+};
+
+static struct regulator_ops mt6397_volt_table_ops = {
+	.list_voltage = regulator_list_voltage_table,
+	.map_voltage = regulator_map_voltage_iterate,
+	.set_voltage_sel = mt6397_ldo_set_voltage_sel,
+	.get_voltage_sel = mt6397_ldo_get_voltage_sel,
+	.set_voltage_time_sel = regulator_set_voltage_time_sel,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = mt6397_regulator_is_enabled,
+};
+
+/* The array is indexed by id(MT6397_ID_XXX) */
+static struct mt6397_regulator_info mt6397_regulators[] = {
+	MT6397_BUCK(VPCA15, 700000, 1493750, 6250, buck_volt_range1,
+		MT6397_VCA15_CON7, MT6397_VCA15_CON9,
+		MT6397_VCA15_CON10, MT6397_VCA15_CON12),
+	MT6397_BUCK(VPCA7, 700000, 1493750, 6250, buck_volt_range1,
+		MT6397_VPCA7_CON7, MT6397_VPCA7_CON9,
+		MT6397_VPCA7_CON10, MT6397_VPCA7_CON12),
+	MT6397_BUCK(VSRAMCA15, 700000, 1493750, 6250, buck_volt_range1,
+		MT6397_VSRMCA15_CON7, MT6397_VSRMCA15_CON9,
+		MT6397_VSRMCA15_CON10, MT6397_VSRMCA15_CON12),
+	MT6397_BUCK(VSRAMCA7, 700000, 1493750, 6250, buck_volt_range1,
+		MT6397_VSRMCA7_CON7, MT6397_VSRMCA7_CON9,
+		MT6397_VSRMCA7_CON10, MT6397_VSRMCA7_CON12),
+	MT6397_BUCK(VCORE, 700000, 1493750, 6250, buck_volt_range1,
+		MT6397_VCORE_CON7, MT6397_VCORE_CON9,
+		MT6397_VCORE_CON10, MT6397_VCORE_CON12),
+	MT6397_BUCK(VGPU, 700000, 1493750, 6250, buck_volt_range1,
+		MT6397_VGPU_CON7, MT6397_VGPU_CON9,
+		MT6397_VGPU_CON10, MT6397_VCORE_CON12),
+	MT6397_BUCK(VDRM, 800000, 1593750, 6250, buck_volt_range2,
+		MT6397_VDRM_CON7, MT6397_VDRM_CON9,
+		MT6397_VDRM_CON10, MT6397_VDRM_CON12),
+	MT6397_BUCK_TABLE(VIO18, fixed_1800000_voltage,
+		MT6397_VIO18_CON7, MT6397_VIO18_CON9,
+		MT6397_VIO18_CON10, MT6397_VIO18_CON12),
+	MT6397_LDO(VTCXO, fixed_2800000_voltage,
+		MT6397_ANALDO_CON0, 10, 1, 0, 0, 0),
+	MT6397_LDO(VA28, fixed_2800000_voltage,
+		MT6397_ANALDO_CON1, 14, 1, 0, 0, 0),
+	MT6397_LDO(VCAMA, ldo_volt_table1,
+		MT6397_ANALDO_CON2, 15, 0, MT6397_ANALDO_CON6, 0xC0, 6),
+	MT6397_LDO(VIO28, fixed_2800000_voltage,
+		MT6397_DIGLDO_CON0, 14, 1, 0, 0, 0),
+	MT6397_LDO(USB, fixed_3300000_voltage,
+		MT6397_DIGLDO_CON1, 14, 1, 0, 0, 0),
+	MT6397_LDO(VMC, ldo_volt_table2,
+		MT6397_DIGLDO_CON2, 12, 0, MT6397_DIGLDO_CON29, 0x10, 4),
+	MT6397_LDO(VMCH, ldo_volt_table3,
+		MT6397_DIGLDO_CON3, 14, 0, MT6397_DIGLDO_CON17, 0x80, 7),
+	MT6397_LDO(VEMC3V3, ldo_volt_table3,
+		MT6397_DIGLDO_CON4, 14, 0, MT6397_DIGLDO_CON18, 0x10, 4),
+	MT6397_LDO(VCAMD, ldo_volt_table4,
+		MT6397_DIGLDO_CON5, 15, 0, MT6397_DIGLDO_CON19, 0xE0, 5),
+	MT6397_LDO(VCAMIO, ldo_volt_table5,
+		MT6397_DIGLDO_CON6, 15, 0, MT6397_DIGLDO_CON20, 0xE0, 5),
+	MT6397_LDO(VCAMAF, ldo_volt_table5,
+		MT6397_DIGLDO_CON7, 15, 0, MT6397_DIGLDO_CON21, 0xE0, 5),
+	MT6397_LDO(VGP4, ldo_volt_table5,
+		MT6397_DIGLDO_CON8, 15, 0, MT6397_DIGLDO_CON22, 0xE0, 5),
+	MT6397_LDO(VGP5, ldo_volt_table6,
+		MT6397_DIGLDO_CON9, 15, 0, MT6397_DIGLDO_CON23, 0xE0, 5),
+	MT6397_LDO(VGP6, ldo_volt_table5,
+		MT6397_DIGLDO_CON10, 15, 0, MT6397_DIGLDO_CON33, 0xE0, 5),
+	MT6397_LDO(VIBR, ldo_volt_table7,
+		MT6397_DIGLDO_CON24, 15, 0, MT6397_DIGLDO_CON25, 0xE00, 9),
+};
+
+#define MT6397_REGULATOR_OF_MATCH(_name, _id)			\
+[MT6397_ID_##_id] = {						\
+	.name = #_name,						\
+	.driver_data = &mt6397_regulators[MT6397_ID_##_id],	\
+}
+
+static struct of_regulator_match mt6397_regulator_matches[] = {
+	MT6397_REGULATOR_OF_MATCH(buck_vpca15, VPCA15),
+	MT6397_REGULATOR_OF_MATCH(buck_vpca7, VPCA7),
+	MT6397_REGULATOR_OF_MATCH(buck_vsramca15, VSRAMCA15),
+	MT6397_REGULATOR_OF_MATCH(buck_vsramca7, VSRAMCA7),
+	MT6397_REGULATOR_OF_MATCH(buck_vcore, VCORE),
+	MT6397_REGULATOR_OF_MATCH(buck_vgpu, VGPU),
+	MT6397_REGULATOR_OF_MATCH(buck_vdrm, VDRM),
+	MT6397_REGULATOR_OF_MATCH(buck_vio18, VIO18),
+	MT6397_REGULATOR_OF_MATCH(ldo_vtcxo, VTCXO),
+	MT6397_REGULATOR_OF_MATCH(ldo_va28, VA28),
+	MT6397_REGULATOR_OF_MATCH(ldo_vcama, VCAMA),
+	MT6397_REGULATOR_OF_MATCH(ldo_vio28, VIO28),
+	MT6397_REGULATOR_OF_MATCH(ldo_usb, USB),
+	MT6397_REGULATOR_OF_MATCH(ldo_vmc, VMC),
+	MT6397_REGULATOR_OF_MATCH(ldo_vmch, VMCH),
+	MT6397_REGULATOR_OF_MATCH(ldo_vemc3v3, VEMC3V3),
+	MT6397_REGULATOR_OF_MATCH(ldo_vcamd, VCAMD),
+	MT6397_REGULATOR_OF_MATCH(ldo_vcamio, VCAMIO),
+	MT6397_REGULATOR_OF_MATCH(ldo_vcamaf, VCAMAF),
+	MT6397_REGULATOR_OF_MATCH(ldo_vgp4, VGP4),
+	MT6397_REGULATOR_OF_MATCH(ldo_vgp5, VGP5),
+	MT6397_REGULATOR_OF_MATCH(ldo_vgp6, VGP6),
+	MT6397_REGULATOR_OF_MATCH(ldo_vibr, VIBR),
+};
+
+static int mt6397_regulator_dt_init(struct platform_device *pdev,
+			struct mt6397_regulator_priv *priv)
+{
+	struct mt6397_chip *mt6397 = dev_get_drvdata(pdev->dev.parent);
+	struct device_node *np, *regulators;
+	struct mt6397_regulator_data *rdata;
+	int matched, i, j, ret;
+	unsigned int reg_value;
+
+	if (!priv) {
+		dev_err(&pdev->dev, "regulator private data missing\n");
+		return -EINVAL;
+	}
+
+	np = of_node_get(pdev->dev.parent->of_node);
+	if (!np)
+		return -EINVAL;
+
+	regulators = of_get_child_by_name(np, "regulators");
+	if (!regulators) {
+		dev_err(&pdev->dev, "regulators node not found\n");
+		ret = -EINVAL;
+		goto out;
+	}
+	of_node_put(np);
+	matched = of_regulator_match(&pdev->dev, regulators,
+				mt6397_regulator_matches,
+				ARRAY_SIZE(mt6397_regulator_matches));
+	of_node_put(regulators);
+	if (matched < 0) {
+		dev_err(&pdev->dev, "Error parsing regulator init data: %d\n",
+			matched);
+		return matched;
+	}
+
+	priv->num_regulators = matched;
+	priv->regulators = devm_kzalloc(&pdev->dev,
+			(sizeof(struct mt6397_regulator_data) *
+			ARRAY_SIZE(mt6397_regulator_matches)), GFP_KERNEL);
+	if (!priv->regulators)
+		return -ENOMEM;
+
+	/* Read PMIC chip revision to update constraints and voltage table */
+	if (regmap_read(mt6397->regmap, MT6397_CID, &reg_value) < 0) {
+		dev_err(&pdev->dev, "Failed to read Chip ID\n");
+		return -EIO;
+	}
+	dev_info(&pdev->dev, "Chip ID = 0x%x\n", reg_value);
+
+	if ((reg_value & 0xFF) == MT6397_REGULATOR_ID91) {
+		j = MT6397_ID_VCAMIO;
+		mt6397_regulator_matches[j].init_data->constraints.min_uV =
+		1000000;
+		mt6397_regulators[j].desc.volt_table = ldo_volt_table5_v2;
+	}
+
+	rdata = priv->regulators;
+	for (i = 0; i < ARRAY_SIZE(mt6397_regulator_matches); i++) {
+		rdata->id = i;
+		rdata->name = mt6397_regulator_matches[i].name;
+		rdata->initdata = mt6397_regulator_matches[i].init_data;
+		rdata->reg_node = mt6397_regulator_matches[i].of_node;
+		rdata++;
+	}
+	return 0;
+
+out:
+	of_node_put(np);
+	return ret;
+}
+
+static int mt6397_regulator_probe(struct platform_device *pdev)
+{
+	struct mt6397_chip *mt6397 = dev_get_drvdata(pdev->dev.parent);
+	int ret, i, id, size;
+	struct regulator_config config = {};
+	struct regulator_dev *rdev;
+	struct mt6397_regulator_priv *priv;
+
+	size = sizeof(struct mt6397_regulator_priv);
+	priv = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+	ret = mt6397_regulator_dt_init(pdev, priv);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < MT6397_ID_RG_MAX; i++) {
+		id = priv->regulators[i].id;
+		config.dev = &pdev->dev;
+		config.init_data = priv->regulators[i].initdata;
+		config.of_node = priv->regulators[i].reg_node;
+		config.driver_data = mt6397_regulator_matches[i].driver_data;
+		config.regmap = mt6397->regmap;
+
+		rdev = devm_regulator_register(&pdev->dev,
+				&mt6397_regulators[i].desc, &config);
+		if (IS_ERR(rdev)) {
+			dev_err(&pdev->dev, "failed to register %s\n",
+				mt6397_regulators[id].desc.name);
+			return PTR_ERR(rdev);
+		}
+	}
+
+	return 0;
+}
+
+static const struct platform_device_id mt6397_regulator_id[] = {
+	{"mt6397-regulator", 0},
+	{ },
+};
+
+static struct platform_driver mt6397_regulator_driver = {
+	.driver	= {
+		.name = "mt6397-regulator",
+		.owner = THIS_MODULE,
+	},
+	.probe = mt6397_regulator_probe,
+	.id_table = mt6397_regulator_id,
+};
+
+module_platform_driver(mt6397_regulator_driver);
+
+MODULE_AUTHOR("Flora Fu <flora.fu@mediatek.com>");
+MODULE_DESCRIPTION("Regulator Driver for MediaTek MT6397 PMIC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mt6397-regulator");
diff --git a/include/linux/regulator/mt6397-regulator.h b/include/linux/regulator/mt6397-regulator.h
new file mode 100644
index 0000000..d03be02
--- /dev/null
+++ b/include/linux/regulator/mt6397-regulator.h
@@ -0,0 +1,50 @@ 
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Flora.Fu <flora.fu@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#ifndef __LINUX_REGULATOR_MT6397_H
+#define __LINUX_REGULATOR_MT6397_H
+
+enum {
+	MT6397_ID_VPCA15 = 0,
+	MT6397_ID_VPCA7,
+	MT6397_ID_VSRAMCA15,
+	MT6397_ID_VSRAMCA7,
+	MT6397_ID_VCORE,
+	MT6397_ID_VGPU,
+	MT6397_ID_VDRM,
+	MT6397_ID_VIO18 = 7,
+
+	MT6397_ID_VTCXO,
+	MT6397_ID_VA28,
+	MT6397_ID_VCAMA,
+	MT6397_ID_VIO28,
+	MT6397_ID_USB,
+	MT6397_ID_VMC,
+	MT6397_ID_VMCH,
+	MT6397_ID_VEMC3V3,
+	MT6397_ID_VCAMD,
+	MT6397_ID_VCAMIO,
+	MT6397_ID_VCAMAF,
+	MT6397_ID_VGP4,
+	MT6397_ID_VGP5,
+	MT6397_ID_VGP6,
+	MT6397_ID_VIBR,
+
+	MT6397_ID_RG_MAX,
+};
+#define MT6397_MAX_REGULATOR	MT6397_ID_RG_MAX
+#define MT6397_REGULATOR_ID97	0x97
+#define MT6397_REGULATOR_ID91	0x91
+
+#endif /* __LINUX_REGULATOR_MT6397_H */