diff mbox

[V2,5/6] regulator: add mxs on-chip regulator driver

Message ID 1430346747-28728-6-git-send-email-stefan.wahren@i2se.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Stefan Wahren April 29, 2015, 10:32 p.m. UTC
This patch adds driver support for Freescale i.MX23, i.MX28
on-chip regulators. The driver supports the following components:
dcdc, vddd, vdda and vddio.

Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
---
 drivers/regulator/Kconfig         |    8 +
 drivers/regulator/Makefile        |    1 +
 drivers/regulator/mxs-regulator.c |  540 +++++++++++++++++++++++++++++++++++++
 3 files changed, 549 insertions(+)
 create mode 100644 drivers/regulator/mxs-regulator.c

Comments

Mark Brown May 4, 2015, 12:36 p.m. UTC | #1
On Wed, Apr 29, 2015 at 10:32:26PM +0000, Stefan Wahren wrote:

> +static struct regulator_ops mxs_dcdc_ops = {
> +	.is_enabled		= regulator_is_enabled_regmap,
> +};

Why do we have an is_enabled() operation but no enable or disable
operation?  I'm also not 100% clear what code the DCDCs and LDOs are
sharing here...

> +	initdata = of_get_regulator_init_data(dev, dev->of_node, &info->desc);
> +	if (!initdata) {
> +		dev_err(dev, "missing regulator init data\n");
> +		return -EINVAL;
> +	}

This is not an error, init data is totally optional.

> +	rdev = devm_regulator_register(dev, &info->desc, &config);
> +	if (IS_ERR(rdev)) {
> +		int ret = PTR_ERR(rdev);
> +
> +		dev_err(dev, "%s: failed to register regulator(%d)\n",
> +			__func__, ret);
> +		return ret;
> +	}
> +
> +	if (!of_property_read_u32(dev->of_node, "switching-frequency",
> +				  &switch_freq))
> +		mxs_set_dcdc_freq(rdev, switch_freq);

why are we registering the regulator before we've finished parsing the
DT - I'd expect us to do this first rather than have things potentially
start using the regulator with the confguration not completed.

> +	platform_set_drvdata(pdev, rdev);

This looks unsafe - either we don't need to set this at all or one of
the ops could be invoked when the regulator is registered and try to use
the driver data before it is set.
Stefan Wahren May 4, 2015, 8:34 p.m. UTC | #2
Hi Mark,

> Mark Brown <broonie@kernel.org> hat am 4. Mai 2015 um 14:36 geschrieben:
>
>
> On Wed, Apr 29, 2015 at 10:32:26PM +0000, Stefan Wahren wrote:
>
> > +static struct regulator_ops mxs_dcdc_ops = {
> > + .is_enabled = regulator_is_enabled_regmap,
> > +};
>
> Why do we have an is_enabled() operation but no enable or disable
> operation? 

i'm sorry, i forgot the following point in the changelog:

- move dcdc handling from mxs_power to mxs-regulator

I hope the change is okay. I implemented only is_enabled() to have at least 1
operation.
But it's not really necessary.

> I'm also not 100% clear what code the DCDCs and LDOs are
> sharing here...

Actually only probing and the regulator info structure.

Best regards
Stefan
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stefan Wahren May 7, 2015, 6:37 p.m. UTC | #3
Hi Mark,

> Stefan Wahren <stefan.wahren@i2se.com> hat am 4. Mai 2015 um 22:34
> geschrieben:
>
>
> Hi Mark,
>
> > Mark Brown <broonie@kernel.org> hat am 4. Mai 2015 um 14:36 geschrieben:
> >
> >
> > On Wed, Apr 29, 2015 at 10:32:26PM +0000, Stefan Wahren wrote:
> >
> > > +static struct regulator_ops mxs_dcdc_ops = {
> > > + .is_enabled = regulator_is_enabled_regmap,
> > > +};
> >
> > Why do we have an is_enabled() operation but no enable or disable
> > operation?
>
> i'm sorry, i forgot the following point in the changelog:
>
> - move dcdc handling from mxs_power to mxs-regulator
>
> I hope the change is okay. I implemented only is_enabled() to have at least 1
> operation.
> But it's not really necessary.

