linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] regulator: msm: Add PM8921 regulator driver
@ 2011-03-30 22:16 David Collins
  2011-03-30 22:17 ` [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
                   ` (2 more replies)
  0 siblings, 3 replies; 16+ messages in thread
From: David Collins @ 2011-03-30 22:16 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, David Brown, Daniel Walker,
	Bryan Huntsman, Samuel Ortiz
  Cc: linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner, David Collins

This patch set adds a new regulator driver to control the regulators in
Qualcomm PM8921 PMIC chips.  It also introduces board file changes to use
this new driver with the Qualcomm MSM 8960 platform.

This patch set depends upon the following outstanding PM8XXX and PM8921
patches:

https://patchwork.kernel.org/patch/643231/
https://patchwork.kernel.org/patch/643221/
https://patchwork.kernel.org/patch/643201/
https://patchwork.kernel.org/patch/643211/
https://patchwork.kernel.org/patch/643241/

David Collins (2):
  regulator: pm8921-regulator: Add regulator driver for PM8921
  msm: board-8960: Add support for pm8921-regulator

 arch/arm/mach-msm/Makefile                  |    1 +
 arch/arm/mach-msm/board-msm8960-regulator.c |  303 ++++
 arch/arm/mach-msm/board-msm8960.c           |    6 +
 arch/arm/mach-msm/board-msm8960.h           |   23 +
 drivers/mfd/pm8921-core.c                   |   32 +-
 drivers/regulator/Kconfig                   |   10 +
 drivers/regulator/Makefile                  |    1 +
 drivers/regulator/pm8921-regulator.c        | 2211 +++++++++++++++++++++++++++
 include/linux/mfd/pm8xxx/pm8921.h           |    3 +
 include/linux/regulator/pm8921-regulator.h  |   97 ++
 10 files changed, 2686 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-msm/board-msm8960-regulator.c
 create mode 100644 arch/arm/mach-msm/board-msm8960.h
 create mode 100644 drivers/regulator/pm8921-regulator.c
 create mode 100644 include/linux/regulator/pm8921-regulator.h

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.


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

