regulator: add driver for hi6421 voltage regulator
authorGuodong Xu <guodong.xu@linaro.org>
Wed, 13 Aug 2014 11:33:42 +0000 (19:33 +0800)
committerMark Brown <broonie@linaro.org>
Sat, 16 Aug 2014 21:58:15 +0000 (16:58 -0500)
Add driver support for HiSilicon Hi6421 voltage regulators.

Two rules for regulator enabling are defined in hi6421 spec:
1) Between disable and enable of each regulator (LDOs or BUCKs), there must
   be a protection gap. Use @off_on_delay of regulator core to implement this.
2) No two regulators can be enabled at the same time. Use mutex in
   hi6421_regulator_pdata to ensure this. A protection gap of 100us is added
   into each LDO/BUCK's .enable_time.

Signed-off-by: Guodong Xu <guodong.xu@linaro.org>
Signed-off-by: Mark Brown <broonie@linaro.org>
drivers/regulator/Kconfig
drivers/regulator/Makefile
drivers/regulator/hi6421-regulator.c [new file with mode: 0644]

index 2dc8289..e6b98ed 100644 (file)
@@ -240,6 +240,16 @@ config REGULATOR_GPIO
          and the platform has to provide a mapping of GPIO-states
          to target volts/amps.
 
+config REGULATOR_HI6421
+       tristate "HiSilicon Hi6421 PMIC voltage regulator support"
+       depends on MFD_HI6421_PMIC && OF
+       help
+         This driver provides support for the voltage regulators on the
+         HiSilicon Hi6421 PMU / Codec IC.
+         Hi6421 is a multi-function device which, on regulator part, provides
+         21 general purpose LDOs, 3 dedicated LDOs, and 5 BUCKs. All
+         of them come with support to either ECO (idle) or sleep mode.
+
 config REGULATOR_ISL6271A
        tristate "Intersil ISL6271A Power regulator"
        depends on I2C
index aa4a6aa..5513e2c 100644 (file)
@@ -32,6 +32,7 @@ obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o
 obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o
 obj-$(CONFIG_REGULATOR_FAN53555) += fan53555.o
 obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
+obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o
 obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
 obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o
 obj-$(CONFIG_REGULATOR_LP3972) += lp3972.o