do we need enable / disable operation?

In the bootloader code enabling the DC-DC is critical. There is a known issue
which falsely triggers the brownout detection.

>
> > I'm also not 100% clear what code the DCDCs and LDOs are
> > sharing here...
>
> Actually only probing and the regulator info structure.

How can i improve that?

>
> Best regards
> Stefan
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown May 7, 2015, 6:52 p.m. UTC | #4
On Thu, May 07, 2015 at 08:37:18PM +0200, Stefan Wahren wrote:
> > Stefan Wahren <stefan.wahren@i2se.com> hat am 4. Mai 2015 um 22:34

> > i'm sorry, i forgot the following point in the changelog:

> > - move dcdc handling from mxs_power to mxs-regulator

> > I hope the change is okay. I implemented only is_enabled() to have at least 1
> > operation.
> > But it's not really necessary.

> do we need enable / disable operation?

> In the bootloader code enabling the DC-DC is critical. There is a known issue
> which falsely triggers the brownout detection.

Why would the driver not provide these operations?

> > > I'm also not 100% clear what code the DCDCs and LDOs are
> > > sharing here...

> > Actually only probing and the regulator info structure.

> How can i improve that?

If the devices aren't sharing code then they should be separate drivers.
Stefan Wahren May 7, 2015, 7:25 p.m. UTC | #5
> Mark Brown <broonie@kernel.org> hat am 7. Mai 2015 um 20:52 geschrieben:
>
>
> On Thu, May 07, 2015 at 08:37:18PM +0200, Stefan Wahren wrote:
> > > Stefan Wahren <stefan.wahren@i2se.com> hat am 4. Mai 2015 um 22:34
>
> > > i'm sorry, i forgot the following point in the changelog:
>
> > > - move dcdc handling from mxs_power to mxs-regulator
>
> > > I hope the change is okay. I implemented only is_enabled() to have at
> > > least 1
> > > operation.
> > > But it's not really necessary.
>
> > do we need enable / disable operation?
>
> > In the bootloader code enabling the DC-DC is critical. There is a known
> > issue
> > which falsely triggers the brownout detection.
>
> Why would the driver not provide these operations?

I didn't implement this operation because the suggested workaround for the
hardware issue isn't very nice 
(disable brownout detection during enabling DC-DC). I was afraid of the
implementation.

>
> > > > I'm also not 100% clear what code the DCDCs and LDOs are
> > > > sharing here...
>
> > > Actually only probing and the regulator info structure.
>
> > How can i improve that?
>
> If the devices aren't sharing code then they should be separate drivers.

Okay. 

mxs-regulator-ldo.c
mxs-regulator-dcdc.c

What do you think of?
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown May 7, 2015, 7:32 p.m. UTC | #6
On Thu, May 07, 2015 at 09:25:19PM +0200, Stefan Wahren wrote:
> > Mark Brown <broonie@kernel.org> hat am 7. Mai 2015 um 20:52 geschrieben:

> > > In the bootloader code enabling the DC-DC is critical. There is a known
> > > issue
> > > which falsely triggers the brownout detection.

> > Why would the driver not provide these operations?

> I didn't implement this operation because the suggested workaround for the
> hardware issue isn't very nice 
> (disable brownout detection during enabling DC-DC). I was afraid of the
> implementation.

That doesn't sound too complicated?

> > If the devices aren't sharing code then they should be separate drivers.

> Okay. 

> mxs-regulator-ldo.c
> mxs-regulator-dcdc.c

> What do you think of?

Sure.
Stefan Wahren May 26, 2015, 12:16 p.m. UTC | #7
> Mark Brown <broonie@kernel.org> hat am 7. Mai 2015 um 21:32 geschrieben:
>
>
> On Thu, May 07, 2015 at 09:25:19PM +0200, Stefan Wahren wrote:
> > > Mark Brown <broonie@kernel.org> hat am 7. Mai 2015 um 20:52 geschrieben:
>
> > > > In the bootloader code enabling the DC-DC is critical. There is a known
> > > > issue
> > > > which falsely triggers the brownout detection.
>
> > > Why would the driver not provide these operations?
>
> > I didn't implement this operation because the suggested workaround for the
> > hardware issue isn't very nice
> > (disable brownout detection during enabling DC-DC). I was afraid of the
> > implementation.
>
> That doesn't sound too complicated?
>