* [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-03-30 22:16 [PATCH 0/2] regulator: msm: Add PM8921 regulator driver David Collins
@ 2011-03-30 22:17 ` David Collins
  2011-03-30 23:46   ` Mark Brown
  2011-03-30 22:17 ` [PATCH 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
  2011-04-29 23:07 ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver David Collins
  2 siblings, 1 reply; 16+ messages in thread
From: David Collins @ 2011-03-30 22:17 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, Samuel Ortiz
  Cc: David Brown, Daniel Walker, Bryan Huntsman, linux-kernel,
	linux-arm-msm, linux-arm-kernel, linux-arm-msm-owner,
	David Collins

Create a regulator driver to control all regulators on the Qualcomm
PM8921 PMIC chip.  This chip contains many different types of
regulators with a wide range of abilities and voltage ranges.

Eight different regulator types are available on the PM8921.  These
are managed via 7 different type values in the driver:

LDO      - low drop out regulator (supports both NMOS and PMOS LDOs)
NLDO1200 - 1.2A NMOS LDO (different control structure than other LDOs)
SMPS     - switched-mode power supply
FTSMPS   - fast transient SMPS
VS       - voltage switch
VS300    - 300mA voltage switch (different control structure than
             other switches)
NCP      - negative charge pump

The driver interfaces with the PMIC using Qualcomm's SSBI bus.
Calls to this bus are abtracted through the pm8xxx_readb/writeb API.

Regulator framework operation callback functions work as expected with
one exception: pm8921_vreg_get_mode and pm8921_vreg_set_mode.  These
callbacks are shared by all regulator types in the driver.  The mode
setting scheme was modified from the normal framework usage to
accommodate pin control.  Pin control allows a regulator that has been
disabled by software writing to its control registers to become
enabled by a hardware pin.  Many of the PM8921 regulators support pin
control (as do those on other Qualcomm PMIC chips).  It is primarily
used by peripheral devices that need to be able to actively manage
power usage while the main processor which is connected to the PMIC
SSBI bus is asleep.

Pin control is handled in the PM8921 regulator driver via the set_mode
callback function pm8921_vreg_set_mode.  It treats mode values as
follows:

REGULATOR_MODE_FAST    - set to high power mode (HPM)
REGULATOR_MODE_NORMAL  - remove a vote for pin control
REGULATOR_MODE_IDLE    - vote for pin control (ref-counted)
REGULATOR_MODE_STANDBY - set to low power mode (LPM)

There is an additional caveat that pin control mode will override LPM
such that, a set mode call for LPM will be ignored if the pin control
reference count is greater than 0.  Similarly, HPM will override pin
control mode.  Transitivity is not maintained though, because the mode
can be changed from HPM to LPM.  This ensures that it is still
possible to transition freely between LPM and HPM with calls to
regulator_set_optimum_mode.

Signed-off-by: David Collins <collinsd@codeaurora.org>
---
 drivers/mfd/pm8921-core.c                  |   32 +-
 drivers/regulator/Kconfig                  |   10 +
 drivers/regulator/Makefile                 |    1 +
 drivers/regulator/pm8921-regulator.c       | 2211 ++++++++++++++++++++++++++++
 include/linux/mfd/pm8xxx/pm8921.h          |    3 +
 include/linux/regulator/pm8921-regulator.h |   97 ++
 6 files changed, 2353 insertions(+), 1 deletions(-)
 create mode 100644 drivers/regulator/pm8921-regulator.c
 create mode 100644 include/linux/regulator/pm8921-regulator.h

diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c
index d0a991c..3b2ca69 100644
--- a/drivers/mfd/pm8921-core.c
+++ b/drivers/mfd/pm8921-core.c
@@ -119,8 +119,9 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data
 					   struct pm8921 *pmic,
 					   u32 rev)
 {
-	int ret = 0, irq_base = 0;
+	int ret = 0, irq_base = 0, i;
 	struct pm_irq_chip *irq_chip;
+	static struct mfd_cell *mfd_regulators;
 
 	if (pdata->irq_pdata) {
 		pdata->irq_pdata->irq_cdata.nirqs = PM8921_NR_IRQS;
@@ -162,6 +163,35 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data
 		}
 	}
 
+	/* Add one device for each regulator used by the board. */
+	if (pdata->num_regulators > 0 && pdata->regulator_pdatas) {
+		mfd_regulators = kzalloc(sizeof(struct mfd_cell)
+					 * (pdata->num_regulators), GFP_KERNEL);
+		if (!mfd_regulators) {
+			pr_err("Cannot allocate %d bytes for pm8921 regulator "
+				"mfd cells\n", sizeof(struct mfd_cell)
+						* (pdata->num_regulators));
+			ret = -ENOMEM;
+			goto bail;
+		}
+		for (i = 0; i < pdata->num_regulators; i++) {
+			mfd_regulators[i].name = PM8921_REGULATOR_DEV_NAME;
+			mfd_regulators[i].id = pdata->regulator_pdatas[i].id;
+			mfd_regulators[i].platform_data =
+				&(pdata->regulator_pdatas[i]);
+			mfd_regulators[i].data_size =
+				sizeof(struct pm8921_regulator_platform_data);
+		}
+		ret = mfd_add_devices(pmic->dev, 0, mfd_regulators,
+				pdata->num_regulators, NULL, irq_base);
+		kfree(mfd_regulators);
+		if (ret) {
+			pr_err("Failed to add regulator subdevices ret=%d\n",
+				ret);
+			goto bail;
+		}
+	}
+
 	return 0;
 bail:
 	if (pmic->irq_chip) {
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index e1d9436..c212156 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -272,5 +272,15 @@ config REGULATOR_TPS6524X
 	  serial interface currently supported on the sequencer serial
 	  port controller.
 
+config REGULATOR_PM8921
+	tristate "Qualcomm PM8921 PMIC Power regulators"
+	depends on MFD_PM8921_CORE
+	default y if MFD_PM8921_CORE
+	help
+	  This driver supports voltage regulators in the Qualcomm PM8921 PMIC
+	  chip.  The PM8921 provides several different varieties of LDO and
+	  switching regulators.  It also provides a negative charge pump and
+	  voltage switches.
+
 endif
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 0b5e88c..bec35ac 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -40,5 +40,6 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o
 obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o
 obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
 obj-$(CONFIG_REGULATOR_AB8500)	+= ab8500.o
+obj-$(CONFIG_REGULATOR_PM8921) += pm8921-regulator.o
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/pm8921-regulator.c b/drivers/regulator/pm8921-regulator.c
new file mode 100644
index 0000000..f686757
--- /dev/null
+++ b/drivers/regulator/pm8921-regulator.c
@@ -0,0 +1,2211 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/pm8921-regulator.h>
+#include <linux/mfd/pm8xxx/core.h>
+
+#define REGULATOR_TYPE_LDO		0
+#define REGULATOR_TYPE_NLDO1200		1
+#define REGULATOR_TYPE_SMPS		2
+#define REGULATOR_TYPE_FTSMPS		3
+#define REGULATOR_TYPE_VS		4
+#define REGULATOR_TYPE_VS300		5
+#define REGULATOR_TYPE_NCP		6
+
+/* Common Masks */
+#define REGULATOR_ENABLE_MASK		0x80
+#define REGULATOR_ENABLE		0x80
+#define REGULATOR_DISABLE		0x00
+
+#define REGULATOR_BANK_MASK		0xF0
+#define REGULATOR_BANK_SEL(n)		((n) << 4)
+#define REGULATOR_BANK_WRITE		0x80
+
+#define LDO_TEST_BANKS			7
+#define NLDO1200_TEST_BANKS		5
+#define SMPS_TEST_BANKS			8
+#define REGULATOR_TEST_BANKS_MAX	SMPS_TEST_BANKS
+
+/* LDO masks and values */
+
+/* CTRL register */
+#define LDO_ENABLE_MASK			0x80
+#define LDO_ENABLE			0x80
+#define LDO_PULL_DOWN_ENABLE_MASK	0x40
+#define LDO_PULL_DOWN_ENABLE		0x40
+
+#define LDO_CTRL_PM_MASK		0x20
+#define LDO_CTRL_PM_HPM			0x00
+#define LDO_CTRL_PM_LPM			0x20
+
+#define LDO_CTRL_VPROG_MASK		0x1F
+
+/* TEST register bank 0 */
+#define LDO_TEST_LPM_MASK		0x40
+#define LDO_TEST_LPM_SEL_CTRL		0x00
+#define LDO_TEST_LPM_SEL_TCXO		0x40
+
+/* TEST register bank 2 */
+#define LDO_TEST_VPROG_UPDATE_MASK	0x08
+#define LDO_TEST_RANGE_SEL_MASK		0x04
+#define LDO_TEST_FINE_STEP_MASK		0x02
+#define LDO_TEST_FINE_STEP_SHIFT	1
+
+/* TEST register bank 4 */
+#define LDO_TEST_RANGE_EXT_MASK		0x01
+
+/* TEST register bank 5 */
+#define LDO_TEST_PIN_CTRL_MASK		0x0F
+#define LDO_TEST_PIN_CTRL_EN3		0x08
+#define LDO_TEST_PIN_CTRL_EN2		0x04
+#define LDO_TEST_PIN_CTRL_EN1		0x02
+#define LDO_TEST_PIN_CTRL_EN0		0x01
+
+/* TEST register bank 6 */
+#define LDO_TEST_PIN_CTRL_LPM_MASK	0x0F
+
+/* Allowable voltage ranges */
+#define PLDO_LOW_UV_MIN			750000
+#define PLDO_LOW_UV_MAX			1537500
+#define PLDO_LOW_FINE_STEP_UV		12500
+
+#define PLDO_NORM_UV_MIN		1500000
+#define PLDO_NORM_UV_MAX		3075000
+#define PLDO_NORM_FINE_STEP_UV		25000
+
+#define PLDO_HIGH_UV_MIN		1750000
+#define PLDO_HIGH_UV_MAX		4900000
+#define PLDO_HIGH_FINE_STEP_UV		50000
+
+#define NLDO_UV_MIN			750000
+#define NLDO_UV_MAX			1537500
+#define NLDO_FINE_STEP_UV		12500
+
+/* NLDO1200 masks and values */
+
+/* CTRL register */
+#define NLDO1200_ENABLE_MASK		0x80
+#define NLDO1200_LDO_ENABLE		0x80
+
+/* Legacy mode */
+#define NLDO1200_LEGACY_PM_MASK		0x20
+#define NLDO1200_LEGACY_PM_HPM		0x00
+#define NLDO1200_LEGACY_PM_LPM		0x20
+
+/* Advanced mode */
+#define NLDO1200_CTRL_RANGE_MASK	0x40
+#define NLDO1200_CTRL_RANGE_HIGH	0x00
+#define NLDO1200_CTRL_RANGE_LOW		0x40
+#define NLDO1200_CTRL_VPROG_MASK	0x3F
+
+#define NLDO1200_LOW_UV_MIN		375000
+#define NLDO1200_LOW_UV_MAX		768750
+#define NLDO1200_LOW_UV_STEP		6250
+
+#define NLDO1200_HIGH_UV_MIN		750000
+#define NLDO1200_HIGH_UV_MAX		1537500
+#define NLDO1200_HIGH_UV_STEP		12500
+
+/* TEST register bank 0 */
+#define NLDO1200_TEST_LPM_MASK		0x04
+#define NLDO1200_TEST_LPM_SEL_CTRL	0x00
+#define NLDO1200_TEST_LPM_SEL_TCXO	0x04
+
+/* TEST register bank 1 */
+#define NLDO1200_PULL_DOWN_ENABLE_MASK	0x02
+#define NLDO1200_PULL_DOWN_ENABLE	0x02
+
+/* TEST register bank 2 */
+#define NLDO1200_ADVANCED_MODE_MASK	0x08
+#define NLDO1200_ADVANCED_MODE		0x00
+#define NLDO1200_LEGACY_MODE		0x08
+
+/* Advanced mode power mode control */
+#define NLDO1200_ADVANCED_PM_MASK	0x02
+#define NLDO1200_ADVANCED_PM_HPM	0x00
+#define NLDO1200_ADVANCED_PM_LPM	0x02
+
+#define NLDO1200_IN_ADVANCED_MODE(vreg) \
+	((vreg->test_reg[2] & NLDO1200_ADVANCED_MODE_MASK) \
+	 == NLDO1200_ADVANCED_MODE)
+
+/* SMPS masks and values */
+
+/* CTRL register */
+
+/* Legacy mode */
+#define SMPS_LEGACY_ENABLE		0x80
+#define SMPS_LEGACY_PULL_DOWN_ENABLE	0x40
+#define SMPS_LEGACY_VREF_SEL_MASK	0x20
+#define SMPS_LEGACY_VPROG_MASK		0x1F
+
+/* Advanced mode */
+#define SMPS_ADVANCED_BAND_MASK		0xC0
+#define SMPS_ADVANCED_BAND_OFF		0x00
+#define SMPS_ADVANCED_BAND_1		0x40
+#define SMPS_ADVANCED_BAND_2		0x80
+#define SMPS_ADVANCED_BAND_3		0xC0
+#define SMPS_ADVANCED_VPROG_MASK	0x3F
+
+/* Legacy mode voltage ranges */
+#define SMPS_MODE1_UV_MIN		1500000
+#define SMPS_MODE1_UV_MAX		3050000
+#define SMPS_MODE1_UV_STEP		50000
+
+#define SMPS_MODE2_UV_MIN		750000
+#define SMPS_MODE2_UV_MAX		1525000
+#define SMPS_MODE2_UV_STEP		25000
+
+#define SMPS_MODE3_UV_MIN		375000
+#define SMPS_MODE3_UV_MAX		1150000
+#define SMPS_MODE3_UV_STEP		25000
+
+/* Advanced mode voltage ranges */
+#define SMPS_BAND3_UV_MIN		1500000
+#define SMPS_BAND3_UV_MAX		3075000
+#define SMPS_BAND3_UV_STEP		25000
+
+#define SMPS_BAND2_UV_MIN		750000
+#define SMPS_BAND2_UV_MAX		1537500
+#define SMPS_BAND2_UV_STEP		12500
+
+#define SMPS_BAND1_UV_MIN		375000
+#define SMPS_BAND1_UV_MAX		1162500
+#define SMPS_BAND1_UV_STEP		12500
+
+#define SMPS_UV_MIN			SMPS_MODE3_UV_MIN
+#define SMPS_UV_MAX			SMPS_MODE1_UV_MAX
+
+/* Test2 register bank 1 */
+#define SMPS_LEGACY_VLOW_SEL_MASK	0x01
+
+/* Test2 register bank 6 */
+#define SMPS_ADVANCED_PULL_DOWN_ENABLE	0x08
+
+/* Test2 register bank 7 */
+#define SMPS_ADVANCED_MODE_MASK		0x02
+#define SMPS_ADVANCED_MODE		0x02
+#define SMPS_LEGACY_MODE		0x00
+
+#define SMPS_IN_ADVANCED_MODE(vreg) \
+	((vreg->test_reg[7] & SMPS_ADVANCED_MODE_MASK) == SMPS_ADVANCED_MODE)
+
+/* BUCK_SLEEP_CNTRL register */
+#define SMPS_PIN_CTRL_MASK		0xF0
+#define SMPS_PIN_CTRL_A1		0x80
+#define SMPS_PIN_CTRL_A0		0x40
+#define SMPS_PIN_CTRL_D1		0x20
+#define SMPS_PIN_CTRL_D0		0x10
+
+#define SMPS_PIN_CTRL_LPM_MASK		0x0F
+#define SMPS_PIN_CTRL_LPM_A1		0x08
+#define SMPS_PIN_CTRL_LPM_A0		0x04
+#define SMPS_PIN_CTRL_LPM_D1		0x02
+#define SMPS_PIN_CTRL_LPM_D0		0x01
+
+/* BUCK_CLOCK_CNTRL register */
+#define SMPS_CLK_DIVIDE2		0x40
+
+#define SMPS_CLK_CTRL_MASK		0x30
+#define SMPS_CLK_CTRL_FOLLOW_TCXO	0x00
+#define SMPS_CLK_CTRL_PWM		0x10
+#define SMPS_CLK_CTRL_PFM		0x20
+
+/* FTSMPS masks and values */
+
+/* CTRL register */
+#define FTSMPS_VCTRL_BAND_MASK		0xC0
+#define FTSMPS_VCTRL_BAND_OFF		0x00
+#define FTSMPS_VCTRL_BAND_1		0x40
+#define FTSMPS_VCTRL_BAND_2		0x80
+#define FTSMPS_VCTRL_BAND_3		0xC0
+#define FTSMPS_VCTRL_VPROG_MASK		0x3F
+
+#define FTSMPS_BAND1_UV_MIN		350000
+#define FTSMPS_BAND1_UV_MAX		650000
+#define FTSMPS_BAND1_UV_STEP		6250
+
+#define FTSMPS_BAND2_UV_MIN		700000
+#define FTSMPS_BAND2_UV_MAX		1400000
+#define FTSMPS_BAND2_UV_STEP		12500
+
+#define FTSMPS_BAND3_UV_SETPOINT_MIN	1500000
+#define FTSMPS_BAND3_UV_MIN		1400000
+#define FTSMPS_BAND3_UV_MAX		3300000
+#define FTSMPS_BAND3_UV_STEP		50000
+
+#define FTSMPS_UV_MIN			FTSMPS_BAND_1_UV_MIN
+#define FTSMPS_UV_MAX			FTSMPS_BAND_3_UV_MAX
+
+/* FTS_CNFG1 register bank 0 */
+#define FTSMPS_CNFG1_PM_MASK		0x0C
+#define FTSMPS_CNFG1_PM_PWM		0x00
+#define FTSMPS_CNFG1_PM_PFM		0x08
+
+/* PWR_CNFG register */
+#define FTSMPS_PULL_DOWN_ENABLE_MASK	0x40
+#define FTSMPS_PULL_DOWN_ENABLE		0x40
+
+/*
+ * Band 1 of PMIC 8921 FTSMPS regulators only supports set points with the 3
+ * LSB's equal to 0.  This is accomplished in the macro by truncating the bits.
+ */
+#define PM8921_FTSMPS_BAND_1_COMPENSATE(vprog)	((vprog) & ~0x7)
+
+/* VS masks and values */
+
+/* CTRL register */
+#define VS_ENABLE_MASK			0x80
+#define VS_ENABLE			0x80
+#define VS_PULL_DOWN_ENABLE_MASK	0x40
+#define VS_PULL_DOWN_ENABLE		0x40
+
+#define VS_PIN_CTRL_MASK		0x0F
+#define VS_PIN_CTRL_EN0			0x08
+#define VS_PIN_CTRL_EN1			0x04
+#define VS_PIN_CTRL_EN2			0x02
+#define VS_PIN_CTRL_EN3			0x01
+
+/* VS300 masks and values */
+
+/* CTRL register */
+#define VS300_CTRL_ENABLE_MASK		0xC0
+#define VS300_CTRL_DISABLE		0x00
+#define VS300_CTRL_ENABLE		0x40
+
+#define VS300_PULL_DOWN_ENABLE_MASK	0x20
+#define VS300_PULL_DOWN_ENABLE		0x20
+
+/* NCP masks and values */
+
+/* CTRL register */
+#define NCP_ENABLE_MASK			0x80
+#define NCP_ENABLE			0x80
+#define NCP_VPROG_MASK			0x1F
+
+#define NCP_UV_MIN			1500000
+#define NCP_UV_MAX			3050000
+#define NCP_UV_STEP			50000
+
+struct pm8921_vreg {
+	/* Configuration data */
+	struct regulator_dev			*rdev;
+	struct device				*dev;
+	const char				*name;
+	struct pm8921_regulator_platform_data	pdata;
+	const int				hpm_min_load;
+	const u16				ctrl_addr;
+	const u16				test_addr;
+	const u16				clk_ctrl_addr;
+	const u16				sleep_ctrl_addr;
+	const u16				pfm_ctrl_addr;
+	const u16				pwr_cnfg_addr;
+	const u8				type;
+	/* State data */
+	int					save_uV;
+	int					save_uA;
+	unsigned				pc_vote;
+	unsigned				optimum;
+	u8				test_reg[REGULATOR_TEST_BANKS_MAX];
+	u8					ctrl_reg;
+	u8					clk_ctrl_reg;
+	u8					sleep_ctrl_reg;
+	u8					pfm_ctrl_reg;
+	u8					pwr_cnfg_reg;
+	u8					state;
+};
+
+#define vreg_err(vreg, fmt, ...) \
+	pr_err("%s: " fmt, vreg->name, ##__VA_ARGS__)
+
+#define LDO(_id, _ctrl_addr, _test_addr, _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_LDO, \
+		.ctrl_addr	= _ctrl_addr, \
+		.test_addr	= _test_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define NLDO1200(_id, _ctrl_addr, _test_addr, _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_NLDO1200, \
+		.ctrl_addr	= _ctrl_addr, \
+		.test_addr	= _test_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define SMPS(_id, _ctrl_addr, _test_addr, _clk_ctrl_addr, _sleep_ctrl_addr, \
+	     _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_SMPS, \
+		.ctrl_addr	= _ctrl_addr, \
+		.test_addr	= _test_addr, \
+		.clk_ctrl_addr	= _clk_ctrl_addr, \
+		.sleep_ctrl_addr = _sleep_ctrl_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define FTSMPS(_id, _pwm_ctrl_addr, _fts_cnfg1_addr, _pfm_ctrl_addr, \
+	       _pwr_cnfg_addr, _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_FTSMPS, \
+		.ctrl_addr	= _pwm_ctrl_addr, \
+		.test_addr	= _fts_cnfg1_addr, \
+		.pfm_ctrl_addr = _pfm_ctrl_addr, \
+		.pwr_cnfg_addr = _pwr_cnfg_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define VS(_id, _ctrl_addr) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_VS, \
+		.ctrl_addr	= _ctrl_addr, \
+	}
+
+#define VS300(_id, _ctrl_addr) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_VS300, \
+		.ctrl_addr	= _ctrl_addr, \
+	}
+
+#define NCP(_id, _ctrl_addr) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_NCP, \
+		.ctrl_addr	= _ctrl_addr, \
+	}
+
+static struct pm8921_vreg pm8921_vreg[] = {
+	/*  id   ctrl   test   hpm_min */
+	LDO(L1,  0x0AE, 0x0AF, LDO_150),
+	LDO(L2,  0x0B0, 0x0B1, LDO_150),
+	LDO(L3,  0x0B2, 0x0B3, LDO_150),
+	LDO(L4,  0x0B4, 0x0B5, LDO_50),
+	LDO(L5,  0x0B6, 0x0B7, LDO_300),
+	LDO(L6,  0x0B8, 0x0B9, LDO_600),
+	LDO(L7,  0x0BA, 0x0BB, LDO_150),
+	LDO(L8,  0x0BC, 0x0BD, LDO_300),
+	LDO(L9,  0x0BE, 0x0BF, LDO_300),
+	LDO(L10, 0x0C0, 0x0C1, LDO_600),
+	LDO(L11, 0x0C2, 0x0C3, LDO_150),
+	LDO(L12, 0x0C4, 0x0C5, LDO_150),
+	LDO(L14, 0x0C8, 0x0C9, LDO_50),
+	LDO(L15, 0x0CA, 0x0CB, LDO_150),
+	LDO(L16, 0x0CC, 0x0CD, LDO_300),
+	LDO(L17, 0x0CE, 0x0CF, LDO_150),
+	LDO(L18, 0x0D0, 0x0D1, LDO_150),
+	LDO(L21, 0x0D6, 0x0D7, LDO_150),
+	LDO(L22, 0x0D8, 0x0D9, LDO_150),
+	LDO(L23, 0x0DA, 0x0DB, LDO_150),
+
+	/*       id   ctrl   test   hpm_min */
+	NLDO1200(L24, 0x0DC, 0x0DD, LDO_1200),
+	NLDO1200(L25, 0x0DE, 0x0DF, LDO_1200),
+	NLDO1200(L26, 0x0E0, 0x0E1, LDO_1200),
+	NLDO1200(L27, 0x0E2, 0x0E3, LDO_1200),
+	NLDO1200(L28, 0x0E4, 0x0E5, LDO_1200),
+
+	/*  id   ctrl   test   hpm_min */
+	LDO(L29, 0x0E6, 0x0E7, LDO_150),
+
+	/*   id  ctrl   test2  clk    sleep  hpm_min */
+	SMPS(S1, 0x1D0, 0x1D5, 0x009, 0x1D2, SMPS_1500),
+	SMPS(S2, 0x1D8, 0x1DD, 0x00A, 0x1DA, SMPS_1500),
+	SMPS(S3, 0x1E0, 0x1E5, 0x00B, 0x1E2, SMPS_1500),
+	SMPS(S4, 0x1E8, 0x1ED, 0x011, 0x1EA, SMPS_1500),
+
+	/*     id  ctrl fts_cnfg1 pfm  pwr_cnfg  hpm_min */
+	FTSMPS(S5, 0x025, 0x02E, 0x026, 0x032, SMPS_2000),
+	FTSMPS(S6, 0x036, 0x03F, 0x037, 0x043, SMPS_2000),
+
+	/*   id  ctrl   test2  clk    sleep  hpm_min */
+	SMPS(S7, 0x1F0, 0x1F5, 0x012, 0x1F2, SMPS_1500),
+	SMPS(S8, 0x1F8, 0x1FD, 0x013, 0x1FA, SMPS_1500),
+
+	/* id		ctrl */
+	VS(LVS1,	0x060),
+	VS300(LVS2,     0x062),
+	VS(LVS3,	0x064),
+	VS(LVS4,	0x066),
+	VS(LVS5,	0x068),
+	VS(LVS6,	0x06A),
+	VS(LVS7,	0x06C),
+	VS300(USB_OTG,  0x06E),
+	VS300(HDMI_MVS, 0x070),
+
+	/*  id   ctrl */
+	NCP(NCP, 0x090),
+};
+
+static int pm8921_vreg_write(struct pm8921_vreg *vreg, u16 addr, u8 val,
+			     u8 mask, u8 *reg_save)
+{
+	int rc = 0;
+	u8 reg;
+
+	reg = (*reg_save & ~mask) | (val & mask);
+	if (reg != *reg_save)
+		rc = pm8xxx_writeb(vreg->dev->parent, addr, reg);
+
+	if (rc)
+		pr_err("pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", addr, rc);
+	else
+		*reg_save = reg;
+
+	return rc;
+}
+
+static int pm8921_vreg_is_pin_controlled(struct pm8921_vreg *vreg)
+{
+	int ret = 0;
+
+	switch (vreg->type) {
+	case REGULATOR_TYPE_LDO:
+		ret = ((vreg->test_reg[5] & LDO_TEST_PIN_CTRL_MASK) << 4)
+			| (vreg->test_reg[6] & LDO_TEST_PIN_CTRL_LPM_MASK);
+		break;
+	case REGULATOR_TYPE_SMPS:
+		ret = vreg->sleep_ctrl_reg
+			& (SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK);
+		break;
+	case REGULATOR_TYPE_VS:
+		ret = vreg->ctrl_reg & VS_PIN_CTRL_MASK;
+		break;
+	}
+
+	return ret;
+}
+
+static int _pm8921_vreg_is_enabled(struct pm8921_vreg *vreg)
+{
+	int rc = 0;
+
+	/*
+	 * All regulator types except advanced mode SMPS, FTSMPS, and VS300 have
+	 * enable bit in bit 7 of the control register.  Pin control also does
+	 * not work for advanced mode SMPS.
+	 */
+	switch (vreg->type) {
+	case REGULATOR_TYPE_FTSMPS:
+		if ((vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK)
+		    != FTSMPS_VCTRL_BAND_OFF)
+			rc = 1;
+		break;
+	case REGULATOR_TYPE_VS300:
+		if ((vreg->ctrl_reg & VS300_CTRL_ENABLE_MASK)
+		    != VS300_CTRL_DISABLE)
+			rc = 1;
+		break;
+	case REGULATOR_TYPE_SMPS:
+		if (SMPS_IN_ADVANCED_MODE(vreg)) {
+			if ((vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK)
+			    != SMPS_ADVANCED_BAND_OFF)
+				rc = 1;
+		} else if ((vreg->ctrl_reg & SMPS_LEGACY_ENABLE)
+			   || pm8921_vreg_is_pin_controlled(vreg)) {
+			rc = 1;
+		}
+		break;
+	default:
+		if (((vreg->ctrl_reg & REGULATOR_ENABLE_MASK)
+			== REGULATOR_ENABLE)
+		    || pm8921_vreg_is_pin_controlled(vreg))
+			rc = 1;
+	}
+
+	return rc;
+}
+
+static int pm8921_vreg_is_enabled(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_vreg_is_enabled(vreg);
+}
+
+static int pm8921_pldo_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int vmin, fine_step;
+	u8 range_ext, range_sel, vprog, fine_step_reg;
+
+	fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK;
+	range_sel = vreg->test_reg[2] & LDO_TEST_RANGE_SEL_MASK;
+	range_ext = vreg->test_reg[4] & LDO_TEST_RANGE_EXT_MASK;
+	vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK;
+
+	vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT);
+
+	if (range_sel) {
+		/* low range mode */
+		fine_step = PLDO_LOW_FINE_STEP_UV;
+		vmin = PLDO_LOW_UV_MIN;
+	} else if (!range_ext) {
+		/* normal mode */
+		fine_step = PLDO_NORM_FINE_STEP_UV;
+		vmin = PLDO_NORM_UV_MIN;
+	} else {
+		/* high range mode */
+		fine_step = PLDO_HIGH_FINE_STEP_UV;
+		vmin = PLDO_HIGH_UV_MIN;
+	}
+
+	return fine_step * vprog + vmin;
+}
+
+static int pm8921_pldo_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int vmin, rc = 0;
+	unsigned vprog, fine_step;
+	u8 range_ext, range_sel, fine_step_reg;
+
+	if (min_uV < PLDO_LOW_UV_MIN || min_uV > PLDO_HIGH_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", min_uV);
+		return -EINVAL;
+	}
+
+	if (min_uV < PLDO_NORM_UV_MIN) {
+		vmin = PLDO_LOW_UV_MIN;
+		fine_step = PLDO_LOW_FINE_STEP_UV;
+		range_ext = 0;
+		range_sel = LDO_TEST_RANGE_SEL_MASK;
+	} else if (min_uV < PLDO_NORM_UV_MAX + PLDO_NORM_FINE_STEP_UV) {
+		vmin = PLDO_NORM_UV_MIN;
+		fine_step = PLDO_NORM_FINE_STEP_UV;
+		range_ext = 0;
+		range_sel = 0;
+	} else {
+		vmin = PLDO_HIGH_UV_MIN;
+		fine_step = PLDO_HIGH_FINE_STEP_UV;
+		range_ext = LDO_TEST_RANGE_EXT_MASK;
+		range_sel = 0;
+	}
+
+	vprog = (min_uV - vmin) / fine_step;
+	fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT;
+	vprog >>= 1;
+
+	/*
+	 * Disable program voltage update if range extension, range select,
+	 * or fine step have changed and the regulator is enabled.
+	 */
+	if (_pm8921_vreg_is_enabled(vreg) &&
+		(((range_ext ^ vreg->test_reg[4]) & LDO_TEST_RANGE_EXT_MASK)
+		|| ((range_sel ^ vreg->test_reg[2]) & LDO_TEST_RANGE_SEL_MASK)
+		|| ((fine_step_reg ^ vreg->test_reg[2])
+			& LDO_TEST_FINE_STEP_MASK))) {
+		rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			REGULATOR_BANK_SEL(2) | REGULATOR_BANK_WRITE,
+			REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK,
+			&vreg->test_reg[2]);
+		if (rc)
+			goto bail;
+	}
+
+	/* Write new voltage. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, vprog,
+				LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Write range extension. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			range_ext | REGULATOR_BANK_SEL(4)
+			 | REGULATOR_BANK_WRITE,
+			LDO_TEST_RANGE_EXT_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[4]);
+	if (rc)
+		goto bail;
+
+	/* Write fine step, range select and program voltage update. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			fine_step_reg | range_sel | REGULATOR_BANK_SEL(2)
+			 | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK,
+			LDO_TEST_FINE_STEP_MASK | LDO_TEST_RANGE_SEL_MASK
+			 | REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK,
+			&vreg->test_reg[2]);
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	u8 vprog, fine_step_reg;
+
+	fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK;
+	vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK;
+
+	vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT);
+
+	return NLDO_FINE_STEP_UV * vprog + NLDO_UV_MIN;
+}
+
+static int pm8921_nldo_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned vprog, fine_step_reg;
+	int rc;
+
+	if (min_uV < NLDO_UV_MIN || min_uV > NLDO_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", min_uV);
+		return -EINVAL;
+	}
+
+	vprog = (min_uV - NLDO_UV_MIN) / NLDO_FINE_STEP_UV;
+	fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT;
+	vprog >>= 1;
+
+	/* Write new voltage. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, vprog,
+				LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Write fine step. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			fine_step_reg | REGULATOR_BANK_SEL(2)
+			 | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK,
+			LDO_TEST_FINE_STEP_MASK | REGULATOR_BANK_MASK
+			 | LDO_TEST_VPROG_UPDATE_MASK,
+		       &vreg->test_reg[2]);
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int _pm8921_nldo1200_get_voltage(struct pm8921_vreg *vreg)
+{
+	int vprog, uV = 0;
+
+	if (!NLDO1200_IN_ADVANCED_MODE(vreg)) {
+		pr_warn("%s: currently in legacy mode; voltage unknown.\n",
+			vreg->name);
+		return vreg->save_uV;
+	}
+
+	vprog = vreg->ctrl_reg & NLDO1200_CTRL_VPROG_MASK;
+
+	if ((vreg->ctrl_reg & NLDO1200_CTRL_RANGE_MASK)
+	    == NLDO1200_CTRL_RANGE_LOW)
+		uV = vprog * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN;
+	else
+		uV = vprog * NLDO1200_HIGH_UV_STEP + NLDO1200_HIGH_UV_MIN;
+
+	return uV;
+}
+
+static int pm8921_nldo1200_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_nldo1200_get_voltage(vreg);
+}
+
+static int _pm8921_nldo1200_set_voltage(struct pm8921_vreg *vreg, int uV)
+{
+	u8 vprog, range;
+	int rc;
+
+	if (uV < NLDO1200_LOW_UV_MIN || uV > NLDO1200_HIGH_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", uV);
+		return -EINVAL;
+	}
+
+	if (uV < NLDO1200_HIGH_UV_MIN) {
+		vprog = ((uV - NLDO1200_LOW_UV_MIN) / NLDO1200_LOW_UV_STEP);
+		vprog &= NLDO1200_CTRL_VPROG_MASK;
+		range = NLDO1200_CTRL_RANGE_LOW;
+	} else {
+		vprog = ((uV - NLDO1200_HIGH_UV_MIN) / NLDO1200_HIGH_UV_STEP);
+		vprog &= NLDO1200_CTRL_VPROG_MASK;
+		range = NLDO1200_CTRL_RANGE_HIGH;
+	}
+
+	/* Set to advanced mode */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+		NLDO1200_ADVANCED_MODE, NLDO1200_ADVANCED_MODE_MASK,
+		&vreg->test_reg[2]);
+	if (rc)
+		goto bail;
+
+	/* Set voltage and range selection. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, vprog | range,
+			NLDO1200_CTRL_VPROG_MASK | NLDO1200_CTRL_RANGE_MASK,
+			&vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo1200_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_nldo1200_set_voltage(vreg, min_uV);
+}
+
+static int pm8921_smps_get_voltage_advanced(struct pm8921_vreg *vreg)
+{
+	u8 vprog, band;
+	int uV = 0;
+
+	vprog = vreg->ctrl_reg & SMPS_ADVANCED_VPROG_MASK;
+	band = vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK;
+
+	if (band == SMPS_ADVANCED_BAND_1)
+		uV = vprog * SMPS_BAND1_UV_STEP + SMPS_BAND1_UV_MIN;
+	else if (band == SMPS_ADVANCED_BAND_2)
+		uV = vprog * SMPS_BAND2_UV_STEP + SMPS_BAND2_UV_MIN;
+	else if (band == SMPS_ADVANCED_BAND_3)
+		uV = vprog * SMPS_BAND3_UV_STEP + SMPS_BAND3_UV_MIN;
+	else
+		uV = vreg->save_uV;
+
+	return uV;
+}
+
+static int pm8921_smps_get_voltage_legacy(struct pm8921_vreg *vreg)
+{
+	u8 vlow, vref, vprog;
+	int uV;
+
+	vlow = vreg->test_reg[1] & SMPS_LEGACY_VLOW_SEL_MASK;
+	vref = vreg->ctrl_reg & SMPS_LEGACY_VREF_SEL_MASK;
+	vprog = vreg->ctrl_reg & SMPS_LEGACY_VPROG_MASK;
+
+	if (vlow && vref) {
+		/* mode 3 */
+		uV = vprog * SMPS_MODE3_UV_STEP + SMPS_MODE3_UV_MIN;
+	} else if (vref) {
+		/* mode 2 */
+		uV = vprog * SMPS_MODE2_UV_STEP + SMPS_MODE2_UV_MIN;
+	} else {
+		/* mode 1 */
+		uV = vprog * SMPS_MODE1_UV_STEP + SMPS_MODE1_UV_MIN;
+	}
+
+	return uV;
+}
+
+static int _pm8921_smps_get_voltage(struct pm8921_vreg *vreg)
+{
+	if (SMPS_IN_ADVANCED_MODE(vreg))
+		return pm8921_smps_get_voltage_advanced(vreg);
+
+	return pm8921_smps_get_voltage_legacy(vreg);
+}
+
+static int pm8921_smps_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_smps_get_voltage(vreg);
+}
+
+static int pm8921_smps_set_voltage_advanced(struct pm8921_vreg *vreg, int uV,
+					    int force_on)
+{
+	u8 vprog, band;
+	int rc, new_uV;
+
+	if (uV < SMPS_BAND1_UV_MIN || uV > SMPS_BAND3_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", uV);
+		return -EINVAL;
+	}
+
+	if (uV < SMPS_BAND2_UV_MIN) {
+		vprog = ((uV - SMPS_BAND1_UV_MIN) / SMPS_BAND1_UV_STEP);
+		band = SMPS_ADVANCED_BAND_1;
+		new_uV = SMPS_BAND1_UV_MIN + vprog * SMPS_BAND1_UV_STEP;
+	} else if (uV < SMPS_BAND3_UV_MIN) {
+		vprog = ((uV - SMPS_BAND2_UV_MIN) / SMPS_BAND2_UV_STEP);
+		band = SMPS_ADVANCED_BAND_2;
+		new_uV = SMPS_BAND2_UV_MIN + vprog * SMPS_BAND2_UV_STEP;
+	} else {
+		vprog = ((uV - SMPS_BAND3_UV_MIN) / SMPS_BAND3_UV_STEP);
+		band = SMPS_ADVANCED_BAND_3;
+		new_uV = SMPS_BAND3_UV_MIN + vprog * SMPS_BAND3_UV_STEP;
+	}
+
+	/* Do not set band if regulator currently disabled. */
+	if (!_pm8921_vreg_is_enabled(vreg) && !force_on)
+		band = SMPS_ADVANCED_BAND_OFF;
+
+	/* Set advanced mode bit to 1. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr, SMPS_ADVANCED_MODE
+		| REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7),
+		SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK,
+		&vreg->test_reg[7]);
+	if (rc)
+		goto bail;
+
+	/* Set voltage and voltage band. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, band | vprog,
+			SMPS_ADVANCED_BAND_MASK | SMPS_ADVANCED_VPROG_MASK,
+			&vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	vreg->save_uV = new_uV;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_set_voltage_legacy(struct pm8921_vreg *vreg, int uV)
+{
+	u8 vlow, vref, vprog, pd, en;
+	int rc;
+
+	if (uV < SMPS_MODE3_UV_MIN || uV > SMPS_MODE1_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", uV);
+		return -EINVAL;
+	}
+
+	if (uV < SMPS_MODE2_UV_MIN) {
+		vprog = ((uV - SMPS_MODE3_UV_MIN) / SMPS_MODE3_UV_STEP);
+		vref = SMPS_LEGACY_VREF_SEL_MASK;
+		vlow = SMPS_LEGACY_VLOW_SEL_MASK;
+	} else if (uV < SMPS_MODE1_UV_MIN) {
+		vprog = ((uV - SMPS_MODE2_UV_MIN) / SMPS_MODE2_UV_STEP);
+		vref = SMPS_LEGACY_VREF_SEL_MASK;
+		vlow = 0;
+	} else {
+		vprog = ((uV - SMPS_MODE1_UV_MIN) / SMPS_MODE1_UV_STEP);
+		vref = 0;
+		vlow = 0;
+	}
+
+	/* set vlow bit for ultra low voltage mode */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+		vlow | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(1),
+		REGULATOR_BANK_MASK | SMPS_LEGACY_VLOW_SEL_MASK,
+		&vreg->test_reg[1]);
+	if (rc)
+		goto bail;
+
+	/* Set advanced mode bit to 0. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr, SMPS_LEGACY_MODE
+		| REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7),
+		SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK,
+		&vreg->test_reg[7]);
+	if (rc)
+		goto bail;
+
+	en = (_pm8921_vreg_is_enabled(vreg) ? SMPS_LEGACY_ENABLE : 0);
+	pd = (vreg->pdata.pull_down_enable ? SMPS_LEGACY_PULL_DOWN_ENABLE : 0);
+
+	/* Set voltage (and the rest of the control register). */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, en | pd | vref | vprog,
+		SMPS_LEGACY_ENABLE | SMPS_LEGACY_PULL_DOWN_ENABLE
+		| SMPS_LEGACY_VREF_SEL_MASK | SMPS_LEGACY_VPROG_MASK,
+		&vreg->ctrl_reg);
+
+	vreg->save_uV = pm8921_smps_get_voltage_legacy(vreg);
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	if (SMPS_IN_ADVANCED_MODE(vreg) || !pm8921_vreg_is_pin_controlled(vreg))
+		rc = pm8921_smps_set_voltage_advanced(vreg, min_uV, 0);
+	else
+		rc = pm8921_smps_set_voltage_legacy(vreg, min_uV);
+
+	return rc;
+}
+
+static int _pm8921_ftsmps_get_voltage(struct pm8921_vreg *vreg)
+{
+	u8 vprog, band;
+	int uV = 0;
+
+	if ((vreg->test_reg[0] & FTSMPS_CNFG1_PM_MASK) == FTSMPS_CNFG1_PM_PFM) {
+		vprog = vreg->pfm_ctrl_reg & FTSMPS_VCTRL_VPROG_MASK;
+		band = vreg->pfm_ctrl_reg & FTSMPS_VCTRL_BAND_MASK;
+		if (band == FTSMPS_VCTRL_BAND_OFF && vprog == 0) {
+			/* PWM_VCTRL overrides PFM_VCTRL */
+			vprog = vreg->ctrl_reg & FTSMPS_VCTRL_VPROG_MASK;
+			band = vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK;
+		}
+	} else {
+		vprog = vreg->ctrl_reg & FTSMPS_VCTRL_VPROG_MASK;
+		band = vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK;
+	}
+
+	if (band == FTSMPS_VCTRL_BAND_1)
+		uV = vprog * FTSMPS_BAND1_UV_STEP + FTSMPS_BAND1_UV_MIN;
+	else if (band == FTSMPS_VCTRL_BAND_2)
+		uV = vprog * FTSMPS_BAND2_UV_STEP + FTSMPS_BAND2_UV_MIN;
+	else if (band == FTSMPS_VCTRL_BAND_3)
+		uV = vprog * FTSMPS_BAND3_UV_STEP + FTSMPS_BAND3_UV_MIN;
+	else
+		uV = vreg->save_uV;
+
+	return uV;
+}
+
+static int pm8921_ftsmps_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_ftsmps_get_voltage(vreg);
+}
+
+static int _pm8921_ftsmps_set_voltage(struct pm8921_vreg *vreg, int uV,
+				      int force_on)
+{
+	int rc, new_uV;
+	u8 vprog, band;
+
+	if (uV < FTSMPS_BAND1_UV_MIN || uV > FTSMPS_BAND3_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", uV);
+		return -EINVAL;
+	}
+
+	/* Round down for set points in the gaps between bands. */
+	if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN)
+		uV = FTSMPS_BAND1_UV_MAX;
+	else if (uV > FTSMPS_BAND2_UV_MAX
+			&& uV < FTSMPS_BAND3_UV_SETPOINT_MIN)
+		uV = FTSMPS_BAND2_UV_MAX;
+
+	if (uV < FTSMPS_BAND2_UV_MIN) {
+		vprog = ((uV - FTSMPS_BAND1_UV_MIN) / FTSMPS_BAND1_UV_STEP);
+		vprog = PM8921_FTSMPS_BAND_1_COMPENSATE(vprog);
+		band = FTSMPS_VCTRL_BAND_1;
+		new_uV = FTSMPS_BAND1_UV_MIN + vprog * FTSMPS_BAND1_UV_STEP;
+	} else if (uV < FTSMPS_BAND3_UV_SETPOINT_MIN) {
+		vprog = ((uV - FTSMPS_BAND2_UV_MIN) / FTSMPS_BAND2_UV_STEP);
+		band = FTSMPS_VCTRL_BAND_2;
+		new_uV = FTSMPS_BAND2_UV_MIN + vprog * FTSMPS_BAND2_UV_STEP;
+	} else {
+		vprog = ((uV - FTSMPS_BAND3_UV_MIN) / FTSMPS_BAND3_UV_STEP);
+		band = FTSMPS_VCTRL_BAND_3;
+		new_uV = FTSMPS_BAND3_UV_MIN + vprog * FTSMPS_BAND3_UV_STEP;
+	}
+
+	/*
+	 * Do not set voltage if regulator is currently disabled because doing
+	 * so will enable it.
+	 */
+	if (_pm8921_vreg_is_enabled(vreg) || force_on) {
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, band | vprog,
+			FTSMPS_VCTRL_BAND_MASK | FTSMPS_VCTRL_VPROG_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		/* Program PFM_VCTRL as 0x00 so that PWM_VCTRL overrides it. */
+		rc = pm8921_vreg_write(vreg, vreg->pfm_ctrl_addr, 0x00,
+			FTSMPS_VCTRL_BAND_MASK | FTSMPS_VCTRL_VPROG_MASK,
+			&vreg->pfm_ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	vreg->save_uV = new_uV;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ftsmps_set_voltage(struct regulator_dev *rdev, int min_uV,
+				     int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_ftsmps_set_voltage(vreg, min_uV, 0);
+}
+
+static int pm8921_ncp_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	u8 vprog;
+
+	vprog = vreg->ctrl_reg & NCP_VPROG_MASK;
+
+	return NCP_UV_MIN + vprog * NCP_UV_STEP;
+}
+
+static int pm8921_ncp_set_voltage(struct regulator_dev *rdev, int min_uV,
+				  int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+	u8 val;
+
+	if (min_uV < NCP_UV_MIN || min_uV > NCP_UV_MAX) {
+		vreg_err(vreg, "requested voltage %d is outside of allowed "
+				"range.\n", min_uV);
+		return -EINVAL;
+	}
+
+	val = (min_uV - NCP_UV_MIN) / NCP_UV_STEP;
+
+	/* voltage setting */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, val, NCP_VPROG_MASK,
+			&vreg->ctrl_reg);
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vreg_set_pin_ctrl(struct pm8921_vreg *vreg, int on)
+{
+	int rc = 0, bank;
+	u8 val = 0, mask;
+	unsigned pc = vreg->pdata.pin_ctrl, pf = vreg->pdata.pin_fn;
+
+	switch (vreg->type) {
+	case REGULATOR_TYPE_LDO:
+		if (on) {
+			if (pc & PM8921_VREG_PIN_CTRL_D0)
+				val |= LDO_TEST_PIN_CTRL_EN0;
+			if (pc & PM8921_VREG_PIN_CTRL_D1)
+				val |= LDO_TEST_PIN_CTRL_EN1;
+			if (pc & PM8921_VREG_PIN_CTRL_A0)
+				val |= LDO_TEST_PIN_CTRL_EN2;
+			if (pc & PM8921_VREG_PIN_CTRL_A1)
+				val |= LDO_TEST_PIN_CTRL_EN3;
+
+			bank = (pf == PM8921_VREG_PIN_FN_ENABLE ? 5 : 6);
+			rc = pm8921_vreg_write(vreg, vreg->test_addr,
+				val | REGULATOR_BANK_SEL(bank)
+				  | REGULATOR_BANK_WRITE,
+				LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+				&vreg->test_reg[bank]);
+			if (rc)
+				goto bail;
+
+			val = LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE
+				| REGULATOR_BANK_SEL(0);
+			mask = LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK;
+			rc = pm8921_vreg_write(vreg, vreg->test_addr, val, mask,
+						&vreg->test_reg[0]);
+			if (rc)
+				goto bail;
+
+			if (pf == PM8921_VREG_PIN_FN_ENABLE) {
+				/* Pin control ON/OFF */
+				rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+					LDO_CTRL_PM_HPM,
+					LDO_ENABLE_MASK | LDO_CTRL_PM_MASK,
+					&vreg->ctrl_reg);
+				if (rc)
+					goto bail;
+			} else {
+				/* Pin control LPM/HPM */
+				rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+					LDO_ENABLE | LDO_CTRL_PM_LPM,
+					LDO_ENABLE_MASK | LDO_CTRL_PM_MASK,
+					&vreg->ctrl_reg);
+				if (rc)
+					goto bail;
+			}
+		} else {
+			/* Pin control off */
+			rc = pm8921_vreg_write(vreg, vreg->test_addr,
+				REGULATOR_BANK_SEL(5) | REGULATOR_BANK_WRITE,
+				LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+				&vreg->test_reg[5]);
+			if (rc)
+				goto bail;
+
+			rc = pm8921_vreg_write(vreg, vreg->test_addr,
+				REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE,
+				LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+				&vreg->test_reg[6]);
+			if (rc)
+				goto bail;
+		}
+		break;
+
+	case REGULATOR_TYPE_SMPS:
+		if (on) {
+			if (pf == PM8921_VREG_PIN_FN_ENABLE) {
+				/* Pin control ON/OFF */
+				if (pc & PM8921_VREG_PIN_CTRL_D0)
+					val |= SMPS_PIN_CTRL_D0;
+				if (pc & PM8921_VREG_PIN_CTRL_D1)
+					val |= SMPS_PIN_CTRL_D1;
+				if (pc & PM8921_VREG_PIN_CTRL_A0)
+					val |= SMPS_PIN_CTRL_A0;
+				if (pc & PM8921_VREG_PIN_CTRL_A1)
+					val |= SMPS_PIN_CTRL_A1;
+			} else {
+				/* Pin control LPM/HPM */
+				if (pc & PM8921_VREG_PIN_CTRL_D0)
+					val |= SMPS_PIN_CTRL_LPM_D0;
+				if (pc & PM8921_VREG_PIN_CTRL_D1)
+					val |= SMPS_PIN_CTRL_LPM_D1;
+				if (pc & PM8921_VREG_PIN_CTRL_A0)
+					val |= SMPS_PIN_CTRL_LPM_A0;
+				if (pc & PM8921_VREG_PIN_CTRL_A1)
+					val |= SMPS_PIN_CTRL_LPM_A1;
+			}
+
+			rc = pm8921_smps_set_voltage_legacy(vreg,
+							    vreg->save_uV);
+			if (rc)
+				goto bail;
+
+			rc = pm8921_vreg_write(vreg, vreg->sleep_ctrl_addr, val,
+				SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK,
+				&vreg->sleep_ctrl_reg);
+			if (rc)
+				goto bail;
+
+			rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+				(pf == PM8921_VREG_PIN_FN_ENABLE
+				       ? 0 : SMPS_LEGACY_ENABLE),
+				SMPS_LEGACY_ENABLE, &vreg->ctrl_reg);
+			if (rc)
+				goto bail;
+
+			rc = pm8921_vreg_write(vreg, vreg->clk_ctrl_addr,
+				(pf == PM8921_VREG_PIN_FN_ENABLE
+				       ? SMPS_CLK_CTRL_PWM : SMPS_CLK_CTRL_PFM),
+				SMPS_CLK_CTRL_MASK, &vreg->clk_ctrl_reg);
+			if (rc)
+				goto bail;
+		} else {
+			/* Pin control off */
+			if (!SMPS_IN_ADVANCED_MODE(vreg)) {
+				if (_pm8921_vreg_is_enabled(vreg))
+					val = SMPS_LEGACY_ENABLE;
+				rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+					val, SMPS_LEGACY_ENABLE,
+					&vreg->ctrl_reg);
+				if (rc)
+					goto bail;
+			}
+
+			rc = pm8921_vreg_write(vreg, vreg->sleep_ctrl_addr, 0,
+				SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK,
+				&vreg->sleep_ctrl_reg);
+			if (rc)
+				goto bail;
+
+			rc = pm8921_smps_set_voltage_advanced(vreg,
+							      vreg->save_uV, 0);
+			if (rc)
+				goto bail;
+		}
+		break;
+
+	case REGULATOR_TYPE_VS:
+		if (on) {
+			if (pc & PM8921_VREG_PIN_CTRL_D0)
+				val |= VS_PIN_CTRL_EN0;
+			if (pc & PM8921_VREG_PIN_CTRL_D1)
+				val |= VS_PIN_CTRL_EN1;
+			if (pc & PM8921_VREG_PIN_CTRL_A0)
+				val |= VS_PIN_CTRL_EN2;
+			if (pc & PM8921_VREG_PIN_CTRL_A1)
+				val |= VS_PIN_CTRL_EN3;
+
+			rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, val,
+					VS_PIN_CTRL_MASK | VS_ENABLE_MASK,
+					&vreg->ctrl_reg);
+			if (rc)
+				goto bail;
+		} else {
+			/* Pin control off */
+			if (_pm8921_vreg_is_enabled(vreg))
+				val = VS_ENABLE;
+
+			rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, val,
+					VS_ENABLE_MASK | VS_PIN_CTRL_MASK,
+					&vreg->ctrl_reg);
+			if (rc)
+				goto bail;
+		}
+		break;
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static unsigned int pm8921_vreg_get_mode(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	/* Check physical pin control state. */
+	switch (vreg->type) {
+	case REGULATOR_TYPE_LDO:
+		if (!(vreg->ctrl_reg & LDO_ENABLE_MASK)
+		    && (vreg->test_reg[5] & LDO_TEST_PIN_CTRL_MASK))
+			return REGULATOR_MODE_IDLE;
+		else if ((vreg->ctrl_reg & LDO_ENABLE_MASK)
+		    && (vreg->ctrl_reg & LDO_CTRL_PM_MASK)
+		    && (vreg->test_reg[6] & LDO_TEST_PIN_CTRL_LPM_MASK))
+			return REGULATOR_MODE_IDLE;
+		break;
+	case REGULATOR_TYPE_SMPS:
+		if (!SMPS_IN_ADVANCED_MODE(vreg)
+		    && !(vreg->ctrl_reg & SMPS_LEGACY_ENABLE)
+		    && (vreg->sleep_ctrl_reg & SMPS_PIN_CTRL_MASK))
+			return REGULATOR_MODE_IDLE;
+		else if (!SMPS_IN_ADVANCED_MODE(vreg)
+		    && (vreg->ctrl_reg & SMPS_LEGACY_ENABLE)
+		    && ((vreg->clk_ctrl_reg & SMPS_CLK_CTRL_MASK)
+			== SMPS_CLK_CTRL_PFM)
+		    && (vreg->sleep_ctrl_reg & SMPS_PIN_CTRL_LPM_MASK))
+			return REGULATOR_MODE_IDLE;
+		break;
+	case REGULATOR_TYPE_VS:
+		if (!(vreg->ctrl_reg & VS_ENABLE_MASK)
+		    && (vreg->ctrl_reg & VS_PIN_CTRL_MASK))
+			return REGULATOR_MODE_IDLE;
+	}
+
+	if (vreg->optimum == REGULATOR_MODE_FAST)
+		return REGULATOR_MODE_FAST;
+	else if (vreg->pc_vote)
+		return REGULATOR_MODE_IDLE;
+	else if (vreg->optimum == REGULATOR_MODE_STANDBY)
+		return REGULATOR_MODE_STANDBY;
+	return REGULATOR_MODE_FAST;
+}
+
+static int pm8921_ldo_set_mode(struct pm8921_vreg *vreg, unsigned int mode)
+{
+	int rc = 0;
+	u8 mask, val;
+
+	switch (mode) {
+	case REGULATOR_MODE_FAST:
+		/* HPM */
+		val = (_pm8921_vreg_is_enabled(vreg) ? LDO_ENABLE : 0)
+			| LDO_CTRL_PM_HPM;
+		mask = LDO_ENABLE_MASK | LDO_CTRL_PM_MASK;
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, val, mask,
+					&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		if (pm8921_vreg_is_pin_controlled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 0);
+		if (rc)
+			goto bail;
+		break;
+
+	case REGULATOR_MODE_STANDBY:
+		/* LPM */
+		val = (_pm8921_vreg_is_enabled(vreg) ? LDO_ENABLE : 0)
+			| LDO_CTRL_PM_LPM;
+		mask = LDO_ENABLE_MASK | LDO_CTRL_PM_MASK;
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, val, mask,
+					&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		val = LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE
+			| REGULATOR_BANK_SEL(0);
+		mask = LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK;
+		rc = pm8921_vreg_write(vreg, vreg->test_addr, val, mask,
+					&vreg->test_reg[0]);
+		if (rc)
+			goto bail;
+
+		if (pm8921_vreg_is_pin_controlled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 0);
+		if (rc)
+			goto bail;
+		break;
+
+	case REGULATOR_MODE_IDLE:
+		/* Pin Control */
+		if (_pm8921_vreg_is_enabled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 1);
+		if (rc)
+			goto bail;
+		break;
+
+	default:
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo1200_set_mode(struct pm8921_vreg *vreg, unsigned int mode)
+{
+	int rc = 0;
+
+	if (mode != REGULATOR_MODE_FAST && mode != REGULATOR_MODE_STANDBY) {
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+	/*
+	 * Make sure that advanced mode is in use.  If it isn't, then set it
+	 * and update the voltage accordingly.
+	 */
+	if (!NLDO1200_IN_ADVANCED_MODE(vreg)) {
+		rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			NLDO1200_ADVANCED_MODE, NLDO1200_ADVANCED_MODE_MASK,
+			&vreg->test_reg[2]);
+		if (rc)
+			goto bail;
+
+		_pm8921_nldo1200_set_voltage(vreg, vreg->save_uV);
+	}
+
+	if (mode == REGULATOR_MODE_FAST) {
+		/* HPM */
+		rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			NLDO1200_ADVANCED_PM_HPM, NLDO1200_ADVANCED_PM_MASK,
+			&vreg->test_reg[2]);
+	} else {
+		/* LPM */
+		rc = pm8921_vreg_write(vreg, vreg->test_addr,
+			NLDO1200_ADVANCED_PM_LPM, NLDO1200_ADVANCED_PM_MASK,
+			&vreg->test_reg[2]);
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_set_mode(struct pm8921_vreg *vreg, unsigned int mode)
+{
+	int rc = 0;
+
+	switch (mode) {
+	case REGULATOR_MODE_FAST:
+		/* HPM */
+		rc = pm8921_vreg_write(vreg, vreg->clk_ctrl_addr,
+				       SMPS_CLK_CTRL_PWM, SMPS_CLK_CTRL_MASK,
+				       &vreg->clk_ctrl_reg);
+		if (rc)
+			goto bail;
+
+		if (pm8921_vreg_is_pin_controlled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 0);
+		if (rc)
+			goto bail;
+		break;
+
+	case REGULATOR_MODE_STANDBY:
+		/* LPM */
+		rc = pm8921_vreg_write(vreg, vreg->clk_ctrl_addr,
+				       SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK,
+				       &vreg->clk_ctrl_reg);
+		if (rc)
+			goto bail;
+
+		if (pm8921_vreg_is_pin_controlled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 0);
+		if (rc)
+			goto bail;
+		break;
+
+	case REGULATOR_MODE_IDLE:
+		/* Pin Control */
+		if (_pm8921_vreg_is_enabled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 1);
+		if (rc)
+			goto bail;
+		break;
+
+	default:
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ftsmps_set_mode(struct pm8921_vreg *vreg, unsigned int mode)
+{
+	int rc = 0;
+
+	if (mode == REGULATOR_MODE_FAST) {
+		/* HPM */
+		rc = pm8921_vreg_write(vreg, vreg->test_addr,
+				FTSMPS_CNFG1_PM_PWM, FTSMPS_CNFG1_PM_MASK,
+				&vreg->test_reg[0]);
+	} else if (mode == REGULATOR_MODE_STANDBY) {
+		/* LPM */
+		rc = pm8921_vreg_write(vreg, vreg->test_addr,
+				FTSMPS_CNFG1_PM_PFM, FTSMPS_CNFG1_PM_MASK,
+				&vreg->test_reg[0]);
+	} else {
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_lvs_set_mode(struct pm8921_vreg *vreg, unsigned int mode)
+{
+	int rc = 0;
+
+	if (mode == REGULATOR_MODE_IDLE) {
+		/* Use pin control. */
+		if (_pm8921_vreg_is_enabled(vreg))
+			rc = pm8921_vreg_set_pin_ctrl(vreg, 1);
+	} else {
+		/* Turn off pin control. */
+		rc = pm8921_vreg_set_pin_ctrl(vreg, 0);
+	}
+
+	return rc;
+}
+
+/*
+ * Optimum mode programming:
+ * REGULATOR_MODE_FAST: Go to HPM (highest priority)
+ * REGULATOR_MODE_STANDBY: Go to pin ctrl mode if there are any pin ctrl
+ * votes, else go to LPM
+ *
+ * Pin ctrl mode voting via regulator set_mode:
+ * REGULATOR_MODE_IDLE: Go to pin ctrl mode if the optimum mode is LPM, else
+ * go to HPM
+ * REGULATOR_MODE_NORMAL: Go to LPM if it is the optimum mode, else go to HPM
+ */
+static int pm8921_vreg_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned prev_optimum = vreg->optimum, prev_pc_vote = vreg->pc_vote;
+	int rc = 0, new_mode = REGULATOR_MODE_FAST;
+
+	/* Determine new mode to go into. */
+	switch (mode) {
+	case REGULATOR_MODE_FAST:
+		new_mode = REGULATOR_MODE_FAST;
+		vreg->optimum = mode;
+		break;
+
+	case REGULATOR_MODE_STANDBY:
+		if (vreg->pc_vote)
+			new_mode = REGULATOR_MODE_IDLE;
+		else
+			new_mode = REGULATOR_MODE_STANDBY;
+		vreg->optimum = mode;
+		break;
+
+	case REGULATOR_MODE_IDLE:
+		if (vreg->pc_vote++)
+			return rc; /* already taken care of */
+
+		if (vreg->optimum == REGULATOR_MODE_FAST)
+			new_mode = REGULATOR_MODE_FAST;
+		else
+			new_mode = REGULATOR_MODE_IDLE;
+		break;
+
+	case REGULATOR_MODE_NORMAL:
+		if (vreg->pc_vote && --(vreg->pc_vote))
+			return rc; /* already taken care of */
+
+		if (vreg->optimum == REGULATOR_MODE_STANDBY)
+			new_mode = REGULATOR_MODE_STANDBY;
+		else
+			new_mode = REGULATOR_MODE_FAST;
+		break;
+
+	default:
+		vreg_err(vreg, "unknown mode, mode=%u\n", mode);
+		return -EINVAL;
+	}
+
+	switch (vreg->type) {
+	case REGULATOR_TYPE_LDO:
+		rc = pm8921_ldo_set_mode(vreg, new_mode);
+		break;
+	case REGULATOR_TYPE_NLDO1200:
+		rc = pm8921_nldo1200_set_mode(vreg, new_mode);
+		break;
+	case REGULATOR_TYPE_SMPS:
+		rc = pm8921_smps_set_mode(vreg, new_mode);
+		break;
+	case REGULATOR_TYPE_FTSMPS:
+		rc = pm8921_ftsmps_set_mode(vreg, new_mode);
+		break;
+	case REGULATOR_TYPE_VS:
+		rc = pm8921_lvs_set_mode(vreg, new_mode);
+		break;
+	}
+
+	if (rc) {
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+		vreg->optimum = prev_optimum;
+		vreg->pc_vote = prev_pc_vote;
+	}
+
+	return rc;
+}
+
+static unsigned int pm8921_vreg_get_optimum_mode(struct regulator_dev *rdev,
+		int input_uV, int output_uV, int load_uA)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	vreg->save_uA = load_uA;
+
+	if (load_uA >= vreg->hpm_min_load)
+		return REGULATOR_MODE_FAST;
+
+	return REGULATOR_MODE_STANDBY;
+}
+
+static int pm8921_vreg_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int mode, rc = 0;
+
+	mode = pm8921_vreg_get_mode(rdev);
+
+	if (mode == REGULATOR_MODE_IDLE) {
+		/* Turn on pin control. */
+		rc = pm8921_vreg_set_pin_ctrl(vreg, 1);
+		if (rc)
+			goto bail;
+		return rc;
+	}
+	/*
+	 * All regulator types except advanced mode SMPS, FTSMPS, and VS300 have
+	 * enable bit in bit 7 of the control register.
+	 */
+	switch (vreg->type) {
+	case REGULATOR_TYPE_SMPS:
+		if (SMPS_IN_ADVANCED_MODE(vreg)
+		    || !pm8921_vreg_is_pin_controlled(vreg)) {
+			/* Enable in advanced mode if not using pin control. */
+			rc = pm8921_smps_set_voltage_advanced(vreg,
+							vreg->save_uV, 1);
+		} else {
+			/* Leave in legacy mode because pin control is used. */
+			rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+					SMPS_LEGACY_ENABLE, SMPS_LEGACY_ENABLE,
+					&vreg->ctrl_reg);
+		}
+		break;
+	case REGULATOR_TYPE_FTSMPS:
+		rc = _pm8921_ftsmps_set_voltage(vreg, vreg->save_uV, 1);
+		break;
+	case REGULATOR_TYPE_VS300:
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, VS300_CTRL_ENABLE,
+				       VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg);
+		break;
+	default:
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, REGULATOR_ENABLE,
+					REGULATOR_ENABLE_MASK, &vreg->ctrl_reg);
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vreg_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	/* Turn off pin control. */
+	rc = pm8921_vreg_set_pin_ctrl(vreg, 0);
+	if (rc)
+		goto bail;
+
+	/* Disable in local control register. */
+	if (vreg->type == REGULATOR_TYPE_SMPS && SMPS_IN_ADVANCED_MODE(vreg)) {
+		/* Change SMPS to legacy mode before disabling. */
+		rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV);
+		if (rc)
+			goto bail;
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, REGULATOR_DISABLE,
+			REGULATOR_ENABLE_MASK, &vreg->ctrl_reg);
+	} else if (vreg->type == REGULATOR_TYPE_FTSMPS) {
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+			FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+		rc = pm8921_vreg_write(vreg, vreg->pfm_ctrl_addr,
+			FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK,
+			&vreg->pfm_ctrl_reg);
+	} else if (vreg->type == REGULATOR_TYPE_VS300) {
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+			VS300_CTRL_DISABLE, VS300_CTRL_ENABLE_MASK,
+			&vreg->ctrl_reg);
+	} else {
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, REGULATOR_DISABLE,
+			REGULATOR_ENABLE_MASK, &vreg->ctrl_reg);
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static struct regulator_ops pm8921_pldo_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_pldo_set_voltage,
+	.get_voltage		= pm8921_pldo_get_voltage,
+	.set_mode		= pm8921_vreg_set_mode,
+	.get_mode		= pm8921_vreg_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_nldo_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_nldo_set_voltage,
+	.get_voltage		= pm8921_nldo_get_voltage,
+	.set_mode		= pm8921_vreg_set_mode,
+	.get_mode		= pm8921_vreg_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_nldo1200_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_nldo1200_set_voltage,
+	.get_voltage		= pm8921_nldo1200_get_voltage,
+	.set_mode		= pm8921_vreg_set_mode,
+	.get_mode		= pm8921_vreg_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_smps_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_smps_set_voltage,
+	.get_voltage		= pm8921_smps_get_voltage,
+	.set_mode		= pm8921_vreg_set_mode,
+	.get_mode		= pm8921_vreg_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_ftsmps_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_ftsmps_set_voltage,
+	.get_voltage		= pm8921_ftsmps_get_voltage,
+	.set_mode		= pm8921_vreg_set_mode,
+	.get_mode		= pm8921_vreg_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_vs_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_mode		= pm8921_vreg_set_mode,
+	.get_mode		= pm8921_vreg_get_mode,
+};
+
+static struct regulator_ops pm8921_vs300_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+};
+
+static struct regulator_ops pm8921_ncp_ops = {
+	.enable			= pm8921_vreg_enable,
+	.disable		= pm8921_vreg_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_ncp_set_voltage,
+	.get_voltage		= pm8921_ncp_get_voltage,
+};
+
+#define VREG_DESC(_id, _name, _ops) \
+	[PM8921_VREG_ID_##_id] = { \
+		.id	= PM8921_VREG_ID_##_id, \
+		.name	= _name, \
+		.ops	= _ops, \
+		.type	= REGULATOR_VOLTAGE, \
+		.owner	= THIS_MODULE, \
+	}
+
+static struct regulator_desc pm8921_vreg_description[] = {
+	VREG_DESC(L1,  "8921_l1",  &pm8921_nldo_ops),
+	VREG_DESC(L2,  "8921_l2",  &pm8921_nldo_ops),
+	VREG_DESC(L3,  "8921_l3",  &pm8921_pldo_ops),
+	VREG_DESC(L4,  "8921_l4",  &pm8921_pldo_ops),
+	VREG_DESC(L5,  "8921_l5",  &pm8921_pldo_ops),
+	VREG_DESC(L6,  "8921_l6",  &pm8921_pldo_ops),
+	VREG_DESC(L7,  "8921_l7",  &pm8921_pldo_ops),
+	VREG_DESC(L8,  "8921_l8",  &pm8921_pldo_ops),
+	VREG_DESC(L9,  "8921_l9",  &pm8921_pldo_ops),
+	VREG_DESC(L10, "8921_l10", &pm8921_pldo_ops),
+	VREG_DESC(L11, "8921_l11", &pm8921_pldo_ops),
+	VREG_DESC(L12, "8921_l12", &pm8921_nldo_ops),
+	VREG_DESC(L14, "8921_l14", &pm8921_pldo_ops),
+	VREG_DESC(L15, "8921_l15", &pm8921_pldo_ops),
+	VREG_DESC(L16, "8921_l16", &pm8921_pldo_ops),
+	VREG_DESC(L17, "8921_l17", &pm8921_pldo_ops),
+	VREG_DESC(L18, "8921_l18", &pm8921_nldo_ops),
+	VREG_DESC(L21, "8921_l21", &pm8921_pldo_ops),
+	VREG_DESC(L22, "8921_l22", &pm8921_pldo_ops),
+	VREG_DESC(L23, "8921_l23", &pm8921_pldo_ops),
+	VREG_DESC(L24, "8921_l24", &pm8921_nldo1200_ops),
+	VREG_DESC(L25, "8921_l25", &pm8921_nldo1200_ops),
+	VREG_DESC(L26, "8921_l26", &pm8921_nldo1200_ops),
+	VREG_DESC(L27, "8921_l27", &pm8921_nldo1200_ops),
+	VREG_DESC(L28, "8921_l28", &pm8921_nldo1200_ops),
+	VREG_DESC(L29, "8921_l29", &pm8921_pldo_ops),
+
+	VREG_DESC(S1, "8921_s1", &pm8921_smps_ops),
+	VREG_DESC(S2, "8921_s2", &pm8921_smps_ops),
+	VREG_DESC(S3, "8921_s3", &pm8921_smps_ops),
+	VREG_DESC(S4, "8921_s4", &pm8921_smps_ops),
+	VREG_DESC(S5, "8921_s5", &pm8921_ftsmps_ops),
+	VREG_DESC(S6, "8921_s6", &pm8921_ftsmps_ops),
+	VREG_DESC(S7, "8921_s7", &pm8921_smps_ops),
+	VREG_DESC(S8, "8921_s8", &pm8921_smps_ops),
+
+	VREG_DESC(LVS1, "8921_lvs1", &pm8921_vs_ops),
+	VREG_DESC(LVS2, "8921_lvs2", &pm8921_vs300_ops),
+	VREG_DESC(LVS3, "8921_lvs3", &pm8921_vs_ops),
+	VREG_DESC(LVS4, "8921_lvs4", &pm8921_vs_ops),
+	VREG_DESC(LVS5, "8921_lvs5", &pm8921_vs_ops),
+	VREG_DESC(LVS6, "8921_lvs6", &pm8921_vs_ops),
+	VREG_DESC(LVS7, "8921_lvs7", &pm8921_vs_ops),
+
+	VREG_DESC(USB_OTG, "8921_usb_otg", &pm8921_vs300_ops),
+	VREG_DESC(HDMI_MVS, "8921_hdmi_mvs", &pm8921_vs300_ops),
+	VREG_DESC(NCP, "8921_ncp", &pm8921_ncp_ops),
+};
+
+static int pm8921_init_ldo(struct pm8921_vreg *vreg)
+{
+	int rc = 0, i;
+	u8 bank;
+
+	/* Save the current test register state. */
+	for (i = 0; i < LDO_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	if ((vreg->ctrl_reg & LDO_CTRL_PM_MASK) == LDO_CTRL_PM_LPM)
+		vreg->optimum = REGULATOR_MODE_STANDBY;
+	else
+		vreg->optimum = REGULATOR_MODE_FAST;
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+		     (vreg->pdata.pull_down_enable ? LDO_PULL_DOWN_ENABLE : 0),
+		     LDO_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg);
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_nldo1200(struct pm8921_vreg *vreg)
+{
+	int rc = 0, i;
+	u8 bank;
+
+	/* Save the current test register state. */
+	for (i = 0; i < LDO_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	/*
+	 * get_voltage must return >0 in order for regulator_set_optimum_mode
+	 * to succeed, but voltage may be unknown at probe time.
+	 */
+	vreg->save_uV = 1; /* This is not a no-op. */
+	vreg->save_uV = _pm8921_nldo1200_get_voltage(vreg);
+
+	if (NLDO1200_IN_ADVANCED_MODE(vreg)) {
+		if ((vreg->test_reg[2] & NLDO1200_ADVANCED_PM_MASK)
+		    == NLDO1200_ADVANCED_PM_LPM)
+			vreg->optimum = REGULATOR_MODE_STANDBY;
+		else
+			vreg->optimum = REGULATOR_MODE_FAST;
+	} else {
+		if ((vreg->ctrl_reg & NLDO1200_LEGACY_PM_MASK)
+		    == NLDO1200_LEGACY_PM_LPM)
+			vreg->optimum = REGULATOR_MODE_STANDBY;
+		else
+			vreg->optimum = REGULATOR_MODE_FAST;
+	}
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+		 (vreg->pdata.pull_down_enable ? NLDO1200_PULL_DOWN_ENABLE : 0),
+		 NLDO1200_PULL_DOWN_ENABLE_MASK, &vreg->test_reg[1]);
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_smps(struct pm8921_vreg *vreg)
+{
+	int rc = 0, i;
+	u8 bank;
+
+	/* Save the current test2 register state. */
+	for (i = 0; i < SMPS_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	/* Save the current clock control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->clk_ctrl_addr,
+			  &vreg->clk_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current sleep control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->sleep_ctrl_addr,
+			  &vreg->sleep_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/*
+	 * get_voltage must return >0 in order for regulator_set_optimum_mode
+	 * to succeed, but voltage may be unknown at probe time.
+	 */
+	vreg->save_uV = 1; /* This is not a no-op. */
+	vreg->save_uV = _pm8921_smps_get_voltage(vreg);
+
+	if ((vreg->clk_ctrl_reg & SMPS_CLK_CTRL_MASK) == SMPS_CLK_CTRL_PFM)
+		vreg->optimum = REGULATOR_MODE_STANDBY;
+	else
+		vreg->optimum = REGULATOR_MODE_FAST;
+
+	/* Set advanced mode pull down enable based on platform data. */
+	rc = pm8921_vreg_write(vreg, vreg->test_addr,
+		(vreg->pdata.pull_down_enable
+			? SMPS_ADVANCED_PULL_DOWN_ENABLE : 0)
+		| REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE,
+		REGULATOR_BANK_MASK | SMPS_ADVANCED_PULL_DOWN_ENABLE,
+		&vreg->test_reg[6]);
+	if (rc)
+		goto bail;
+
+	if (!SMPS_IN_ADVANCED_MODE(vreg)) {
+		/* Set legacy mode pull down enable based on platform data. */
+		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+			(vreg->pdata.pull_down_enable
+				? SMPS_LEGACY_PULL_DOWN_ENABLE : 0),
+			SMPS_LEGACY_PULL_DOWN_ENABLE, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_ftsmps(struct pm8921_vreg *vreg)
+{
+	int rc, i;
+	u8 bank;
+
+	/* Store current regulator register values. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->pfm_ctrl_addr,
+			  &vreg->pfm_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->pwr_cnfg_addr,
+			  &vreg->pwr_cnfg_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current fts_cnfg1 register state (uses 'test' member). */
+	for (i = 0; i < SMPS_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	/*
+	 * get_voltage must return >0 in order for regulator_set_optimum_mode
+	 * to succeed, but voltage may be unknown at probe time.
+	 */
+	vreg->save_uV = 1; /* This is not a no-op. */
+	vreg->save_uV = _pm8921_ftsmps_get_voltage(vreg);
+
+	if ((vreg->test_reg[0] & FTSMPS_CNFG1_PM_MASK) == FTSMPS_CNFG1_PM_PFM)
+		vreg->optimum = REGULATOR_MODE_STANDBY;
+	else
+		vreg->optimum = REGULATOR_MODE_FAST;
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_write(vreg, vreg->pwr_cnfg_addr,
+		(vreg->pdata.pull_down_enable ? FTSMPS_PULL_DOWN_ENABLE : 0),
+		FTSMPS_PULL_DOWN_ENABLE_MASK, &vreg->pwr_cnfg_reg);
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_vs(struct pm8921_vreg *vreg)
+{
+	int rc = 0;
+
+	vreg->optimum = REGULATOR_MODE_NORMAL;
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+		     (vreg->pdata.pull_down_enable ? VS_PULL_DOWN_ENABLE : 0),
+		     VS_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_vs300(struct pm8921_vreg *vreg)
+{
+	int rc;
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_write(vreg, vreg->ctrl_addr,
+		    (vreg->pdata.pull_down_enable ? VS300_PULL_DOWN_ENABLE : 0),
+		    VS300_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_regulator(struct pm8921_vreg *vreg)
+{
+	int rc = 0;
+
+	/* save the current control register state */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
+	switch (vreg->type) {
+	case REGULATOR_TYPE_LDO:
+		rc = pm8921_init_ldo(vreg);
+		break;
+	case REGULATOR_TYPE_NLDO1200:
+		rc = pm8921_init_nldo1200(vreg);
+		break;
+	case REGULATOR_TYPE_SMPS:
+		rc = pm8921_init_smps(vreg);
+		break;
+	case REGULATOR_TYPE_FTSMPS:
+		rc = pm8921_init_ftsmps(vreg);
+		break;
+	case REGULATOR_TYPE_VS:
+		rc = pm8921_init_vs(vreg);
+		break;
+	case REGULATOR_TYPE_VS300:
+		rc = pm8921_init_vs300(vreg);
+		break;
+	}
+
+	return rc;
+}
+
+static int __devinit pm8921_vreg_probe(struct platform_device *pdev)
+{
+	struct regulator_desc *rdesc;
+	struct pm8921_vreg *vreg;
+	const char *reg_name = "";
+	int rc = 0;
+
+	if (pdev == NULL)
+		return -EINVAL;
+
+	if (pdev->id >= 0 && pdev->id < PM8921_VREG_ID_MAX) {
+		rdesc = &pm8921_vreg_description[pdev->id];
+		vreg = &pm8921_vreg[pdev->id];
+		memcpy(&(vreg->pdata), pdev->dev.platform_data,
+			sizeof(struct pm8921_regulator_platform_data));
+		reg_name = pm8921_vreg_description[pdev->id].name;
+		vreg->name = reg_name;
+		vreg->dev = &pdev->dev;
+
+		rc = pm8921_init_regulator(vreg);
+		if (rc)
+			goto bail;
+
+		vreg->rdev = regulator_register(rdesc, &pdev->dev,
+				&(vreg->pdata.init_data), vreg);
+		if (IS_ERR(vreg->rdev)) {
+			rc = PTR_ERR(vreg->rdev);
+			vreg->rdev = NULL;
+			pr_err("regulator_register failed for %s, rc=%d\n",
+				reg_name, rc);
+		}
+	} else {
+		rc = -ENODEV;
+	}
+
+bail:
+	if (rc)
+		pr_err("error for %s, rc=%d\n", reg_name, rc);
+
+	return rc;
+}
+
+static int __devexit pm8921_vreg_remove(struct platform_device *pdev)
+{
+	regulator_unregister(pm8921_vreg[pdev->id].rdev);
+	return 0;
+}
+
+static struct platform_driver pm8921_vreg_driver = {
+	.probe	= pm8921_vreg_probe,
+	.remove	= __devexit_p(pm8921_vreg_remove),
+	.driver	= {
+		.name	= PM8921_REGULATOR_DEV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init pm8921_vreg_init(void)
+{
+	return platform_driver_register(&pm8921_vreg_driver);
+}
+module_init(pm8921_vreg_init);
+
+static void __exit pm8921_vreg_exit(void)
+{
+	platform_driver_unregister(&pm8921_vreg_driver);
+}
+module_exit(pm8921_vreg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8921 regulator driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" PM8921_REGULATOR_DEV_NAME);
diff --git a/include/linux/mfd/pm8xxx/pm8921.h b/include/linux/mfd/pm8xxx/pm8921.h
index 19cffb2..c2794c3 100644
--- a/include/linux/mfd/pm8xxx/pm8921.h
+++ b/include/linux/mfd/pm8xxx/pm8921.h
@@ -22,6 +22,7 @@
 #include <linux/mfd/pm8xxx/irq.h>
 #include <linux/mfd/pm8xxx/gpio.h>
 #include <linux/mfd/pm8xxx/mpp.h>
+#include <linux/regulator/pm8921-regulator.h>
 
 #define PM8921_NR_IRQS		256
 
@@ -44,6 +45,8 @@ struct pm8921_platform_data {
 	struct pm8xxx_irq_platform_data		*irq_pdata;
 	struct pm8xxx_gpio_platform_data	*gpio_pdata;
 	struct pm8xxx_mpp_platform_data		*mpp_pdata;
+	struct pm8921_regulator_platform_data	*regulator_pdatas;
+	int					num_regulators;
 };
 
 #endif
diff --git a/include/linux/regulator/pm8921-regulator.h b/include/linux/regulator/pm8921-regulator.h
new file mode 100644
index 0000000..2622459
--- /dev/null
+++ b/include/linux/regulator/pm8921-regulator.h
@@ -0,0 +1,97 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 __PM8921_REGULATOR_H__
+#define __PM8921_REGULATOR_H__
+
+#include <linux/regulator/machine.h>
+
+#define PM8921_REGULATOR_DEV_NAME	"pm8921-regulator"
+
+enum pm8921_vreg_id {
+	PM8921_VREG_ID_L1 = 0,
+	PM8921_VREG_ID_L2,
+	PM8921_VREG_ID_L3,
+	PM8921_VREG_ID_L4,
+	PM8921_VREG_ID_L5,
+	PM8921_VREG_ID_L6,
+	PM8921_VREG_ID_L7,
+	PM8921_VREG_ID_L8,
+	PM8921_VREG_ID_L9,
+	PM8921_VREG_ID_L10,
+	PM8921_VREG_ID_L11,
+	PM8921_VREG_ID_L12,
+	PM8921_VREG_ID_L14,
+	PM8921_VREG_ID_L15,
+	PM8921_VREG_ID_L16,
+	PM8921_VREG_ID_L17,
+	PM8921_VREG_ID_L18,
+	PM8921_VREG_ID_L21,
+	PM8921_VREG_ID_L22,
+	PM8921_VREG_ID_L23,
+	PM8921_VREG_ID_L24,
+	PM8921_VREG_ID_L25,
+	PM8921_VREG_ID_L26,
+	PM8921_VREG_ID_L27,
+	PM8921_VREG_ID_L28,
+	PM8921_VREG_ID_L29,
+	PM8921_VREG_ID_S1,
+	PM8921_VREG_ID_S2,
+	PM8921_VREG_ID_S3,
+	PM8921_VREG_ID_S4,
+	PM8921_VREG_ID_S5,
+	PM8921_VREG_ID_S6,
+	PM8921_VREG_ID_S7,
+	PM8921_VREG_ID_S8,
+	PM8921_VREG_ID_LVS1,
+	PM8921_VREG_ID_LVS2,
+	PM8921_VREG_ID_LVS3,
+	PM8921_VREG_ID_LVS4,
+	PM8921_VREG_ID_LVS5,
+	PM8921_VREG_ID_LVS6,
+	PM8921_VREG_ID_LVS7,
+	PM8921_VREG_ID_USB_OTG,
+	PM8921_VREG_ID_HDMI_MVS,
+	PM8921_VREG_ID_NCP,
+	PM8921_VREG_ID_MAX,
+};
+
+#define PM8921_VREG_PIN_CTRL_NONE	0x00
+#define PM8921_VREG_PIN_CTRL_A0		0x01
+#define PM8921_VREG_PIN_CTRL_A1		0x02
+#define PM8921_VREG_PIN_CTRL_D0		0x04
+#define PM8921_VREG_PIN_CTRL_D1		0x08
+
+/* Minimum high power mode loads in uA. */
+#define PM8921_VREG_LDO_50_HPM_MIN_LOAD		5000
+#define PM8921_VREG_LDO_150_HPM_MIN_LOAD	10000
+#define PM8921_VREG_LDO_300_HPM_MIN_LOAD	10000
+#define PM8921_VREG_LDO_600_HPM_MIN_LOAD	10000
+#define PM8921_VREG_LDO_1200_HPM_MIN_LOAD	10000
+#define PM8921_VREG_SMPS_1500_HPM_MIN_LOAD	100000
+#define PM8921_VREG_SMPS_2000_HPM_MIN_LOAD	100000
+
+/* Pin ctrl enables/disables or toggles high/low power modes */
+enum pm8921_vreg_pin_function {
+	PM8921_VREG_PIN_FN_ENABLE = 0,
+	PM8921_VREG_PIN_FN_MODE,
+};
+
+struct pm8921_regulator_platform_data {
+	struct regulator_init_data	init_data;
+	int				id;
+	unsigned			pull_down_enable;
+	unsigned			pin_ctrl;
+	enum pm8921_vreg_pin_function	pin_fn;
+};
+
+#endif
-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.


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

* [PATCH 2/2] msm: board-8960: Add support for pm8921-regulator
  2011-03-30 22:16 [PATCH 0/2] regulator: msm: Add PM8921 regulator driver David Collins
  2011-03-30 22:17 ` [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
@ 2011-03-30 22:17 ` David Collins
  2011-03-31  0:10   ` Mark Brown
  2011-04-29 23:07 ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver David Collins
  2 siblings, 1 reply; 16+ messages in thread
From: David Collins @ 2011-03-30 22:17 UTC (permalink / raw)
  To: David Brown, Daniel Walker, Bryan Huntsman
  Cc: Liam Girdwood, Mark Brown, Samuel Ortiz, linux-kernel,
	linux-arm-msm, linux-arm-kernel, linux-arm-msm-owner,
	David Collins

Add board-msm8960-regulator.c to contain pm8921-regulator structure
declarations.  NULL consumer names in the regulator_consumer_supply
lists are used for debugging and as place holders.  They will
eventually be changed to use more appropriate debug device names.
The primary configuration table msm_pm8921_regulator_pdata specifies
the following characteristics for each regulator: if it is always on,
if it should have its pull down set when disabled, its min and max
voltage constraints, its supply regulator, and its pin control pins.

Signed-off-by: David Collins <collinsd@codeaurora.org>
---
 arch/arm/mach-msm/Makefile                  |    1 +
 arch/arm/mach-msm/board-msm8960-regulator.c |  303 +++++++++++++++++++++++++++
 arch/arm/mach-msm/board-msm8960.c           |    6 +
 arch/arm/mach-msm/board-msm8960.h           |   23 ++
 4 files changed, 333 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-msm/board-msm8960-regulator.c
 create mode 100644 arch/arm/mach-msm/board-msm8960.h

diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index a4f55ec..c590fa9 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_ARCH_MSM7X30) += board-msm7x30.o devices-msm7x30.o
 obj-$(CONFIG_ARCH_QSD8X50) += board-qsd8x50.o devices-qsd8x50.o
 obj-$(CONFIG_ARCH_MSM8X60) += board-msm8x60.o
 obj-$(CONFIG_ARCH_MSM8960) += board-msm8960.o devices-msm8960.o
+obj-$(CONFIG_ARCH_MSM8960) += board-msm8960-regulator.o
 
 obj-$(CONFIG_ARCH_MSM7X30) += gpiomux-v1.o gpiomux.o
 obj-$(CONFIG_ARCH_QSD8X50) += gpiomux-v1.o gpiomux.o
diff --git a/arch/arm/mach-msm/board-msm8960-regulator.c b/arch/arm/mach-msm/board-msm8960-regulator.c
new file mode 100644
index 0000000..0d1f5ab
--- /dev/null
+++ b/arch/arm/mach-msm/board-msm8960-regulator.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/regulator/pm8921-regulator.h>
+
+#include "board-msm8960.h"
+
+#define VREG_CONSUMERS(_id) \
+	static struct regulator_consumer_supply \
+	pm8921_vreg_consumers_##_id[] __devinitdata
+
+/*
+ * Consumer specific regulator names:
+ *			 regulator name		consumer dev_name
+ */
+VREG_CONSUMERS(L1) = {
+	REGULATOR_SUPPLY("8921_l1",		NULL),
+};
+VREG_CONSUMERS(L2) = {
+	REGULATOR_SUPPLY("8921_l2",		NULL),
+};
+VREG_CONSUMERS(L3) = {
+	REGULATOR_SUPPLY("8921_l3",		NULL),
+	REGULATOR_SUPPLY("HSUSB_3p3",		"msm_otg"),
+};
+VREG_CONSUMERS(L4) = {
+	REGULATOR_SUPPLY("8921_l4",		NULL),
+	REGULATOR_SUPPLY("HSUSB_1p8",		"msm_otg"),
+};
+VREG_CONSUMERS(L5) = {
+	REGULATOR_SUPPLY("8921_l5",		NULL),
+	REGULATOR_SUPPLY("sdc_vdd",		"msm_sdcc.1"),
+};
+VREG_CONSUMERS(L6) = {
+	REGULATOR_SUPPLY("8921_l6",		NULL),
+	REGULATOR_SUPPLY("sdc_vdd",		"msm_sdcc.3"),
+};
+VREG_CONSUMERS(L7) = {
+	REGULATOR_SUPPLY("8921_l7",		NULL),
+	REGULATOR_SUPPLY("sdc_vddp",		"msm_sdcc.3"),
+};
+VREG_CONSUMERS(L8) = {
+	REGULATOR_SUPPLY("8921_l8",		NULL),
+};
+VREG_CONSUMERS(L9) = {
+	REGULATOR_SUPPLY("8921_l9",		NULL),
+};
+VREG_CONSUMERS(L10) = {
+	REGULATOR_SUPPLY("8921_l10",		NULL),
+};
+VREG_CONSUMERS(L11) = {
+	REGULATOR_SUPPLY("8921_l11",		NULL),
+};
+VREG_CONSUMERS(L12) = {
+	REGULATOR_SUPPLY("8921_l12",		NULL),
+};
+VREG_CONSUMERS(L14) = {
+	REGULATOR_SUPPLY("8921_l14",		NULL),
+};
+VREG_CONSUMERS(L15) = {
+	REGULATOR_SUPPLY("8921_l15",		NULL),
+};
+VREG_CONSUMERS(L16) = {
+	REGULATOR_SUPPLY("8921_l16",		NULL),
+};
+VREG_CONSUMERS(L17) = {
+	REGULATOR_SUPPLY("8921_l17",		NULL),
+};
+VREG_CONSUMERS(L18) = {
+	REGULATOR_SUPPLY("8921_l18",		NULL),
+};
+VREG_CONSUMERS(L21) = {
+	REGULATOR_SUPPLY("8921_l21",		NULL),
+};
+VREG_CONSUMERS(L22) = {
+	REGULATOR_SUPPLY("8921_l22",		NULL),
+};
+VREG_CONSUMERS(L23) = {
+	REGULATOR_SUPPLY("8921_l23",		NULL),
+};
+VREG_CONSUMERS(L24) = {
+	REGULATOR_SUPPLY("8921_l24",		NULL),
+};
+VREG_CONSUMERS(L25) = {
+	REGULATOR_SUPPLY("8921_l25",		NULL),
+};
+VREG_CONSUMERS(L26) = {
+	REGULATOR_SUPPLY("8921_l26",		NULL),
+};
+VREG_CONSUMERS(L27) = {
+	REGULATOR_SUPPLY("8921_l27",		NULL),
+};
+VREG_CONSUMERS(L28) = {
+	REGULATOR_SUPPLY("8921_l28",		NULL),
+};
+VREG_CONSUMERS(L29) = {
+	REGULATOR_SUPPLY("8921_l29",		NULL),
+};
+VREG_CONSUMERS(S1) = {
+	REGULATOR_SUPPLY("8921_s1",		NULL),
+};
+VREG_CONSUMERS(S2) = {
+	REGULATOR_SUPPLY("8921_s2",		NULL),
+};
+VREG_CONSUMERS(S3) = {
+	REGULATOR_SUPPLY("8921_s3",		NULL),
+	REGULATOR_SUPPLY("HSUSB_VDDCX",		"msm_otg"),
+};
+VREG_CONSUMERS(S4) = {
+	REGULATOR_SUPPLY("8921_s4",		NULL),
+	REGULATOR_SUPPLY("sdc_vccq",		"msm_sdcc.1"),
+};
+VREG_CONSUMERS(S5) = {
+	REGULATOR_SUPPLY("8921_s5",		NULL),
+};
+VREG_CONSUMERS(S6) = {
+	REGULATOR_SUPPLY("8921_s6",		NULL),
+};
+VREG_CONSUMERS(S7) = {
+	REGULATOR_SUPPLY("8921_s7",		NULL),
+};
+VREG_CONSUMERS(S8) = {
+	REGULATOR_SUPPLY("8921_s8",		NULL),
+};
+VREG_CONSUMERS(LVS1) = {
+	REGULATOR_SUPPLY("8921_lvs1",		NULL),
+	REGULATOR_SUPPLY("sdc_vdd",		"msm_sdcc.4"),
+};
+VREG_CONSUMERS(LVS2) = {
+	REGULATOR_SUPPLY("8921_lvs2",		NULL),
+};
+VREG_CONSUMERS(LVS3) = {
+	REGULATOR_SUPPLY("8921_lvs3",		NULL),
+};
+VREG_CONSUMERS(LVS4) = {
+	REGULATOR_SUPPLY("8921_lvs4",		NULL),
+};
+VREG_CONSUMERS(LVS5) = {
+	REGULATOR_SUPPLY("8921_lvs5",		NULL),
+};
+VREG_CONSUMERS(LVS6) = {
+	REGULATOR_SUPPLY("8921_lvs6",		NULL),
+};
+VREG_CONSUMERS(LVS7) = {
+	REGULATOR_SUPPLY("8921_lvs7",		NULL),
+};
+VREG_CONSUMERS(USB_OTG) = {
+	REGULATOR_SUPPLY("8921_usb_otg",	NULL),
+};
+VREG_CONSUMERS(HDMI_MVS) = {
+	REGULATOR_SUPPLY("8921_hdmi_mvs",	NULL),
+};
+VREG_CONSUMERS(NCP) = {
+	REGULATOR_SUPPLY("8921_ncp",		NULL),
+};
+
+#define PM8921_VREG_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, \
+			 _pull_down, _pin_ctrl, _pin_fn, _always_on, \
+			 _supply_regulator) \
+	{ \
+		.init_data		= { \
+			.constraints		= { \
+				.valid_modes_mask	= _modes, \
+				.valid_ops_mask		= _ops, \
+				.min_uV			= _min_uV, \
+				.max_uV			= _max_uV, \
+				.input_uV		= _max_uV, \
+				.apply_uV		= _apply_uV, \
+				.always_on		= _always_on, \
+			}, \
+			.num_consumer_supplies	= \
+				ARRAY_SIZE(pm8921_vreg_consumers_##_id), \
+			.consumer_supplies	= pm8921_vreg_consumers_##_id, \
+			.supply_regulator	= _supply_regulator, \
+		}, \
+		.id			= PM8921_VREG_ID_##_id, \
+		.pull_down_enable	= _pull_down, \
+		.pin_ctrl		= _pin_ctrl, \
+		.pin_fn			= _pin_fn, \
+	}
+
+#define PM8921_VREG_INIT_LDO(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator, _pin_ctrl) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \
+		REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE | \
+		REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, _pin_ctrl, \
+		PM8921_VREG_PIN_FN_ENABLE, _always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_NLDO1200(_id, _always_on, _pull_down, _min_uV, \
+		_max_uV, _supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \
+		REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, \
+		PM8921_VREG_PIN_CTRL_NONE, PM8921_VREG_PIN_FN_ENABLE, \
+		_always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_SMPS(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator, _pin_ctrl) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \
+		REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE | \
+		REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, _pin_ctrl, \
+		PM8921_VREG_PIN_FN_ENABLE, _always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_FTSMPS(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_FAST | \
+		REGULATOR_MODE_STANDBY, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, \
+		PM8921_VREG_PIN_CTRL_NONE, PM8921_VREG_PIN_FN_ENABLE, \
+		_always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_VS(_id, _always_on, _pull_down, _supply_regulator, \
+		_pin_ctrl) \
+	PM8921_VREG_INIT(_id, 0, 0, REGULATOR_MODE_NORMAL | \
+		REGULATOR_MODE_IDLE, REGULATOR_CHANGE_STATUS | \
+		REGULATOR_CHANGE_MODE, 0, _pull_down, _pin_ctrl, \
+		PM8921_VREG_PIN_FN_ENABLE, _always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_VS300(_id, _always_on, _pull_down, _supply_regulator) \
+	PM8921_VREG_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, _pull_down, \
+		PM8921_VREG_PIN_CTRL_NONE, PM8921_VREG_PIN_FN_ENABLE, \
+		_always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_NCP(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL | \
+		REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, \
+		PM8921_VREG_PIN_CTRL_NONE, PM8921_VREG_PIN_FN_ENABLE, \
+		_always_on, _supply_regulator)
+
+/* Regulator constraints */
+struct pm8921_regulator_platform_data
+msm_pm8921_regulator_pdata[] __devinitdata = {
+	PM8921_VREG_INIT_SMPS(S1, 0, 0, 1225000, 1225000, NULL,      0),
+	PM8921_VREG_INIT_SMPS(S2, 0, 0, 1300000, 1300000, NULL,      0),
+	PM8921_VREG_INIT_SMPS(S3, 0, 0, 1050000, 1050000, NULL,      0),
+	PM8921_VREG_INIT_SMPS(S4, 0, 0, 1800000, 1800000, NULL,      0),
+	PM8921_VREG_INIT_FTSMPS(S5, 0, 0, 1050000, 1050000, NULL),
+	PM8921_VREG_INIT_FTSMPS(S6, 0, 0, 1050000, 1050000, NULL),
+	PM8921_VREG_INIT_SMPS(S7, 0, 0, 1150000, 1150000, NULL,      0),
+	PM8921_VREG_INIT_SMPS(S8, 0, 0, 2200000, 2200000, NULL,      0),
+
+	PM8921_VREG_INIT_LDO(L1,  0, 0, 1050000, 1050000, "8921_s4", 0),
+	PM8921_VREG_INIT_LDO(L2,  0, 0, 1200000, 1200000, "8921_s4", 0),
+	PM8921_VREG_INIT_LDO(L3,  0, 0, 3075000, 3075000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L4,  0, 0, 1800000, 1800000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L5,  0, 0, 2950000, 2950000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L6,  0, 0, 2950000, 2950000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L7,  0, 0, 2950000, 2950000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L8,  0, 0, 3000000, 3000000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L9,  0, 0, 2850000, 2850000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L10, 0, 0, 2900000, 2900000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L11, 0, 0, 2850000, 2850000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L12, 0, 0, 1200000, 1200000, "8921_s4", 0),
+	PM8921_VREG_INIT_LDO(L14, 0, 0, 1800000, 1800000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L15, 0, 0, 1800000, 2950000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L16, 0, 0, 3000000, 3000000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L17, 0, 0, 1800000, 2950000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L18, 0, 0, 1300000, 1300000, "8921_s4", 0),
+	PM8921_VREG_INIT_LDO(L21, 0, 0, 1900000, 1900000, "8921_s8", 0),
+	PM8921_VREG_INIT_LDO(L22, 0, 0, 2750000, 2750000, NULL,      0),
+	PM8921_VREG_INIT_LDO(L23, 0, 0, 1800000, 1800000, "8921_s8", 0),
+	PM8921_VREG_INIT_NLDO1200(L24, 0, 0, 1050000, 1050000, "8921_s1"),
+	PM8921_VREG_INIT_NLDO1200(L25, 0, 0, 1225000, 1225000, "8921_s1"),
+	PM8921_VREG_INIT_NLDO1200(L26, 0, 0, 1050000, 1050000, "8921_s7"),
+	PM8921_VREG_INIT_NLDO1200(L27, 0, 0, 1050000, 1050000, "8921_s7"),
+	PM8921_VREG_INIT_NLDO1200(L28, 0, 0, 1050000, 1050000, "8921_s7"),
+	PM8921_VREG_INIT_LDO(L29, 0, 0, 2050000, 2100000, "8921_s8", 0),
+
+	PM8921_VREG_INIT_VS(LVS1, 0, 0,			  "8921_s4", 0),
+	PM8921_VREG_INIT_VS300(LVS2, 0, 0,			  "8921_s1"),
+	PM8921_VREG_INIT_VS(LVS3, 0, 0,			  "8921_s4", 0),
+	PM8921_VREG_INIT_VS(LVS4, 0, 0,			  "8921_s4", 0),
+	PM8921_VREG_INIT_VS(LVS5, 0, 0,			  "8921_s4", 0),
+	PM8921_VREG_INIT_VS(LVS6, 0, 0,			  "8921_s4", 0),
+	PM8921_VREG_INIT_VS(LVS7, 0, 0,			  "8921_s4", 0),
+
+	PM8921_VREG_INIT_VS300(USB_OTG,  0, 0,		  NULL),
+	PM8921_VREG_INIT_VS300(HDMI_MVS, 0, 0,		  NULL),
+
+	PM8921_VREG_INIT_NCP(NCP, 0, 0, 1800000, 1800000, "8921_l6"),
+};
+
+int msm_pm8921_regulator_pdata_len __devinitdata =
+	ARRAY_SIZE(msm_pm8921_regulator_pdata);
diff --git a/arch/arm/mach-msm/board-msm8960.c b/arch/arm/mach-msm/board-msm8960.c
index 1c90789..5cd2c79 100644
--- a/arch/arm/mach-msm/board-msm8960.c
+++ b/arch/arm/mach-msm/board-msm8960.c
@@ -27,6 +27,7 @@
 #include <mach/irqs.h>
 
 #include "devices.h"
+#include "board-msm8960.h"
 
 /* Macros assume PMIC GPIOs and MPPs start at 1 */
 #define PM8921_GPIO_BASE		NR_GPIO_IRQS
@@ -91,6 +92,7 @@ static struct pm8921_platform_data pm8921_platform_data __devinitdata = {
 	.irq_pdata		= &pm8xxx_irq_pdata,
 	.gpio_pdata		= &pm8xxx_gpio_pdata,
 	.mpp_pdata		= &pm8xxx_mpp_pdata,
+	.regulator_pdatas	= msm_pm8921_regulator_pdata,
 };
 
 static struct msm_ssbi_platform_data msm8960_ssbi_pm8921_pdata __initdata = {
@@ -107,6 +109,8 @@ static void __init msm8960_sim_init(void)
 	msm8960_device_ssbi_pm8921.dev.platform_data =
 					&msm8960_ssbi_pm8921_pdata;
 
+	pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len;
+
 	platform_add_devices(sim_devices, ARRAY_SIZE(sim_devices));
 }
 
@@ -116,6 +120,8 @@ static void __init msm8960_rumi3_init(void)
 	msm8960_device_ssbi_pm8921.dev.platform_data =
 					&msm8960_ssbi_pm8921_pdata;
 
+	pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len;
+
 	platform_add_devices(rumi3_devices, ARRAY_SIZE(rumi3_devices));
 }
 
diff --git a/arch/arm/mach-msm/board-msm8960.h b/arch/arm/mach-msm/board-msm8960.h
new file mode 100644
index 0000000..040b033
--- /dev/null
+++ b/arch/arm/mach-msm/board-msm8960.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 __ARCH_ARM_MACH_MSM_BOARD_MSM8960_H
+#define __ARCH_ARM_MACH_MSM_BOARD_MSM8960_H
+
+#include <linux/mfd/pm8xxx/pm8921.h>
+
+extern struct pm8921_regulator_platform_data
+	msm_pm8921_regulator_pdata[] __devinitdata;
+
+extern int msm_pm8921_regulator_pdata_len __devinitdata;
+
+#endif
-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.


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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-03-30 22:17 ` [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
@ 2011-03-30 23:46   ` Mark Brown
  2011-03-31  1:37     ` David Collins
  0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2011-03-30 23:46 UTC (permalink / raw)
  To: David Collins
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On Wed, Mar 30, 2011 at 03:17:12PM -0700, David Collins wrote:

> Regulator framework operation callback functions work as expected with
> one exception: pm8921_vreg_get_mode and pm8921_vreg_set_mode.  These
> callbacks are shared by all regulator types in the driver.  The mode
> setting scheme was modified from the normal framework usage to
> accommodate pin control.  Pin control allows a regulator that has been
> disabled by software writing to its control registers to become
> enabled by a hardware pin.  Many of the PM8921 regulators support pin

Don't do this.  There's two problems with it.  One is that you're
abusing a standard API for an unrelated purpose which will confuse
anything that tries to use the API as normal, the other is that this
sounds like a fairly widely supported feature (although normally one
that is used with a GPIO on the CPU, there's a few examples of this in
mainline).

> REGULATOR_MODE_FAST    - set to high power mode (HPM)
> REGULATOR_MODE_NORMAL  - remove a vote for pin control
> REGULATOR_MODE_IDLE    - vote for pin control (ref-counted)
> REGULATOR_MODE_STANDBY - set to low power mode (LPM)

> There is an additional caveat that pin control mode will override LPM
> such that, a set mode call for LPM will be ignored if the pin control
> reference count is greater than 0.  Similarly, HPM will override pin
> control mode.  Transitivity is not maintained though, because the mode
> can be changed from HPM to LPM.  This ensures that it is still
> possible to transition freely between LPM and HPM with calls to
> regulator_set_optimum_mode.

What is the voting that's been referred to here?

> --- a/drivers/mfd/pm8921-core.c
> +++ b/drivers/mfd/pm8921-core.c
> @@ -119,8 +119,9 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data
>  					   struct pm8921 *pmic,
>  					   u32 rev)
>  {
> -	int ret = 0, irq_base = 0;
> +	int ret = 0, irq_base = 0, i;
>  	struct pm_irq_chip *irq_chip;

Don't mix initialised and non-initialised definitions.

> +	/* Add one device for each regulator used by the board. */
> +	if (pdata->num_regulators > 0 && pdata->regulator_pdatas) {
> +		mfd_regulators = kzalloc(sizeof(struct mfd_cell)
> +					 * (pdata->num_regulators), GFP_KERNEL);
> +		if (!mfd_regulators) {
> +			pr_err("Cannot allocate %d bytes for pm8921 regulator "
> +				"mfd cells\n", sizeof(struct mfd_cell)
> +						* (pdata->num_regulators));
> +			ret = -ENOMEM;
> +			goto bail;
> +		}

I know some devices do follow this pattern but it's simpler to just
register the regulators that are physically present on the device
unconditionally.

> +static int pm8921_vreg_write(struct pm8921_vreg *vreg, u16 addr, u8 val,
> +			     u8 mask, u8 *reg_save)

The function is confusingly named - it's actually a bitmask update but
write() would normally suggest a straight write of an absolute value.

> +{
> +	int rc = 0;
> +	u8 reg;
> +
> +	reg = (*reg_save & ~mask) | (val & mask);
> +	if (reg != *reg_save)
> +		rc = pm8xxx_writeb(vreg->dev->parent, addr, reg);
> +
> +	if (rc)
> +		pr_err("pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", addr, rc);
> +	else
> +		*reg_save = reg;
> +
> +	return rc;
> +}

This needs locking if any registers are shared between multiple
regulators.

> +	if (min_uV < PLDO_LOW_UV_MIN || min_uV > PLDO_HIGH_UV_MAX) {
> +		vreg_err(vreg, "requested voltage %d is outside of allowed "
> +				"range.\n", min_uV);

Don't split text over multiple lines, it's not helpful when grepping.

> +static int pm8921_nldo1200_set_voltage(struct regulator_dev *rdev, int min_uV,
> +				   int max_uV, unsigned *selector)
> +{
> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
> +
> +	return _pm8921_nldo1200_set_voltage(vreg, min_uV);
> +}

I have to say I'm not finding the indirection to the _ functions is
actually adding anything to the code here.

> +	/* Round down for set points in the gaps between bands. */
> +	if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN)
> +		uV = FTSMPS_BAND1_UV_MAX;
> +	else if (uV > FTSMPS_BAND2_UV_MAX
> +			&& uV < FTSMPS_BAND3_UV_SETPOINT_MIN)
> +		uV = FTSMPS_BAND2_UV_MAX;

That seems broken - it means you'll go under voltage on what was
requested.  You should ensure that you're always within the requested
range so if the higher band minimum is in the range you should select
that, otherwise you should error out.

In general you're not checking that your voltage selection actually
satisfies the request that went in...

> +static int pm8921_ftsmps_set_voltage(struct regulator_dev *rdev, int min_uV,
> +				     int max_uV, unsigned *selector)
> +{
> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
> +
> +	return _pm8921_ftsmps_set_voltage(vreg, min_uV, 0);
> +}

...note how you discard the upper limit on the requested voltage here
before going into the implementation.

> +static unsigned int pm8921_vreg_get_optimum_mode(struct regulator_dev *rdev,
> +		int input_uV, int output_uV, int load_uA)
> +{
> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
> +
> +	vreg->save_uA = load_uA;

What's this about?  There's no other references to save_uA in the driver
and it looks suspicous.

> +	if (load_uA >= vreg->hpm_min_load)
> +		return REGULATOR_MODE_FAST;
> +
> +	return REGULATOR_MODE_STANDBY;

Using an else would be clearer and less error prone.

> +static int pm8921_vreg_enable(struct regulator_dev *rdev)
> +{
> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
> +	int mode, rc = 0;
> +
> +	mode = pm8921_vreg_get_mode(rdev);
> +
> +	if (mode == REGULATOR_MODE_IDLE) {
> +		/* Turn on pin control. */
> +		rc = pm8921_vreg_set_pin_ctrl(vreg, 1);
> +		if (rc)
> +			goto bail;
> +		return rc;
> +	}

This looks wrong, it's not actually enabling the regulator but putting
it into pin control mode instead.  If something actually wanted the
regulator enabling it's going to be disappointed.

> +	/* Disable in local control register. */
> +	if (vreg->type == REGULATOR_TYPE_SMPS && SMPS_IN_ADVANCED_MODE(vreg)) {
> +		/* Change SMPS to legacy mode before disabling. */
> +		rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV);
> +		if (rc)
> +			goto bail;
> +		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, REGULATOR_DISABLE,
> +			REGULATOR_ENABLE_MASK, &vreg->ctrl_reg);
> +	} else if (vreg->type == REGULATOR_TYPE_FTSMPS) {

This would all be a lot more legible with a switch statement for the
regulator types.

> +static int __devinit pm8921_vreg_probe(struct platform_device *pdev)
> +{
> +	struct regulator_desc *rdesc;
> +	struct pm8921_vreg *vreg;
> +	const char *reg_name = "";
> +	int rc = 0;
> +
> +	if (pdev == NULL)
> +		return -EINVAL;
> +
> +	if (pdev->id >= 0 && pdev->id < PM8921_VREG_ID_MAX) {
> +		rdesc = &pm8921_vreg_description[pdev->id];
> +		vreg = &pm8921_vreg[pdev->id];
> +		memcpy(&(vreg->pdata), pdev->dev.platform_data,
> +			sizeof(struct pm8921_regulator_platform_data));
> +		reg_name = pm8921_vreg_description[pdev->id].name;
> +		vreg->name = reg_name;
> +		vreg->dev = &pdev->dev;
> +
> +		rc = pm8921_init_regulator(vreg);
> +		if (rc)
> +			goto bail;
> +

This function is just a switch statement per regulator, may aswell expan
ithere.  Or restructure things so that you've got a driver per regulator
type - that would also mean you'd be able to get the device IDs to
correspond to the regulator numbers which would probably be clearer.

> +static int __init pm8921_vreg_init(void)
> +{
> +	return platform_driver_register(&pm8921_vreg_driver);
> +}
> +module_init(pm8921_vreg_init);

subsys_initcall().

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

* Re: [PATCH 2/2] msm: board-8960: Add support for pm8921-regulator
  2011-03-30 22:17 ` [PATCH 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
@ 2011-03-31  0:10   ` Mark Brown
  0 siblings, 0 replies; 16+ messages in thread
From: Mark Brown @ 2011-03-31  0:10 UTC (permalink / raw)
  To: David Collins
  Cc: David Brown, Daniel Walker, Bryan Huntsman, Liam Girdwood,
	Samuel Ortiz, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On Wed, Mar 30, 2011 at 03:17:46PM -0700, David Collins wrote:

> +VREG_CONSUMERS(L1) = {
> +	REGULATOR_SUPPLY("8921_l1",		NULL),
> +};

All these supplies named after the regulator name should be removed.
Supplies define the mapping from the regulators on the board to the
supplies on the devices on the board, if any driver is requesting a
specific regulator name then there's an abstraction issue which means
that these should be redundant.

In general if you've got a supply that doesn't have a dev_name
associated with it something is wrong.

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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-03-30 23:46   ` Mark Brown
@ 2011-03-31  1:37     ` David Collins
  2011-03-31 23:44       ` Mark Brown
  0 siblings, 1 reply; 16+ messages in thread
From: David Collins @ 2011-03-31  1:37 UTC (permalink / raw)
  To: Mark Brown
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On 03/30/11 16:46, Mark Brown wrote:
> On Wed, Mar 30, 2011 at 03:17:12PM -0700, David Collins wrote:
> 
>> Regulator framework operation callback functions work as expected with
>> one exception: pm8921_vreg_get_mode and pm8921_vreg_set_mode.  These
>> callbacks are shared by all regulator types in the driver.  The mode
>> setting scheme was modified from the normal framework usage to
>> accommodate pin control.  Pin control allows a regulator that has been
>> disabled by software writing to its control registers to become
>> enabled by a hardware pin.  Many of the PM8921 regulators support pin
> 
> Don't do this.  There's two problems with it.  One is that you're
> abusing a standard API for an unrelated purpose which will confuse
> anything that tries to use the API as normal, the other is that this
> sounds like a fairly widely supported feature (although normally one
> that is used with a GPIO on the CPU, there's a few examples of this in
> mainline).

Could you point out an example of a better way to handle pin control?  I
agree that regulator_set_mode is not the way to do it.  Could some new
regulator framework API be created to handle it?  I'm not sure about the
best way to generalize the interface.

>> REGULATOR_MODE_FAST    - set to high power mode (HPM)
>> REGULATOR_MODE_NORMAL  - remove a vote for pin control
>> REGULATOR_MODE_IDLE    - vote for pin control (ref-counted)
>> REGULATOR_MODE_STANDBY - set to low power mode (LPM)
> 
>> There is an additional caveat that pin control mode will override LPM
>> such that, a set mode call for LPM will be ignored if the pin control
>> reference count is greater than 0.  Similarly, HPM will override pin
>> control mode.  Transitivity is not maintained though, because the mode
>> can be changed from HPM to LPM.  This ensures that it is still
>> possible to transition freely between LPM and HPM with calls to
>> regulator_set_optimum_mode.
> 
> What is the voting that's been referred to here?

Voting here means calling regulator_set_mode(reg, REGULATOR_MODE_IDLE).
These calls into the pm8921-regulator driver set_mode call back function
are ref-counted to decide when to actually set pin control mode.

>> --- a/drivers/mfd/pm8921-core.c
>> +++ b/drivers/mfd/pm8921-core.c
>> @@ -119,8 +119,9 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data
>>  					   struct pm8921 *pmic,
>>  					   u32 rev)
>>  {
>> -	int ret = 0, irq_base = 0;
>> +	int ret = 0, irq_base = 0, i;
>>  	struct pm_irq_chip *irq_chip;
> 
> Don't mix initialised and non-initialised definitions.

I will update this.

>> +	/* Add one device for each regulator used by the board. */
>> +	if (pdata->num_regulators > 0 && pdata->regulator_pdatas) {
>> +		mfd_regulators = kzalloc(sizeof(struct mfd_cell)
>> +					 * (pdata->num_regulators), GFP_KERNEL);
>> +		if (!mfd_regulators) {
>> +			pr_err("Cannot allocate %d bytes for pm8921 regulator "
>> +				"mfd cells\n", sizeof(struct mfd_cell)
>> +						* (pdata->num_regulators));
>> +			ret = -ENOMEM;
>> +			goto bail;
>> +		}
> 
> I know some devices do follow this pattern but it's simpler to just
> register the regulators that are physically present on the device
> unconditionally.

Regulator registration needs to continue being based on which regulators
are configured via the board file platform data because there will be need
in our system in the near future to use a different (shared) regulator
driver to access some of the same physical regulators.

We consider this to be the native PMIC 8921 regulator driver because it
accesses the PMIC directly.  There will be a subsequent PMIC 8921
regulator driver which issues requests to a separate processor which
aggregates our requests with those of other SOC processors.

>> +static int pm8921_vreg_write(struct pm8921_vreg *vreg, u16 addr, u8 val,
>> +			     u8 mask, u8 *reg_save)
> 
> The function is confusingly named - it's actually a bitmask update but
> write() would normally suggest a straight write of an absolute value.

I will change this to something more sane.

>> +{
>> +	int rc = 0;
>> +	u8 reg;
>> +
>> +	reg = (*reg_save & ~mask) | (val & mask);
>> +	if (reg != *reg_save)
>> +		rc = pm8xxx_writeb(vreg->dev->parent, addr, reg);
>> +
>> +	if (rc)
>> +		pr_err("pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", addr, rc);
>> +	else
>> +		*reg_save = reg;
>> +
>> +	return rc;
>> +}
> 
> This needs locking if any registers are shared between multiple
> regulators.

There are no registers shared between multiple regulators.  The mutex
locks held inside of the regulator framework should be sufficient.

>> +	if (min_uV < PLDO_LOW_UV_MIN || min_uV > PLDO_HIGH_UV_MAX) {
>> +		vreg_err(vreg, "requested voltage %d is outside of allowed "
>> +				"range.\n", min_uV);
> 
> Don't split text over multiple lines, it's not helpful when grepping.

I can change this, I'll just have to be ready to ignore checkpatch.pl errors.

>> +static int pm8921_nldo1200_set_voltage(struct regulator_dev *rdev, int min_uV,
>> +				   int max_uV, unsigned *selector)
>> +{
>> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
>> +
>> +	return _pm8921_nldo1200_set_voltage(vreg, min_uV);
>> +}
> 
> I have to say I'm not finding the indirection to the _ functions is
> actually adding anything to the code here.

The _* functions are used so that only relevant data needs to be passed.
It is preferable to grab vreg from rdev only once in the call chain.

>> +	/* Round down for set points in the gaps between bands. */
>> +	if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN)
>> +		uV = FTSMPS_BAND1_UV_MAX;
>> +	else if (uV > FTSMPS_BAND2_UV_MAX
>> +			&& uV < FTSMPS_BAND3_UV_SETPOINT_MIN)
>> +		uV = FTSMPS_BAND2_UV_MAX;
> 
> That seems broken - it means you'll go under voltage on what was
> requested.  You should ensure that you're always within the requested
> range so if the higher band minimum is in the range you should select
> that, otherwise you should error out.

This rounding is done to ensure that the calculations below always yield a
register program voltage that is valid.  I could change this to pick the
minimum of the higher band instead.

In this example, consider the second condition which is checking for
min_uV in the range (1400000, 1500000).  What should the correct result be
if a consumer calls regulator_set_voltage(reg, 1450000, 1450000) (assuming
that it is the only consumer so far and that the constraints step for the
regulator allow the value)?  As the driver is written, it would set the
voltage to 1.4V.  However, I think that 1.5V is more reasonable.
Returning an error for this seems counter productive.

> In general you're not checking that your voltage selection actually
> satisfies the request that went in...
> 
>> +static int pm8921_ftsmps_set_voltage(struct regulator_dev *rdev, int min_uV,
>> +				     int max_uV, unsigned *selector)
>> +{
>> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
>> +
>> +	return _pm8921_ftsmps_set_voltage(vreg, min_uV, 0);
>> +}
> 
> ...note how you discard the upper limit on the requested voltage here
> before going into the implementation.

Yes, it sets the voltage based exclusively on min_uV.

>> +static unsigned int pm8921_vreg_get_optimum_mode(struct regulator_dev *rdev,
>> +		int input_uV, int output_uV, int load_uA)
>> +{
>> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
>> +
>> +	vreg->save_uA = load_uA;
> 
> What's this about?  There's no other references to save_uA in the driver
> and it looks suspicous.

save_uA should be removed.

>> +	if (load_uA >= vreg->hpm_min_load)
>> +		return REGULATOR_MODE_FAST;
>> +
>> +	return REGULATOR_MODE_STANDBY;
> 
> Using an else would be clearer and less error prone.

I'll change it.

>> +static int pm8921_vreg_enable(struct regulator_dev *rdev)
>> +{
>> +	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
>> +	int mode, rc = 0;
>> +
>> +	mode = pm8921_vreg_get_mode(rdev);
>> +
>> +	if (mode == REGULATOR_MODE_IDLE) {
>> +		/* Turn on pin control. */
>> +		rc = pm8921_vreg_set_pin_ctrl(vreg, 1);
>> +		if (rc)
>> +			goto bail;
>> +		return rc;
>> +	}
> 
> This looks wrong, it's not actually enabling the regulator but putting
> it into pin control mode instead.  If something actually wanted the
> regulator enabling it's going to be disappointed.

This comes back to the way that priorities are handled in order to get
into pin control mode.  If the regulator is set to pin control mode before
regulator_enable is called, then pin control should be set instead of
enabling the regulator normally.  If it was in HPM instead (which would
override pin control) then it would be enabled normally.
regulator_disable turns off the regulator and also turns off pin control
so that it is guaranteed to not output any voltage.

This setup will be improved with the addition of a better API to
manipulate pin control.

>> +	/* Disable in local control register. */
>> +	if (vreg->type == REGULATOR_TYPE_SMPS && SMPS_IN_ADVANCED_MODE(vreg)) {
>> +		/* Change SMPS to legacy mode before disabling. */
>> +		rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV);
>> +		if (rc)
>> +			goto bail;
>> +		rc = pm8921_vreg_write(vreg, vreg->ctrl_addr, REGULATOR_DISABLE,
>> +			REGULATOR_ENABLE_MASK, &vreg->ctrl_reg);
>> +	} else if (vreg->type == REGULATOR_TYPE_FTSMPS) {
> 
> This would all be a lot more legible with a switch statement for the
> regulator types.

I'll change it to a switch statement.

>> +static int __devinit pm8921_vreg_probe(struct platform_device *pdev)
>> +{
>> +	struct regulator_desc *rdesc;
>> +	struct pm8921_vreg *vreg;
>> +	const char *reg_name = "";
>> +	int rc = 0;
>> +
>> +	if (pdev == NULL)
>> +		return -EINVAL;
>> +
>> +	if (pdev->id >= 0 && pdev->id < PM8921_VREG_ID_MAX) {
>> +		rdesc = &pm8921_vreg_description[pdev->id];
>> +		vreg = &pm8921_vreg[pdev->id];
>> +		memcpy(&(vreg->pdata), pdev->dev.platform_data,
>> +			sizeof(struct pm8921_regulator_platform_data));
>> +		reg_name = pm8921_vreg_description[pdev->id].name;
>> +		vreg->name = reg_name;
>> +		vreg->dev = &pdev->dev;
>> +
>> +		rc = pm8921_init_regulator(vreg);
>> +		if (rc)
>> +			goto bail;
>> +
> 
> This function is just a switch statement per regulator, may aswell expan
> ithere.  Or restructure things so that you've got a driver per regulator
> type - that would also mean you'd be able to get the device IDs to
> correspond to the regulator numbers which would probably be clearer.

I separated out the type specific init functions because they are
relatively lengthy and independent.  Wouldn't putting that code directly
into a switch statement in the probe function be hard to follow?

>> +static int __init pm8921_vreg_init(void)
>> +{
>> +	return platform_driver_register(&pm8921_vreg_driver);
>> +}
>> +module_init(pm8921_vreg_init);
> 
> subsys_initcall().

I'll change it.

- David

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-03-31  1:37     ` David Collins
@ 2011-03-31 23:44       ` Mark Brown
  2011-04-01 23:28         ` David Collins
  0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2011-03-31 23:44 UTC (permalink / raw)
  To: David Collins
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On Wed, Mar 30, 2011 at 06:37:22PM -0700, David Collins wrote:
> On 03/30/11 16:46, Mark Brown wrote:

> > Don't do this.  There's two problems with it.  One is that you're
> > abusing a standard API for an unrelated purpose which will confuse
> > anything that tries to use the API as normal, the other is that this
> > sounds like a fairly widely supported feature (although normally one
> > that is used with a GPIO on the CPU, there's a few examples of this in
> > mainline).

> Could you point out an example of a better way to handle pin control?  I
> agree that regulator_set_mode is not the way to do it.  Could some new
> regulator framework API be created to handle it?  I'm not sure about the
> best way to generalize the interface.

The wm831x DCDCs and several of the Maxim PMICs have GPIO based control,
though as indicated it's currently set up for CPU control rather than
for control from an external device.  In the case of the wm831x this
could be switched to and from hardware control at runtime though that's
not a feature that's currently used.  There's also some devices which
have seperate controls for different users that do contention control in
hardware.

Without actually knowing what the pin control implementation you have
does and how it's used it's hard to suggest something specific.  I
still don't have a good handle on what is voting, what is actually being
controlled or how it interacts with software control by Linux.  We've
jumped to discussion of the implementation without understanding the
problem.  Could you perhaps go back to square one and explain what
exactly you mean when you say "pin control"?

> >> REGULATOR_MODE_NORMAL  - remove a vote for pin control
> >> REGULATOR_MODE_IDLE    - vote for pin control (ref-counted)

> > What is the voting that's been referred to here?

> Voting here means calling regulator_set_mode(reg, REGULATOR_MODE_IDLE).
> These calls into the pm8921-regulator driver set_mode call back function
> are ref-counted to decide when to actually set pin control mode.

OK, that's definitely not going to work well with set_mode() - it relies
on nothing except drivers that know about your reference counting scheme
ever calling set_mode() and the core code never learning how to do
things like suppress duplicate sets.

> > I know some devices do follow this pattern but it's simpler to just
> > register the regulators that are physically present on the device
> > unconditionally.

> Regulator registration needs to continue being based on which regulators
> are configured via the board file platform data because there will be need
> in our system in the near future to use a different (shared) regulator
> driver to access some of the same physical regulators.

> We consider this to be the native PMIC 8921 regulator driver because it
> accesses the PMIC directly.  There will be a subsequent PMIC 8921
> regulator driver which issues requests to a separate processor which
> aggregates our requests with those of other SOC processors.

That's not an issue - the regulator API won't write to the regulators
unless the machine driver explicitly tells it that this is OK so all
that will happen is that we'll be able to read back the state of the
regulators directly.  If your driver is modifying the state of the
regulators without the API telling it to then we should fix that as the
no-write behaviour is an important safety feature.

> > This needs locking if any registers are shared between multiple
> > regulators.

> There are no registers shared between multiple regulators.  The mutex
> locks held inside of the regulator framework should be sufficient.

OK, a comment would help - this is a very common error in regulator
drivers as many regulators put all the enable bits for the system in a
single register.

> >> +	if (min_uV < PLDO_LOW_UV_MIN || min_uV > PLDO_HIGH_UV_MAX) {
> >> +		vreg_err(vreg, "requested voltage %d is outside of allowed "
> >> +				"range.\n", min_uV);

> > Don't split text over multiple lines, it's not helpful when grepping.

> I can change this, I'll just have to be ready to ignore checkpatch.pl errors.

checkpatch isn't always right on style issues.  In this particular case
having the string on a line by itself will probably avoid the issue.

> >> +	/* Round down for set points in the gaps between bands. */
> >> +	if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN)
> >> +		uV = FTSMPS_BAND1_UV_MAX;
> >> +	else if (uV > FTSMPS_BAND2_UV_MAX
> >> +			&& uV < FTSMPS_BAND3_UV_SETPOINT_MIN)
> >> +		uV = FTSMPS_BAND2_UV_MAX;

> > That seems broken - it means you'll go under voltage on what was
> > requested.  You should ensure that you're always within the requested
> > range so if the higher band minimum is in the range you should select
> > that, otherwise you should error out.

> This rounding is done to ensure that the calculations below always yield a
> register program voltage that is valid.  I could change this to pick the
> minimum of the higher band instead.

Note the thing about rechecking against the bounds - you need to look at
*both* limits.

> In this example, consider the second condition which is checking for
> min_uV in the range (1400000, 1500000).  What should the correct result be
> if a consumer calls regulator_set_voltage(reg, 1450000, 1450000) (assuming
> that it is the only consumer so far and that the constraints step for the
> regulator allow the value)?  As the driver is written, it would set the
> voltage to 1.4V.  However, I think that 1.5V is more reasonable.
> Returning an error for this seems counter productive.

The driver should return an error.  If the consumer wanted exactly 1.45V
then that's what the system should deliver, if the consumer is happy
with other voltages then it should say so.  The specification by range
is there because some consumers are sensitive to particular voltages and
need to get what they asked for, or they may have much more flexibility
in one direction or the other (like DVFS where it's usually no problem
to be overvoltage but being undervoltage will usually fail).  By
allowing the consumer to specify a range we allow the consumer to
control this, having the API or the drivers take guesses on behalf of
the consumer means it's hard for a consumer driver to reliably
interoperate with different regulators.

The API provides a mechanism for enumerating the set of supported
voltages (which IIRC you're not implementing - you should) which
consumers can use to ensure good interoperation.

> > This function is just a switch statement per regulator, may aswell expan
> > ithere.  Or restructure things so that you've got a driver per regulator
> > type - that would also mean you'd be able to get the device IDs to
> > correspond to the regulator numbers which would probably be clearer.

> I separated out the type specific init functions because they are
> relatively lengthy and independent.  Wouldn't putting that code directly
> into a switch statement in the probe function be hard to follow?

Right, but the _init() function is basically just a switch statement
which calls these functions AFAIR - inlining the switch statement
doesn't mean inlining the functions too.

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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-03-31 23:44       ` Mark Brown
@ 2011-04-01 23:28         ` David Collins
  2011-04-02  2:50           ` Mark Brown
  0 siblings, 1 reply; 16+ messages in thread
From: David Collins @ 2011-04-01 23:28 UTC (permalink / raw)
  To: Mark Brown
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On 03/31/2011 04:44 PM, Mark Brown wrote:
> Without actually knowing what the pin control implementation you have
> does and how it's used it's hard to suggest something specific.  I
> still don't have a good handle on what is voting, what is actually being
> controlled or how it interacts with software control by Linux.  We've
> jumped to discussion of the implementation without understanding the
> problem.  Could you perhaps go back to square one and explain what
> exactly you mean when you say "pin control"?

The PMIC 8921 has 4 pin control input pins named: A0, A1, D0, D1 (as noted
in pm8921-regulator.h from my patch).  Each regulator has a pin control
register which provides 8 configuration bits.  4 of these bits correspond
to transitioning the regulator from off to on when the corresponding pin
control input(s) is/are active.  The physical enable state of the
regulator thus becomes the logical OR of the control register enable bit
and any pin control inputs that have been masked for use.

The other 4 bits in the pin control register for a given regulator decide
if the regulator should transition into high power mode (HPM) when the
selected pin control input is asserted.  Any combination of pin control
inputs may be masked for use.  The important use cases then become: OFF ->
ON (HPM) and ON (LPM) -> ON (HPM).  These correspond to
PM8921_VREG_PIN_FN_ENABLE and PM8921_VREG_PIN_FN_MODE respectively in the
header file.

Pin control is typically used by external chips (not the PMIC or the main
CPU) that need to have some say in the state of a regulator.  An example
would be a wifi chip that can enable and disable the regulator powering
its radio circuitry while the main processor is asleep.  It would then
wake the main processor up with an interrupt when new data was available
to processor.

There are cases in which multiple consumer devices are supplied by one
regulator where one consumer is controlled by a driver on the main
processor and another consumer is an independent chip that utilizes pin
control on the regulator.  The first consumer would desire the regulator
to be disabled during sleep because there is no way for it to respond to
the device while asleep.  However, the regulator would need to be
configured for pin control to ensure that it is enabled when the second
consumer expects it to be.  It is also normal to mask off pin control if
the second consumer is to be disabled.

The end result is that three quantities need to be controlled for the
regulator which aren't entirely orthogonal: enable/disable, LPM/HPM, pin
control ON/pin control OFF.  (It would also be useful if the type of pin
control (OFF/HPM or LPM/HPM) could be configured at runtime, but it is ok
if it is statically configured via platform data.)

Do you know of a generic (or specific it is ok to do so) API that I can
add to the regulator framework to support pin control?

>> Regulator registration needs to continue being based on which regulators
>> are configured via the board file platform data because there will be need
>> in our system in the near future to use a different (shared) regulator
>> driver to access some of the same physical regulators.
> 
>> We consider this to be the native PMIC 8921 regulator driver because it
>> accesses the PMIC directly.  There will be a subsequent PMIC 8921
>> regulator driver which issues requests to a separate processor which
>> aggregates our requests with those of other SOC processors.
> 
> That's not an issue - the regulator API won't write to the regulators
> unless the machine driver explicitly tells it that this is OK so all
> that will happen is that we'll be able to read back the state of the
> regulators directly.  If your driver is modifying the state of the
> regulators without the API telling it to then we should fix that as the
> no-write behaviour is an important safety feature.

The regulator probe function reads the regulator register values to figure
out the initial hardware state.  It also sets some regulator controls not
handled by other regulator framework callback functions; e.g.: pull down
enable.  I'm not sure if this could be moved into an init_data
regulator_init callback because that pointer would need to be specified in
the board file where the function would be unknown.

Also, it would pollute the system with unusable devices if all natively
controlled regulator devices were registered if the shared driver was
being used for many regulators.

>>> This needs locking if any registers are shared between multiple
>>> regulators.
> 
>> There are no registers shared between multiple regulators.  The mutex
>> locks held inside of the regulator framework should be sufficient.
> 
> OK, a comment would help - this is a very common error in regulator
> drivers as many regulators put all the enable bits for the system in a
> single register.

I'll add a comment.

>>>> +	/* Round down for set points in the gaps between bands. */
>>>> +	if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN)
>>>> +		uV = FTSMPS_BAND1_UV_MAX;
>>>> +	else if (uV > FTSMPS_BAND2_UV_MAX
>>>> +			&& uV < FTSMPS_BAND3_UV_SETPOINT_MIN)
>>>> +		uV = FTSMPS_BAND2_UV_MAX;
> 
>>> That seems broken - it means you'll go under voltage on what was
>>> requested.  You should ensure that you're always within the requested
>>> range so if the higher band minimum is in the range you should select
>>> that, otherwise you should error out.
> 
>> This rounding is done to ensure that the calculations below always yield a
>> register program voltage that is valid.  I could change this to pick the
>> minimum of the higher band instead.
> 
> Note the thing about rechecking against the bounds - you need to look at
> *both* limits.
> 
>> In this example, consider the second condition which is checking for
>> min_uV in the range (1400000, 1500000).  What should the correct result be
>> if a consumer calls regulator_set_voltage(reg, 1450000, 1450000) (assuming
>> that it is the only consumer so far and that the constraints step for the
>> regulator allow the value)?  As the driver is written, it would set the
>> voltage to 1.4V.  However, I think that 1.5V is more reasonable.
>> Returning an error for this seems counter productive.
> 
> The driver should return an error.  If the consumer wanted exactly 1.45V
> then that's what the system should deliver, if the consumer is happy
> with other voltages then it should say so.  The specification by range
> is there because some consumers are sensitive to particular voltages and
> need to get what they asked for, or they may have much more flexibility
> in one direction or the other (like DVFS where it's usually no problem
> to be overvoltage but being undervoltage will usually fail).  By
> allowing the consumer to specify a range we allow the consumer to
> control this, having the API or the drivers take guesses on behalf of
> the consumer means it's hard for a consumer driver to reliably
> interoperate with different regulators.

I guess I will change the set voltage callbacks so that they round up the
vprog value:
	vprog = (min_uV - band_min_uV + step_size - 1) / step_size
instead of
	vprog = (min_uV - band_min_uV) / step_size
It will also check that the resulting output voltage is <= max_uV.

> The API provides a mechanism for enumerating the set of supported
> voltages (which IIRC you're not implementing - you should) which
> consumers can use to ensure good interoperation.

I should be able to provide a list_voltage callback in the driver.

>>> This function is just a switch statement per regulator, may aswell expan
>>> ithere.  Or restructure things so that you've got a driver per regulator
>>> type - that would also mean you'd be able to get the device IDs to
>>> correspond to the regulator numbers which would probably be clearer.
> 
>> I separated out the type specific init functions because they are
>> relatively lengthy and independent.  Wouldn't putting that code directly
>> into a switch statement in the probe function be hard to follow?
> 
> Right, but the _init() function is basically just a switch statement
> which calls these functions AFAIR - inlining the switch statement
> doesn't mean inlining the functions too.

I misunderstood before.  I will move the switch statement.

- David

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-04-01 23:28         ` David Collins
@ 2011-04-02  2:50           ` Mark Brown
  2011-04-04 17:13             ` David Collins
  0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2011-04-02  2:50 UTC (permalink / raw)
  To: David Collins
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On Fri, Apr 01, 2011 at 04:28:29PM -0700, David Collins wrote:

> the device while asleep.  However, the regulator would need to be
> configured for pin control to ensure that it is enabled when the second
> consumer expects it to be.  It is also normal to mask off pin control if
> the second consumer is to be disabled.

This all seems fairly standard and like many other regulators except for
the the masking off the pin control.  The only unusual thing is that the
hardware control is being managed by software at runtime, usually it's
set up and then left alone by software.

The first thing I'm thinking here is that this maps fairly well onto a
semi-virtual regulator where the operations map onto the control for the
hardware mode.  The enable/disable control via the pin is totally
orthogonal to the control via the regular register interface, and it
sounds like the mode control is too though that was less clear to me.
Enabling would then translate into turning pin control on rather than
actually enabling but that seems OK.  Does that sound like it'd work?

> >> We consider this to be the native PMIC 8921 regulator driver because it
> >> accesses the PMIC directly.  There will be a subsequent PMIC 8921
> >> regulator driver which issues requests to a separate processor which
> >> aggregates our requests with those of other SOC processors.

> > That's not an issue - the regulator API won't write to the regulators
> > unless the machine driver explicitly tells it that this is OK so all
> > that will happen is that we'll be able to read back the state of the
> > regulators directly.  If your driver is modifying the state of the
> > regulators without the API telling it to then we should fix that as the
> > no-write behaviour is an important safety feature.

> The regulator probe function reads the regulator register values to figure
> out the initial hardware state.  It also sets some regulator controls not
> handled by other regulator framework callback functions; e.g.: pull down

That just sounds like platform data which could just work in the same
way as the regulator core - no platform data would mean nothing gets
changed.

> enable.  I'm not sure if this could be moved into an init_data
> regulator_init callback because that pointer would need to be specified in
> the board file where the function would be unknown.

I'm sorry, I don't understand what you're saying here.  Could you
clarify?

> Also, it would pollute the system with unusable devices if all natively
> controlled regulator devices were registered if the shared driver was
> being used for many regulators.

Depends if you view it as pollution or not; also note that the devices
aren't totally unusuable as you can still use them for readback.

> I guess I will change the set voltage callbacks so that they round up the
> vprog value:
> 	vprog = (min_uV - band_min_uV + step_size - 1) / step_size
> instead of
> 	vprog = (min_uV - band_min_uV) / step_size
> It will also check that the resulting output voltage is <= max_uV.

That sounds right, yes.

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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-04-02  2:50           ` Mark Brown
@ 2011-04-04 17:13             ` David Collins
  2011-04-04 23:19               ` Mark Brown
  0 siblings, 1 reply; 16+ messages in thread
From: David Collins @ 2011-04-04 17:13 UTC (permalink / raw)
  To: Mark Brown
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On 04/01/2011 07:50 PM, Mark Brown wrote:
> This all seems fairly standard and like many other regulators except for
> the the masking off the pin control.  The only unusual thing is that the
> hardware control is being managed by software at runtime, usually it's
> set up and then left alone by software.
> 
> The first thing I'm thinking here is that this maps fairly well onto a
> semi-virtual regulator where the operations map onto the control for the
> hardware mode.  The enable/disable control via the pin is totally
> orthogonal to the control via the regular register interface, and it
> sounds like the mode control is too though that was less clear to me.
> Enabling would then translate into turning pin control on rather than
> actually enabling but that seems OK.  Does that sound like it'd work?

So what you are suggesting is that two regulators are registered for a
single physical regulator.  The first operates normally with respect to
enable, set mode, set voltage, etc.  The second will be used exclusively
for pin control in which regulator_enable and regulator_disable will map
to enabling and disabling pin control.  I suppose that this should be ok
as long as it isn't confusing for consumers to utilize.  However, I will
definitely only register pin control regulators which are marked for use
with pin control via some of the platform data values.  Otherwise the
number of regulators registered would double unnecessarily.

Also, while conceptually pin control should be independent of other
regulator features, it becomes nonorthogonal in practice.  For instance,
some regulators have finer voltage stepping available in advanced
operating mode, but pin control cannot be used in this advanced mode.  The
result is that set_voltage becomes tried to pin control for these
regulators.  The outcome is that the new pin control semi-virtual
regulator would be tightly coupled to its real sibling regulator.
Implementing it in this manner may get complicated.

I suppose there is also a question of what regulator_disable should mean
in terms of outcome regulator state.  Thus far, I have interpreted it to
mean that the regulator is definitely turned off physically.  As such, pin
control must be disabled if it was previously enabled.  Do you think this
is the best approach to take, or should the concepts of regulator enable
and regulator pin control enable be orthogonal?

>> The regulator probe function reads the regulator register values to figure
>> out the initial hardware state.  It also sets some regulator controls not
>> handled by other regulator framework callback functions; e.g.: pull down
> 
> That just sounds like platform data which could just work in the same
> way as the regulator core - no platform data would mean nothing gets
> changed.

That is more or less how the patch already works.  If platform data for a
given regulator is not passed to the pm8921-core, then that regulator is
never probed (via platform device) and the regulator_dev is not created
for it (in the platform driver probe).  The loop in pm8921_add_subdevices
which adds the pm8921-regulator devices is important because it must
iterate in topological order with respect to the regulator supply chain.
This ordering is arbitrary and unknown to either the pm8921-core or the
pm8921-regulator.  If there is no strong requirement for this to change, I
would prefer to keep it as is.

>> enable.  I'm not sure if this could be moved into an init_data
>> regulator_init callback because that pointer would need to be specified in
>> the board file where the function would be unknown.
> 
> I'm sorry, I don't understand what you're saying here.  Could you
> clarify?

struct regulator_init_data has a member: regulator_init.  However, I don't
think there is a good way to make use of it for this system because that
function pointer must be specified statically in a board file, but the
regulator register init function is private to the pm8921-regulator driver.

>> Also, it would pollute the system with unusable devices if all natively
>> controlled regulator devices were registered if the shared driver was
>> being used for many regulators.
> 
> Depends if you view it as pollution or not; also note that the devices
> aren't totally unusuable as you can still use them for readback.

They wouldn't be useful for readback either because register values are
only read during initialization.  The remainder of the regulator usage is
write-only.  This works under the assumption that PMIC registers will not
be modified by any other writers.  This assumption is true except in the
case of a regulator managed via the shared interface.

- David

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

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

* Re: [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-04-04 17:13             ` David Collins
@ 2011-04-04 23:19               ` Mark Brown
  0 siblings, 0 replies; 16+ messages in thread
From: Mark Brown @ 2011-04-04 23:19 UTC (permalink / raw)
  To: David Collins
  Cc: Liam Girdwood, Samuel Ortiz, David Brown, Daniel Walker,
	Bryan Huntsman, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On Mon, Apr 04, 2011 at 10:13:08AM -0700, David Collins wrote:

> as long as it isn't confusing for consumers to utilize.  However, I will
> definitely only register pin control regulators which are marked for use
> with pin control via some of the platform data values.  Otherwise the
> number of regulators registered would double unnecessarily.

That's what I'd expect - in a very real sense the extra regulators don't
exist unless a pin is assigned to them.

> Also, while conceptually pin control should be independent of other
> regulator features, it becomes nonorthogonal in practice.  For instance,
> some regulators have finer voltage stepping available in advanced
> operating mode, but pin control cannot be used in this advanced mode.  The
> result is that set_voltage becomes tried to pin control for these
> regulators.  The outcome is that the new pin control semi-virtual
> regulator would be tightly coupled to its real sibling regulator.
> Implementing it in this manner may get complicated.

The pin and non-pin control are tied together anyway.

> I suppose there is also a question of what regulator_disable should mean
> in terms of outcome regulator state.  Thus far, I have interpreted it to
> mean that the regulator is definitely turned off physically.  As such, pin
> control must be disabled if it was previously enabled.  Do you think this
> is the best approach to take, or should the concepts of regulator enable
> and regulator pin control enable be orthogonal?

I'd keep them orthogonal - regulator_disable() doesn't mean disable the
regulator, it means that the consumer no longer requires that the
regulator be enabled.  Constraints or other consumers may still keep it
enabled.  Ensuring that the consumer can actually power the regulator
off is a system design issue that'd apply either way.

> > That just sounds like platform data which could just work in the same
> > way as the regulator core - no platform data would mean nothing gets
> > changed.

> That is more or less how the patch already works.  If platform data for a
> given regulator is not passed to the pm8921-core, then that regulator is
> never probed (via platform device) and the regulator_dev is not created
> for it (in the platform driver probe).  The loop in pm8921_add_subdevices
> which adds the pm8921-regulator devices is important because it must
> iterate in topological order with respect to the regulator supply chain.
> This ordering is arbitrary and unknown to either the pm8921-core or the
> pm8921-regulator.  If there is no strong requirement for this to change, I
> would prefer to keep it as is.

It does correlate with drivers that feel fragile, usually to do with
neither the MFD core nor the regulator driver being sure quite which
regulators actually exist.

> >> enable.  I'm not sure if this could be moved into an init_data
> >> regulator_init callback because that pointer would need to be specified in
> >> the board file where the function would be unknown.

> > I'm sorry, I don't understand what you're saying here.  Could you
> > clarify?

> struct regulator_init_data has a member: regulator_init.  However, I don't
> think there is a good way to make use of it for this system because that
> function pointer must be specified statically in a board file, but the
> regulator register init function is private to the pm8921-regulator driver.

I don't follow how this is connected to the above - why would one wish
to configure this with a callback rather than data?

> > Depends if you view it as pollution or not; also note that the devices
> > aren't totally unusuable as you can still use them for readback.

> They wouldn't be useful for readback either because register values are
> only read during initialization.  The remainder of the regulator usage is
> write-only.  This works under the assumption that PMIC registers will not
> be modified by any other writers.  This assumption is true except in the
> case of a regulator managed via the shared interface.

Is this enforced in hardware?

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

* [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver
  2011-03-30 22:16 [PATCH 0/2] regulator: msm: Add PM8921 regulator driver David Collins
  2011-03-30 22:17 ` [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
  2011-03-30 22:17 ` [PATCH 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
@ 2011-04-29 23:07 ` David Collins
  2011-04-29 23:09   ` [PATCH v2 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
                     ` (2 more replies)
  2 siblings, 3 replies; 16+ messages in thread
From: David Collins @ 2011-04-29 23:07 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, David Brown, Daniel Walker,
	Bryan Huntsman, Samuel Ortiz
  Cc: linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner, David Collins

This patch set adds a new regulator driver to control the regulators in
Qualcomm PM8921 PMIC chips.  It also introduces board file changes to use
this new driver with the Qualcomm MSM 8960 platform.

This patch set depends upon the following outstanding PM8XXX and PM8921
patches:

https://patchwork.kernel.org/patch/688832/
https://patchwork.kernel.org/patch/688882/
https://patchwork.kernel.org/patch/735362/
https://patchwork.kernel.org/patch/735372/
https://patchwork.kernel.org/patch/688842/

Updates from V1:
Major modifications were made based on Mark Brown's suggestions.

Major changes:
 1. A set of semi-virtual regulators were added to support pin-control.
 2. The set_mode callbacks were modified so that they act normal and do not
    control pin-control.
 3. Changed set_voltage callbacks to round up min_uV to the next available set
    point and to bail out if that set point is greater than max_uV.
 4. Added list_voltage callbacks.
 5. Enable and disable callbacks were separated out per regualtor type to
    handle differing requirements for pin controllable regulators.

Minor changes:
 1. Moved kfree of regulator mfd_cells to the PMIC core remove function.
 2. Simplified handling of unknown voltages in regulators in which enable state
    and output voltage are set using the same bits.
 3. Made symbol names more consistent.
 4. Modified how overlapping voltage ranges are handled to ensure most
    efficient operation.
 5. Corrected names used for PMIC 8921 pin control inputs.
 6. Removed unused member: struct pm8921_vreg.state
 7. Renamed pm8921_vreg_write to pm8921_vreg_masked_write to better illustrate
    its functionality.
 8. Added pm8921_vreg_masked_write_forced to handle registers that must be
    overwritten even if their value is unchanged in order for hardware to
    change state.
 9. Mutex locking was added for all callback functions which touch pin
    controllable regulators because the regulator framework will have no way
    of knowing that two separate regualtor_dev's modify the same physical
    regulators.
10. Debug/error messages were moved so that they do not span multiple lines.
11. pm8921_init_regulator was removed and the switch statement in it was moved
    into pm8921_vreg_probe.
12. pm8921_vreg_init was changed from module_init to subsys_init.
13. Comments were added describing the enums and structs in pm8921-regulator.h.
14. system_uA platform data was added to quantify the max current draw 
    intrinsic to a system that is not associated with any consumer.
15. Changed NULL to "reg-debug-consumer" in the board file.
16. Remove pin-control column in board file for normal regulator constraints.

David Collins (2):
  regulator: pm8921-regulator: Add regulator driver for PM8921
  msm: board-8960: Add support for pm8921-regulator

 arch/arm/mach-msm/Makefile                  |    1 +
 arch/arm/mach-msm/board-msm8960-regulator.c |  306 +++
 arch/arm/mach-msm/board-msm8960.c           |    4 +
 arch/arm/mach-msm/board-msm8960.h           |   23 +
 drivers/mfd/pm8921-core.c                   |   34 +
 drivers/regulator/Kconfig                   |   10 +
 drivers/regulator/Makefile                  |    1 +
 drivers/regulator/pm8921-regulator.c        | 3025 +++++++++++++++++++++++++++
 include/linux/mfd/pm8xxx/pm8921.h           |    3 +
 include/linux/regulator/pm8921-regulator.h  |  155 ++
 10 files changed, 3562 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-msm/board-msm8960-regulator.c
 create mode 100644 arch/arm/mach-msm/board-msm8960.h
 create mode 100644 drivers/regulator/pm8921-regulator.c
 create mode 100644 include/linux/regulator/pm8921-regulator.h

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.


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

* [PATCH v2 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921
  2011-04-29 23:07 ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver David Collins
@ 2011-04-29 23:09   ` David Collins
  2011-04-29 23:11   ` [PATCH v2 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
  2011-05-03  9:48   ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver Mark Brown
  2 siblings, 0 replies; 16+ messages in thread
From: David Collins @ 2011-04-29 23:09 UTC (permalink / raw)
  To: Liam Girdwood, Mark Brown, Samuel Ortiz
  Cc: David Brown, Daniel Walker, Bryan Huntsman, linux-kernel,
	linux-arm-msm, linux-arm-kernel, linux-arm-msm-owner,
	David Collins

Create a regulator driver to control all regulators on a Qualcomm
PM8921 PMIC chip.  This chip contains many different types of
regulators with a wide range of abilities and voltage ranges.

Eight different regulator types are available on the PM8921.  These
are managed via 7 different type values in the driver:

LDO      - low drop out regulator (supports both NMOS and PMOS LDOs)
NLDO1200 - 1.2A NMOS LDO (different control structure than other LDOs)
SMPS     - switched-mode power supply
FTSMPS   - fast transient SMPS
VS       - voltage switch
VS300    - 300mA voltage switch (different control structure than
             other switches)
NCP      - negative charge pump

The driver interfaces with the PMIC using Qualcomm's SSBI bus.
Calls to this bus are abstracted through the pm8xxx_readb/writeb API.

Pin control of the regulators which support it (LDO, SMPS, VS) is
provided via a secondary set of semi-virtual regulator devices.
These use the enable and disable callbacks to enable and disable
pin control of their associated regulator.

Signed-off-by: David Collins <collinsd@codeaurora.org>
---
 drivers/mfd/pm8921-core.c                  |   34 +
 drivers/regulator/Kconfig                  |   10 +
 drivers/regulator/Makefile                 |    1 +
 drivers/regulator/pm8921-regulator.c       | 3025 ++++++++++++++++++++++++++++
 include/linux/mfd/pm8xxx/pm8921.h          |    3 +
 include/linux/regulator/pm8921-regulator.h |  155 ++
 6 files changed, 3228 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/pm8921-regulator.c
 create mode 100644 include/linux/regulator/pm8921-regulator.h

diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c
index 2fbd42d..fa69a05 100644
--- a/drivers/mfd/pm8921-core.c
+++ b/drivers/mfd/pm8921-core.c
@@ -30,6 +30,7 @@
 struct pm8921 {
 	struct device			*dev;
 	struct pm_irq_chip		*irq_chip;
+	struct mfd_cell			*mfd_regulators;
 };
 
 static int pm8921_readb(const struct device *dev, u16 addr, u8 *val)
@@ -121,6 +122,8 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data
 {
 	int ret = 0, irq_base = 0;
 	struct pm_irq_chip *irq_chip;
+	static struct mfd_cell *mfd_regulators;
+	int i;
 
 	if (pdata->irq_pdata) {
 		pdata->irq_pdata->irq_cdata.nirqs = PM8921_NR_IRQS;
@@ -160,6 +163,36 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data
 		}
 	}
 
+	/* Add one device for each regulator used by the board. */
+	if (pdata->num_regulators > 0 && pdata->regulator_pdatas) {
+		mfd_regulators = kzalloc(sizeof(struct mfd_cell)
+					 * (pdata->num_regulators), GFP_KERNEL);
+		if (!mfd_regulators) {
+			pr_err("Cannot allocate %d bytes for pm8921 regulator "
+				"mfd cells\n", sizeof(struct mfd_cell)
+						* (pdata->num_regulators));
+			ret = -ENOMEM;
+			goto bail;
+		}
+		for (i = 0; i < pdata->num_regulators; i++) {
+			mfd_regulators[i].name = PM8921_REGULATOR_DEV_NAME;
+			mfd_regulators[i].id = pdata->regulator_pdatas[i].id;
+			mfd_regulators[i].platform_data =
+				&(pdata->regulator_pdatas[i]);
+			mfd_regulators[i].pdata_size =
+				sizeof(struct pm8921_regulator_platform_data);
+		}
+		ret = mfd_add_devices(pmic->dev, 0, mfd_regulators,
+				pdata->num_regulators, NULL, irq_base);
+		if (ret) {
+			pr_err("Failed to add regulator subdevices ret=%d\n",
+				ret);
+			kfree(mfd_regulators);
+			goto bail;
+		}
+		pmic->mfd_regulators = mfd_regulators;
+	}
+
 	return 0;
 bail:
 	if (pmic->irq_chip) {
@@ -245,6 +278,7 @@ static int __devexit pm8921_remove(struct platform_device *pdev)
 		pmic->irq_chip = NULL;
 	}
 	platform_set_drvdata(pdev, NULL);
+	kfree(pmic->mfd_regulators);
 	kfree(pmic);
 
 	return 0;
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index b9f29e0..c9e569a 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -290,5 +290,15 @@ config REGULATOR_TPS6524X
 	  serial interface currently supported on the sequencer serial
 	  port controller.
 
+config REGULATOR_PM8921
+	tristate "Qualcomm PM8921 PMIC Power regulators"
+	depends on MFD_PM8921_CORE
+	default y if MFD_PM8921_CORE
+	help
+	  This driver supports voltage regulators in the Qualcomm PM8921 PMIC
+	  chip.  The PM8921 provides several different varieties of LDO and
+	  switching regulators.  It also provides a negative charge pump and
+	  voltage switches.
+
 endif
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index d72a427..7e60924 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -41,5 +41,6 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o
 obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o
 obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
 obj-$(CONFIG_REGULATOR_AB8500)	+= ab8500.o
+obj-$(CONFIG_REGULATOR_PM8921) += pm8921-regulator.o
 
 ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/pm8921-regulator.c b/drivers/regulator/pm8921-regulator.c
new file mode 100644
index 0000000..cb56d4b
--- /dev/null
+++ b/drivers/regulator/pm8921-regulator.c
@@ -0,0 +1,3025 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/pm8921-regulator.h>
+#include <linux/mfd/pm8xxx/core.h>
+
+#define REGULATOR_TYPE_LDO		0
+#define REGULATOR_TYPE_NLDO1200		1
+#define REGULATOR_TYPE_SMPS		2
+#define REGULATOR_TYPE_FTSMPS		3
+#define REGULATOR_TYPE_VS		4
+#define REGULATOR_TYPE_VS300		5
+#define REGULATOR_TYPE_NCP		6
+
+/* Common Masks */
+#define REGULATOR_ENABLE_MASK		0x80
+#define REGULATOR_ENABLE		0x80
+#define REGULATOR_DISABLE		0x00
+
+#define REGULATOR_BANK_MASK		0xF0
+#define REGULATOR_BANK_SEL(n)		((n) << 4)
+#define REGULATOR_BANK_WRITE		0x80
+
+#define LDO_TEST_BANKS			7
+#define NLDO1200_TEST_BANKS		5
+#define SMPS_TEST_BANKS			8
+#define REGULATOR_TEST_BANKS_MAX	SMPS_TEST_BANKS
+
+/*
+ * This voltage in uV is returned by get_voltage functions when there is no way
+ * to determine the current voltage level.  It is needed because the regulator
+ * framework treats a 0 uV voltage as an error.
+ */
+#define VOLTAGE_UNKNOWN			1
+
+/* LDO masks and values */
+
+/* CTRL register */
+#define LDO_ENABLE_MASK			0x80
+#define LDO_DISABLE			0x00
+#define LDO_ENABLE			0x80
+#define LDO_PULL_DOWN_ENABLE_MASK	0x40
+#define LDO_PULL_DOWN_ENABLE		0x40
+
+#define LDO_CTRL_PM_MASK		0x20
+#define LDO_CTRL_PM_HPM			0x00
+#define LDO_CTRL_PM_LPM			0x20
+
+#define LDO_CTRL_VPROG_MASK		0x1F
+
+/* TEST register bank 0 */
+#define LDO_TEST_LPM_MASK		0x40
+#define LDO_TEST_LPM_SEL_CTRL		0x00
+#define LDO_TEST_LPM_SEL_TCXO		0x40
+
+/* TEST register bank 2 */
+#define LDO_TEST_VPROG_UPDATE_MASK	0x08
+#define LDO_TEST_RANGE_SEL_MASK		0x04
+#define LDO_TEST_FINE_STEP_MASK		0x02
+#define LDO_TEST_FINE_STEP_SHIFT	1
+
+/* TEST register bank 4 */
+#define LDO_TEST_RANGE_EXT_MASK		0x01
+
+/* TEST register bank 5 */
+#define LDO_TEST_PIN_CTRL_MASK		0x0F
+#define LDO_TEST_PIN_CTRL_EN3		0x08
+#define LDO_TEST_PIN_CTRL_EN2		0x04
+#define LDO_TEST_PIN_CTRL_EN1		0x02
+#define LDO_TEST_PIN_CTRL_EN0		0x01
+
+/* TEST register bank 6 */
+#define LDO_TEST_PIN_CTRL_LPM_MASK	0x0F
+
+
+/*
+ * If a given voltage could be output by two ranges, then the preferred one must
+ * be determined by the range limits.  Specified voltage ranges should must
+ * not overlap.
+ *
+ * Allowable voltage ranges:
+ */
+#define PLDO_LOW_UV_MIN			750000
+#define PLDO_LOW_UV_MAX			1487500
+#define PLDO_LOW_UV_FINE_STEP		12500
+
+#define PLDO_NORM_UV_MIN		1500000
+#define PLDO_NORM_UV_MAX		3075000
+#define PLDO_NORM_UV_FINE_STEP		25000
+
+#define PLDO_HIGH_UV_MIN		1750000
+#define PLDO_HIGH_UV_SET_POINT_MIN	3100000
+#define PLDO_HIGH_UV_MAX		4900000
+#define PLDO_HIGH_UV_FINE_STEP		50000
+
+#define PLDO_LOW_SET_POINTS		((PLDO_LOW_UV_MAX - PLDO_LOW_UV_MIN) \
+						/ PLDO_LOW_UV_FINE_STEP + 1)
+#define PLDO_NORM_SET_POINTS		((PLDO_NORM_UV_MAX - PLDO_NORM_UV_MIN) \
+						/ PLDO_NORM_UV_FINE_STEP + 1)
+#define PLDO_HIGH_SET_POINTS		((PLDO_HIGH_UV_MAX \
+						- PLDO_HIGH_UV_SET_POINT_MIN) \
+					/ PLDO_HIGH_UV_FINE_STEP + 1)
+#define PLDO_SET_POINTS			(PLDO_LOW_SET_POINTS \
+						+ PLDO_NORM_SET_POINTS \
+						+ PLDO_HIGH_SET_POINTS)
+
+#define NLDO_UV_MIN			750000
+#define NLDO_UV_MAX			1537500
+#define NLDO_UV_FINE_STEP		12500
+
+#define NLDO_SET_POINTS			((NLDO_UV_MAX - NLDO_UV_MIN) \
+						/ NLDO_UV_FINE_STEP + 1)
+
+/* NLDO1200 masks and values */
+
+/* CTRL register */
+#define NLDO1200_ENABLE_MASK		0x80
+#define NLDO1200_DISABLE		0x00
+#define NLDO1200_ENABLE			0x80
+
+/* Legacy mode */
+#define NLDO1200_LEGACY_PM_MASK		0x20
+#define NLDO1200_LEGACY_PM_HPM		0x00
+#define NLDO1200_LEGACY_PM_LPM		0x20
+
+/* Advanced mode */
+#define NLDO1200_CTRL_RANGE_MASK	0x40
+#define NLDO1200_CTRL_RANGE_HIGH	0x00
+#define NLDO1200_CTRL_RANGE_LOW		0x40
+#define NLDO1200_CTRL_VPROG_MASK	0x3F
+
+#define NLDO1200_LOW_UV_MIN		375000
+#define NLDO1200_LOW_UV_MAX		743750
+#define NLDO1200_LOW_UV_STEP		6250
+
+#define NLDO1200_HIGH_UV_MIN		750000
+#define NLDO1200_HIGH_UV_MAX		1537500
+#define NLDO1200_HIGH_UV_STEP		12500
+
+#define NLDO1200_LOW_SET_POINTS		((NLDO1200_LOW_UV_MAX \
+						- NLDO1200_LOW_UV_MIN) \
+					/ NLDO1200_LOW_UV_STEP + 1)
+#define NLDO1200_HIGH_SET_POINTS	((NLDO1200_HIGH_UV_MAX \
+						- NLDO1200_HIGH_UV_MIN) \
+					/ NLDO1200_HIGH_UV_STEP + 1)
+#define NLDO1200_SET_POINTS		(NLDO1200_LOW_SET_POINTS \
+						+ NLDO1200_HIGH_SET_POINTS)
+
+/* TEST register bank 0 */
+#define NLDO1200_TEST_LPM_MASK		0x04
+#define NLDO1200_TEST_LPM_SEL_CTRL	0x00
+#define NLDO1200_TEST_LPM_SEL_TCXO	0x04
+
+/* TEST register bank 1 */
+#define NLDO1200_PULL_DOWN_ENABLE_MASK	0x02
+#define NLDO1200_PULL_DOWN_ENABLE	0x02
+
+/* TEST register bank 2 */
+#define NLDO1200_ADVANCED_MODE_MASK	0x08
+#define NLDO1200_ADVANCED_MODE		0x00
+#define NLDO1200_LEGACY_MODE		0x08
+
+/* Advanced mode power mode control */
+#define NLDO1200_ADVANCED_PM_MASK	0x02
+#define NLDO1200_ADVANCED_PM_HPM	0x00
+#define NLDO1200_ADVANCED_PM_LPM	0x02
+
+#define NLDO1200_IN_ADVANCED_MODE(vreg) \
+	((vreg->test_reg[2] & NLDO1200_ADVANCED_MODE_MASK) \
+	 == NLDO1200_ADVANCED_MODE)
+
+/* SMPS masks and values */
+
+/* CTRL register */
+
+/* Legacy mode */
+#define SMPS_LEGACY_ENABLE_MASK		0x80
+#define SMPS_LEGACY_DISABLE		0x00
+#define SMPS_LEGACY_ENABLE		0x80
+#define SMPS_LEGACY_PULL_DOWN_ENABLE	0x40
+#define SMPS_LEGACY_VREF_SEL_MASK	0x20
+#define SMPS_LEGACY_VPROG_MASK		0x1F
+
+/* Advanced mode */
+#define SMPS_ADVANCED_BAND_MASK		0xC0
+#define SMPS_ADVANCED_BAND_OFF		0x00
+#define SMPS_ADVANCED_BAND_1		0x40
+#define SMPS_ADVANCED_BAND_2		0x80
+#define SMPS_ADVANCED_BAND_3		0xC0
+#define SMPS_ADVANCED_VPROG_MASK	0x3F
+
+/* Legacy mode voltage ranges */
+#define SMPS_MODE3_UV_MIN		375000
+#define SMPS_MODE3_UV_MAX		725000
+#define SMPS_MODE3_UV_STEP		25000
+
+#define SMPS_MODE2_UV_MIN		750000
+#define SMPS_MODE2_UV_MAX		1475000
+#define SMPS_MODE2_UV_STEP		25000
+
+#define SMPS_MODE1_UV_MIN		1500000
+#define SMPS_MODE1_UV_MAX		3050000
+#define SMPS_MODE1_UV_STEP		50000
+
+#define SMPS_MODE3_SET_POINTS		((SMPS_MODE3_UV_MAX \
+						- SMPS_MODE3_UV_MIN) \
+					/ SMPS_MODE3_UV_STEP + 1)
+#define SMPS_MODE2_SET_POINTS		((SMPS_MODE2_UV_MAX \
+						- SMPS_MODE2_UV_MIN) \
+					/ SMPS_MODE2_UV_STEP + 1)
+#define SMPS_MODE1_SET_POINTS		((SMPS_MODE1_UV_MAX \
+						- SMPS_MODE1_UV_MIN) \
+					/ SMPS_MODE1_UV_STEP + 1)
+#define SMPS_LEGACY_SET_POINTS		(SMPS_MODE3_SET_POINTS \
+						+ SMPS_MODE2_SET_POINTS \
+						+ SMPS_MODE1_SET_POINTS)
+
+/* Advanced mode voltage ranges */
+#define SMPS_BAND1_UV_MIN		375000
+#define SMPS_BAND1_UV_MAX		737500
+#define SMPS_BAND1_UV_STEP		12500
+
+#define SMPS_BAND2_UV_MIN		750000
+#define SMPS_BAND2_UV_MAX		1487500
+#define SMPS_BAND2_UV_STEP		12500
+
+#define SMPS_BAND3_UV_MIN		1500000
+#define SMPS_BAND3_UV_MAX		3075000
+#define SMPS_BAND3_UV_STEP		25000
+
+#define SMPS_BAND1_SET_POINTS		((SMPS_BAND1_UV_MAX \
+						- SMPS_BAND1_UV_MIN) \
+					/ SMPS_BAND1_UV_STEP + 1)
+#define SMPS_BAND2_SET_POINTS		((SMPS_BAND2_UV_MAX \
+						- SMPS_BAND2_UV_MIN) \
+					/ SMPS_BAND2_UV_STEP + 1)
+#define SMPS_BAND3_SET_POINTS		((SMPS_BAND3_UV_MAX \
+						- SMPS_BAND3_UV_MIN) \
+					/ SMPS_BAND3_UV_STEP + 1)
+#define SMPS_ADVANCED_SET_POINTS	(SMPS_BAND1_SET_POINTS \
+						+ SMPS_BAND2_SET_POINTS \
+						+ SMPS_BAND3_SET_POINTS)
+
+/* Test2 register bank 1 */
+#define SMPS_LEGACY_VLOW_SEL_MASK	0x01
+
+/* Test2 register bank 6 */
+#define SMPS_ADVANCED_PULL_DOWN_ENABLE	0x08
+
+/* Test2 register bank 7 */
+#define SMPS_ADVANCED_MODE_MASK		0x02
+#define SMPS_ADVANCED_MODE		0x02
+#define SMPS_LEGACY_MODE		0x00
+
+#define SMPS_IN_ADVANCED_MODE(vreg) \
+	((vreg->test_reg[7] & SMPS_ADVANCED_MODE_MASK) == SMPS_ADVANCED_MODE)
+
+/* BUCK_SLEEP_CNTRL register */
+#define SMPS_PIN_CTRL_MASK		0xF0
+#define SMPS_PIN_CTRL_EN3		0x80
+#define SMPS_PIN_CTRL_EN2		0x40
+#define SMPS_PIN_CTRL_EN1		0x20
+#define SMPS_PIN_CTRL_EN0		0x10
+
+#define SMPS_PIN_CTRL_LPM_MASK		0x0F
+#define SMPS_PIN_CTRL_LPM_EN3		0x08
+#define SMPS_PIN_CTRL_LPM_EN2		0x04
+#define SMPS_PIN_CTRL_LPM_EN1		0x02
+#define SMPS_PIN_CTRL_LPM_EN0		0x01
+
+/* BUCK_CLOCK_CNTRL register */
+#define SMPS_CLK_DIVIDE2		0x40
+
+#define SMPS_CLK_CTRL_MASK		0x30
+#define SMPS_CLK_CTRL_FOLLOW_TCXO	0x00
+#define SMPS_CLK_CTRL_PWM		0x10
+#define SMPS_CLK_CTRL_PFM		0x20
+
+/* FTSMPS masks and values */
+
+/* CTRL register */
+#define FTSMPS_VCTRL_BAND_MASK		0xC0
+#define FTSMPS_VCTRL_BAND_OFF		0x00
+#define FTSMPS_VCTRL_BAND_1		0x40
+#define FTSMPS_VCTRL_BAND_2		0x80
+#define FTSMPS_VCTRL_BAND_3		0xC0
+#define FTSMPS_VCTRL_VPROG_MASK		0x3F
+
+#define FTSMPS_BAND1_UV_MIN		350000
+#define FTSMPS_BAND1_UV_MAX		650000
+/* 3 LSB's of program voltage must be 0 in band 1. */
+/* Logical step size */
+#define FTSMPS_BAND1_UV_LOG_STEP	50000
+/* Physical step size */
+#define FTSMPS_BAND1_UV_PHYS_STEP	6250
+
+#define FTSMPS_BAND2_UV_MIN		700000
+#define FTSMPS_BAND2_UV_MAX		1400000
+#define FTSMPS_BAND2_UV_STEP		12500
+
+#define FTSMPS_BAND3_UV_MIN		1400000
+#define FTSMPS_BAND3_UV_SET_POINT_MIN	1500000
+#define FTSMPS_BAND3_UV_MAX		3300000
+#define FTSMPS_BAND3_UV_STEP		50000
+
+#define FTSMPS_BAND1_SET_POINTS		((FTSMPS_BAND1_UV_MAX \
+						- FTSMPS_BAND1_UV_MIN) \
+					/ FTSMPS_BAND1_UV_LOG_STEP + 1)
+#define FTSMPS_BAND2_SET_POINTS		((FTSMPS_BAND2_UV_MAX \
+						- FTSMPS_BAND2_UV_MIN) \
+					/ FTSMPS_BAND2_UV_STEP + 1)
+#define FTSMPS_BAND3_SET_POINTS		((FTSMPS_BAND3_UV_MAX \
+					  - FTSMPS_BAND3_UV_SET_POINT_MIN) \
+					/ FTSMPS_BAND3_UV_STEP + 1)
+#define FTSMPS_SET_POINTS		(FTSMPS_BAND1_SET_POINTS \
+						+ FTSMPS_BAND2_SET_POINTS \
+						+ FTSMPS_BAND3_SET_POINTS)
+
+/* FTS_CNFG1 register bank 0 */
+#define FTSMPS_CNFG1_PM_MASK		0x0C
+#define FTSMPS_CNFG1_PM_PWM		0x00
+#define FTSMPS_CNFG1_PM_PFM		0x08
+
+/* PWR_CNFG register */
+#define FTSMPS_PULL_DOWN_ENABLE_MASK	0x40
+#define FTSMPS_PULL_DOWN_ENABLE		0x40
+
+/* VS masks and values */
+
+/* CTRL register */
+#define VS_ENABLE_MASK			0x80
+#define VS_DISABLE			0x00
+#define VS_ENABLE			0x80
+#define VS_PULL_DOWN_ENABLE_MASK	0x40
+#define VS_PULL_DOWN_ENABLE		0x40
+
+#define VS_PIN_CTRL_MASK		0x0F
+#define VS_PIN_CTRL_EN0			0x08
+#define VS_PIN_CTRL_EN1			0x04
+#define VS_PIN_CTRL_EN2			0x02
+#define VS_PIN_CTRL_EN3			0x01
+
+/* VS300 masks and values */
+
+/* CTRL register */
+#define VS300_CTRL_ENABLE_MASK		0xC0
+#define VS300_CTRL_DISABLE		0x00
+#define VS300_CTRL_ENABLE		0x40
+
+#define VS300_PULL_DOWN_ENABLE_MASK	0x20
+#define VS300_PULL_DOWN_ENABLE		0x20
+
+/* NCP masks and values */
+
+/* CTRL register */
+#define NCP_ENABLE_MASK			0x80
+#define NCP_DISABLE			0x00
+#define NCP_ENABLE			0x80
+#define NCP_VPROG_MASK			0x1F
+
+#define NCP_UV_MIN			1500000
+#define NCP_UV_MAX			3050000
+#define NCP_UV_STEP			50000
+
+#define NCP_SET_POINTS			((NCP_UV_MAX - NCP_UV_MIN) \
+						/ NCP_UV_STEP + 1)
+
+#define IS_REAL_REGULATOR(id)		((id) >= 0 && \
+					 (id) < PM8921_VREG_ID_L1_PC)
+
+struct pm8921_vreg {
+	/* Configuration data */
+	struct regulator_dev			*rdev;
+	struct regulator_dev			*rdev_pc;
+	struct device				*dev;
+	struct device				*dev_pc;
+	const char				*name;
+	struct pm8921_regulator_platform_data	pdata;
+	const int				hpm_min_load;
+	const u16				ctrl_addr;
+	const u16				test_addr;
+	const u16				clk_ctrl_addr;
+	const u16				sleep_ctrl_addr;
+	const u16				pfm_ctrl_addr;
+	const u16				pwr_cnfg_addr;
+	const u8				type;
+	/* State data */
+	struct mutex				pc_lock;
+	int					save_uV;
+	int					mode;
+	bool					is_enabled;
+	bool					is_enabled_pc;
+	u8				test_reg[REGULATOR_TEST_BANKS_MAX];
+	u8					ctrl_reg;
+	u8					clk_ctrl_reg;
+	u8					sleep_ctrl_reg;
+	u8					pfm_ctrl_reg;
+	u8					pwr_cnfg_reg;
+};
+
+#define vreg_err(vreg, fmt, ...) \
+	pr_err("%s: " fmt, vreg->name, ##__VA_ARGS__)
+
+#define LDO(_id, _ctrl_addr, _test_addr, _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_LDO, \
+		.ctrl_addr	= _ctrl_addr, \
+		.test_addr	= _test_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define NLDO1200(_id, _ctrl_addr, _test_addr, _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_NLDO1200, \
+		.ctrl_addr	= _ctrl_addr, \
+		.test_addr	= _test_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define SMPS(_id, _ctrl_addr, _test_addr, _clk_ctrl_addr, _sleep_ctrl_addr, \
+	     _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_SMPS, \
+		.ctrl_addr	= _ctrl_addr, \
+		.test_addr	= _test_addr, \
+		.clk_ctrl_addr	= _clk_ctrl_addr, \
+		.sleep_ctrl_addr = _sleep_ctrl_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define FTSMPS(_id, _pwm_ctrl_addr, _fts_cnfg1_addr, _pfm_ctrl_addr, \
+	       _pwr_cnfg_addr, _hpm_min_load) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_FTSMPS, \
+		.ctrl_addr	= _pwm_ctrl_addr, \
+		.test_addr	= _fts_cnfg1_addr, \
+		.pfm_ctrl_addr = _pfm_ctrl_addr, \
+		.pwr_cnfg_addr = _pwr_cnfg_addr, \
+		.hpm_min_load	= PM8921_VREG_##_hpm_min_load##_HPM_MIN_LOAD, \
+	}
+
+#define VS(_id, _ctrl_addr) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_VS, \
+		.ctrl_addr	= _ctrl_addr, \
+	}
+
+#define VS300(_id, _ctrl_addr) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_VS300, \
+		.ctrl_addr	= _ctrl_addr, \
+	}
+
+#define NCP(_id, _ctrl_addr) \
+	[PM8921_VREG_ID_##_id] = { \
+		.type		= REGULATOR_TYPE_NCP, \
+		.ctrl_addr	= _ctrl_addr, \
+	}
+
+static struct pm8921_vreg pm8921_vreg[] = {
+	/*  id   ctrl   test   hpm_min */
+	LDO(L1,  0x0AE, 0x0AF, LDO_150),
+	LDO(L2,  0x0B0, 0x0B1, LDO_150),
+	LDO(L3,  0x0B2, 0x0B3, LDO_150),
+	LDO(L4,  0x0B4, 0x0B5, LDO_50),
+	LDO(L5,  0x0B6, 0x0B7, LDO_300),
+	LDO(L6,  0x0B8, 0x0B9, LDO_600),
+	LDO(L7,  0x0BA, 0x0BB, LDO_150),
+	LDO(L8,  0x0BC, 0x0BD, LDO_300),
+	LDO(L9,  0x0BE, 0x0BF, LDO_300),
+	LDO(L10, 0x0C0, 0x0C1, LDO_600),
+	LDO(L11, 0x0C2, 0x0C3, LDO_150),
+	LDO(L12, 0x0C4, 0x0C5, LDO_150),
+	LDO(L14, 0x0C8, 0x0C9, LDO_50),
+	LDO(L15, 0x0CA, 0x0CB, LDO_150),
+	LDO(L16, 0x0CC, 0x0CD, LDO_300),
+	LDO(L17, 0x0CE, 0x0CF, LDO_150),
+	LDO(L18, 0x0D0, 0x0D1, LDO_150),
+	LDO(L21, 0x0D6, 0x0D7, LDO_150),
+	LDO(L22, 0x0D8, 0x0D9, LDO_150),
+	LDO(L23, 0x0DA, 0x0DB, LDO_150),
+
+	/*       id   ctrl   test   hpm_min */
+	NLDO1200(L24, 0x0DC, 0x0DD, LDO_1200),
+	NLDO1200(L25, 0x0DE, 0x0DF, LDO_1200),
+	NLDO1200(L26, 0x0E0, 0x0E1, LDO_1200),
+	NLDO1200(L27, 0x0E2, 0x0E3, LDO_1200),
+	NLDO1200(L28, 0x0E4, 0x0E5, LDO_1200),
+
+	/*  id   ctrl   test   hpm_min */
+	LDO(L29, 0x0E6, 0x0E7, LDO_150),
+
+	/*   id  ctrl   test2  clk    sleep  hpm_min */
+	SMPS(S1, 0x1D0, 0x1D5, 0x009, 0x1D2, SMPS_1500),
+	SMPS(S2, 0x1D8, 0x1DD, 0x00A, 0x1DA, SMPS_1500),
+	SMPS(S3, 0x1E0, 0x1E5, 0x00B, 0x1E2, SMPS_1500),
+	SMPS(S4, 0x1E8, 0x1ED, 0x011, 0x1EA, SMPS_1500),
+
+	/*     id  ctrl fts_cnfg1 pfm  pwr_cnfg  hpm_min */
+	FTSMPS(S5, 0x025, 0x02E, 0x026, 0x032, SMPS_2000),
+	FTSMPS(S6, 0x036, 0x03F, 0x037, 0x043, SMPS_2000),
+
+	/*   id  ctrl   test2  clk    sleep  hpm_min */
+	SMPS(S7, 0x1F0, 0x1F5, 0x012, 0x1F2, SMPS_1500),
+	SMPS(S8, 0x1F8, 0x1FD, 0x013, 0x1FA, SMPS_1500),
+
+	/* id		ctrl */
+	VS(LVS1,	0x060),
+	VS300(LVS2,     0x062),
+	VS(LVS3,	0x064),
+	VS(LVS4,	0x066),
+	VS(LVS5,	0x068),
+	VS(LVS6,	0x06A),
+	VS(LVS7,	0x06C),
+	VS300(USB_OTG,  0x06E),
+	VS300(HDMI_MVS, 0x070),
+
+	/*  id   ctrl */
+	NCP(NCP, 0x090),
+};
+
+/*
+ * Perform a masked write to a PMIC register only if the new value differs
+ * from the last value written to the register.  This removes redundant
+ * register writing.
+ *
+ * No locking is required because registers are not shared between regulators.
+ */
+static int pm8921_vreg_masked_write(struct pm8921_vreg *vreg, u16 addr, u8 val,
+		u8 mask, u8 *reg_save)
+{
+	int rc = 0;
+	u8 reg;
+
+	reg = (*reg_save & ~mask) | (val & mask);
+	if (reg != *reg_save)
+		rc = pm8xxx_writeb(vreg->dev->parent, addr, reg);
+
+	if (rc)
+		pr_err("pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", addr, rc);
+	else
+		*reg_save = reg;
+
+	return rc;
+}
+
+/*
+ * Perform a masked write to a PMIC register without checking the previously
+ * written value.  This is needed for registers that must be rewritten even if
+ * the value hasn't changed in order for changes in other registers to take
+ * effect.
+ */
+static int pm8921_vreg_masked_write_forced(struct pm8921_vreg *vreg, u16 addr,
+		u8 val, u8 mask, u8 *reg_save)
+{
+	int rc = 0;
+	u8 reg;
+
+	reg = (*reg_save & ~mask) | (val & mask);
+	rc = pm8xxx_writeb(vreg->dev->parent, addr, reg);
+
+	if (rc)
+		pr_err("pm8xxx_writeb failed; addr=0x%03X, rc=%d\n", addr, rc);
+	else
+		*reg_save = reg;
+
+	return rc;
+}
+
+static int pm8921_vreg_is_pin_controlled(struct pm8921_vreg *vreg)
+{
+	int ret = 0;
+
+	switch (vreg->type) {
+	case REGULATOR_TYPE_LDO:
+		ret = ((vreg->test_reg[5] & LDO_TEST_PIN_CTRL_MASK) << 4)
+			| (vreg->test_reg[6] & LDO_TEST_PIN_CTRL_LPM_MASK);
+		break;
+	case REGULATOR_TYPE_SMPS:
+		ret = vreg->sleep_ctrl_reg
+			& (SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK);
+		break;
+	case REGULATOR_TYPE_VS:
+		ret = vreg->ctrl_reg & VS_PIN_CTRL_MASK;
+		break;
+	}
+
+	return ret;
+}
+
+/*
+ * Returns the logical pin control enable state because the pin control options
+ * present in the hardware out of restart could be different from those desired
+ * by the consumer.
+ */
+static int pm8921_vreg_pin_control_is_enabled(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int enabled;
+
+	mutex_lock(&vreg->pc_lock);
+	enabled = vreg->is_enabled_pc;
+	mutex_unlock(&vreg->pc_lock);
+
+	return enabled;
+}
+
+/* Returns the physical enable state of the regulator. */
+static int _pm8921_vreg_is_enabled(struct pm8921_vreg *vreg)
+{
+	int rc = 0;
+
+	/*
+	 * All regulator types except advanced mode SMPS, FTSMPS, and VS300 have
+	 * enable bit in bit 7 of the control register.
+	 */
+	switch (vreg->type) {
+	case REGULATOR_TYPE_FTSMPS:
+		if ((vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK)
+		    != FTSMPS_VCTRL_BAND_OFF)
+			rc = 1;
+		break;
+	case REGULATOR_TYPE_VS300:
+		if ((vreg->ctrl_reg & VS300_CTRL_ENABLE_MASK)
+		    != VS300_CTRL_DISABLE)
+			rc = 1;
+		break;
+	case REGULATOR_TYPE_SMPS:
+		if (SMPS_IN_ADVANCED_MODE(vreg)) {
+			if ((vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK)
+			    != SMPS_ADVANCED_BAND_OFF)
+				rc = 1;
+			break;
+		}
+		/* Fall through for legacy mode SMPS. */
+	default:
+		if ((vreg->ctrl_reg & REGULATOR_ENABLE_MASK)
+		    == REGULATOR_ENABLE)
+			rc = 1;
+	}
+
+	return rc;
+}
+
+/*
+ * Returns the logical enable state of the regulator which may be different from
+ * the physical enable state thanks to HPM/LPM pin control.
+ */
+static int pm8921_vreg_is_enabled(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int enabled;
+
+	if (vreg->type == REGULATOR_TYPE_LDO
+	    || vreg->type == REGULATOR_TYPE_SMPS
+	    || vreg->type == REGULATOR_TYPE_VS) {
+		/* Pin controllable */
+		mutex_lock(&vreg->pc_lock);
+		enabled = vreg->is_enabled;
+		mutex_unlock(&vreg->pc_lock);
+	} else {
+		/* Not pin controlable */
+		enabled = _pm8921_vreg_is_enabled(vreg);
+	}
+
+	return enabled;
+}
+
+static int pm8921_pldo_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int vmin, fine_step;
+	u8 range_ext, range_sel, vprog, fine_step_reg;
+
+	mutex_lock(&vreg->pc_lock);
+
+	fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK;
+	range_sel = vreg->test_reg[2] & LDO_TEST_RANGE_SEL_MASK;
+	range_ext = vreg->test_reg[4] & LDO_TEST_RANGE_EXT_MASK;
+	vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT);
+
+	if (range_sel) {
+		/* low range mode */
+		fine_step = PLDO_LOW_UV_FINE_STEP;
+		vmin = PLDO_LOW_UV_MIN;
+	} else if (!range_ext) {
+		/* normal mode */
+		fine_step = PLDO_NORM_UV_FINE_STEP;
+		vmin = PLDO_NORM_UV_MIN;
+	} else {
+		/* high range mode */
+		fine_step = PLDO_HIGH_UV_FINE_STEP;
+		vmin = PLDO_HIGH_UV_MIN;
+	}
+
+	return fine_step * vprog + vmin;
+}
+
+static int pm8921_pldo_list_voltage(struct regulator_dev *rdev,
+				    unsigned selector)
+{
+	int uV;
+
+	if (selector >= PLDO_SET_POINTS)
+		return 0;
+
+	if (selector < PLDO_LOW_SET_POINTS)
+		uV = selector * PLDO_LOW_UV_FINE_STEP + PLDO_LOW_UV_MIN;
+	else if (selector < (PLDO_LOW_SET_POINTS + PLDO_NORM_SET_POINTS))
+		uV = (selector - PLDO_LOW_SET_POINTS) * PLDO_NORM_UV_FINE_STEP
+			+ PLDO_NORM_UV_MIN;
+	else
+		uV = (selector - PLDO_LOW_SET_POINTS - PLDO_NORM_SET_POINTS)
+				* PLDO_HIGH_UV_FINE_STEP
+			+ PLDO_HIGH_UV_SET_POINT_MIN;
+
+	return uV;
+}
+
+static int pm8921_pldo_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0, uV = min_uV;
+	int vmin;
+	unsigned vprog, fine_step;
+	u8 range_ext, range_sel, fine_step_reg, prev_reg;
+	bool reg_changed = false;
+
+	if (uV < PLDO_LOW_UV_MIN && max_uV >= PLDO_LOW_UV_MIN)
+		uV = PLDO_LOW_UV_MIN;
+
+	if (uV < PLDO_LOW_UV_MIN || uV > PLDO_HIGH_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, PLDO_LOW_UV_MIN, PLDO_HIGH_UV_MAX);
+		return -EINVAL;
+	}
+
+	if (uV > PLDO_NORM_UV_MAX) {
+		vmin = PLDO_HIGH_UV_MIN;
+		fine_step = PLDO_HIGH_UV_FINE_STEP;
+		range_ext = LDO_TEST_RANGE_EXT_MASK;
+		range_sel = 0;
+	} else if (uV > PLDO_LOW_UV_MAX) {
+		vmin = PLDO_NORM_UV_MIN;
+		fine_step = PLDO_NORM_UV_FINE_STEP;
+		range_ext = 0;
+		range_sel = 0;
+	} else {
+		vmin = PLDO_LOW_UV_MIN;
+		fine_step = PLDO_LOW_UV_FINE_STEP;
+		range_ext = 0;
+		range_sel = LDO_TEST_RANGE_SEL_MASK;
+	}
+
+	vprog = (uV - vmin + fine_step - 1) / fine_step;
+	uV = vprog * fine_step + vmin;
+	fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT;
+	vprog >>= 1;
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	mutex_lock(&vreg->pc_lock);
+
+	/* Write fine step, range select and program voltage update. */
+	prev_reg = vreg->test_reg[2];
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			fine_step_reg | range_sel | REGULATOR_BANK_SEL(2)
+			 | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK,
+			LDO_TEST_FINE_STEP_MASK | LDO_TEST_RANGE_SEL_MASK
+			 | REGULATOR_BANK_MASK | LDO_TEST_VPROG_UPDATE_MASK,
+			&vreg->test_reg[2]);
+	if (rc)
+		goto bail;
+	if (prev_reg != vreg->test_reg[2])
+		reg_changed = true;
+
+	/* Write range extension. */
+	prev_reg = vreg->test_reg[4];
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			range_ext | REGULATOR_BANK_SEL(4)
+			 | REGULATOR_BANK_WRITE,
+			LDO_TEST_RANGE_EXT_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[4]);
+	if (rc)
+		goto bail;
+	if (prev_reg != vreg->test_reg[4])
+		reg_changed = true;
+
+	/* Write new voltage. */
+	if (reg_changed) {
+		/*
+		 * Force a CTRL register write even if the value hasn't changed.
+		 * This is neccessary because range select, range extension, and
+		 * fine step will not update until a value is written into the
+		 * control register.
+		 */
+		rc = pm8921_vreg_masked_write_forced(vreg, vreg->ctrl_addr,
+			vprog, LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg);
+	} else {
+		/* Only write to control register if new value is different. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, vprog,
+			LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg);
+	}
+bail:
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	u8 vprog, fine_step_reg;
+
+	mutex_lock(&vreg->pc_lock);
+
+	fine_step_reg = vreg->test_reg[2] & LDO_TEST_FINE_STEP_MASK;
+	vprog = vreg->ctrl_reg & LDO_CTRL_VPROG_MASK;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	vprog = (vprog << 1) | (fine_step_reg >> LDO_TEST_FINE_STEP_SHIFT);
+
+	return NLDO_UV_FINE_STEP * vprog + NLDO_UV_MIN;
+}
+
+static int pm8921_nldo_list_voltage(struct regulator_dev *rdev,
+				    unsigned selector)
+{
+	if (selector >= NLDO_SET_POINTS)
+		return 0;
+
+	return selector * NLDO_UV_FINE_STEP + NLDO_UV_MIN;
+}
+
+static int pm8921_nldo_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned vprog, fine_step_reg, prev_reg;
+	int rc;
+	int uV = min_uV;
+
+	if (uV < NLDO_UV_MIN && max_uV >= NLDO_UV_MIN)
+		uV = NLDO_UV_MIN;
+
+	if (uV < NLDO_UV_MIN || uV > NLDO_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, NLDO_UV_MIN, NLDO_UV_MAX);
+		return -EINVAL;
+	}
+
+	vprog = (uV - NLDO_UV_MIN + NLDO_UV_FINE_STEP - 1) / NLDO_UV_FINE_STEP;
+	uV = vprog * NLDO_UV_FINE_STEP + NLDO_UV_MIN;
+	fine_step_reg = (vprog & 1) << LDO_TEST_FINE_STEP_SHIFT;
+	vprog >>= 1;
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	mutex_lock(&vreg->pc_lock);
+
+	/* Write fine step. */
+	prev_reg = vreg->test_reg[2];
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			fine_step_reg | REGULATOR_BANK_SEL(2)
+			 | REGULATOR_BANK_WRITE | LDO_TEST_VPROG_UPDATE_MASK,
+			LDO_TEST_FINE_STEP_MASK | REGULATOR_BANK_MASK
+			 | LDO_TEST_VPROG_UPDATE_MASK,
+		       &vreg->test_reg[2]);
+	if (rc)
+		goto bail;
+
+	/* Write new voltage. */
+	if (prev_reg != vreg->test_reg[2]) {
+		/*
+		 * Force a CTRL register write even if the value hasn't changed.
+		 * This is neccessary because fine step will not update until a
+		 * value is written into the control register.
+		 */
+		rc = pm8921_vreg_masked_write_forced(vreg, vreg->ctrl_addr,
+			vprog, LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg);
+	} else {
+		/* Only write to control register if new value is different. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, vprog,
+			LDO_CTRL_VPROG_MASK, &vreg->ctrl_reg);
+	}
+bail:
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int _pm8921_nldo1200_get_voltage(struct pm8921_vreg *vreg)
+{
+	int uV = 0;
+	int vprog;
+
+	if (!NLDO1200_IN_ADVANCED_MODE(vreg)) {
+		pr_warn("%s: currently in legacy mode; voltage unknown.\n",
+			vreg->name);
+		return vreg->save_uV;
+	}
+
+	vprog = vreg->ctrl_reg & NLDO1200_CTRL_VPROG_MASK;
+
+	if ((vreg->ctrl_reg & NLDO1200_CTRL_RANGE_MASK)
+	    == NLDO1200_CTRL_RANGE_LOW)
+		uV = vprog * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN;
+	else
+		uV = vprog * NLDO1200_HIGH_UV_STEP + NLDO1200_HIGH_UV_MIN;
+
+	return uV;
+}
+
+static int pm8921_nldo1200_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_nldo1200_get_voltage(vreg);
+}
+
+static int pm8921_nldo1200_list_voltage(struct regulator_dev *rdev,
+					unsigned selector)
+{
+	int uV;
+
+	if (selector >= NLDO1200_SET_POINTS)
+		return 0;
+
+	if (selector < NLDO1200_LOW_SET_POINTS)
+		uV = selector * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN;
+	else
+		uV = (selector - NLDO1200_LOW_SET_POINTS)
+				* NLDO1200_HIGH_UV_STEP
+			+ NLDO1200_HIGH_UV_MIN;
+
+	return uV;
+}
+
+static int _pm8921_nldo1200_set_voltage(struct pm8921_vreg *vreg, int min_uV,
+		int max_uV)
+{
+	u8 vprog, range;
+	int rc;
+	int uV = min_uV;
+
+	if (uV < NLDO1200_LOW_UV_MIN && max_uV >= NLDO1200_LOW_UV_MIN)
+		uV = NLDO1200_LOW_UV_MIN;
+
+	if (uV < NLDO1200_LOW_UV_MIN || uV > NLDO1200_HIGH_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, NLDO_UV_MIN, NLDO_UV_MAX);
+		return -EINVAL;
+	}
+
+	if (uV > NLDO1200_LOW_UV_MAX) {
+		vprog = (uV - NLDO1200_HIGH_UV_MIN + NLDO1200_HIGH_UV_STEP - 1)
+			/ NLDO1200_HIGH_UV_STEP;
+		uV = vprog * NLDO1200_HIGH_UV_STEP + NLDO1200_HIGH_UV_MIN;
+		vprog &= NLDO1200_CTRL_VPROG_MASK;
+		range = NLDO1200_CTRL_RANGE_HIGH;
+	} else {
+		vprog = (uV - NLDO1200_LOW_UV_MIN + NLDO1200_LOW_UV_STEP - 1)
+			/ NLDO1200_LOW_UV_STEP;
+		uV = vprog * NLDO1200_LOW_UV_STEP + NLDO1200_LOW_UV_MIN;
+		vprog &= NLDO1200_CTRL_VPROG_MASK;
+		range = NLDO1200_CTRL_RANGE_LOW;
+	}
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	/* Set to advanced mode */
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+		NLDO1200_ADVANCED_MODE | REGULATOR_BANK_SEL(2)
+		| REGULATOR_BANK_WRITE, NLDO1200_ADVANCED_MODE_MASK
+		| REGULATOR_BANK_MASK, &vreg->test_reg[2]);
+	if (rc)
+		goto bail;
+
+	/* Set voltage and range selection. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, vprog | range,
+			NLDO1200_CTRL_VPROG_MASK | NLDO1200_CTRL_RANGE_MASK,
+			&vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	vreg->save_uV = uV;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo1200_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_nldo1200_set_voltage(vreg, min_uV, max_uV);
+}
+
+static int pm8921_smps_get_voltage_advanced(struct pm8921_vreg *vreg)
+{
+	u8 vprog, band;
+	int uV = 0;
+
+	vprog = vreg->ctrl_reg & SMPS_ADVANCED_VPROG_MASK;
+	band = vreg->ctrl_reg & SMPS_ADVANCED_BAND_MASK;
+
+	if (band == SMPS_ADVANCED_BAND_1)
+		uV = vprog * SMPS_BAND1_UV_STEP + SMPS_BAND1_UV_MIN;
+	else if (band == SMPS_ADVANCED_BAND_2)
+		uV = vprog * SMPS_BAND2_UV_STEP + SMPS_BAND2_UV_MIN;
+	else if (band == SMPS_ADVANCED_BAND_3)
+		uV = vprog * SMPS_BAND3_UV_STEP + SMPS_BAND3_UV_MIN;
+	else if (vreg->save_uV > 0)
+		uV = vreg->save_uV;
+	else
+		uV = VOLTAGE_UNKNOWN;
+
+	return uV;
+}
+
+static int pm8921_smps_get_voltage_legacy(struct pm8921_vreg *vreg)
+{
+	u8 vlow, vref, vprog;
+	int uV;
+
+	vlow = vreg->test_reg[1] & SMPS_LEGACY_VLOW_SEL_MASK;
+	vref = vreg->ctrl_reg & SMPS_LEGACY_VREF_SEL_MASK;
+	vprog = vreg->ctrl_reg & SMPS_LEGACY_VPROG_MASK;
+
+	if (vlow && vref) {
+		/* mode 3 */
+		uV = vprog * SMPS_MODE3_UV_STEP + SMPS_MODE3_UV_MIN;
+	} else if (vref) {
+		/* mode 2 */
+		uV = vprog * SMPS_MODE2_UV_STEP + SMPS_MODE2_UV_MIN;
+	} else {
+		/* mode 1 */
+		uV = vprog * SMPS_MODE1_UV_STEP + SMPS_MODE1_UV_MIN;
+	}
+
+	return uV;
+}
+
+static int _pm8921_smps_get_voltage(struct pm8921_vreg *vreg)
+{
+	if (SMPS_IN_ADVANCED_MODE(vreg))
+		return pm8921_smps_get_voltage_advanced(vreg);
+
+	return pm8921_smps_get_voltage_legacy(vreg);
+}
+
+static int pm8921_smps_list_voltage(struct regulator_dev *rdev,
+				    unsigned selector)
+{
+	int uV;
+
+	if (selector >= SMPS_ADVANCED_SET_POINTS)
+		return 0;
+
+	if (selector < SMPS_BAND1_SET_POINTS)
+		uV = selector * SMPS_BAND1_UV_STEP + SMPS_BAND1_UV_MIN;
+	else if (selector < (SMPS_BAND1_SET_POINTS + SMPS_BAND2_SET_POINTS))
+		uV = (selector - SMPS_BAND1_SET_POINTS) * SMPS_BAND2_UV_STEP
+			+ SMPS_BAND2_UV_MIN;
+	else
+		uV = (selector - SMPS_BAND1_SET_POINTS - SMPS_BAND2_SET_POINTS)
+				* SMPS_BAND3_UV_STEP
+			+ SMPS_BAND3_UV_MIN;
+
+	return uV;
+}
+
+static int pm8921_smps_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int uV;
+
+	mutex_lock(&vreg->pc_lock);
+	uV = _pm8921_smps_get_voltage(vreg);
+	mutex_unlock(&vreg->pc_lock);
+
+	return uV;
+}
+
+static int pm8921_smps_set_voltage_advanced(struct pm8921_vreg *vreg,
+					   int min_uV, int max_uV, int force_on)
+{
+	u8 vprog, band;
+	int rc;
+	int uV = min_uV;
+
+	if (uV < SMPS_BAND1_UV_MIN && max_uV >= SMPS_BAND1_UV_MIN)
+		uV = SMPS_BAND1_UV_MIN;
+
+	if (uV < SMPS_BAND1_UV_MIN || uV > SMPS_BAND3_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, SMPS_BAND1_UV_MIN, SMPS_BAND3_UV_MAX);
+		return -EINVAL;
+	}
+
+	if (uV > SMPS_BAND2_UV_MAX) {
+		vprog = (uV - SMPS_BAND3_UV_MIN + SMPS_BAND3_UV_STEP - 1)
+			/ SMPS_BAND3_UV_STEP;
+		band = SMPS_ADVANCED_BAND_3;
+		uV = SMPS_BAND3_UV_MIN + vprog * SMPS_BAND3_UV_STEP;
+	} else if (uV > SMPS_BAND1_UV_MAX) {
+		vprog = (uV - SMPS_BAND2_UV_MIN + SMPS_BAND2_UV_STEP - 1)
+			/ SMPS_BAND2_UV_STEP;
+		band = SMPS_ADVANCED_BAND_2;
+		uV = SMPS_BAND2_UV_MIN + vprog * SMPS_BAND2_UV_STEP;
+	} else {
+		vprog = (uV - SMPS_BAND1_UV_MIN + SMPS_BAND1_UV_STEP - 1)
+			/ SMPS_BAND1_UV_STEP;
+		band = SMPS_ADVANCED_BAND_1;
+		uV = SMPS_BAND1_UV_MIN + vprog * SMPS_BAND1_UV_STEP;
+	}
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	/* Do not set band if regulator currently disabled. */
+	if (!_pm8921_vreg_is_enabled(vreg) && !force_on)
+		band = SMPS_ADVANCED_BAND_OFF;
+
+	/* Set advanced mode bit to 1. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, SMPS_ADVANCED_MODE
+		| REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7),
+		SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK,
+		&vreg->test_reg[7]);
+	if (rc)
+		goto bail;
+
+	/* Set voltage and voltage band. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, band | vprog,
+			SMPS_ADVANCED_BAND_MASK | SMPS_ADVANCED_VPROG_MASK,
+			&vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	vreg->save_uV = uV;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_set_voltage_legacy(struct pm8921_vreg *vreg, int min_uV,
+					  int max_uV)
+{
+	u8 vlow, vref, vprog, pd, en;
+	int rc;
+	int uV = min_uV;
+
+
+	if (uV < SMPS_MODE3_UV_MIN && max_uV >= SMPS_MODE3_UV_MIN)
+		uV = SMPS_MODE3_UV_MIN;
+
+	if (uV < SMPS_MODE3_UV_MIN || uV > SMPS_MODE1_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, SMPS_MODE3_UV_MIN, SMPS_MODE1_UV_MAX);
+		return -EINVAL;
+	}
+
+	if (uV > SMPS_MODE2_UV_MAX) {
+		vprog = (uV - SMPS_MODE1_UV_MIN + SMPS_MODE1_UV_STEP - 1)
+			/ SMPS_MODE1_UV_STEP;
+		vref = 0;
+		vlow = 0;
+		uV = SMPS_MODE1_UV_MIN + vprog * SMPS_MODE1_UV_STEP;
+	} else if (uV > SMPS_MODE3_UV_MAX) {
+		vprog = (uV - SMPS_MODE2_UV_MIN + SMPS_MODE2_UV_STEP - 1)
+			/ SMPS_MODE2_UV_STEP;
+		vref = SMPS_LEGACY_VREF_SEL_MASK;
+		vlow = 0;
+		uV = SMPS_MODE2_UV_MIN + vprog * SMPS_MODE2_UV_STEP;
+	} else {
+		vprog = (uV - SMPS_MODE3_UV_MIN + SMPS_MODE3_UV_STEP - 1)
+			/ SMPS_MODE3_UV_STEP;
+		vref = SMPS_LEGACY_VREF_SEL_MASK;
+		vlow = SMPS_LEGACY_VLOW_SEL_MASK;
+		uV = SMPS_MODE3_UV_MIN + vprog * SMPS_MODE3_UV_STEP;
+	}
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	/* set vlow bit for ultra low voltage mode */
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+		vlow | REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(1),
+		REGULATOR_BANK_MASK | SMPS_LEGACY_VLOW_SEL_MASK,
+		&vreg->test_reg[1]);
+	if (rc)
+		goto bail;
+
+	/* Set advanced mode bit to 0. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, SMPS_LEGACY_MODE
+		| REGULATOR_BANK_WRITE | REGULATOR_BANK_SEL(7),
+		SMPS_ADVANCED_MODE_MASK | REGULATOR_BANK_MASK,
+		&vreg->test_reg[7]);
+	if (rc)
+		goto bail;
+
+	en = (_pm8921_vreg_is_enabled(vreg) ? SMPS_LEGACY_ENABLE : 0);
+	pd = (vreg->pdata.pull_down_enable ? SMPS_LEGACY_PULL_DOWN_ENABLE : 0);
+
+	/* Set voltage (and the rest of the control register). */
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+		en | pd | vref | vprog,
+		SMPS_LEGACY_ENABLE_MASK | SMPS_LEGACY_PULL_DOWN_ENABLE
+		  | SMPS_LEGACY_VREF_SEL_MASK | SMPS_LEGACY_VPROG_MASK,
+		&vreg->ctrl_reg);
+
+	vreg->save_uV = uV;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (SMPS_IN_ADVANCED_MODE(vreg) || !pm8921_vreg_is_pin_controlled(vreg))
+		rc = pm8921_smps_set_voltage_advanced(vreg, min_uV, max_uV, 0);
+	else
+		rc = pm8921_smps_set_voltage_legacy(vreg, min_uV, max_uV);
+
+	mutex_unlock(&vreg->pc_lock);
+
+	return rc;
+}
+
+static int _pm8921_ftsmps_get_voltage(struct pm8921_vreg *vreg)
+{
+	u8 vprog, band;
+	int uV = 0;
+
+	if ((vreg->test_reg[0] & FTSMPS_CNFG1_PM_MASK) == FTSMPS_CNFG1_PM_PFM) {
+		vprog = vreg->pfm_ctrl_reg & FTSMPS_VCTRL_VPROG_MASK;
+		band = vreg->pfm_ctrl_reg & FTSMPS_VCTRL_BAND_MASK;
+		if (band == FTSMPS_VCTRL_BAND_OFF && vprog == 0) {
+			/* PWM_VCTRL overrides PFM_VCTRL */
+			vprog = vreg->ctrl_reg & FTSMPS_VCTRL_VPROG_MASK;
+			band = vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK;
+		}
+	} else {
+		vprog = vreg->ctrl_reg & FTSMPS_VCTRL_VPROG_MASK;
+		band = vreg->ctrl_reg & FTSMPS_VCTRL_BAND_MASK;
+	}
+
+	if (band == FTSMPS_VCTRL_BAND_1)
+		uV = vprog * FTSMPS_BAND1_UV_PHYS_STEP + FTSMPS_BAND1_UV_MIN;
+	else if (band == FTSMPS_VCTRL_BAND_2)
+		uV = vprog * FTSMPS_BAND2_UV_STEP + FTSMPS_BAND2_UV_MIN;
+	else if (band == FTSMPS_VCTRL_BAND_3)
+		uV = vprog * FTSMPS_BAND3_UV_STEP + FTSMPS_BAND3_UV_MIN;
+	else if (vreg->save_uV > 0)
+		uV = vreg->save_uV;
+	else
+		uV = VOLTAGE_UNKNOWN;
+
+	return uV;
+}
+
+static int pm8921_ftsmps_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_ftsmps_get_voltage(vreg);
+}
+
+static int pm8921_ftsmps_list_voltage(struct regulator_dev *rdev,
+				      unsigned selector)
+{
+	int uV;
+
+	if (selector >= FTSMPS_SET_POINTS)
+		return 0;
+
+	if (selector < FTSMPS_BAND1_SET_POINTS)
+		uV = selector * FTSMPS_BAND1_UV_LOG_STEP + FTSMPS_BAND1_UV_MIN;
+	else if (selector < (FTSMPS_BAND1_SET_POINTS + FTSMPS_BAND2_SET_POINTS))
+		uV = (selector - FTSMPS_BAND1_SET_POINTS) * FTSMPS_BAND2_UV_STEP
+			+ FTSMPS_BAND2_UV_MIN;
+	else
+		uV = (selector - FTSMPS_BAND1_SET_POINTS
+			- FTSMPS_BAND2_SET_POINTS)
+				* FTSMPS_BAND3_UV_STEP
+			+ FTSMPS_BAND3_UV_SET_POINT_MIN;
+
+	return uV;
+}
+
+static int _pm8921_ftsmps_set_voltage(struct pm8921_vreg *vreg, int min_uV,
+				      int max_uV, int force_on)
+{
+	int rc;
+	u8 vprog, band;
+	int uV = min_uV;
+
+	if (uV < FTSMPS_BAND1_UV_MIN && max_uV >= FTSMPS_BAND1_UV_MIN)
+		uV = FTSMPS_BAND1_UV_MIN;
+
+	if (uV < FTSMPS_BAND1_UV_MIN || uV > FTSMPS_BAND3_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, FTSMPS_BAND1_UV_MIN,
+			 FTSMPS_BAND3_UV_MAX);
+		return -EINVAL;
+	}
+
+	/* Round up for set points in the gaps between bands. */
+	if (uV > FTSMPS_BAND1_UV_MAX && uV < FTSMPS_BAND2_UV_MIN)
+		uV = FTSMPS_BAND2_UV_MIN;
+	else if (uV > FTSMPS_BAND2_UV_MAX
+			&& uV < FTSMPS_BAND3_UV_SET_POINT_MIN)
+		uV = FTSMPS_BAND3_UV_SET_POINT_MIN;
+
+	if (uV > FTSMPS_BAND2_UV_MAX) {
+		vprog = (uV - FTSMPS_BAND3_UV_MIN + FTSMPS_BAND3_UV_STEP - 1)
+			/ FTSMPS_BAND3_UV_STEP;
+		band = FTSMPS_VCTRL_BAND_3;
+		uV = FTSMPS_BAND3_UV_MIN + vprog * FTSMPS_BAND3_UV_STEP;
+	} else if (uV > FTSMPS_BAND1_UV_MAX) {
+		vprog = (uV - FTSMPS_BAND2_UV_MIN + FTSMPS_BAND2_UV_STEP - 1)
+			/ FTSMPS_BAND2_UV_STEP;
+		band = FTSMPS_VCTRL_BAND_2;
+		uV = FTSMPS_BAND2_UV_MIN + vprog * FTSMPS_BAND2_UV_STEP;
+	} else {
+		vprog = (uV - FTSMPS_BAND1_UV_MIN
+				+ FTSMPS_BAND1_UV_LOG_STEP - 1)
+			/ FTSMPS_BAND1_UV_LOG_STEP;
+		uV = FTSMPS_BAND1_UV_MIN + vprog * FTSMPS_BAND1_UV_LOG_STEP;
+		vprog *= FTSMPS_BAND1_UV_LOG_STEP / FTSMPS_BAND1_UV_PHYS_STEP;
+		band = FTSMPS_VCTRL_BAND_1;
+	}
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	/*
+	 * Do not set voltage if regulator is currently disabled because doing
+	 * so will enable it.
+	 */
+	if (_pm8921_vreg_is_enabled(vreg) || force_on) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			band | vprog,
+			FTSMPS_VCTRL_BAND_MASK | FTSMPS_VCTRL_VPROG_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		/* Program PFM_VCTRL as 0x00 so that PWM_VCTRL overrides it. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->pfm_ctrl_addr, 0x00,
+			FTSMPS_VCTRL_BAND_MASK | FTSMPS_VCTRL_VPROG_MASK,
+			&vreg->pfm_ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	vreg->save_uV = uV;
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ftsmps_set_voltage(struct regulator_dev *rdev, int min_uV,
+				     int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+
+	return _pm8921_ftsmps_set_voltage(vreg, min_uV, max_uV, 0);
+}
+
+static int pm8921_ncp_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	u8 vprog;
+
+	vprog = vreg->ctrl_reg & NCP_VPROG_MASK;
+
+	return NCP_UV_MIN + vprog * NCP_UV_STEP;
+}
+
+static int pm8921_ncp_list_voltage(struct regulator_dev *rdev,
+				   unsigned selector)
+{
+	if (selector >= NCP_SET_POINTS)
+		return 0;
+
+	return selector * NCP_UV_STEP + NCP_UV_MIN;
+}
+
+static int pm8921_ncp_set_voltage(struct regulator_dev *rdev, int min_uV,
+				  int max_uV, unsigned *selector)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+	int uV = min_uV;
+	u8 val;
+
+	if (uV < NCP_UV_MIN && max_uV >= NCP_UV_MIN)
+		uV = NCP_UV_MIN;
+
+	if (uV < NCP_UV_MIN || uV > NCP_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, NCP_UV_MIN, NCP_UV_MAX);
+		return -EINVAL;
+	}
+
+	val = (uV - NCP_UV_MIN + NCP_UV_STEP - 1) / NCP_UV_STEP;
+	uV = val * NCP_UV_STEP + NCP_UV_MIN;
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	/* voltage setting */
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val,
+			NCP_VPROG_MASK, &vreg->ctrl_reg);
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static unsigned int pm8921_ldo_get_mode(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned int mode = 0;
+
+	mutex_lock(&vreg->pc_lock);
+	mode = vreg->mode;
+	mutex_unlock(&vreg->pc_lock);
+
+	return mode;
+}
+
+static int pm8921_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) {
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (mode == REGULATOR_MODE_NORMAL
+	    || (vreg->is_enabled_pc
+		&& vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE)) {
+		/* HPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			LDO_CTRL_PM_HPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+	} else {
+		/* LPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE
+			  | REGULATOR_BANK_SEL(0),
+			LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[0]);
+	}
+
+bail:
+	if (!rc)
+		vreg->mode = mode;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static unsigned int pm8921_nldo1200_get_mode(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned int mode = 0;
+
+	if (NLDO1200_IN_ADVANCED_MODE(vreg)) {
+		/* Advanced mode */
+		if ((vreg->test_reg[2] & NLDO1200_ADVANCED_PM_MASK)
+		    == NLDO1200_ADVANCED_PM_LPM)
+			mode = REGULATOR_MODE_IDLE;
+		else
+			mode = REGULATOR_MODE_NORMAL;
+	} else {
+		/* Legacy mode */
+		if ((vreg->ctrl_reg & NLDO1200_LEGACY_PM_MASK)
+		    == NLDO1200_LEGACY_PM_LPM)
+			mode = REGULATOR_MODE_IDLE;
+		else
+			mode = REGULATOR_MODE_NORMAL;
+	}
+
+	return mode;
+}
+
+static int pm8921_nldo1200_set_mode(struct regulator_dev *rdev,
+				    unsigned int mode)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) {
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+	/*
+	 * Make sure that advanced mode is in use.  If it isn't, then set it
+	 * and update the voltage accordingly.
+	 */
+	if (!NLDO1200_IN_ADVANCED_MODE(vreg)) {
+		rc = _pm8921_nldo1200_set_voltage(vreg, vreg->save_uV,
+			vreg->save_uV);
+		if (rc)
+			goto bail;
+	}
+
+	if (mode == REGULATOR_MODE_NORMAL) {
+		/* HPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			NLDO1200_ADVANCED_PM_HPM | REGULATOR_BANK_WRITE
+			| REGULATOR_BANK_SEL(2), NLDO1200_ADVANCED_PM_MASK
+			| REGULATOR_BANK_MASK, &vreg->test_reg[2]);
+	} else {
+		/* LPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			NLDO1200_ADVANCED_PM_LPM | REGULATOR_BANK_WRITE
+			| REGULATOR_BANK_SEL(2), NLDO1200_ADVANCED_PM_MASK
+			| REGULATOR_BANK_MASK, &vreg->test_reg[2]);
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static unsigned int pm8921_smps_get_mode(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned int mode = 0;
+
+	mutex_lock(&vreg->pc_lock);
+	mode = vreg->mode;
+	mutex_unlock(&vreg->pc_lock);
+
+	return mode;
+}
+
+static int pm8921_smps_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) {
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (mode == REGULATOR_MODE_NORMAL
+	    || (vreg->is_enabled_pc
+		&& vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE)) {
+		/* HPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr,
+				       SMPS_CLK_CTRL_PWM, SMPS_CLK_CTRL_MASK,
+				       &vreg->clk_ctrl_reg);
+	} else {
+		/* LPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr,
+				       SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK,
+				       &vreg->clk_ctrl_reg);
+	}
+
+	if (!rc)
+		vreg->mode = mode;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static unsigned int pm8921_ftsmps_get_mode(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned int mode = 0;
+
+	if ((vreg->test_reg[0] & FTSMPS_CNFG1_PM_MASK) == FTSMPS_CNFG1_PM_PFM)
+		mode = REGULATOR_MODE_IDLE;
+	else
+		mode = REGULATOR_MODE_NORMAL;
+
+	return mode;
+}
+
+static int pm8921_ftsmps_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	if (mode == REGULATOR_MODE_NORMAL) {
+		/* HPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+				FTSMPS_CNFG1_PM_PWM | REGULATOR_BANK_WRITE
+				| REGULATOR_BANK_SEL(0), FTSMPS_CNFG1_PM_MASK
+				| REGULATOR_BANK_MASK, &vreg->test_reg[0]);
+	} else if (mode == REGULATOR_MODE_IDLE) {
+		/* LPM */
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+				FTSMPS_CNFG1_PM_PFM | REGULATOR_BANK_WRITE
+				| REGULATOR_BANK_SEL(0), FTSMPS_CNFG1_PM_MASK
+				| REGULATOR_BANK_MASK, &vreg->test_reg[0]);
+	} else {
+		vreg_err(vreg, "invalid mode: %u\n", mode);
+		return -EINVAL;
+	}
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static unsigned int pm8921_vreg_get_optimum_mode(struct regulator_dev *rdev,
+		int input_uV, int output_uV, int load_uA)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	unsigned int mode;
+
+	if (load_uA + vreg->pdata.system_uA >= vreg->hpm_min_load)
+		mode = REGULATOR_MODE_NORMAL;
+	else
+		mode = REGULATOR_MODE_IDLE;
+
+	return mode;
+}
+
+static int pm8921_ldo_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc, val;
+
+	mutex_lock(&vreg->pc_lock);
+
+	/*
+	 * Choose HPM if previously set to HPM or if pin control is enabled in
+	 * on/off mode.
+	 */
+	val = LDO_CTRL_PM_LPM;
+	if (vreg->mode == REGULATOR_MODE_NORMAL
+		|| (vreg->is_enabled_pc
+			&& vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE))
+		val = LDO_CTRL_PM_HPM;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val | LDO_ENABLE,
+		LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled = true;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ldo_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	/*
+	 * Only disable the regulator if it isn't still required for HPM/LPM
+	 * pin control.
+	 */
+	if (!vreg->is_enabled_pc
+	    || vreg->pdata.pin_fn != PM8921_VREG_PIN_FN_MODE) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			LDO_DISABLE, LDO_ENABLE_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	/* Change to LPM if HPM/LPM pin control is enabled. */
+	if (vreg->is_enabled_pc
+	    && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_MODE) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE
+			  | REGULATOR_BANK_SEL(0),
+			LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[0]);
+	}
+
+	if (!rc)
+		vreg->is_enabled = false;
+bail:
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo1200_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NLDO1200_ENABLE,
+		NLDO1200_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_nldo1200_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NLDO1200_DISABLE,
+		NLDO1200_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+	int val;
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (SMPS_IN_ADVANCED_MODE(vreg)
+	     || !pm8921_vreg_is_pin_controlled(vreg)) {
+		/* Enable in advanced mode if not using pin control. */
+		rc = pm8921_smps_set_voltage_advanced(vreg, vreg->save_uV,
+			vreg->save_uV, 1);
+	} else {
+		rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV,
+			vreg->save_uV);
+		if (rc)
+			goto bail;
+
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			SMPS_LEGACY_ENABLE, SMPS_LEGACY_ENABLE_MASK,
+			&vreg->ctrl_reg);
+	}
+
+	/*
+	 * Choose HPM if previously set to HPM or if pin control is enabled in
+	 * on/off mode.
+	 */
+	val = SMPS_CLK_CTRL_PFM;
+	if (vreg->mode == REGULATOR_MODE_NORMAL
+		|| (vreg->is_enabled_pc
+			&& vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE))
+		val = SMPS_CLK_CTRL_PWM;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr, val,
+			SMPS_CLK_CTRL_MASK, &vreg->clk_ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled = true;
+bail:
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (SMPS_IN_ADVANCED_MODE(vreg)) {
+		/* Change SMPS to legacy mode before disabling. */
+		rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV,
+				vreg->save_uV);
+		if (rc)
+			goto bail;
+	}
+
+	/*
+	 * Only disable the regulator if it isn't still required for HPM/LPM
+	 * pin control.
+	 */
+	if (!vreg->is_enabled_pc
+	    || vreg->pdata.pin_fn != PM8921_VREG_PIN_FN_MODE) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			SMPS_LEGACY_DISABLE, SMPS_LEGACY_ENABLE_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	/* Change to LPM if HPM/LPM pin control is enabled. */
+	if (vreg->is_enabled_pc
+	    && vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_MODE)
+		rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr,
+		       SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK,
+		       &vreg->clk_ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled = false;
+
+bail:
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ftsmps_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = _pm8921_ftsmps_set_voltage(vreg, vreg->save_uV, vreg->save_uV, 1);
+
+	if (rc)
+		vreg_err(vreg, "set voltage failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ftsmps_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+		FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->pfm_ctrl_addr,
+		FTSMPS_VCTRL_BAND_OFF, FTSMPS_VCTRL_BAND_MASK,
+		&vreg->pfm_ctrl_reg);
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vs_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS_ENABLE,
+		VS_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled = true;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vs_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS_DISABLE,
+		VS_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled = false;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vs300_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS300_CTRL_ENABLE,
+		VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vs300_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, VS300_CTRL_DISABLE,
+		VS300_CTRL_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ncp_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NCP_ENABLE,
+		NCP_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ncp_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, NCP_DISABLE,
+		NCP_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ldo_pin_control_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+	int bank;
+	u8 val = 0;
+	u8 mask;
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1)
+		val |= LDO_TEST_PIN_CTRL_EN0;
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0)
+		val |= LDO_TEST_PIN_CTRL_EN1;
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1)
+		val |= LDO_TEST_PIN_CTRL_EN2;
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2)
+		val |= LDO_TEST_PIN_CTRL_EN3;
+
+	bank = (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE ? 5 : 6);
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+		val | REGULATOR_BANK_SEL(bank) | REGULATOR_BANK_WRITE,
+		LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+		&vreg->test_reg[bank]);
+	if (rc)
+		goto bail;
+
+	/* Unset pin control bits in unused bank. */
+	bank = (bank == 5 ? 6 : 5);
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+		REGULATOR_BANK_SEL(bank) | REGULATOR_BANK_WRITE,
+		LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+		&vreg->test_reg[bank]);
+	if (rc)
+		goto bail;
+
+	val = LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE
+		| REGULATOR_BANK_SEL(0);
+	mask = LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK;
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr, val, mask,
+		&vreg->test_reg[0]);
+	if (rc)
+		goto bail;
+
+	if (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE) {
+		/* Pin control ON/OFF */
+		val = LDO_CTRL_PM_HPM;
+		/* Leave physically enabled if already enabled. */
+		val |= (vreg->is_enabled ? LDO_ENABLE : LDO_DISABLE);
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val,
+			LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	} else {
+		/* Pin control LPM/HPM */
+		val = LDO_ENABLE;
+		/* Leave in HPM if already enabled in HPM. */
+		val |= (vreg->is_enabled && vreg->mode == REGULATOR_MODE_NORMAL
+			?  LDO_CTRL_PM_HPM : LDO_CTRL_PM_LPM);
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val,
+			LDO_ENABLE_MASK | LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+bail:
+	if (!rc)
+		vreg->is_enabled_pc = true;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_ldo_pin_control_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			REGULATOR_BANK_SEL(5) | REGULATOR_BANK_WRITE,
+			LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[5]);
+	if (rc)
+		goto bail;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE,
+			LDO_TEST_PIN_CTRL_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[6]);
+
+	/*
+	 * Physically disable the regulator if it was enabled in HPM/LPM pin
+	 * control mode previously and it logically should not be enabled.
+	 */
+	if ((vreg->ctrl_reg & LDO_ENABLE_MASK) == LDO_ENABLE
+	    && !vreg->is_enabled) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			LDO_DISABLE, LDO_ENABLE_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	/* Change to LPM if LPM was enabled. */
+	if (vreg->is_enabled && vreg->mode == REGULATOR_MODE_IDLE) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			LDO_CTRL_PM_LPM, LDO_CTRL_PM_MASK, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			LDO_TEST_LPM_SEL_CTRL | REGULATOR_BANK_WRITE
+			  | REGULATOR_BANK_SEL(0),
+			LDO_TEST_LPM_MASK | REGULATOR_BANK_MASK,
+			&vreg->test_reg[0]);
+		if (rc)
+			goto bail;
+	}
+
+bail:
+	if (!rc)
+		vreg->is_enabled_pc = false;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_pin_control_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+	u8 val = 0;
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE) {
+		/* Pin control ON/OFF */
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1)
+			val |= SMPS_PIN_CTRL_EN0;
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0)
+			val |= SMPS_PIN_CTRL_EN1;
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1)
+			val |= SMPS_PIN_CTRL_EN2;
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2)
+			val |= SMPS_PIN_CTRL_EN3;
+	} else {
+		/* Pin control LPM/HPM */
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1)
+			val |= SMPS_PIN_CTRL_LPM_EN0;
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0)
+			val |= SMPS_PIN_CTRL_LPM_EN1;
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1)
+			val |= SMPS_PIN_CTRL_LPM_EN2;
+		if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2)
+			val |= SMPS_PIN_CTRL_LPM_EN3;
+	}
+
+	rc = pm8921_smps_set_voltage_legacy(vreg, vreg->save_uV, vreg->save_uV);
+	if (rc)
+		goto bail;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->sleep_ctrl_addr, val,
+			SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK,
+			&vreg->sleep_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/*
+	 * Physically enable the regulator if using HPM/LPM pin control mode or
+	 * if the regulator should be logically left on.
+	 */
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+		((vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_MODE
+		  || vreg->is_enabled) ?
+			SMPS_LEGACY_ENABLE : SMPS_LEGACY_DISABLE),
+		SMPS_LEGACY_ENABLE_MASK, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/*
+	 * Set regulator to HPM if using on/off pin control or if the regulator
+	 * is already enabled in HPM.  Otherwise, set it to LPM.
+	 */
+	rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr,
+			(vreg->pdata.pin_fn == PM8921_VREG_PIN_FN_ENABLE
+			 || (vreg->is_enabled
+			     && vreg->mode == REGULATOR_MODE_NORMAL)
+				? SMPS_CLK_CTRL_PWM : SMPS_CLK_CTRL_PFM),
+			SMPS_CLK_CTRL_MASK, &vreg->clk_ctrl_reg);
+
+bail:
+	if (!rc)
+		vreg->is_enabled_pc = true;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_smps_pin_control_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->sleep_ctrl_addr, 0,
+			SMPS_PIN_CTRL_MASK | SMPS_PIN_CTRL_LPM_MASK,
+			&vreg->sleep_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/*
+	 * Physically disable the regulator if it was enabled in HPM/LPM pin
+	 * control mode previously and it logically should not be enabled.
+	 */
+	if ((vreg->ctrl_reg & SMPS_LEGACY_ENABLE_MASK) == SMPS_LEGACY_ENABLE
+	    && vreg->is_enabled == false) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			SMPS_LEGACY_DISABLE, SMPS_LEGACY_ENABLE_MASK,
+			&vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	/* Change to LPM if LPM was enabled. */
+	if (vreg->is_enabled && vreg->mode == REGULATOR_MODE_IDLE) {
+		rc = pm8921_vreg_masked_write(vreg, vreg->clk_ctrl_addr,
+		       SMPS_CLK_CTRL_PFM, SMPS_CLK_CTRL_MASK,
+		       &vreg->clk_ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+	rc = pm8921_smps_set_voltage_advanced(vreg, vreg->save_uV,
+			vreg->save_uV, 0);
+
+bail:
+	if (!rc)
+		vreg->is_enabled_pc = false;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vs_pin_control_enable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+	u8 val = 0;
+
+	mutex_lock(&vreg->pc_lock);
+
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_D1)
+		val |= VS_PIN_CTRL_EN0;
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A0)
+		val |= VS_PIN_CTRL_EN1;
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A1)
+		val |= VS_PIN_CTRL_EN2;
+	if (vreg->pdata.pin_ctrl & PM8921_VREG_PIN_CTRL_A2)
+		val |= VS_PIN_CTRL_EN3;
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, val,
+			VS_PIN_CTRL_MASK | VS_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled_pc = true;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_vs_pin_control_disable(struct regulator_dev *rdev)
+{
+	struct pm8921_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&vreg->pc_lock);
+
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr, 0,
+				      VS_PIN_CTRL_MASK, &vreg->ctrl_reg);
+
+	if (!rc)
+		vreg->is_enabled_pc = false;
+
+	mutex_unlock(&vreg->pc_lock);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+/* Real regulator operations. */
+static struct regulator_ops pm8921_pldo_ops = {
+	.enable			= pm8921_ldo_enable,
+	.disable		= pm8921_ldo_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_pldo_set_voltage,
+	.get_voltage		= pm8921_pldo_get_voltage,
+	.list_voltage		= pm8921_pldo_list_voltage,
+	.set_mode		= pm8921_ldo_set_mode,
+	.get_mode		= pm8921_ldo_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_nldo_ops = {
+	.enable			= pm8921_ldo_enable,
+	.disable		= pm8921_ldo_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_nldo_set_voltage,
+	.get_voltage		= pm8921_nldo_get_voltage,
+	.list_voltage		= pm8921_nldo_list_voltage,
+	.set_mode		= pm8921_ldo_set_mode,
+	.get_mode		= pm8921_ldo_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_nldo1200_ops = {
+	.enable			= pm8921_nldo1200_enable,
+	.disable		= pm8921_nldo1200_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_nldo1200_set_voltage,
+	.get_voltage		= pm8921_nldo1200_get_voltage,
+	.list_voltage		= pm8921_nldo1200_list_voltage,
+	.set_mode		= pm8921_nldo1200_set_mode,
+	.get_mode		= pm8921_nldo1200_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_smps_ops = {
+	.enable			= pm8921_smps_enable,
+	.disable		= pm8921_smps_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_smps_set_voltage,
+	.get_voltage		= pm8921_smps_get_voltage,
+	.list_voltage		= pm8921_smps_list_voltage,
+	.set_mode		= pm8921_smps_set_mode,
+	.get_mode		= pm8921_smps_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_ftsmps_ops = {
+	.enable			= pm8921_ftsmps_enable,
+	.disable		= pm8921_ftsmps_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_ftsmps_set_voltage,
+	.get_voltage		= pm8921_ftsmps_get_voltage,
+	.list_voltage		= pm8921_ftsmps_list_voltage,
+	.set_mode		= pm8921_ftsmps_set_mode,
+	.get_mode		= pm8921_ftsmps_get_mode,
+	.get_optimum_mode	= pm8921_vreg_get_optimum_mode,
+};
+
+static struct regulator_ops pm8921_vs_ops = {
+	.enable			= pm8921_vs_enable,
+	.disable		= pm8921_vs_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+};
+
+static struct regulator_ops pm8921_vs300_ops = {
+	.enable			= pm8921_vs300_enable,
+	.disable		= pm8921_vs300_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+};
+
+static struct regulator_ops pm8921_ncp_ops = {
+	.enable			= pm8921_ncp_enable,
+	.disable		= pm8921_ncp_disable,
+	.is_enabled		= pm8921_vreg_is_enabled,
+	.set_voltage		= pm8921_ncp_set_voltage,
+	.get_voltage		= pm8921_ncp_get_voltage,
+	.list_voltage		= pm8921_ncp_list_voltage,
+};
+
+/* Pin control regulator operations. */
+static struct regulator_ops pm8921_ldo_pc_ops = {
+	.enable			= pm8921_ldo_pin_control_enable,
+	.disable		= pm8921_ldo_pin_control_disable,
+	.is_enabled		= pm8921_vreg_pin_control_is_enabled,
+};
+
+static struct regulator_ops pm8921_smps_pc_ops = {
+	.enable			= pm8921_smps_pin_control_enable,
+	.disable		= pm8921_smps_pin_control_disable,
+	.is_enabled		= pm8921_vreg_pin_control_is_enabled,
+};
+
+static struct regulator_ops pm8921_vs_pc_ops = {
+	.enable			= pm8921_vs_pin_control_enable,
+	.disable		= pm8921_vs_pin_control_disable,
+	.is_enabled		= pm8921_vreg_pin_control_is_enabled,
+};
+
+#define VREG_DESC(_id, _name, _ops, _n_voltages) \
+	[PM8921_VREG_ID_##_id] = { \
+		.id	= PM8921_VREG_ID_##_id, \
+		.name	= _name, \
+		.n_voltages = _n_voltages, \
+		.ops	= _ops, \
+		.type	= REGULATOR_VOLTAGE, \
+		.owner	= THIS_MODULE, \
+	}
+
+static struct regulator_desc pm8921_vreg_description[] = {
+	VREG_DESC(L1,  "8921_l1",  &pm8921_nldo_ops, NLDO_SET_POINTS),
+	VREG_DESC(L2,  "8921_l2",  &pm8921_nldo_ops, NLDO_SET_POINTS),
+	VREG_DESC(L3,  "8921_l3",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L4,  "8921_l4",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L5,  "8921_l5",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L6,  "8921_l6",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L7,  "8921_l7",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L8,  "8921_l8",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L9,  "8921_l9",  &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L10, "8921_l10", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L11, "8921_l11", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L12, "8921_l12", &pm8921_nldo_ops, NLDO_SET_POINTS),
+	VREG_DESC(L14, "8921_l14", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L15, "8921_l15", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L16, "8921_l16", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L17, "8921_l17", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L18, "8921_l18", &pm8921_nldo_ops, NLDO_SET_POINTS),
+	VREG_DESC(L21, "8921_l21", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L22, "8921_l22", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L23, "8921_l23", &pm8921_pldo_ops, PLDO_SET_POINTS),
+	VREG_DESC(L24, "8921_l24", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS),
+	VREG_DESC(L25, "8921_l25", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS),
+	VREG_DESC(L26, "8921_l26", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS),
+	VREG_DESC(L27, "8921_l27", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS),
+	VREG_DESC(L28, "8921_l28", &pm8921_nldo1200_ops, NLDO1200_SET_POINTS),
+	VREG_DESC(L29, "8921_l29", &pm8921_pldo_ops, PLDO_SET_POINTS),
+
+	VREG_DESC(S1, "8921_s1", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS),
+	VREG_DESC(S2, "8921_s2", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS),
+	VREG_DESC(S3, "8921_s3", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS),
+	VREG_DESC(S4, "8921_s4", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS),
+	VREG_DESC(S5, "8921_s5", &pm8921_ftsmps_ops, FTSMPS_SET_POINTS),
+	VREG_DESC(S6, "8921_s6", &pm8921_ftsmps_ops, FTSMPS_SET_POINTS),
+	VREG_DESC(S7, "8921_s7", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS),
+	VREG_DESC(S8, "8921_s8", &pm8921_smps_ops, SMPS_ADVANCED_SET_POINTS),
+
+	VREG_DESC(LVS1, "8921_lvs1", &pm8921_vs_ops, 0),
+	VREG_DESC(LVS2, "8921_lvs2", &pm8921_vs300_ops, 0),
+	VREG_DESC(LVS3, "8921_lvs3", &pm8921_vs_ops, 0),
+	VREG_DESC(LVS4, "8921_lvs4", &pm8921_vs_ops, 0),
+	VREG_DESC(LVS5, "8921_lvs5", &pm8921_vs_ops, 0),
+	VREG_DESC(LVS6, "8921_lvs6", &pm8921_vs_ops, 0),
+	VREG_DESC(LVS7, "8921_lvs7", &pm8921_vs_ops, 0),
+
+	VREG_DESC(USB_OTG, "8921_usb_otg", &pm8921_vs300_ops, 0),
+	VREG_DESC(HDMI_MVS, "8921_hdmi_mvs", &pm8921_vs300_ops, 0),
+	VREG_DESC(NCP, "8921_ncp", &pm8921_ncp_ops, NCP_SET_POINTS),
+
+	VREG_DESC(L1_PC,  "8921_l1_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L2_PC,  "8921_l2_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L3_PC,  "8921_l3_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L4_PC,  "8921_l4_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L5_PC,  "8921_l5_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L6_PC,  "8921_l6_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L7_PC,  "8921_l7_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L8_PC,  "8921_l8_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L9_PC,  "8921_l9_pc",  &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L10_PC, "8921_l10_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L11_PC, "8921_l11_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L12_PC, "8921_l12_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L14_PC, "8921_l14_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L15_PC, "8921_l15_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L16_PC, "8921_l16_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L17_PC, "8921_l17_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L18_PC, "8921_l18_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L21_PC, "8921_l21_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L22_PC, "8921_l22_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L23_PC, "8921_l23_pc", &pm8921_ldo_pc_ops, 0),
+	VREG_DESC(L29_PC, "8921_l29_pc", &pm8921_ldo_pc_ops, 0),
+
+	VREG_DESC(S1_PC, "8921_s1_pc", &pm8921_smps_pc_ops, 0),
+	VREG_DESC(S2_PC, "8921_s2_pc", &pm8921_smps_pc_ops, 0),
+	VREG_DESC(S3_PC, "8921_s3_pc", &pm8921_smps_pc_ops, 0),
+	VREG_DESC(S4_PC, "8921_s4_pc", &pm8921_smps_pc_ops, 0),
+	VREG_DESC(S7_PC, "8921_s7_pc", &pm8921_smps_pc_ops, 0),
+	VREG_DESC(S8_PC, "8921_s8_pc", &pm8921_smps_pc_ops, 0),
+
+	VREG_DESC(LVS1_PC, "8921_lvs1_pc", &pm8921_vs_pc_ops, 0),
+	VREG_DESC(LVS3_PC, "8921_lvs3_pc", &pm8921_vs_pc_ops, 0),
+	VREG_DESC(LVS4_PC, "8921_lvs4_pc", &pm8921_vs_pc_ops, 0),
+	VREG_DESC(LVS5_PC, "8921_lvs5_pc", &pm8921_vs_pc_ops, 0),
+	VREG_DESC(LVS6_PC, "8921_lvs6_pc", &pm8921_vs_pc_ops, 0),
+	VREG_DESC(LVS7_PC, "8921_lvs7_pc", &pm8921_vs_pc_ops, 0),
+};
+
+static int pm8921_init_ldo(struct pm8921_vreg *vreg, bool is_real)
+{
+	int rc = 0;
+	int i;
+	u8 bank;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current test register state. */
+	for (i = 0; i < LDO_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	if (is_real) {
+		/* Set pull down enable based on platform data. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+		      (vreg->pdata.pull_down_enable ? LDO_PULL_DOWN_ENABLE : 0),
+		      LDO_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg);
+
+		vreg->is_enabled = !!_pm8921_vreg_is_enabled(vreg);
+
+		vreg->mode = ((vreg->ctrl_reg & LDO_CTRL_PM_MASK)
+					== LDO_CTRL_PM_LPM ?
+				REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL);
+	}
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_nldo1200(struct pm8921_vreg *vreg)
+{
+	int rc = 0;
+	int i;
+	u8 bank;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current test register state. */
+	for (i = 0; i < LDO_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	vreg->save_uV = _pm8921_nldo1200_get_voltage(vreg);
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+		 (vreg->pdata.pull_down_enable ? NLDO1200_PULL_DOWN_ENABLE : 0)
+		 | REGULATOR_BANK_SEL(1) | REGULATOR_BANK_WRITE,
+		 NLDO1200_PULL_DOWN_ENABLE_MASK | REGULATOR_BANK_MASK,
+		 &vreg->test_reg[1]);
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_smps(struct pm8921_vreg *vreg, bool is_real)
+{
+	int rc = 0;
+	int i;
+	u8 bank;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current test2 register state. */
+	for (i = 0; i < SMPS_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	/* Save the current clock control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->clk_ctrl_addr,
+			  &vreg->clk_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current sleep control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->sleep_ctrl_addr,
+			  &vreg->sleep_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	vreg->save_uV = _pm8921_smps_get_voltage(vreg);
+
+	if (is_real) {
+		/* Set advanced mode pull down enable based on platform data. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->test_addr,
+			(vreg->pdata.pull_down_enable
+				? SMPS_ADVANCED_PULL_DOWN_ENABLE : 0)
+			| REGULATOR_BANK_SEL(6) | REGULATOR_BANK_WRITE,
+			REGULATOR_BANK_MASK | SMPS_ADVANCED_PULL_DOWN_ENABLE,
+			&vreg->test_reg[6]);
+		if (rc)
+			goto bail;
+
+		vreg->is_enabled = !!_pm8921_vreg_is_enabled(vreg);
+
+		vreg->mode = ((vreg->clk_ctrl_reg & SMPS_CLK_CTRL_MASK)
+					== SMPS_CLK_CTRL_PFM ?
+				REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL);
+	}
+
+	if (!SMPS_IN_ADVANCED_MODE(vreg) && is_real) {
+		/* Set legacy mode pull down enable based on platform data. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+			(vreg->pdata.pull_down_enable
+				? SMPS_LEGACY_PULL_DOWN_ENABLE : 0),
+			SMPS_LEGACY_PULL_DOWN_ENABLE, &vreg->ctrl_reg);
+		if (rc)
+			goto bail;
+	}
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_ftsmps(struct pm8921_vreg *vreg)
+{
+	int rc, i;
+	u8 bank;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc)
+		goto bail;
+
+	/* Store current regulator register values. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->pfm_ctrl_addr,
+			  &vreg->pfm_ctrl_reg);
+	if (rc)
+		goto bail;
+
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->pwr_cnfg_addr,
+			  &vreg->pwr_cnfg_reg);
+	if (rc)
+		goto bail;
+
+	/* Save the current fts_cnfg1 register state (uses 'test' member). */
+	for (i = 0; i < SMPS_TEST_BANKS; i++) {
+		bank = REGULATOR_BANK_SEL(i);
+		rc = pm8xxx_writeb(vreg->dev->parent, vreg->test_addr, bank);
+		if (rc)
+			goto bail;
+
+		rc = pm8xxx_readb(vreg->dev->parent, vreg->test_addr,
+				  &vreg->test_reg[i]);
+		if (rc)
+			goto bail;
+		vreg->test_reg[i] |= REGULATOR_BANK_WRITE;
+	}
+
+	vreg->save_uV = _pm8921_ftsmps_get_voltage(vreg);
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->pwr_cnfg_addr,
+		(vreg->pdata.pull_down_enable ? FTSMPS_PULL_DOWN_ENABLE : 0),
+		FTSMPS_PULL_DOWN_ENABLE_MASK, &vreg->pwr_cnfg_reg);
+
+bail:
+	if (rc)
+		vreg_err(vreg, "pm8xxx_readb/writeb failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_vs(struct pm8921_vreg *vreg, bool is_real)
+{
+	int rc = 0;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (is_real) {
+		/* Set pull down enable based on platform data. */
+		rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+		       (vreg->pdata.pull_down_enable ? VS_PULL_DOWN_ENABLE : 0),
+		       VS_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg);
+
+		if (rc)
+			vreg_err(vreg,
+				"pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+		vreg->is_enabled = !!_pm8921_vreg_is_enabled(vreg);
+	}
+
+	return rc;
+}
+
+static int pm8921_init_vs300(struct pm8921_vreg *vreg)
+{
+	int rc;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Set pull down enable based on platform data. */
+	rc = pm8921_vreg_masked_write(vreg, vreg->ctrl_addr,
+		    (vreg->pdata.pull_down_enable ? VS300_PULL_DOWN_ENABLE : 0),
+		    VS300_PULL_DOWN_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8921_vreg_masked_write failed, rc=%d\n", rc);
+
+	return rc;
+}
+
+static int pm8921_init_ncp(struct pm8921_vreg *vreg)
+{
+	int rc;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+int pc_id_to_real_id(int id)
+{
+	int real_id;
+
+	if (id >= PM8921_VREG_ID_L1_PC && id <= PM8921_VREG_ID_L23_PC)
+		real_id = id - PM8921_VREG_ID_L1_PC;
+	else if (id >= PM8921_VREG_ID_L29_PC && id <= PM8921_VREG_ID_S4_PC)
+		real_id = id - PM8921_VREG_ID_L29_PC + PM8921_VREG_ID_L29;
+	else if (id >= PM8921_VREG_ID_S7_PC && id <= PM8921_VREG_ID_LVS1_PC)
+		real_id = id - PM8921_VREG_ID_S7_PC + PM8921_VREG_ID_S7;
+	else
+		real_id = id - PM8921_VREG_ID_LVS3_PC + PM8921_VREG_ID_LVS3;
+
+	return real_id;
+}
+
+static int __devinit pm8921_vreg_probe(struct platform_device *pdev)
+{
+	const struct pm8921_regulator_platform_data *pdata;
+	enum pm8921_vreg_pin_function pin_fn;
+	struct regulator_desc *rdesc;
+	struct pm8921_vreg *vreg;
+	const char *reg_name = "";
+	unsigned pin_ctrl;
+	int rc = 0, id = pdev->id;
+
+	if (pdev == NULL)
+		return -EINVAL;
+
+	if (pdev->id >= 0 && pdev->id < PM8921_VREG_ID_MAX) {
+		pdata = pdev->dev.platform_data;
+		rdesc = &pm8921_vreg_description[pdev->id];
+		if (!IS_REAL_REGULATOR(pdev->id))
+			id = pc_id_to_real_id(pdev->id);
+		vreg = &pm8921_vreg[id];
+		reg_name = pm8921_vreg_description[pdev->id].name;
+		if (!pdata) {
+			pr_err("%s requires platform data\n", reg_name);
+			return -EINVAL;
+		}
+
+		mutex_lock(&vreg->pc_lock);
+
+		if (IS_REAL_REGULATOR(pdev->id)) {
+			/* Do not modify pin control and pin function values. */
+			pin_ctrl = vreg->pdata.pin_ctrl;
+			pin_fn = vreg->pdata.pin_fn;
+			memcpy(&(vreg->pdata), pdata,
+				sizeof(struct pm8921_regulator_platform_data));
+			vreg->pdata.pin_ctrl = pin_ctrl;
+			vreg->pdata.pin_fn = pin_fn;
+			vreg->dev = &pdev->dev;
+			vreg->name = reg_name;
+		} else {
+			/* Pin control regulator */
+			if ((pdata->pin_ctrl &
+			   (PM8921_VREG_PIN_CTRL_D1 | PM8921_VREG_PIN_CTRL_A0
+			   | PM8921_VREG_PIN_CTRL_A1 | PM8921_VREG_PIN_CTRL_A2))
+			      == PM8921_VREG_PIN_CTRL_NONE) {
+				pr_err("%s: no pin control input specified\n",
+					reg_name);
+				mutex_unlock(&vreg->pc_lock);
+				return -EINVAL;
+			}
+			vreg->pdata.pin_ctrl = pdata->pin_ctrl;
+			vreg->pdata.pin_fn = pdata->pin_fn;
+			vreg->dev_pc = &pdev->dev;
+			if (!vreg->dev)
+				vreg->dev = &pdev->dev;
+			if (!vreg->name)
+				vreg->name = reg_name;
+		}
+
+		/* Initialize register values. */
+		switch (vreg->type) {
+		case REGULATOR_TYPE_LDO:
+			rc = pm8921_init_ldo(vreg, IS_REAL_REGULATOR(pdev->id));
+			break;
+		case REGULATOR_TYPE_NLDO1200:
+			rc = pm8921_init_nldo1200(vreg);
+			break;
+		case REGULATOR_TYPE_SMPS:
+			rc = pm8921_init_smps(vreg,
+						IS_REAL_REGULATOR(pdev->id));
+			break;
+		case REGULATOR_TYPE_FTSMPS:
+			rc = pm8921_init_ftsmps(vreg);
+			break;
+		case REGULATOR_TYPE_VS:
+			rc = pm8921_init_vs(vreg, IS_REAL_REGULATOR(pdev->id));
+			break;
+		case REGULATOR_TYPE_VS300:
+			rc = pm8921_init_vs300(vreg);
+			break;
+		case REGULATOR_TYPE_NCP:
+			rc = pm8921_init_ncp(vreg);
+			break;
+		}
+
+		mutex_unlock(&vreg->pc_lock);
+
+		if (rc)
+			goto bail;
+
+		if (IS_REAL_REGULATOR(pdev->id)) {
+			vreg->rdev = regulator_register(rdesc, &pdev->dev,
+					&(pdata->init_data), vreg);
+			if (IS_ERR(vreg->rdev)) {
+				rc = PTR_ERR(vreg->rdev);
+				vreg->rdev = NULL;
+				pr_err("regulator_register failed: %s, rc=%d\n",
+					reg_name, rc);
+			}
+		} else {
+			vreg->rdev_pc = regulator_register(rdesc, &pdev->dev,
+					&(pdata->init_data), vreg);
+			if (IS_ERR(vreg->rdev_pc)) {
+				rc = PTR_ERR(vreg->rdev_pc);
+				vreg->rdev_pc = NULL;
+				pr_err("regulator_register failed: %s, rc=%d\n",
+					reg_name, rc);
+			}
+		}
+	} else {
+		rc = -ENODEV;
+	}
+
+bail:
+	if (rc)
+		pr_err("error for %s, rc=%d\n", reg_name, rc);
+
+	return rc;
+}
+
+static int __devexit pm8921_vreg_remove(struct platform_device *pdev)
+{
+	if (IS_REAL_REGULATOR(pdev->id))
+		regulator_unregister(pm8921_vreg[pdev->id].rdev);
+	else
+		regulator_unregister(
+			pm8921_vreg[pc_id_to_real_id(pdev->id)].rdev_pc);
+
+	return 0;
+}
+
+static struct platform_driver pm8921_vreg_driver = {
+	.probe	= pm8921_vreg_probe,
+	.remove	= __devexit_p(pm8921_vreg_remove),
+	.driver	= {
+		.name	= PM8921_REGULATOR_DEV_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init pm8921_vreg_init(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pm8921_vreg); i++)
+		mutex_init(&pm8921_vreg[i].pc_lock);
+
+	return platform_driver_register(&pm8921_vreg_driver);
+}
+subsys_initcall(pm8921_vreg_init);
+
+static void __exit pm8921_vreg_exit(void)
+{
+	platform_driver_unregister(&pm8921_vreg_driver);
+}
+module_exit(pm8921_vreg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8921 regulator driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" PM8921_REGULATOR_DEV_NAME);
diff --git a/include/linux/mfd/pm8xxx/pm8921.h b/include/linux/mfd/pm8xxx/pm8921.h
index 19cffb2..c2794c3 100644
--- a/include/linux/mfd/pm8xxx/pm8921.h
+++ b/include/linux/mfd/pm8xxx/pm8921.h
@@ -22,6 +22,7 @@
 #include <linux/mfd/pm8xxx/irq.h>
 #include <linux/mfd/pm8xxx/gpio.h>
 #include <linux/mfd/pm8xxx/mpp.h>
+#include <linux/regulator/pm8921-regulator.h>
 
 #define PM8921_NR_IRQS		256
 
@@ -44,6 +45,8 @@ struct pm8921_platform_data {
 	struct pm8xxx_irq_platform_data		*irq_pdata;
 	struct pm8xxx_gpio_platform_data	*gpio_pdata;
 	struct pm8xxx_mpp_platform_data		*mpp_pdata;
+	struct pm8921_regulator_platform_data	*regulator_pdatas;
+	int					num_regulators;
 };
 
 #endif
diff --git a/include/linux/regulator/pm8921-regulator.h b/include/linux/regulator/pm8921-regulator.h
new file mode 100644
index 0000000..5ed5b0d
--- /dev/null
+++ b/include/linux/regulator/pm8921-regulator.h
@@ -0,0 +1,155 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 __PM8921_REGULATOR_H__
+#define __PM8921_REGULATOR_H__
+
+#include <linux/regulator/machine.h>
+
+#define PM8921_REGULATOR_DEV_NAME	"pm8921-regulator"
+
+/**
+ * enum pm8921_vreg_id - PMIC 8921 regulator ID numbers
+ */
+enum pm8921_vreg_id {
+	PM8921_VREG_ID_L1 = 0,
+	PM8921_VREG_ID_L2,
+	PM8921_VREG_ID_L3,
+	PM8921_VREG_ID_L4,
+	PM8921_VREG_ID_L5,
+	PM8921_VREG_ID_L6,
+	PM8921_VREG_ID_L7,
+	PM8921_VREG_ID_L8,
+	PM8921_VREG_ID_L9,
+	PM8921_VREG_ID_L10,
+	PM8921_VREG_ID_L11,
+	PM8921_VREG_ID_L12,
+	PM8921_VREG_ID_L14,
+	PM8921_VREG_ID_L15,
+	PM8921_VREG_ID_L16,
+	PM8921_VREG_ID_L17,
+	PM8921_VREG_ID_L18,
+	PM8921_VREG_ID_L21,
+	PM8921_VREG_ID_L22,
+	PM8921_VREG_ID_L23,
+	PM8921_VREG_ID_L24,
+	PM8921_VREG_ID_L25,
+	PM8921_VREG_ID_L26,
+	PM8921_VREG_ID_L27,
+	PM8921_VREG_ID_L28,
+	PM8921_VREG_ID_L29,
+	PM8921_VREG_ID_S1,
+	PM8921_VREG_ID_S2,
+	PM8921_VREG_ID_S3,
+	PM8921_VREG_ID_S4,
+	PM8921_VREG_ID_S5,
+	PM8921_VREG_ID_S6,
+	PM8921_VREG_ID_S7,
+	PM8921_VREG_ID_S8,
+	PM8921_VREG_ID_LVS1,
+	PM8921_VREG_ID_LVS2,
+	PM8921_VREG_ID_LVS3,
+	PM8921_VREG_ID_LVS4,
+	PM8921_VREG_ID_LVS5,
+	PM8921_VREG_ID_LVS6,
+	PM8921_VREG_ID_LVS7,
+	PM8921_VREG_ID_USB_OTG,
+	PM8921_VREG_ID_HDMI_MVS,
+	PM8921_VREG_ID_NCP,
+	/* The following are IDs for regulator devices to enable pin control. */
+	PM8921_VREG_ID_L1_PC,
+	PM8921_VREG_ID_L2_PC,
+	PM8921_VREG_ID_L3_PC,
+	PM8921_VREG_ID_L4_PC,
+	PM8921_VREG_ID_L5_PC,
+	PM8921_VREG_ID_L6_PC,
+	PM8921_VREG_ID_L7_PC,
+	PM8921_VREG_ID_L8_PC,
+	PM8921_VREG_ID_L9_PC,
+	PM8921_VREG_ID_L10_PC,
+	PM8921_VREG_ID_L11_PC,
+	PM8921_VREG_ID_L12_PC,
+	PM8921_VREG_ID_L14_PC,
+	PM8921_VREG_ID_L15_PC,
+	PM8921_VREG_ID_L16_PC,
+	PM8921_VREG_ID_L17_PC,
+	PM8921_VREG_ID_L18_PC,
+	PM8921_VREG_ID_L21_PC,
+	PM8921_VREG_ID_L22_PC,
+	PM8921_VREG_ID_L23_PC,
+
+	PM8921_VREG_ID_L29_PC,
+	PM8921_VREG_ID_S1_PC,
+	PM8921_VREG_ID_S2_PC,
+	PM8921_VREG_ID_S3_PC,
+	PM8921_VREG_ID_S4_PC,
+
+	PM8921_VREG_ID_S7_PC,
+	PM8921_VREG_ID_S8_PC,
+	PM8921_VREG_ID_LVS1_PC,
+
+	PM8921_VREG_ID_LVS3_PC,
+	PM8921_VREG_ID_LVS4_PC,
+	PM8921_VREG_ID_LVS5_PC,
+	PM8921_VREG_ID_LVS6_PC,
+	PM8921_VREG_ID_LVS7_PC,
+
+	PM8921_VREG_ID_MAX,
+};
+
+/* Pin control input pins. */
+#define PM8921_VREG_PIN_CTRL_NONE	0x00
+#define PM8921_VREG_PIN_CTRL_D1		0x01
+#define PM8921_VREG_PIN_CTRL_A0		0x02
+#define PM8921_VREG_PIN_CTRL_A1		0x04
+#define PM8921_VREG_PIN_CTRL_A2		0x08
+
+/* Minimum high power mode loads in uA. */
+#define PM8921_VREG_LDO_50_HPM_MIN_LOAD		5000
+#define PM8921_VREG_LDO_150_HPM_MIN_LOAD	10000
+#define PM8921_VREG_LDO_300_HPM_MIN_LOAD	10000
+#define PM8921_VREG_LDO_600_HPM_MIN_LOAD	10000
+#define PM8921_VREG_LDO_1200_HPM_MIN_LOAD	10000
+#define PM8921_VREG_SMPS_1500_HPM_MIN_LOAD	100000
+#define PM8921_VREG_SMPS_2000_HPM_MIN_LOAD	100000
+
+/**
+ * enum pm8921_vreg_pin_function - action to perform when pin control is active
+ * %PM8921_VREG_PIN_FN_ENABLE:	pin control enables the regulator
+ * %PM8921_VREG_PIN_FN_MODE:	pin control changes mode from LPM to HPM
+ */
+enum pm8921_vreg_pin_function {
+	PM8921_VREG_PIN_FN_ENABLE = 0,
+	PM8921_VREG_PIN_FN_MODE,
+};
+
+/**
+ * struct pm8921_regulator_platform_data - PMIC 8921 regulator platform data
+ * @init_data:		regulator constraints
+ * @id:			regulator id; from enum pm8921_vreg_id
+ * @pull_down_enable:	0 = no pulldown, 1 = pulldown when regulator disabled
+ * @pin_ctrl:		pin control inputs to use for the regulator; should be
+ *			a combination of PM8921_VREG_PIN_CTRL_* values
+ * @pin_fn:		action to perform when pin control pin is active
+ * @system_uA:		current drawn from regulator not accounted for by any
+ *			regulator framework consumer
+ */
+struct pm8921_regulator_platform_data {
+	struct regulator_init_data	init_data;
+	enum pm8921_vreg_id		id;
+	unsigned			pull_down_enable;
+	unsigned			pin_ctrl;
+	enum pm8921_vreg_pin_function	pin_fn;
+	int				system_uA;
+};
+
+#endif
-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.


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

* [PATCH v2 2/2] msm: board-8960: Add support for pm8921-regulator
  2011-04-29 23:07 ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver David Collins
  2011-04-29 23:09   ` [PATCH v2 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
@ 2011-04-29 23:11   ` David Collins
  2011-05-03 16:08     ` Mark Brown
  2011-05-03  9:48   ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver Mark Brown
  2 siblings, 1 reply; 16+ messages in thread
From: David Collins @ 2011-04-29 23:11 UTC (permalink / raw)
  To: David Brown, Daniel Walker, Bryan Huntsman
  Cc: Liam Girdwood, Mark Brown, Samuel Ortiz, linux-kernel,
	linux-arm-msm, linux-arm-kernel, linux-arm-msm-owner,
	David Collins

Add board-msm8960-regulator.c to contain pm8921-regulator structure
declarations.  The primary configuration table
msm_pm8921_regulator_pdata specifies the following characteristics
for each regulator: if it is always on, if it should have its pull
down set when disabled, its min and max voltage constraints, and its
supply regulator.

Signed-off-by: David Collins <collinsd@codeaurora.org>
---
 arch/arm/mach-msm/Makefile                  |    1 +
 arch/arm/mach-msm/board-msm8960-regulator.c |  306 +++++++++++++++++++++++++++
 arch/arm/mach-msm/board-msm8960.c           |    4 +
 arch/arm/mach-msm/board-msm8960.h           |   23 ++
 4 files changed, 334 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-msm/board-msm8960-regulator.c
 create mode 100644 arch/arm/mach-msm/board-msm8960.h

diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index 84d49db..5ee9318 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_ARCH_MSM7X30) += board-msm7x30.o devices-msm7x30.o
 obj-$(CONFIG_ARCH_QSD8X50) += board-qsd8x50.o devices-qsd8x50.o
 obj-$(CONFIG_ARCH_MSM8X60) += board-msm8x60.o devices-msm8x60.o
 obj-$(CONFIG_ARCH_MSM8960) += board-msm8960.o devices-msm8960.o
+obj-$(CONFIG_ARCH_MSM8960) += board-msm8960-regulator.o
 
 obj-$(CONFIG_ARCH_MSM7X30) += gpiomux-v1.o gpiomux.o
 obj-$(CONFIG_ARCH_QSD8X50) += gpiomux-8x50.o gpiomux-v1.o gpiomux.o
diff --git a/arch/arm/mach-msm/board-msm8960-regulator.c b/arch/arm/mach-msm/board-msm8960-regulator.c
new file mode 100644
index 0000000..08362d8
--- /dev/null
+++ b/arch/arm/mach-msm/board-msm8960-regulator.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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/regulator/pm8921-regulator.h>
+
+#include "board-msm8960.h"
+
+#define PM8921_VREG_CONSUMERS(_id) \
+	static struct regulator_consumer_supply \
+	pm8921_vreg_consumers_##_id[] __devinitdata
+
+/*
+ * Consumer specific regulator names:
+ *			 regulator name		consumer dev_name
+ */
+PM8921_VREG_CONSUMERS(L1) = {
+	REGULATOR_SUPPLY("8921_l1",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L2) = {
+	REGULATOR_SUPPLY("8921_l2",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L3) = {
+	REGULATOR_SUPPLY("8921_l3",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("HSUSB_3p3",		"msm_otg"),
+};
+PM8921_VREG_CONSUMERS(L4) = {
+	REGULATOR_SUPPLY("8921_l4",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("HSUSB_1p8",		"msm_otg"),
+};
+PM8921_VREG_CONSUMERS(L5) = {
+	REGULATOR_SUPPLY("8921_l5",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("sdc_vdd",		"msm_sdcc.1"),
+};
+PM8921_VREG_CONSUMERS(L6) = {
+	REGULATOR_SUPPLY("8921_l6",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("sdc_vdd",		"msm_sdcc.3"),
+};
+PM8921_VREG_CONSUMERS(L7) = {
+	REGULATOR_SUPPLY("8921_l7",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("sdc_vddp",		"msm_sdcc.3"),
+};
+PM8921_VREG_CONSUMERS(L8) = {
+	REGULATOR_SUPPLY("8921_l8",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L9) = {
+	REGULATOR_SUPPLY("8921_l9",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L10) = {
+	REGULATOR_SUPPLY("8921_l10",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L11) = {
+	REGULATOR_SUPPLY("8921_l11",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L12) = {
+	REGULATOR_SUPPLY("8921_l12",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L14) = {
+	REGULATOR_SUPPLY("8921_l14",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L15) = {
+	REGULATOR_SUPPLY("8921_l15",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L16) = {
+	REGULATOR_SUPPLY("8921_l16",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L17) = {
+	REGULATOR_SUPPLY("8921_l17",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L18) = {
+	REGULATOR_SUPPLY("8921_l18",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L21) = {
+	REGULATOR_SUPPLY("8921_l21",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L22) = {
+	REGULATOR_SUPPLY("8921_l22",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L23) = {
+	REGULATOR_SUPPLY("8921_l23",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L24) = {
+	REGULATOR_SUPPLY("8921_l24",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L25) = {
+	REGULATOR_SUPPLY("8921_l25",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L26) = {
+	REGULATOR_SUPPLY("8921_l26",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L27) = {
+	REGULATOR_SUPPLY("8921_l27",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L28) = {
+	REGULATOR_SUPPLY("8921_l28",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(L29) = {
+	REGULATOR_SUPPLY("8921_l29",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(S1) = {
+	REGULATOR_SUPPLY("8921_s1",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(S2) = {
+	REGULATOR_SUPPLY("8921_s2",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(S3) = {
+	REGULATOR_SUPPLY("8921_s3",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("HSUSB_VDDCX",		"msm_otg"),
+};
+PM8921_VREG_CONSUMERS(S4) = {
+	REGULATOR_SUPPLY("8921_s4",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("sdc_vccq",		"msm_sdcc.1"),
+};
+PM8921_VREG_CONSUMERS(S5) = {
+	REGULATOR_SUPPLY("8921_s5",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(S6) = {
+	REGULATOR_SUPPLY("8921_s6",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(S7) = {
+	REGULATOR_SUPPLY("8921_s7",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(S8) = {
+	REGULATOR_SUPPLY("8921_s8",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(LVS1) = {
+	REGULATOR_SUPPLY("8921_lvs1",		"reg-debug-consumer"),
+	REGULATOR_SUPPLY("sdc_vdd",		"msm_sdcc.4"),
+};
+PM8921_VREG_CONSUMERS(LVS2) = {
+	REGULATOR_SUPPLY("8921_lvs2",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(LVS3) = {
+	REGULATOR_SUPPLY("8921_lvs3",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(LVS4) = {
+	REGULATOR_SUPPLY("8921_lvs4",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(LVS5) = {
+	REGULATOR_SUPPLY("8921_lvs5",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(LVS6) = {
+	REGULATOR_SUPPLY("8921_lvs6",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(LVS7) = {
+	REGULATOR_SUPPLY("8921_lvs7",		"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(USB_OTG) = {
+	REGULATOR_SUPPLY("8921_usb_otg",	"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(HDMI_MVS) = {
+	REGULATOR_SUPPLY("8921_hdmi_mvs",	"reg-debug-consumer"),
+};
+PM8921_VREG_CONSUMERS(NCP) = {
+	REGULATOR_SUPPLY("8921_ncp",		"reg-debug-consumer"),
+};
+
+#define PM8921_VREG_INIT(_id, _min_uV, _max_uV, _modes, _ops, _apply_uV, \
+			 _pull_down, _always_on, _supply_regulator) \
+	{ \
+		.init_data = { \
+			.constraints = { \
+				.valid_modes_mask	= _modes, \
+				.valid_ops_mask		= _ops, \
+				.min_uV			= _min_uV, \
+				.max_uV			= _max_uV, \
+				.input_uV		= _max_uV, \
+				.apply_uV		= _apply_uV, \
+				.always_on		= _always_on, \
+			}, \
+			.num_consumer_supplies	= \
+				ARRAY_SIZE(pm8921_vreg_consumers_##_id), \
+			.consumer_supplies	= pm8921_vreg_consumers_##_id, \
+			.supply_regulator	= _supply_regulator, \
+		}, \
+		.id			= PM8921_VREG_ID_##_id, \
+		.pull_down_enable	= _pull_down, \
+	}
+
+#define PM8921_VREG_INIT_LDO(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \
+		| REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, _always_on, \
+		_supply_regulator)
+
+#define PM8921_VREG_INIT_NLDO1200(_id, _always_on, _pull_down, _min_uV, \
+		_max_uV, _supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \
+		| REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, _always_on, \
+		_supply_regulator)
+
+#define PM8921_VREG_INIT_SMPS(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL \
+		| REGULATOR_MODE_IDLE, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS | REGULATOR_CHANGE_MODE | \
+		REGULATOR_CHANGE_DRMS, 0, _pull_down, _always_on, \
+		_supply_regulator)
+
+#define PM8921_VREG_INIT_FTSMPS(_id, _always_on, _pull_down, _min_uV, _max_uV, \
+		_supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, REGULATOR_MODE_NORMAL, \
+		REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS \
+		| REGULATOR_CHANGE_MODE, 0, _pull_down, _always_on, \
+		_supply_regulator)
+
+#define PM8921_VREG_INIT_VS(_id, _always_on, _pull_down, _supply_regulator) \
+	PM8921_VREG_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, _pull_down, \
+		_always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_VS300(_id, _always_on, _pull_down, _supply_regulator) \
+	PM8921_VREG_INIT(_id, 0, 0, 0, REGULATOR_CHANGE_STATUS, 0, _pull_down, \
+		_always_on, _supply_regulator)
+
+#define PM8921_VREG_INIT_NCP(_id, _always_on, _min_uV, _max_uV, \
+		_supply_regulator) \
+	PM8921_VREG_INIT(_id, _min_uV, _max_uV, 0, REGULATOR_CHANGE_VOLTAGE | \
+		REGULATOR_CHANGE_STATUS, 0, 0, _always_on, _supply_regulator)
+
+/* Pin control initialization */
+#define PM8921_PC_INIT(_id, _always_on, _pin_fn, _pin_ctrl, _supply_regulator) \
+	{ \
+		.init_data = { \
+			.constraints = { \
+				.valid_ops_mask	= REGULATOR_CHANGE_STATUS, \
+				.always_on	= _always_on, \
+			}, \
+			.num_consumer_supplies	= \
+				ARRAY_SIZE(pm8921_vreg_consumers_##_id##_PC), \
+			.consumer_supplies = pm8921_vreg_consumers_##_id##_PC, \
+			.supply_regulator  = _supply_regulator, \
+		}, \
+		.id	  = PM8921_VREG_ID_##_id##_PC, \
+		.pin_fn	  = PM8921_VREG_PIN_FN_##_pin_fn, \
+		.pin_ctrl = _pin_ctrl, \
+	}
+
+/* Regulator constraints */
+struct pm8921_regulator_platform_data
+msm_pm8921_regulator_pdata[] __devinitdata = {
+	/*		      ID  always_on pd min_uV   max_uV   supply */
+	PM8921_VREG_INIT_SMPS(S1,	 0, 1, 1225000, 1225000, NULL),
+	PM8921_VREG_INIT_SMPS(S2,	 0, 1, 1300000, 1300000, NULL),
+	PM8921_VREG_INIT_SMPS(S3,	 0, 1, 1050000, 1050000, NULL),
+	PM8921_VREG_INIT_SMPS(S4,	 0, 1, 1800000, 1800000, NULL),
+	PM8921_VREG_INIT_FTSMPS(S5,	 0, 1, 1050000, 1050000, NULL),
+	PM8921_VREG_INIT_FTSMPS(S6,	 0, 1, 1050000, 1050000, NULL),
+	PM8921_VREG_INIT_SMPS(S7,	 0, 1, 1150000, 1150000, NULL),
+	PM8921_VREG_INIT_SMPS(S8,	 0, 1, 2200000, 2200000, NULL),
+
+	PM8921_VREG_INIT_LDO(L1,	 0, 1, 1050000, 1050000, "8921_s4"),
+	PM8921_VREG_INIT_LDO(L2,	 0, 1, 1200000, 1200000, "8921_s4"),
+	PM8921_VREG_INIT_LDO(L3,	 0, 1, 3075000, 3075000, NULL),
+	PM8921_VREG_INIT_LDO(L4,	 0, 1, 1800000, 1800000, NULL),
+	PM8921_VREG_INIT_LDO(L5,	 0, 1, 2950000, 2950000, NULL),
+	PM8921_VREG_INIT_LDO(L6,	 0, 1, 2950000, 2950000, NULL),
+	PM8921_VREG_INIT_LDO(L7,	 0, 1, 2950000, 2950000, NULL),
+	PM8921_VREG_INIT_LDO(L8,	 0, 1, 3000000, 3000000, NULL),
+	PM8921_VREG_INIT_LDO(L9,	 0, 1, 2850000, 2850000, NULL),
+	PM8921_VREG_INIT_LDO(L10,	 0, 1, 2900000, 2900000, NULL),
+	PM8921_VREG_INIT_LDO(L11,	 0, 1, 2850000, 2850000, NULL),
+	PM8921_VREG_INIT_LDO(L12,	 0, 1, 1200000, 1200000, "8921_s4"),
+	PM8921_VREG_INIT_LDO(L14,	 0, 1, 1800000, 1800000, NULL),
+	PM8921_VREG_INIT_LDO(L15,	 0, 1, 1800000, 2950000, NULL),
+	PM8921_VREG_INIT_LDO(L16,	 0, 1, 3000000, 3000000, NULL),
+	PM8921_VREG_INIT_LDO(L17,	 0, 1, 1800000, 2950000, NULL),
+	PM8921_VREG_INIT_LDO(L18,	 0, 1, 1300000, 1300000, "8921_s4"),
+	PM8921_VREG_INIT_LDO(L21,	 0, 1, 1900000, 1900000, "8921_s8"),
+	PM8921_VREG_INIT_LDO(L22,	 0, 1, 2750000, 2750000, NULL),
+	PM8921_VREG_INIT_LDO(L23,	 0, 1, 1800000, 1800000, "8921_s8"),
+	PM8921_VREG_INIT_NLDO1200(L24,	 0, 1, 1050000, 1050000, "8921_s1"),
+	PM8921_VREG_INIT_NLDO1200(L25,	 0, 1, 1225000, 1225000, "8921_s1"),
+	PM8921_VREG_INIT_NLDO1200(L26,	 0, 1, 1050000, 1050000, "8921_s7"),
+	PM8921_VREG_INIT_NLDO1200(L27,	 0, 1, 1050000, 1050000, "8921_s7"),
+	PM8921_VREG_INIT_NLDO1200(L28,	 0, 1, 1050000, 1050000, "8921_s7"),
+	PM8921_VREG_INIT_LDO(L29,	 0, 1, 2050000, 2100000, "8921_s8"),
+
+	PM8921_VREG_INIT_VS(LVS1,	 0, 1,			 "8921_s4"),
+	PM8921_VREG_INIT_VS300(LVS2,	 0, 1,			 "8921_s1"),
+	PM8921_VREG_INIT_VS(LVS3,	 0, 1,			 "8921_s4"),
+	PM8921_VREG_INIT_VS(LVS4,	 0, 1,			 "8921_s4"),
+	PM8921_VREG_INIT_VS(LVS5,	 0, 1,			 "8921_s4"),
+	PM8921_VREG_INIT_VS(LVS6,	 0, 1,			 "8921_s4"),
+	PM8921_VREG_INIT_VS(LVS7,	 0, 1,			 "8921_s4"),
+
+	PM8921_VREG_INIT_VS300(USB_OTG,  0, 1,			 NULL),
+	PM8921_VREG_INIT_VS300(HDMI_MVS, 0, 1,			 NULL),
+
+	PM8921_VREG_INIT_NCP(NCP,	 0,    1800000, 1800000, "8921_l6"),
+};
+
+int msm_pm8921_regulator_pdata_len __devinitdata =
+	ARRAY_SIZE(msm_pm8921_regulator_pdata);
diff --git a/arch/arm/mach-msm/board-msm8960.c b/arch/arm/mach-msm/board-msm8960.c
index 260b338..d505708 100644
--- a/arch/arm/mach-msm/board-msm8960.c
+++ b/arch/arm/mach-msm/board-msm8960.c
@@ -34,6 +34,7 @@
 
 #include "devices.h"
 #include "gpiomux.h"
+#include "board-msm8960.h"
 
 struct msm_gpiomux_config msm_gpiomux_configs[GPIOMUX_NGPIOS] = {};
 /* Macros assume PMIC GPIOs and MPPs start at 1 */
@@ -102,6 +103,7 @@ static struct pm8921_platform_data pm8921_platform_data __devinitdata = {
 	.irq_pdata		= &pm8xxx_irq_pdata,
 	.gpio_pdata		= &pm8xxx_gpio_pdata,
 	.mpp_pdata		= &pm8xxx_mpp_pdata,
+	.regulator_pdatas	= msm_pm8921_regulator_pdata,
 };
 
 static struct platform_device *devices[] __initdata = {
@@ -122,6 +124,8 @@ static void __init msm8960_init(void)
 	msm8960_device_ssbi_pm8921.dev.platform_data =
 					&msm8960_ssbi_pm8921_pdata;
 
+	pm8921_platform_data.num_regulators = msm_pm8921_regulator_pdata_len;
+
 	platform_add_devices(devices, ARRAY_SIZE(devices));
 }
 
diff --git a/arch/arm/mach-msm/board-msm8960.h b/arch/arm/mach-msm/board-msm8960.h
new file mode 100644
index 0000000..040b033
--- /dev/null
+++ b/arch/arm/mach-msm/board-msm8960.h
@@ -0,0 +1,23 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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 __ARCH_ARM_MACH_MSM_BOARD_MSM8960_H
+#define __ARCH_ARM_MACH_MSM_BOARD_MSM8960_H
+
+#include <linux/mfd/pm8xxx/pm8921.h>
+
+extern struct pm8921_regulator_platform_data
+	msm_pm8921_regulator_pdata[] __devinitdata;
+
+extern int msm_pm8921_regulator_pdata_len __devinitdata;
+
+#endif
-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.


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

* Re: [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver
  2011-04-29 23:07 ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver David Collins
  2011-04-29 23:09   ` [PATCH v2 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
  2011-04-29 23:11   ` [PATCH v2 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
@ 2011-05-03  9:48   ` Mark Brown
  2 siblings, 0 replies; 16+ messages in thread
From: Mark Brown @ 2011-05-03  9:48 UTC (permalink / raw)
  To: David Collins
  Cc: Liam Girdwood, David Brown, Daniel Walker, Bryan Huntsman,
	Samuel Ortiz, linux-arm-msm, linux-kernel, linux-arm-kernel,
	linux-arm-msm-owner

On Fri, Apr 29, 2011 at 04:07:01PM -0700, David Collins wrote:
> This patch set adds a new regulator driver to control the regulators in
> Qualcomm PM8921 PMIC chips.  It also introduces board file changes to use
> this new driver with the Qualcomm MSM 8960 platform.

Please don't send patches as followups to old threads, this makes them
hard to find in mail archives.

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

* Re: [PATCH v2 2/2] msm: board-8960: Add support for pm8921-regulator
  2011-04-29 23:11   ` [PATCH v2 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
@ 2011-05-03 16:08     ` Mark Brown
  0 siblings, 0 replies; 16+ messages in thread
From: Mark Brown @ 2011-05-03 16:08 UTC (permalink / raw)
  To: David Collins
  Cc: David Brown, Daniel Walker, Bryan Huntsman, Liam Girdwood,
	Samuel Ortiz, linux-kernel, linux-arm-msm, linux-arm-kernel,
	linux-arm-msm-owner

On Fri, Apr 29, 2011 at 04:11:07PM -0700, David Collins wrote:

> +PM8921_VREG_CONSUMERS(L1) = {
> +	REGULATOR_SUPPLY("8921_l1",		"reg-debug-consumer"),
> +};

Uh, no.  There's no such thing in mainline and having such a thing
enabled in production builds is crazy.  If such a thing *did* exist it
should still be doing the same thing everything else does and requesting
a fixed supply name.

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

end of thread, other threads:[~2011-05-03 16:08 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-03-30 22:16 [PATCH 0/2] regulator: msm: Add PM8921 regulator driver David Collins
2011-03-30 22:17 ` [PATCH 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
2011-03-30 23:46   ` Mark Brown
2011-03-31  1:37     ` David Collins
2011-03-31 23:44       ` Mark Brown
2011-04-01 23:28         ` David Collins
2011-04-02  2:50           ` Mark Brown
2011-04-04 17:13             ` David Collins
2011-04-04 23:19               ` Mark Brown
2011-03-30 22:17 ` [PATCH 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
2011-03-31  0:10   ` Mark Brown
2011-04-29 23:07 ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver David Collins
2011-04-29 23:09   ` [PATCH v2 1/2] regulator: pm8921-regulator: Add regulator driver for PM8921 David Collins
2011-04-29 23:11   ` [PATCH v2 2/2] msm: board-8960: Add support for pm8921-regulator David Collins
2011-05-03 16:08     ` Mark Brown
2011-05-03  9:48   ` [PATCH v2 0/2] regulator: msm: Add PM8921 regulator driver Mark Brown

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).