diff --git a/drivers/regulator/hi6421-regulator.c b/drivers/regulator/hi6421-regulator.c
new file mode 100644 (file)
index 0000000..b40851e
--- /dev/null
@@ -0,0 +1,657 @@
+/*
+ * Device driver for regulators in Hi6421 IC
+ *
+ * Copyright (c) <2011-2014> HiSilicon Technologies Co., Ltd.
+ *              http://www.hisilicon.com
+ * Copyright (c) <2013-2014> Linaro Ltd.
+ *              http://www.linaro.org
+ *
+ * Author: Guodong Xu <guodong.xu@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/mfd/hi6421-pmic.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+
+/*
+ * struct hi6421_regulator_pdata - Hi6421 regulator data of platform device
+ * @lock: mutex to serialize regulator enable
+ */
+struct hi6421_regulator_pdata {
+       struct mutex lock;
+};
+
+/*
+ * struct hi6421_regulator_info - hi6421 regulator information
+ * @dev: device pointer
+ * @desc: regulator description
+ * @regulator: regulator device
+ * @mode_mask: ECO mode bitmask of LDOs; for BUCKs, this masks sleep
+ * @eco_microamp: eco mode load upper limit (in mA), valid for LDOs only
+ * @valid_modes_mask: valid operating modes
+ */
+struct hi6421_regulator_info {
+       struct device           *dev;
+       struct regulator_desc   desc;
+       struct regulator_dev    *regulator;
+       u8              mode_mask;
+       u32             eco_microamp;
+       unsigned int    valid_modes_mask;
+};
+
+/* HI6421 regulators */
+enum hi6421_regulator_id {
+       HI6421_LDO0,
+       HI6421_LDO1,
+       HI6421_LDO2,
+       HI6421_LDO3,
+       HI6421_LDO4,
+       HI6421_LDO5,
+       HI6421_LDO6,
+       HI6421_LDO7,
+       HI6421_LDO8,
+       HI6421_LDO9,
+       HI6421_LDO10,
+       HI6421_LDO11,
+       HI6421_LDO12,
+       HI6421_LDO13,
+       HI6421_LDO14,
+       HI6421_LDO15,
+       HI6421_LDO16,
+       HI6421_LDO17,
+       HI6421_LDO18,
+       HI6421_LDO19,
+       HI6421_LDO20,
+       HI6421_LDOAUDIO,
+       HI6421_BUCK0,
+       HI6421_BUCK1,
+       HI6421_BUCK2,
+       HI6421_BUCK3,
+       HI6421_BUCK4,
+       HI6421_BUCK5,
+       HI6421_NUM_REGULATORS,
+};
+
+#define HI6421_REGULATOR_OF_MATCH(_name, id)                           \
+{                                                                      \
+       .name = #_name,                                                 \
+       .driver_data = (void *) HI6421_##id,                            \
+}
+
+static struct of_regulator_match hi6421_regulator_match[] = {
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout0, LDO0),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout1, LDO1),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout2, LDO2),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout3, LDO3),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout4, LDO4),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout5, LDO5),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout6, LDO6),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout7, LDO7),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout8, LDO8),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout9, LDO9),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout10, LDO10),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout11, LDO11),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout12, LDO12),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout13, LDO13),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout14, LDO14),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout15, LDO15),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout16, LDO16),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout17, LDO17),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout18, LDO18),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout19, LDO19),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout20, LDO20),
+       HI6421_REGULATOR_OF_MATCH(hi6421_vout_audio, LDOAUDIO),
+       HI6421_REGULATOR_OF_MATCH(hi6421_buck0, BUCK0),
+       HI6421_REGULATOR_OF_MATCH(hi6421_buck1, BUCK1),
+       HI6421_REGULATOR_OF_MATCH(hi6421_buck2, BUCK2),
+       HI6421_REGULATOR_OF_MATCH(hi6421_buck3, BUCK3),
+       HI6421_REGULATOR_OF_MATCH(hi6421_buck4, BUCK4),
+       HI6421_REGULATOR_OF_MATCH(hi6421_buck5, BUCK5),
+};
+
+/* LDO 0, 4~7, 9~14, 16~20 have same voltage table. */
+static const unsigned int ldo_0_voltages[] = {
+       1500000, 1800000, 2400000, 2500000,
+       2600000, 2700000, 2850000, 3000000,
+};
+
+/* LDO 8, 15 have same voltage table. */
+static const unsigned int ldo_8_voltages[] = {
+       1500000, 1800000, 2400000, 2600000,
+       2700000, 2850000, 3000000, 3300000,
+};
+
+/* Ranges are sorted in ascending order. */
+static const struct regulator_linear_range ldo_audio_volt_range[] = {
+       REGULATOR_LINEAR_RANGE(2800000, 0, 3, 50000),
+       REGULATOR_LINEAR_RANGE(3000000, 4, 7, 100000),
+};
+
+static const unsigned int buck_3_voltages[] = {
+        950000, 1050000, 1100000, 1117000,
+       1134000, 1150000, 1167000, 1200000,
+};
+
+static const unsigned int buck_4_voltages[] = {
+       1150000, 1200000, 1250000, 1350000,
+       1700000, 1800000, 1900000, 2000000,
+};
+
+static const unsigned int buck_5_voltages[] = {
+       1150000, 1200000, 1250000, 1350000,
+       1600000, 1700000, 1800000, 1900000,
+};
+
+static const struct regulator_ops hi6421_ldo_ops;
+static const struct regulator_ops hi6421_ldo_linear_ops;
+static const struct regulator_ops hi6421_ldo_linear_range_ops;
+static const struct regulator_ops hi6421_buck012_ops;
+static const struct regulator_ops hi6421_buck345_ops;
+
+#define HI6421_LDO_ENABLE_TIME (350)
+/*
+ * _id - LDO id name string
+ * v_table - voltage table
+ * vreg - voltage select register
+ * vmask - voltage select mask
+ * ereg - enable register
+ * emask - enable mask
+ * odelay - off/on delay time in uS
+ * ecomask - eco mode mask
+ * ecoamp - eco mode load uppler limit in mA
+ */
+#define HI6421_LDO(_id, v_table, vreg, vmask, ereg, emask,             \
+                  odelay, ecomask, ecoamp)                             \
+       [HI6421_##_id] = {                                              \
+               .desc = {                                               \
+                       .name           = #_id,                         \
+                       .ops            = &hi6421_ldo_ops,              \
+                       .type           = REGULATOR_VOLTAGE,            \
+                       .id             = HI6421_##_id,                 \
+                       .owner          = THIS_MODULE,                  \
+                       .n_voltages     = ARRAY_SIZE(v_table),          \
+                       .volt_table     = v_table,                      \
+                       .vsel_reg       = HI6421_REG_TO_BUS_ADDR(vreg), \
+                       .vsel_mask      = vmask,                        \
+                       .enable_reg     = HI6421_REG_TO_BUS_ADDR(ereg), \
+                       .enable_mask    = emask,                        \
+                       .enable_time    = HI6421_LDO_ENABLE_TIME,       \
+                       .off_on_delay   = odelay,                       \
+               },                                                      \
+               .mode_mask              = ecomask,                      \
+               .eco_microamp           = ecoamp,                       \
+               .valid_modes_mask       = (REGULATOR_MODE_NORMAL        \
+                                          | REGULATOR_MODE_IDLE),      \
+       }
+
+/* HI6421 LDO1~3 are linear voltage regulators at fixed uV_step
+ *
+ * _id - LDO id name string
+ * _min_uV - minimum voltage supported in uV
+ * n_volt - number of votages available
+ * vstep - voltage increase in each linear step in uV
+ * vreg - voltage select register
+ * vmask - voltage select mask
+ * ereg - enable register
+ * emask - enable mask
+ * odelay - off/on delay time in uS
+ * ecomask - eco mode mask
+ * ecoamp - eco mode load uppler limit in mA
+ */
+#define HI6421_LDO_LINEAR(_id, _min_uV, n_volt, vstep, vreg, vmask,    \
+                         ereg, emask, odelay, ecomask, ecoamp)         \
+       [HI6421_##_id] = {                                              \
+               .desc = {                                               \
+                       .name           = #_id,                         \
+                       .ops            = &hi6421_ldo_linear_ops,       \
+                       .type           = REGULATOR_VOLTAGE,            \
+                       .id             = HI6421_##_id,                 \
+                       .owner          = THIS_MODULE,                  \
+                       .min_uV         = _min_uV,                      \
+                       .n_voltages     = n_volt,                       \
+                       .uV_step        = vstep,                        \
+                       .vsel_reg       = HI6421_REG_TO_BUS_ADDR(vreg), \
+                       .vsel_mask      = vmask,                        \
+                       .enable_reg     = HI6421_REG_TO_BUS_ADDR(ereg), \
+                       .enable_mask    = emask,                        \
+                       .enable_time    = HI6421_LDO_ENABLE_TIME,       \
+                       .off_on_delay   = odelay,                       \
+               },                                                      \
+               .mode_mask              = ecomask,                      \
+               .eco_microamp           = ecoamp,                       \
+               .valid_modes_mask       = (REGULATOR_MODE_NORMAL        \
+                                          | REGULATOR_MODE_IDLE),      \
+       }
+
+/* HI6421 LDOAUDIO is a linear voltage regulator with two 4-step ranges
+ *
+ * _id - LDO id name string
+ * n_volt - number of votages available
+ * volt_ranges - array of regulator_linear_range
+ * vstep - voltage increase in each linear step in uV
+ * vreg - voltage select register
+ * vmask - voltage select mask
+ * ereg - enable register
+ * emask - enable mask
+ * odelay - off/on delay time in uS
+ * ecomask - eco mode mask
+ * ecoamp - eco mode load uppler limit in mA
+ */
+#define HI6421_LDO_LINEAR_RANGE(_id, n_volt, volt_ranges, vreg, vmask, \
+                               ereg, emask, odelay, ecomask, ecoamp)   \
+       [HI6421_##_id] = {                                              \
+               .desc = {                                               \
+                       .name           = #_id,                         \
+                       .ops            = &hi6421_ldo_linear_range_ops, \
+                       .type           = REGULATOR_VOLTAGE,            \
+                       .id             = HI6421_##_id,                 \
+                       .owner          = THIS_MODULE,                  \
+                       .n_voltages     = n_volt,                       \
+                       .linear_ranges  = volt_ranges,                  \
+                       .n_linear_ranges = ARRAY_SIZE(volt_ranges),     \
+                       .vsel_reg       = HI6421_REG_TO_BUS_ADDR(vreg), \
+                       .vsel_mask      = vmask,                        \
+                       .enable_reg     = HI6421_REG_TO_BUS_ADDR(ereg), \
+                       .enable_mask    = emask,                        \
+                       .enable_time    = HI6421_LDO_ENABLE_TIME,       \
+                       .off_on_delay   = odelay,                       \
+               },                                                      \
+               .mode_mask              = ecomask,                      \
+               .eco_microamp           = ecoamp,                       \
+               .valid_modes_mask       = (REGULATOR_MODE_NORMAL        \
+                                          | REGULATOR_MODE_IDLE),      \
+       }
+
+/* HI6421 BUCK0/1/2 are linear voltage regulators at fixed uV_step
+ *
+ * _id - BUCK0/1/2 id name string
+ * vreg - voltage select register
+ * vmask - voltage select mask
+ * ereg - enable register
+ * emask - enable mask
+ * sleepmask - mask of sleep mode
+ * etime - enable time
+ * odelay - off/on delay time in uS
+ */
+#define HI6421_BUCK012(_id, vreg, vmask, ereg, emask, sleepmask,       \
+                       etime, odelay)                                  \
+       [HI6421_##_id] = {                                              \
+               .desc = {                                               \
+                       .name           = #_id,                         \
+                       .ops            = &hi6421_buck012_ops,          \
+                       .type           = REGULATOR_VOLTAGE,            \
+                       .id             = HI6421_##_id,                 \
+                       .owner          = THIS_MODULE,                  \
+                       .min_uV         = 700000,                       \
+                       .n_voltages     = 128,                          \
+                       .uV_step        = 7086,                         \
+                       .vsel_reg       = HI6421_REG_TO_BUS_ADDR(vreg), \
+                       .vsel_mask      = vmask,                        \
+                       .enable_reg     = HI6421_REG_TO_BUS_ADDR(ereg), \
+                       .enable_mask    = emask,                        \
+                       .enable_time    = etime,                        \
+                       .off_on_delay   = odelay,                       \
+               },                                                      \
+               .mode_mask              = sleepmask,                    \
+               .valid_modes_mask       = (REGULATOR_MODE_NORMAL        \
+                                          | REGULATOR_MODE_STANDBY),   \
+       }
+
+/* HI6421 BUCK3/4/5 share similar configurations as LDOs, with exception
+ *  that it supports SLEEP mode, so has different .ops.
+ *
+ * _id - LDO id name string
+ * v_table - voltage table
+ * vreg - voltage select register
+ * vmask - voltage select mask
+ * ereg - enable register
+ * emask - enable mask
+ * odelay - off/on delay time in uS
+ * sleepmask - mask of sleep mode
+ */
+#define HI6421_BUCK345(_id, v_table, vreg, vmask, ereg, emask,         \
+                       odelay, sleepmask)                              \
+       [HI6421_##_id] = {                                              \
+               .desc = {                                               \
+                       .name           = #_id,                         \
+                       .ops            = &hi6421_buck345_ops,          \
+                       .type           = REGULATOR_VOLTAGE,            \
+                       .id             = HI6421_##_id,                 \
+                       .owner          = THIS_MODULE,                  \
+                       .n_voltages     = ARRAY_SIZE(v_table),          \
+                       .volt_table     = v_table,                      \
+                       .vsel_reg       = HI6421_REG_TO_BUS_ADDR(vreg), \
+                       .vsel_mask      = vmask,                        \
+                       .enable_reg     = HI6421_REG_TO_BUS_ADDR(ereg), \
+                       .enable_mask    = emask,                        \
+                       .enable_time    = HI6421_LDO_ENABLE_TIME,       \
+                       .off_on_delay   = odelay,                       \
+               },                                                      \
+               .mode_mask              = sleepmask,                    \
+               .valid_modes_mask       = (REGULATOR_MODE_NORMAL        \
+                                          | REGULATOR_MODE_STANDBY),   \
+       }
+
+/* HI6421 regulator information */
+static struct hi6421_regulator_info
+               hi6421_regulator_info[HI6421_NUM_REGULATORS] = {
+       HI6421_LDO(LDO0, ldo_0_voltages, 0x20, 0x07, 0x20, 0x10,
+                  10000, 0x20, 8000),
+       HI6421_LDO_LINEAR(LDO1, 1700000, 4, 100000, 0x21, 0x03, 0x21, 0x10,
+                         10000, 0x20, 5000),
+       HI6421_LDO_LINEAR(LDO2, 1050000, 8, 50000, 0x22, 0x07, 0x22, 0x10,
+                         20000, 0x20, 8000),
+       HI6421_LDO_LINEAR(LDO3, 1050000, 8, 50000, 0x23, 0x07, 0x23, 0x10,
+                         20000, 0x20, 8000),
+       HI6421_LDO(LDO4, ldo_0_voltages, 0x24, 0x07, 0x24, 0x10,
+                  20000, 0x20, 8000),
+       HI6421_LDO(LDO5, ldo_0_voltages, 0x25, 0x07, 0x25, 0x10,
+                  20000, 0x20, 8000),
+       HI6421_LDO(LDO6, ldo_0_voltages, 0x26, 0x07, 0x26, 0x10,
+                  20000, 0x20, 8000),
+       HI6421_LDO(LDO7, ldo_0_voltages, 0x27, 0x07, 0x27, 0x10,
+                  20000, 0x20, 5000),
+       HI6421_LDO(LDO8, ldo_8_voltages, 0x28, 0x07, 0x28, 0x10,
+                  20000, 0x20, 8000),
+       HI6421_LDO(LDO9, ldo_0_voltages, 0x29, 0x07, 0x29, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO10, ldo_0_voltages, 0x2a, 0x07, 0x2a, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO11, ldo_0_voltages, 0x2b, 0x07, 0x2b, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO12, ldo_0_voltages, 0x2c, 0x07, 0x2c, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO13, ldo_0_voltages, 0x2d, 0x07, 0x2d, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO14, ldo_0_voltages, 0x2e, 0x07, 0x2e, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO15, ldo_8_voltages, 0x2f, 0x07, 0x2f, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO16, ldo_0_voltages, 0x30, 0x07, 0x30, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO17, ldo_0_voltages, 0x31, 0x07, 0x31, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO18, ldo_0_voltages, 0x32, 0x07, 0x32, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO19, ldo_0_voltages, 0x33, 0x07, 0x33, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO(LDO20, ldo_0_voltages, 0x34, 0x07, 0x34, 0x10,
+                  40000, 0x20, 8000),
+       HI6421_LDO_LINEAR_RANGE(LDOAUDIO, 8, ldo_audio_volt_range, 0x36,
+                               0x70, 0x36, 0x01, 40000, 0x02, 5000),
+       HI6421_BUCK012(BUCK0, 0x0d, 0x7f, 0x0c, 0x01, 0x10, 400, 20000),
+       HI6421_BUCK012(BUCK1, 0x0f, 0x7f, 0x0e, 0x01, 0x10, 400, 20000),
+       HI6421_BUCK012(BUCK2, 0x11, 0x7f, 0x10, 0x01, 0x10, 350, 100),
+       HI6421_BUCK345(BUCK3, buck_3_voltages, 0x13, 0x07, 0x12, 0x01,
+                      20000, 0x10),
+       HI6421_BUCK345(BUCK4, buck_4_voltages, 0x15, 0x07, 0x14, 0x01,
+                      20000, 0x10),
+       HI6421_BUCK345(BUCK5, buck_5_voltages, 0x17, 0x07, 0x16, 0x01,
+                      20000, 0x10),
+};
+
+static int hi6421_regulator_enable(struct regulator_dev *rdev)
+{
+       struct hi6421_regulator_pdata *pdata;
+
+       pdata = dev_get_drvdata(rdev->dev.parent);
+       /* hi6421 spec requires regulator enablement must be serialized:
+        *  - Because when BUCK, LDO switching from off to on, it will have
+        *    a huge instantaneous current; so you can not turn on two or
+        *    more LDO or BUCKs simultaneously, or it may burn the chip.
+        */
+       mutex_lock(&pdata->lock);
+
+       /* call regulator regmap helper */
+       regulator_enable_regmap(rdev);
+
+       mutex_unlock(&pdata->lock);
+       return 0;
+}
+
+static unsigned int hi6421_regulator_ldo_get_mode(struct regulator_dev *rdev)
+{
+       struct hi6421_regulator_info *info = rdev_get_drvdata(rdev);
+       u32 reg_val;
+
+       regmap_read(rdev->regmap, rdev->desc->enable_reg, &reg_val);
+       if (reg_val & info->mode_mask)
+               return REGULATOR_MODE_IDLE;
+       else
+               return REGULATOR_MODE_NORMAL;
+}
+
+static unsigned int hi6421_regulator_buck_get_mode(struct regulator_dev *rdev)
+{
+       struct hi6421_regulator_info *info = rdev_get_drvdata(rdev);
+       u32 reg_val;
+
+       regmap_read(rdev->regmap, rdev->desc->enable_reg, &reg_val);
+       if (reg_val & info->mode_mask)
+               return REGULATOR_MODE_STANDBY;
+       else
+               return REGULATOR_MODE_NORMAL;
+}
+
+static int hi6421_regulator_ldo_set_mode(struct regulator_dev *rdev,
+                                               unsigned int mode)
+{
+       struct hi6421_regulator_info *info = rdev_get_drvdata(rdev);
+       u32 new_mode;
+
+       switch (mode) {
+       case REGULATOR_MODE_NORMAL:
+               new_mode = 0;
+               break;
+       case REGULATOR_MODE_IDLE:
+               new_mode = info->mode_mask;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* set mode */
+       regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+                          info->mode_mask, new_mode);
+
+       return 0;
+}
+
+static int hi6421_regulator_buck_set_mode(struct regulator_dev *rdev,
+                                               unsigned int mode)
+{
+       struct hi6421_regulator_info *info = rdev_get_drvdata(rdev);
+       u32 new_mode;
+
+       switch (mode) {
+       case REGULATOR_MODE_NORMAL:
+               new_mode = 0;
+               break;
+       case REGULATOR_MODE_STANDBY:
+               new_mode = info->mode_mask;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* set mode */
+       regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+                          info->mode_mask, new_mode);
+
+       return 0;
+}
+
+unsigned int hi6421_regulator_ldo_get_optimum_mode(struct regulator_dev *rdev,
+                       int input_uV, int output_uV, int load_uA)
+{
+       struct hi6421_regulator_info *info = rdev_get_drvdata(rdev);
+
+       if (load_uA > info->eco_microamp)
+               return REGULATOR_MODE_NORMAL;
+       else
+               return REGULATOR_MODE_IDLE;
+}
+
+static const struct regulator_ops hi6421_ldo_ops = {
+       .is_enabled = regulator_is_enabled_regmap,
+       .enable = hi6421_regulator_enable,
+       .disable = regulator_disable_regmap,
+       .list_voltage = regulator_list_voltage_table,
+       .map_voltage = regulator_map_voltage_ascend,
+       .get_voltage_sel = regulator_get_voltage_sel_regmap,
+       .set_voltage_sel = regulator_set_voltage_sel_regmap,
+       .get_mode = hi6421_regulator_ldo_get_mode,
+       .set_mode = hi6421_regulator_ldo_set_mode,
+       .get_optimum_mode = hi6421_regulator_ldo_get_optimum_mode,
+};
+
+static const struct regulator_ops hi6421_ldo_linear_ops = {
+       .is_enabled = regulator_is_enabled_regmap,
+       .enable = hi6421_regulator_enable,
+       .disable = regulator_disable_regmap,
+       .list_voltage = regulator_list_voltage_linear,
+       .map_voltage = regulator_map_voltage_linear,
+       .get_voltage_sel = regulator_get_voltage_sel_regmap,
+       .set_voltage_sel = regulator_set_voltage_sel_regmap,
+       .get_mode = hi6421_regulator_ldo_get_mode,
+       .set_mode = hi6421_regulator_ldo_set_mode,
+       .get_optimum_mode = hi6421_regulator_ldo_get_optimum_mode,
+};
+
+static const struct regulator_ops hi6421_ldo_linear_range_ops = {
+       .is_enabled = regulator_is_enabled_regmap,
+       .enable = hi6421_regulator_enable,
+       .disable = regulator_disable_regmap,
+       .list_voltage = regulator_list_voltage_linear_range,
+       .map_voltage = regulator_map_voltage_linear_range,
+       .get_voltage_sel = regulator_get_voltage_sel_regmap,
+       .set_voltage_sel = regulator_set_voltage_sel_regmap,
+       .get_mode = hi6421_regulator_ldo_get_mode,
+       .set_mode = hi6421_regulator_ldo_set_mode,
+       .get_optimum_mode = hi6421_regulator_ldo_get_optimum_mode,
+};
+
+static const struct regulator_ops hi6421_buck012_ops = {
+       .is_enabled = regulator_is_enabled_regmap,
+       .enable = hi6421_regulator_enable,
+       .disable = regulator_disable_regmap,
+       .list_voltage = regulator_list_voltage_linear,
+       .map_voltage = regulator_map_voltage_linear,
+       .get_voltage_sel = regulator_get_voltage_sel_regmap,
+       .set_voltage_sel = regulator_set_voltage_sel_regmap,
+       .get_mode = hi6421_regulator_buck_get_mode,
+       .set_mode = hi6421_regulator_buck_set_mode,
+};
+
+static const struct regulator_ops hi6421_buck345_ops = {
+       .is_enabled = regulator_is_enabled_regmap,
+       .enable = hi6421_regulator_enable,
+       .disable = regulator_disable_regmap,
+       .list_voltage = regulator_list_voltage_table,
+       .map_voltage = regulator_map_voltage_ascend,
+       .get_voltage_sel = regulator_get_voltage_sel_regmap,
+       .set_voltage_sel = regulator_set_voltage_sel_regmap,
+       .get_mode = hi6421_regulator_buck_get_mode,
+       .set_mode = hi6421_regulator_buck_set_mode,
+};
+
+static int hi6421_regulator_register(struct platform_device *pdev,
+                                    struct regmap *rmap,
+                                    struct regulator_init_data *init_data,
+                                    int id, struct device_node *np)
+{
+       struct hi6421_regulator_info *info = NULL;
+       struct regulator_config config = { };
+
+       /* assign per-regulator data */
+       info = &hi6421_regulator_info[id];
+       info->dev = &pdev->dev;
+
+       config.dev = &pdev->dev;
+       config.init_data = init_data;
+       config.driver_data = info;
+       config.regmap = rmap;
+       config.of_node = np;
+
+       /* register regulator with framework */
+       info->regulator = devm_regulator_register(&pdev->dev, &info->desc,
+                                               &config);
+       if (IS_ERR(info->regulator)) {
+               dev_err(&pdev->dev, "failed to register regulator %s\n",
+                       info->desc.name);
+               return PTR_ERR(info->regulator);
+       }
+
+       return 0;
+}
+
+static int hi6421_regulator_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np;
+       struct hi6421_pmic *pmic;
+       struct hi6421_regulator_pdata *pdata;
+       int i, ret = 0;
+
+       pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+       if (!pdata)
+               return -ENOMEM;
+       mutex_init(&pdata->lock);
+       platform_set_drvdata(pdev, pdata);
+
+       np = of_get_child_by_name(dev->parent->of_node, "regulators");
+       if (!np)
+               return -ENODEV;
+
+       ret = of_regulator_match(dev, np,
+                                hi6421_regulator_match,
+                                ARRAY_SIZE(hi6421_regulator_match));
+       of_node_put(np);
+       if (ret < 0) {
+               dev_err(dev, "Error parsing regulator init data: %d\n", ret);
+               return ret;
+       }
+
+       pmic = dev_get_drvdata(dev->parent);
+
+       for (i = 0; i < ARRAY_SIZE(hi6421_regulator_info); i++) {
+               ret = hi6421_regulator_register(pdev, pmic->regmap,
+                       hi6421_regulator_match[i].init_data, i,
+                       hi6421_regulator_match[i].of_node);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static struct platform_driver hi6421_regulator_driver = {
+       .driver = {
+               .name   = "hi6421-regulator",
+               .owner  = THIS_MODULE,
+       },
+       .probe  = hi6421_regulator_probe,
+};
+module_platform_driver(hi6421_regulator_driver);
+
+MODULE_AUTHOR("Guodong Xu <guodong.xu@linaro.org>");
+MODULE_DESCRIPTION("Hi6421 regulator driver");
+MODULE_LICENSE("GPL v2");