Okay, i think i managed the brownout handling.

Unfortunately i'm stuck at the next step. The DC-DC (3 way output) can supply 3
LDOs vddd, vdda, vddio. In case of a DC-DC state change the 3 regulators needs
to be reconfigured before.

I tought of REGULATOR_EVENT_PRE_DISABLE and REGULATOR_EVENT_PRE_ENABLE events,
but i'm not sure that's the right way to achieve a synchronization between DC-DC
and consuming LDOs.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown May 27, 2015, 5:33 p.m. UTC | #8
On Tue, May 26, 2015 at 02:16:55PM +0200, Stefan Wahren wrote:
> > Mark Brown <broonie@kernel.org> hat am 7. Mai 2015 um 21:32 geschrieben:

> > > > > In the bootloader code enabling the DC-DC is critical. There is a known
> > > > > issue
> > > > > which falsely triggers the brownout detection.

> > > > Why would the driver not provide these operations?

> > > I didn't implement this operation because the suggested workaround for the
> > > hardware issue isn't very nice
> > > (disable brownout detection during enabling DC-DC). I was afraid of the
> > > implementation.

> > That doesn't sound too complicated?

> Okay, i think i managed the brownout handling.
> 
> Unfortunately i'm stuck at the next step. The DC-DC (3 way output) can supply 3
> LDOs vddd, vdda, vddio. In case of a DC-DC state change the 3 regulators needs
> to be reconfigured before.

> I tought of REGULATOR_EVENT_PRE_DISABLE and REGULATOR_EVENT_PRE_ENABLE events,
> but i'm not sure that's the right way to achieve a synchronization between DC-DC
> and consuming LDOs.

OK, that's possibly sounding a bit complicated.  Is it just a case of
writing some value, doing a reconfiguration and rewriting the original
value or is it something more?  If it's much more complicated than that
then I think putting a comment in the driver explaining what the issue
is would be the right step, that would address the issue with the driver
looking strange.
Stefan Wahren May 27, 2015, 8:58 p.m. UTC | #9
> Mark Brown <broonie@kernel.org> hat am 27. Mai 2015 um 19:33 geschrieben:
>
>
> On Tue, May 26, 2015 at 02:16:55PM +0200, Stefan Wahren wrote:
> > > Mark Brown <broonie@kernel.org> hat am 7. Mai 2015 um 21:32 geschrieben:
>
> > > > > > In the bootloader code enabling the DC-DC is critical. There is a
> > > > > > known
> > > > > > issue
> > > > > > which falsely triggers the brownout detection.
>
> > > > > Why would the driver not provide these operations?
>
> > > > I didn't implement this operation because the suggested workaround for
> > > > the
> > > > hardware issue isn't very nice
> > > > (disable brownout detection during enabling DC-DC). I was afraid of the
> > > > implementation.
>
> > > That doesn't sound too complicated?
>
> > Okay, i think i managed the brownout handling.
> >
> > Unfortunately i'm stuck at the next step. The DC-DC (3 way output) can
> > supply 3
> > LDOs vddd, vdda, vddio. In case of a DC-DC state change the 3 regulators
> > needs
> > to be reconfigured before.
>
> > I tought of REGULATOR_EVENT_PRE_DISABLE and REGULATOR_EVENT_PRE_ENABLE
> > events,
> > but i'm not sure that's the right way to achieve a synchronization between
> > DC-DC
> > and consuming LDOs.
>
> OK, that's possibly sounding a bit complicated. Is it just a case of
> writing some value, doing a reconfiguration and rewriting the original
> value or is it something more?

In case of a single driver instance instead of 4 ones (vddd, vddio, vdda,
dc-dc), things would be much easier.

Here are the steps for disabling DC-DC:

1. configure vddio to work without DC-DC
2. configure vdda to work without DC-DC
3. configure vddd to work without DC-DC
4. backup DC-DC settings
5. disable brownout detection
6. disable DC-DC
7. restore DC-DC settings

My problem is that step 1 to 3 must be done step by step through the other
regulator instances.

> If it's much more complicated than that
> then I think putting a comment in the driver explaining what the issue
> is would be the right step, that would address the issue with the driver
> looking strange.

Enabling or disabling DC-DC is only needed internally by the power subsystem
(e.g. in case of handoff AC to battery), because the DC-DC only supplys the
regulator daisy. From my understanding hardware components could use only the
regulator outputs. In the end a state change is necessary, but it shouldn't be a
public.
--
To unsubscribe from this list: send the line "unsubscribe linux-pm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mark Brown May 28, 2015, 2:26 p.m. UTC | #10
On Wed, May 27, 2015 at 10:58:01PM +0200, Stefan Wahren wrote:

> In case of a single driver instance instead of 4 ones (vddd, vddio, vdda,
> dc-dc), things would be much easier.

> Here are the steps for disabling DC-DC:

> 1. configure vddio to work without DC-DC
> 2. configure vdda to work without DC-DC
> 3. configure vddd to work without DC-DC
> 4. backup DC-DC settings
> 5. disable brownout detection
> 6. disable DC-DC
> 7. restore DC-DC settings

> My problem is that step 1 to 3 must be done step by step through the other
> regulator instances.

For the regulator API steps 1-3 aren't going to be needed since
dependency management means that we won't try to disable a regulator
that we think is in use.

> > If it's much more complicated than that
> > then I think putting a comment in the driver explaining what the issue
> > is would be the right step, that would address the issue with the driver
> > looking strange.

> Enabling or disabling DC-DC is only needed internally by the power subsystem
> (e.g. in case of handoff AC to battery), because the DC-DC only supplys the
> regulator daisy. From my understanding hardware components could use only the
> regulator outputs. In the end a state change is necessary, but it shouldn't be a
> public.

Should this actually be shown as a regulator then and not as part of the
power controller?
diff mbox

Patch

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index a6f116a..7b525f5 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -450,6 +450,14 @@  config REGULATOR_MT6397
 	  This driver supports the control of different power rails of device
 	  through regulator interface.
 
+config REGULATOR_MXS
+	tristate "Freescale MXS on-chip regulators"
+	depends on (MXS_POWER || COMPILE_TEST)
+	help
+	  Say y here to support Freescale MXS on-chip regulators.
+	  It is recommended that this option be enabled on i.MX23,
+	  i.MX28 platform.
+
 config REGULATOR_PALMAS
 	tristate "TI Palmas PMIC Regulators"
 	depends on MFD_PALMAS
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 2c4da15..a3ebf23 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -60,6 +60,7 @@  obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
 obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
 obj-$(CONFIG_REGULATOR_MC13XXX_CORE) +=  mc13xxx-regulator-core.o
 obj-$(CONFIG_REGULATOR_MT6397)	+= mt6397-regulator.o
+obj-$(CONFIG_REGULATOR_MXS) += mxs-regulator.o
 obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
 obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
 obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
diff --git a/drivers/regulator/mxs-regulator.c b/drivers/regulator/mxs-regulator.c
new file mode 100644
index 0000000..e53707b
--- /dev/null
+++ b/drivers/regulator/mxs-regulator.c
@@ -0,0 +1,540 @@ 
+/*
+ * Freescale MXS on-chip regulators
+ *
+ * Embedded Alley Solutions, Inc <source@embeddedalley.com>
+ *
+ * Copyright (C) 2014 Stefan Wahren
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ *
+ * Inspired by imx-bootlets
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+
+/* Powered by linear regulator.  DCDC output is gated off and
+   the linreg output is equal to the target. */
+#define HW_POWER_LINREG_DCDC_OFF		1
+
+/* Powered by linear regulator.  DCDC output is not gated off
+   and is ready for the automatic hardware transistion after a 5V
+   event.  The converters are not enabled when 5V is present. LinReg output
+   is 25mV below target. */
+#define HW_POWER_LINREG_DCDC_READY		2
+
+/* Powered by DCDC converter and the LinReg is on. LinReg output
+   is 25mV below target. */
+#define HW_POWER_DCDC_LINREG_ON			3
+
+/* Powered by DCDC converter and the LinReg is off. LinReg output
+   is 25mV below target. */
+#define HW_POWER_DCDC_LINREG_OFF		4
+
+/* Powered by DCDC converter and the LinReg is ready for the
+   automatic hardware transfer.  The LinReg output is not enabled and
+   depends on the 5V presence to enable the LinRegs.  LinReg offset is 25mV
+   below target. */
+#define HW_POWER_DCDC_LINREG_READY		5
+
+/* Powered by an external source when 5V is present.  This does not
+   necessarily mean the external source is powered by 5V,but the chip needs
+   to be aware that 5V is present. */
+#define HW_POWER_EXTERNAL_SOURCE_5V		6
+
+/* Powered by an external source when 5V is not present.This doesn't
+   necessarily mean the external source is powered by the battery, but the
+   chip needs to be aware that the battery is present */
+#define HW_POWER_EXTERNAL_SOURCE_BATTERY	7
+
+/* Unknown configuration.  This is an error. */
+#define HW_POWER_UNKNOWN_SOURCE			8
+
+/* TODO: Move power register offsets into header file */
+#define HW_POWER_5VCTRL		0x00000010
+#define HW_POWER_VDDDCTRL	0x00000040
+#define HW_POWER_VDDACTRL	0x00000050
+#define HW_POWER_VDDIOCTRL	0x00000060
+#define HW_POWER_MISC		0x00000090
+#define HW_POWER_STS		0x000000c0
+
+#define BM_POWER_STS_VBUSVALID0_STATUS	BIT(15)
+#define BM_POWER_STS_DC_OK		BIT(9)
+
+#define BM_POWER_5VCTRL_ILIMIT_EQ_ZERO	BIT(2)
+#define BM_POWER_5VCTRL_ENABLE_DCDC	BIT(0)
+
+#define BM_POWER_LINREG_OFFSET_DCDC_MODE	BIT(1)
+
+#define SHIFT_FREQSEL			4
+
+#define BM_POWER_MISC_FREQSEL		(7 << SHIFT_FREQSEL)
+
+/* Recommended DC-DC clock source values */
+#define HW_POWER_MISC_FREQSEL_20000_KHZ	1
+#define HW_POWER_MISC_FREQSEL_24000_KHZ	2
+#define HW_POWER_MISC_FREQSEL_19200_KHZ	3
+
+#define HW_POWER_MISC_SEL_PLLCLK	BIT(0)
+
+/* Regulator IDs */
+#define MXS_DCDC	1
+#define MXS_VDDIO	2
+#define MXS_VDDA	3
+#define MXS_VDDD	4
+
+struct mxs_reg_info {
+	/* regulator descriptor */
+	struct regulator_desc desc;
+
+	/* regulator control register */
+	int ctrl_reg;
+
+	/* disable DC-DC output */
+	unsigned int disable_fet_mask;
+
+	/* steps between linreg output and DC-DC target */
+	unsigned int linreg_offset_mask;
+	u8 linreg_offset_shift;
+
+	/* function which determine power source */
+	u8 (*get_power_source)(struct regulator_dev *);
+};
+
+static inline u8 get_linreg_offset(struct mxs_reg_info *ldo, u32 regs)
+{
+	return (regs & ldo->linreg_offset_mask) >> ldo->linreg_offset_shift;
+}
+
+static u8 get_vddio_power_source(struct regulator_dev *reg)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+	u32 v5ctrl, status, base;
+	u8 offset;
+
+	if (regmap_read(reg->regmap, HW_POWER_5VCTRL, &v5ctrl))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, HW_POWER_STS, &status))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, ldo->ctrl_reg, &base))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	offset = get_linreg_offset(ldo, base);
+
+	/* If VBUS valid then 5 V power supply present */
+	if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
+		/* Powered by Linreg, DC-DC is off */
+		if ((base & ldo->disable_fet_mask) &&
+		    !(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)) {
+			return HW_POWER_LINREG_DCDC_OFF;
+		}
+
+		if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC) {
+			/* Powered by DC-DC, Linreg is on */
+			if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+				return HW_POWER_DCDC_LINREG_ON;
+		} else {
+			/* Powered by Linreg, DC-DC is off */
+			if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
+				return HW_POWER_LINREG_DCDC_OFF;
+		}
+	} else {
+		/* Powered by DC-DC, Linreg is on */
+		if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+			return HW_POWER_DCDC_LINREG_ON;
+	}
+
+	return HW_POWER_UNKNOWN_SOURCE;
+}
+
+static u8 get_vdda_vddd_power_source(struct regulator_dev *reg)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+	struct regulator_desc *desc = &ldo->desc;
+	u32 v5ctrl, status, base;
+	u8 offset;
+
+	if (regmap_read(reg->regmap, HW_POWER_5VCTRL, &v5ctrl))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, HW_POWER_STS, &status))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	if (regmap_read(reg->regmap, ldo->ctrl_reg, &base))
+		return HW_POWER_UNKNOWN_SOURCE;
+
+	offset = get_linreg_offset(ldo, base);
+
+	/* DC-DC output is disabled */
+	if (base & ldo->disable_fet_mask) {
+		/* Powered by 5 V supply */
+		if (status & BM_POWER_STS_VBUSVALID0_STATUS)
+			return HW_POWER_EXTERNAL_SOURCE_5V;
+
+		/* Powered by Linreg, DC-DC is off */
+		if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
+			return HW_POWER_LINREG_DCDC_OFF;
+	}
+
+	/* If VBUS valid then 5 V power supply present */
+	if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
+		/* Powered by DC-DC, Linreg is on */
+		if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC)
+			return HW_POWER_DCDC_LINREG_ON;
+
+		/* Powered by Linreg, DC-DC is off */
+		return HW_POWER_LINREG_DCDC_OFF;
+	}
+
+	/* DC-DC is on */
+	if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE) {
+		/* Powered by DC-DC, Linreg is on */
+		if (base & desc->enable_mask)
+			return HW_POWER_DCDC_LINREG_ON;
+
+		/* Powered by DC-DC, Linreg is off */
+		return HW_POWER_DCDC_LINREG_OFF;
+	}
+
+	return HW_POWER_UNKNOWN_SOURCE;
+}
+
+static int mxs_set_dcdc_freq(struct regulator_dev *reg, u32 hz)
+{
+	struct mxs_reg_info *dcdc = rdev_get_drvdata(reg);
+	u32 val;
+	int ret;
+
+	if (dcdc->desc.id != MXS_DCDC) {
+		dev_warn(&reg->dev, "Setting switching freq is not supported\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_read(reg->regmap, HW_POWER_MISC, &val);
+	if (ret)
+		return ret;
+
+	val &= ~BM_POWER_MISC_FREQSEL;
+	val &= ~HW_POWER_MISC_SEL_PLLCLK;
+
+	/*
+	 * Select the PLL/PFD based frequency that the DC-DC converter uses.
+	 * The actual switching frequency driving the power inductor is
+	 * DCDC_CLK/16. Accept only values recommend by Freescale.
+	 */
+	switch (hz) {
+	case 1200000:
+		val |= HW_POWER_MISC_FREQSEL_19200_KHZ << SHIFT_FREQSEL;
+		break;
+	case 1250000:
+		val |= HW_POWER_MISC_FREQSEL_20000_KHZ << SHIFT_FREQSEL;
+		break;
+	case 1500000:
+		val |= HW_POWER_MISC_FREQSEL_24000_KHZ << SHIFT_FREQSEL;
+		break;
+	default:
+		dev_warn(&reg->dev, "Switching freq: %u Hz not supported\n",
+			 hz);
+		return -EINVAL;
+	}
+
+	/* First program FREQSEL */
+	ret = regmap_write(reg->regmap, HW_POWER_MISC, val);
+	if (ret)
+		return ret;
+
+	/* then set PLL as clock for DC-DC converter */
+	val |= HW_POWER_MISC_SEL_PLLCLK;
+
+	return regmap_write(reg->regmap, HW_POWER_MISC, val);
+}
+
+static int mxs_ldo_set_voltage_sel(struct regulator_dev *reg, unsigned sel)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+	struct regulator_desc *desc = &ldo->desc;
+	u32 status = 0;
+	int timeout;
+	int ret;
+
+	ret = regmap_update_bits(reg->regmap, desc->vsel_reg, desc->vsel_mask,
+				 sel);
+	if (ret)
+		return ret;
+
+	if (ldo->get_power_source) {
+		switch (ldo->get_power_source(reg)) {
+		case HW_POWER_LINREG_DCDC_OFF:
+		case HW_POWER_LINREG_DCDC_READY:
+		case HW_POWER_EXTERNAL_SOURCE_5V:
+			/*
+			 * Since the DC-DC converter is off we can't
+			 * trigger on DC_OK. So wait at least 1 ms
+			 * for stabilization.
+			 */
+			usleep_range(1000, 2000);
+			return 0;
+		}
+	}
+
+	/* Make sure DC_OK has changed */
+	usleep_range(15, 20);
+
+	for (timeout = 0; timeout < 20; timeout++) {
+		ret = regmap_read(reg->regmap, HW_POWER_STS, &status);
+
+		if (ret)
+			break;
+
+		/* DC-DC converter control loop has stabilized */
+		if (status & BM_POWER_STS_DC_OK)
+			return 0;
+
+		udelay(1);
+	}
+
+	if (!ret)
+		dev_warn_ratelimited(&reg->dev, "%s: timeout status=0x%08x\n",
+				     __func__, status);
+
+	msleep(20);
+
+	return -ETIMEDOUT;
+}
+
+static int mxs_ldo_is_enabled(struct regulator_dev *reg)
+{
+	struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
+
+	if (ldo->get_power_source) {
+		switch (ldo->get_power_source(reg)) {
+		case HW_POWER_LINREG_DCDC_OFF:
+		case HW_POWER_LINREG_DCDC_READY:
+		case HW_POWER_DCDC_LINREG_ON:
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static struct regulator_ops mxs_dcdc_ops = {
+	.is_enabled		= regulator_is_enabled_regmap,
+};
+
+static struct regulator_ops mxs_ldo_ops = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.map_voltage		= regulator_map_voltage_linear,
+	.set_voltage_sel	= mxs_ldo_set_voltage_sel,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.is_enabled		= mxs_ldo_is_enabled,
+};
+
+static const struct mxs_reg_info mxs_info_dcdc = {
+	.desc = {
+		.name = "dcdc",
+		.id = MXS_DCDC,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.ops = &mxs_dcdc_ops,
+		.enable_reg = HW_POWER_STS,
+		.enable_mask = (1 << 0),
+	},
+};
+
+static const struct mxs_reg_info imx23_info_vddio = {
+	.desc = {
+		.name = "vddio",
+		.id = MXS_VDDIO,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x20,
+		.uV_step = 25000,
+		.linear_min_sel = 0,
+		.min_uV = 2800000,
+		.vsel_reg = HW_POWER_VDDIOCTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+	},
+	.ctrl_reg = HW_POWER_VDDIOCTRL,
+	.disable_fet_mask = 1 << 16,
+	.linreg_offset_mask = 3 << 12,
+	.linreg_offset_shift = 12,
+	.get_power_source = get_vddio_power_source,
+};
+
+static const struct mxs_reg_info imx28_info_vddio = {
+	.desc = {
+		.name = "vddio",
+		.id = MXS_VDDIO,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x11,
+		.uV_step = 50000,
+		.linear_min_sel = 0,
+		.min_uV = 2800000,
+		.vsel_reg = HW_POWER_VDDIOCTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+	},
+	.ctrl_reg = HW_POWER_VDDIOCTRL,
+	.disable_fet_mask = 1 << 16,
+	.linreg_offset_mask = 3 << 12,
+	.linreg_offset_shift = 12,
+	.get_power_source = get_vddio_power_source,
+};
+
+static const struct mxs_reg_info mxs_info_vdda = {
+	.desc = {
+		.name = "vdda",
+		.id = MXS_VDDA,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x20,
+		.uV_step = 25000,
+		.linear_min_sel = 0,
+		.min_uV = 1500000,
+		.vsel_reg = HW_POWER_VDDACTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+		.enable_mask = (1 << 17),
+	},
+	.ctrl_reg = HW_POWER_VDDACTRL,
+	.disable_fet_mask = 1 << 16,
+	.linreg_offset_mask = 3 << 12,
+	.linreg_offset_shift = 12,
+	.get_power_source = get_vdda_vddd_power_source,
+};
+
+static const struct mxs_reg_info mxs_info_vddd = {
+	.desc = {
+		.name = "vddd",
+		.id = MXS_VDDD,
+		.type = REGULATOR_VOLTAGE,
+		.owner = THIS_MODULE,
+		.n_voltages = 0x20,
+		.uV_step = 25000,
+		.linear_min_sel = 0,
+		.min_uV = 800000,
+		.vsel_reg = HW_POWER_VDDDCTRL,
+		.vsel_mask = 0x1f,
+		.ops = &mxs_ldo_ops,
+		.enable_mask = (1 << 21),
+	},
+	.ctrl_reg = HW_POWER_VDDDCTRL,
+	.disable_fet_mask = 1 << 20,
+	.linreg_offset_mask = 3 << 16,
+	.linreg_offset_shift = 16,
+	.get_power_source = get_vdda_vddd_power_source,
+};
+
+static const struct of_device_id of_mxs_regulator_match[] = {
+	{ .compatible = "fsl,imx23-dcdc",  .data = &mxs_info_dcdc },
+	{ .compatible = "fsl,imx28-dcdc",  .data = &mxs_info_dcdc },
+	{ .compatible = "fsl,imx23-vddio", .data = &imx23_info_vddio },
+	{ .compatible = "fsl,imx23-vdda",  .data = &mxs_info_vdda },
+	{ .compatible = "fsl,imx23-vddd",  .data = &mxs_info_vddd },
+	{ .compatible = "fsl,imx28-vddio", .data = &imx28_info_vddio },
+	{ .compatible = "fsl,imx28-vdda",  .data = &mxs_info_vdda },
+	{ .compatible = "fsl,imx28-vddd",  .data = &mxs_info_vddd },
+	{ /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_mxs_regulator_match);
+
+static int mxs_regulator_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct of_device_id *match;
+	struct device_node *parent_np;
+	struct regulator_dev *rdev = NULL;
+	struct mxs_reg_info *info;
+	struct regulator_init_data *initdata;
+	struct regulator_config config = { };
+	u32 switch_freq;
+
+	match = of_match_device(of_mxs_regulator_match, dev);
+	if (!match) {
+		/* We do not expect this to happen */
+		dev_err(dev, "%s: Unable to match device\n", __func__);
+		return -ENODEV;
+	}
+
+	info = devm_kmemdup(dev, match->data, sizeof(struct mxs_reg_info),
+			    GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	initdata = of_get_regulator_init_data(dev, dev->of_node, &info->desc);
+	if (!initdata) {
+		dev_err(dev, "missing regulator init data\n");
+		return -EINVAL;
+	}
+
+	parent_np = of_get_parent(dev->of_node);
+	if (!parent_np)
+		return -ENODEV;
+	config.regmap = syscon_node_to_regmap(parent_np);
+	of_node_put(parent_np);
+	if (IS_ERR(config.regmap))
+		return PTR_ERR(config.regmap);
+
+	config.dev = dev;
+	config.init_data = initdata;
+	config.driver_data = info;
+	config.of_node = dev->of_node;
+
+	rdev = devm_regulator_register(dev, &info->desc, &config);
+	if (IS_ERR(rdev)) {
+		int ret = PTR_ERR(rdev);
+
+		dev_err(dev, "%s: failed to register regulator(%d)\n",
+			__func__, ret);
+		return ret;
+	}
+
+	if (!of_property_read_u32(dev->of_node, "switching-frequency",
+				  &switch_freq))
+		mxs_set_dcdc_freq(rdev, switch_freq);
+
+	platform_set_drvdata(pdev, rdev);
+
+	return 0;
+}
+
+static struct platform_driver mxs_regulator_driver = {
+	.driver = {
+		.name	= "mxs_regulator",
+		.of_match_table = of_mxs_regulator_match,
+	},
+	.probe	= mxs_regulator_probe,
+};
+
+module_platform_driver(mxs_regulator_driver);
+
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_DESCRIPTION("Freescale MXS regulators");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxs_regulator");