diff mbox

[v2,07/10] regulator: Add driver for Maxim 77802 PMIC regulators

Message ID 1402941758-11216-8-git-send-email-javier.martinez@collabora.co.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Javier Martinez Canillas June 16, 2014, 6:02 p.m. UTC
The MAX77802 PMIC has 10 high-efficiency Buck and 32 Low-dropout
(LDO) regulators. This patch adds support for all these regulators
found on the MAX77802 PMIC and is based on a driver added by Simon
Glass to the Chrome OS kernel 3.8 tree.

Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
---

Changes since v1:
 - Remove unneeded check if num_regulators != MAX77802_MAX_REGULATORS.
 - Fix .set_suspend_mode handler comment and split regulators ops for
   regulators that behave differently. Suggested by Mark Brown.
 - Use module_platform_driver() instead of having init/exit functions.
   Suggested by Mark Brown.
 - Use the new descriptor-based GPIO interface instead of the deprecated
   integer based GPIO one. Suggested by Mark Brown.
 - Look for "regulators" child node instead of "voltage-regulators" to be
   consistent with other PMIC drivers. Suggested by Mark Brown.

 drivers/mfd/max77802.c       |   1 +
 drivers/regulator/Kconfig    |   9 +
 drivers/regulator/Makefile   |   1 +
 drivers/regulator/max77802.c | 701 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 712 insertions(+)
 create mode 100644 drivers/regulator/max77802.c

Comments

Mark Brown June 16, 2014, 7:25 p.m. UTC | #1
On Mon, Jun 16, 2014 at 08:02:35PM +0200, Javier Martinez Canillas wrote:

> --- a/drivers/mfd/max77802.c
> +++ b/drivers/mfd/max77802.c
> @@ -37,6 +37,7 @@
>  #include <linux/err.h>
>  
>  static const struct mfd_cell max77802_devs[] = {
> +	{ .name = "max77802-pmic", },
>  };
>  
>  static bool max77802_pmic_is_accessible_reg(struct device *dev,

Please don't do things like this, it makes it harder to apply your
series.  Just register all the devices in the MFD when you add the MFD
driver.

> +	default:
> +		pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n",
> +			rdev->desc->name, mode);
> +		return -EINVAL;

dev_warn().

> +static void max77802_copy_reg(struct device *dev, struct regmap *regmap,
> +			      int from_reg, int to_reg)
> +{
> +	int val;
> +	int ret;
> +
> +	if (from_reg == to_reg)
> +		return;
> +
> +	ret = regmap_read(regmap, from_reg, &val);
> +	if (!ret)
> +		ret = regmap_write(regmap, to_reg, val);
> +
> +	if (ret)
> +		dev_warn(dev, "Copy err %d => %d (%d)\n",
> +			 from_reg, to_reg, ret);
> +}

Again, this looks like it should be generic.

> +static int max77802_pmic_probe(struct platform_device *pdev)
> +{

> +	dev_dbg(&pdev->dev, "%s\n", __func__);

This isn't adding anything, just remove it - the core already logs
probes if you want.

> +	config.dev = &pdev->dev;

Are you sure this shouldn't be the MFD?

> +	for (i = 0; i < MAX77802_MAX_REGULATORS; i++) {
> +		struct regulator_dev *rdev;
> +		int id = pdata->regulators[i].id;
> +
> +		config.init_data = pdata->regulators[i].initdata;
> +		config.of_node = pdata->regulators[i].of_node;
> +
> +		max77802->opmode[id] = MAX77802_OPMODE_NORMAL;

Why isn't this being read from the hardware, this may lead to a
configuration change the first time we pay attention?
Javier Martinez Canillas June 17, 2014, 10:49 a.m. UTC | #2
Hello Mark,

Thanks a lot for your feedback.

On 06/16/2014 09:25 PM, Mark Brown wrote:
> On Mon, Jun 16, 2014 at 08:02:35PM +0200, Javier Martinez Canillas wrote:
> 
>> --- a/drivers/mfd/max77802.c
>> +++ b/drivers/mfd/max77802.c
>> @@ -37,6 +37,7 @@
>>  #include <linux/err.h>
>>  
>>  static const struct mfd_cell max77802_devs[] = {
>> +	{ .name = "max77802-pmic", },
>>  };
>>  
>>  static bool max77802_pmic_is_accessible_reg(struct device *dev,
> 
> Please don't do things like this, it makes it harder to apply your
> series.  Just register all the devices in the MFD when you add the MFD
> driver.
> 

Ok, I'll do that. After all mfd core just omits the devices that don't match.

>> +	default:
>> +		pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n",
>> +			rdev->desc->name, mode);
>> +		return -EINVAL;
> 
> dev_warn().
> 

Ok.

>> +static void max77802_copy_reg(struct device *dev, struct regmap *regmap,
>> +			      int from_reg, int to_reg)
>> +{
>> +	int val;
>> +	int ret;
>> +
>> +	if (from_reg == to_reg)
>> +		return;
>> +
>> +	ret = regmap_read(regmap, from_reg, &val);
>> +	if (!ret)
>> +		ret = regmap_write(regmap, to_reg, val);
>> +
>> +	if (ret)
>> +		dev_warn(dev, "Copy err %d => %d (%d)\n",
>> +			 from_reg, to_reg, ret);
>> +}
> 
> Again, this looks like it should be generic.
> 

Yes, I missed this from your previous feedback, sorry about that.

I'll add a regmap_copy_reg() function to drivers/base/regmap/regmap.c instead.

>> +static int max77802_pmic_probe(struct platform_device *pdev)
>> +{
> 
>> +	dev_dbg(&pdev->dev, "%s\n", __func__);
> 
> This isn't adding anything, just remove it - the core already logs
> probes if you want.
> 

Ok.

>> +	config.dev = &pdev->dev;
> 
> Are you sure this shouldn't be the MFD?
> 

I just looked at regulator_register() and saw that it does rdev->dev.parent =
dev, so yes this has to be the MFD.

>> +	for (i = 0; i < MAX77802_MAX_REGULATORS; i++) {
>> +		struct regulator_dev *rdev;
>> +		int id = pdata->regulators[i].id;
>> +
>> +		config.init_data = pdata->regulators[i].initdata;
>> +		config.of_node = pdata->regulators[i].of_node;
>> +
>> +		max77802->opmode[id] = MAX77802_OPMODE_NORMAL;
> 
> Why isn't this being read from the hardware, this may lead to a
> configuration change the first time we pay attention?
> 

The original Chrome OS driver [0] had a "regulator-op-mode" property similar to
"op_mode" in Documentation/devicetree/bindings/regulator/s5m8767-regulator.txt
to specify the operating mode using DT.

But I removed that since I didn't want to have a specific property for what
appears to be a generic need. I wanted to re-post something along the lines of
what was discussed in [1] and add operating mode support to the generic
regulator code.

So, for now I thought it made sense to set the operating mode to normal on
probe() but I'll change it to read from the hardware if that is better.

I guess I should check in the datasheet if a sane default operating mode for
LDOs is expected when the chip is reseted or if this is left undefined and also
if the bootloader already set this.

Best regards,
Javier

[0]:
https://chromium.googlesource.com/chromiumos/third_party/kernel/+/chromeos-3.8/Documentation/devicetree/bindings/mfd/max77xxx.txt
[1]: https://patchwork.kernel.org/patch/1855331/
Mark Brown June 17, 2014, 2:12 p.m. UTC | #3
On Tue, Jun 17, 2014 at 12:49:56PM +0200, Javier Martinez Canillas wrote:
> On 06/16/2014 09:25 PM, Mark Brown wrote:

> >> +	config.dev = &pdev->dev;

> > Are you sure this shouldn't be the MFD?

> I just looked at regulator_register() and saw that it does rdev->dev.parent =
> dev, so yes this has to be the MFD.

Do the regulators manage to get their supplies?

> So, for now I thought it made sense to set the operating mode to normal on
> probe() but I'll change it to read from the hardware if that is better.

Yes, otherwise if the device is configured otherwise then when we change
the configuration we may break something.

> I guess I should check in the datasheet if a sane default operating mode for
> LDOs is expected when the chip is reseted or if this is left undefined and also
> if the bootloader already set this.

You can't do anything based on the particular bootloader you're using in
your current system, this has to work in other systems.
Javier Martinez Canillas June 17, 2014, 4:05 p.m. UTC | #4
Hello Mark,

On 06/17/2014 04:12 PM, Mark Brown wrote:
> On Tue, Jun 17, 2014 at 12:49:56PM +0200, Javier Martinez Canillas wrote:
>> On 06/16/2014 09:25 PM, Mark Brown wrote:
> 
>> >> +	config.dev = &pdev->dev;
> 
>> > Are you sure this shouldn't be the MFD?
> 
>> I just looked at regulator_register() and saw that it does rdev->dev.parent =
>> dev, so yes this has to be the MFD.
>

I noticed that many drivers set config.dev = &pdev->dev. The original Chrome OS
max77xxx driver and max77686 are two examples but others drivers do the same:

$ git grep "config.dev = &pdev->dev" drivers/regulator/ | wc -l
35
$ git grep "config.dev = pdev->dev.parent" drivers/regulator/ | wc -l
11

And also I see that mfd_add_device() calls
devm_regulator_bulk_register_supply_alias(&pdev->dev,...) so I'm confused now
about what the correct device should be...

> Do the regulators manage to get their supplies?
> 

There are no current support in mainline for the devices that use the regulators
in this PMIC so I can't tell you if consumers manage to get their supplies
correctly (e.g: if regulator_dev_lookup succeeds).

But I see in the kernel log that the regulators are registered and configured as
expected [0] and also the driver in the Chrome OS 3.8 kernel is working for sure
and sets config.dev to &pdev->dev instead of the MFD.

>> So, for now I thought it made sense to set the operating mode to normal on
>> probe() but I'll change it to read from the hardware if that is better.
> 
> Yes, otherwise if the device is configured otherwise then when we change
> the configuration we may break something.
> 
>> I guess I should check in the datasheet if a sane default operating mode for
>> LDOs is expected when the chip is reseted or if this is left undefined and also
>> if the bootloader already set this.
> 
> You can't do anything based on the particular bootloader you're using in
> your current system, this has to work in other systems.
> 

Yes, that's why I thought it was a good idea to set to a default operational
mode but I'll change it to read from the hardware instead.

Thanks a lot and best regards,
Javier

[0]: http://pastebin.com/raw.php?i=8yyMXcGD
Lee Jones June 17, 2014, 9:17 p.m. UTC | #5
> The MAX77802 PMIC has 10 high-efficiency Buck and 32 Low-dropout
> (LDO) regulators. This patch adds support for all these regulators
> found on the MAX77802 PMIC and is based on a driver added by Simon
> Glass to the Chrome OS kernel 3.8 tree.
> 
> Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
> ---
> 
> Changes since v1:
>  - Remove unneeded check if num_regulators != MAX77802_MAX_REGULATORS.
>  - Fix .set_suspend_mode handler comment and split regulators ops for
>    regulators that behave differently. Suggested by Mark Brown.
>  - Use module_platform_driver() instead of having init/exit functions.
>    Suggested by Mark Brown.
>  - Use the new descriptor-based GPIO interface instead of the deprecated
>    integer based GPIO one. Suggested by Mark Brown.
>  - Look for "regulators" child node instead of "voltage-regulators" to be
>    consistent with other PMIC drivers. Suggested by Mark Brown.
> 
>  drivers/mfd/max77802.c       |   1 +

Can you remove all of the MFD changes from patches 7, 8 and 9 and
create new one.  That way there's no requirement for any cross
subsystem messiness.

>  drivers/regulator/Kconfig    |   9 +
>  drivers/regulator/Makefile   |   1 +
>  drivers/regulator/max77802.c | 701 +++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 712 insertions(+)
>  create mode 100644 drivers/regulator/max77802.c
Javier Martinez Canillas June 18, 2014, 9:47 a.m. UTC | #6
Hello Lee,

Thanks a lot for your feedback.

On 06/17/2014 11:17 PM, Lee Jones wrote:
>> The MAX77802 PMIC has 10 high-efficiency Buck and 32 Low-dropout
>> (LDO) regulators. This patch adds support for all these regulators
>> found on the MAX77802 PMIC and is based on a driver added by Simon
>> Glass to the Chrome OS kernel 3.8 tree.
>> 
>> Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
>> ---
>> 
>> Changes since v1:
>>  - Remove unneeded check if num_regulators != MAX77802_MAX_REGULATORS.
>>  - Fix .set_suspend_mode handler comment and split regulators ops for
>>    regulators that behave differently. Suggested by Mark Brown.
>>  - Use module_platform_driver() instead of having init/exit functions.
>>    Suggested by Mark Brown.
>>  - Use the new descriptor-based GPIO interface instead of the deprecated
>>    integer based GPIO one. Suggested by Mark Brown.
>>  - Look for "regulators" child node instead of "voltage-regulators" to be
>>    consistent with other PMIC drivers. Suggested by Mark Brown.
>> 
>>  drivers/mfd/max77802.c       |   1 +
> 
> Can you remove all of the MFD changes from patches 7, 8 and 9 and
> create new one.  That way there's no requirement for any cross
> subsystem messiness.
> 

Sure, Mark already suggested the same and I'll do it on the next version.

But there still be some cross-subsystem dependency/messiness since the
regulator, clk and rtc drivers include the mfd max77802 headers that are added
in Patch 6.

So I guess you should either a) take the whole patch-set through your mfd tree
or b) merge the mfd patches and create an immutable branch that can be pulled by
Mark, Mike and Alessandro.

I don't know what's the preferred workflow in these cases...

>>  drivers/regulator/Kconfig    |   9 +
>>  drivers/regulator/Makefile   |   1 +
>>  drivers/regulator/max77802.c | 701 +++++++++++++++++++++++++++++++++++++++++++
>>  4 files changed, 712 insertions(+)
>>  create mode 100644 drivers/regulator/max77802.c
> 

Best regards,
Javier
Lee Jones June 18, 2014, 2:10 p.m. UTC | #7
On Wed, 18 Jun 2014, Javier Martinez Canillas wrote:

> Hello Lee,
> 
> Thanks a lot for your feedback.
> 
> On 06/17/2014 11:17 PM, Lee Jones wrote:
> >> The MAX77802 PMIC has 10 high-efficiency Buck and 32 Low-dropout
> >> (LDO) regulators. This patch adds support for all these regulators
> >> found on the MAX77802 PMIC and is based on a driver added by Simon
> >> Glass to the Chrome OS kernel 3.8 tree.
> >> 
> >> Signed-off-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk>
> >> ---
> >> 
> >> Changes since v1:
> >>  - Remove unneeded check if num_regulators != MAX77802_MAX_REGULATORS.
> >>  - Fix .set_suspend_mode handler comment and split regulators ops for
> >>    regulators that behave differently. Suggested by Mark Brown.
> >>  - Use module_platform_driver() instead of having init/exit functions.
> >>    Suggested by Mark Brown.
> >>  - Use the new descriptor-based GPIO interface instead of the deprecated
> >>    integer based GPIO one. Suggested by Mark Brown.
> >>  - Look for "regulators" child node instead of "voltage-regulators" to be
> >>    consistent with other PMIC drivers. Suggested by Mark Brown.
> >> 
> >>  drivers/mfd/max77802.c       |   1 +
> > 
> > Can you remove all of the MFD changes from patches 7, 8 and 9 and
> > create new one.  That way there's no requirement for any cross
> > subsystem messiness.
> > 
> 
> Sure, Mark already suggested the same and I'll do it on the next version.
> 
> But there still be some cross-subsystem dependency/messiness since the
> regulator, clk and rtc drivers include the mfd max77802 headers that are added
> in Patch 6.

What I should have said was _avoidable_ or _unnecessary_ messiness.

> So I guess you should either a) take the whole patch-set through your mfd tree
> or b) merge the mfd patches and create an immutable branch that can be pulled by
> Mark, Mike and Alessandro.
> 
> I don't know what's the preferred workflow in these cases...

That's fine.  I'm happy to create shared branches when required.

> >>  drivers/regulator/Kconfig    |   9 +
> >>  drivers/regulator/Makefile   |   1 +
> >>  drivers/regulator/max77802.c | 701 +++++++++++++++++++++++++++++++++++++++++++
> >>  4 files changed, 712 insertions(+)
> >>  create mode 100644 drivers/regulator/max77802.c
> > 
> 
> Best regards,
> Javier
Alessandro Zummo June 19, 2014, 1:32 p.m. UTC | #8
On Wed, 18 Jun 2014 15:10:48 +0100
Lee Jones <lee.jones@linaro.org> wrote:

> > So I guess you should either a) take the whole patch-set through your mfd tree
> > or b) merge the mfd patches and create an immutable branch that can be pulled by
> > Mark, Mike and Alessandro.
> > 
> > I don't know what's the preferred workflow in these cases...  
> 
> That's fine.  I'm happy to create shared branches when required.

 mfd tree will be ok.
Mark Brown June 21, 2014, 8:40 p.m. UTC | #9
On Tue, Jun 17, 2014 at 06:05:29PM +0200, Javier Martinez Canillas wrote:
> On 06/17/2014 04:12 PM, Mark Brown wrote:

> >> I just looked at regulator_register() and saw that it does rdev->dev.parent =
> >> dev, so yes this has to be the MFD.

> I noticed that many drivers set config.dev = &pdev->dev. The original Chrome OS
> max77xxx driver and max77686 are two examples but others drivers do the same:

Not all drivers are DT drivers that bother specifying supplies.

> And also I see that mfd_add_device() calls
> devm_regulator_bulk_register_supply_alias(&pdev->dev,...) so I'm confused now
> about what the correct device should be...

Right, but to do that you need to set those aliases up - have you done
so?

> > Do the regulators manage to get their supplies?

> There are no current support in mainline for the devices that use the regulators
> in this PMIC so I can't tell you if consumers manage to get their supplies
> correctly (e.g: if regulator_dev_lookup succeeds).

That's not really relevant here - I'm asking if the regulators get their
own supplies rather than if anything uses them.
Javier Martinez Canillas June 23, 2014, 9:28 a.m. UTC | #10
Hello Mark,

On 06/21/2014 10:40 PM, Mark Brown wrote:
> On Tue, Jun 17, 2014 at 06:05:29PM +0200, Javier Martinez Canillas wrote:
>> On 06/17/2014 04:12 PM, Mark Brown wrote:
> 
>> >> I just looked at regulator_register() and saw that it does rdev->dev.parent =
>> >> dev, so yes this has to be the MFD.
> 
>> I noticed that many drivers set config.dev = &pdev->dev. The original Chrome OS
>> max77xxx driver and max77686 are two examples but others drivers do the same:
> 
> Not all drivers are DT drivers that bother specifying supplies.
> 
>> And also I see that mfd_add_device() calls
>> devm_regulator_bulk_register_supply_alias(&pdev->dev,...) so I'm confused now
>> about what the correct device should be...
> 
> Right, but to do that you need to set those aliases up - have you done
> so?
> 
>> > Do the regulators manage to get their supplies?
> 
>> There are no current support in mainline for the devices that use the regulators
>> in this PMIC so I can't tell you if consumers manage to get their supplies
>> correctly (e.g: if regulator_dev_lookup succeeds).
> 
> That's not really relevant here - I'm asking if the regulators get their
> own supplies rather than if anything uses them.
>

Sorry if I keep misunderstanding your question but the regulators in this PMIC
don't have a parent supply/regulator node.

If by own supplies you mean the regulators power outputs (voltage/current
constraints), then yes, the regulators manage to get their own voltage output
correctly regardless of the value set in config.dev (&pdev->dev or
pdev->dev.parent).

I see in regulator_register() that config.dev is used to set the value of struct
regulator_dev .dev.parent and that is used in two places in regulator core:

1) In regulator_register() to get the regmap if config->regmap is not set.
2) In regulator_dev_lookup() checks if r->dev.parent is set.

For 1) config.regmap is explicitly set to the MFD regmap in max77802 driver so
config.dev is not used in this case and for 2) the value does not matter since
it only checks that it's not NULL.

Having said that, when I was preparing v3 of the patch-set I noticed that
regulator_register() does:

dev = config->dev;
...
rdev->dev.parent = dev;

So I changed to use MFD device instead of &pdev->dev in the version I posted
last week since the MFD device is the regulator parent.

Best regards,
Javier
Mark Brown June 23, 2014, 9:47 a.m. UTC | #11
On Mon, Jun 23, 2014 at 11:28:25AM +0200, Javier Martinez Canillas wrote:
> On 06/21/2014 10:40 PM, Mark Brown wrote:

> > That's not really relevant here - I'm asking if the regulators get their
> > own supplies rather than if anything uses them.

> Sorry if I keep misunderstanding your question but the regulators in this PMIC
> don't have a parent supply/regulator node.

They should, I'm pretty sure the device does actually regulate one
supply into another.
Javier Martinez Canillas June 24, 2014, 4:43 p.m. UTC | #12
Hello Mark,

On 06/23/2014 11:47 AM, Mark Brown wrote:
> On Mon, Jun 23, 2014 at 11:28:25AM +0200, Javier Martinez Canillas wrote:
>> On 06/21/2014 10:40 PM, Mark Brown wrote:
> 
>> > That's not really relevant here - I'm asking if the regulators get their
>> > own supplies rather than if anything uses them.
> 
>> Sorry if I keep misunderstanding your question but the regulators in this PMIC
>> don't have a parent supply/regulator node.
> 
> They should, I'm pretty sure the device does actually regulate one
> supply into another.
> 

Thanks a lot for the clarification. This was not evident to me when I read the
PMIC datasheet and because both the max77xxx Chrome OS 3.8 and mainline max77686
drivers used a simplistic model of the power scheme.

But Doug confirmed to me that some regulators on this PMIC do indeed use others
regulators as a power supply so I'll change this in the next version of the
patch-set.

Best regards,
Javier
diff mbox

Patch

diff --git a/drivers/mfd/max77802.c b/drivers/mfd/max77802.c
index c29fcdd..c9dcbab 100644
--- a/drivers/mfd/max77802.c
+++ b/drivers/mfd/max77802.c
@@ -37,6 +37,7 @@ 
 #include <linux/err.h>
 
 static const struct mfd_cell max77802_devs[] = {
+	{ .name = "max77802-pmic", },
 };
 
 static bool max77802_pmic_is_accessible_reg(struct device *dev,
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 789eb46..17873d3 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -377,6 +377,15 @@  config REGULATOR_MAX77693
 	  and one current regulator 'CHARGER'. This is suitable for
 	  Exynos-4x12 chips.
 
+config REGULATOR_MAX77802
+	tristate "Maxim 77802 regulator"
+	depends on MFD_MAX77802
+	help
+	  This driver controls a Maxim 77802 regulator
+	  via I2C bus. The provided regulator is suitable for
+	  Exynos-5 chips to control various voltages. It includes
+	  support for control of voltage and ramp speed.
+
 config REGULATOR_MC13XXX_CORE
 	tristate
 
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index d461110..2aea4b6 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -51,6 +51,7 @@  obj-$(CONFIG_REGULATOR_MAX8997) += max8997.o
 obj-$(CONFIG_REGULATOR_MAX8998) += max8998.o
 obj-$(CONFIG_REGULATOR_MAX77686) += max77686.o
 obj-$(CONFIG_REGULATOR_MAX77693) += max77693.o
+obj-$(CONFIG_REGULATOR_MAX77802) += max77802.o
 obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
 obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
 obj-$(CONFIG_REGULATOR_MC13XXX_CORE) +=  mc13xxx-regulator-core.o
diff --git a/drivers/regulator/max77802.c b/drivers/regulator/max77802.c
new file mode 100644
index 0000000..e1609b5
--- /dev/null
+++ b/drivers/regulator/max77802.c
@@ -0,0 +1,701 @@ 
+/*
+ * max77802.c - Regulator driver for the Maxim 77802
+ *
+ * Copyright (C) 2013-2014 Google, Inc
+ * Simon Glass <sjg@chromium.org>
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Chiwoong Byun <woong.byun@smasung.com>
+ * Jonghwa Lee <jonghwa3.lee@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This driver is based on max8997.c
+ */
+
+#include <linux/kernel.h>
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/mfd/max77802.h>
+#include <linux/mfd/max77802-private.h>
+
+/* Default ramp delay in case it is not manually set */
+#define MAX77802_RAMP_DELAY		100000		/* uV/us */
+
+#define MAX77802_OPMODE_SHIFT_LDO	6
+#define MAX77802_OPMODE_BUCK234_SHIFT	4
+#define MAX77802_OPMODE_MASK		0x3
+
+#define MAX77802_VSEL_MASK		0x3F
+#define MAX77802_DVS_VSEL_MASK		0xFF
+
+#define MAX77802_RAMP_RATE_MASK_2BIT	0xC0
+#define MAX77802_RAMP_RATE_SHIFT_2BIT	6
+#define MAX77802_RAMP_RATE_MASK_4BIT	0xF0
+#define MAX77802_RAMP_RATE_SHIFT_4BIT	4
+
+/* LDO16, LDO22 and LDO31 are not available on MAX77802 */
+#define MAX77802_MAX_REGULATORS		(MAX77802_REG_MAX - 3)
+
+/* MAX77802 has two register formats: 2-bit and 4-bit */
+static const unsigned int ramp_table_77802_2bit[] = {
+	12500,
+	25000,
+	50000,
+	100000,
+};
+
+static unsigned int ramp_table_77802_4bit[] = {
+	1000,	2000,	3030,	4000,
+	5000,	5880,	7140,	8330,
+	9090,	10000,	11110,	12500,
+	16670,	25000,	50000,	100000,
+};
+
+struct max77802_regulator_prv {
+	int num_regulators;
+	struct regulator_dev *rdev[MAX77802_MAX_REGULATORS];
+	unsigned int opmode[MAX77802_MAX_REGULATORS];
+};
+
+static int max77802_get_opmode_shift(int id)
+{
+	if (id >= MAX77802_LDO1 && id <= MAX77802_LDO35)
+		return MAX77802_OPMODE_SHIFT_LDO;
+	else if (id == MAX77802_BUCK1 || (id >= MAX77802_BUCK5 &&
+					  id <= MAX77802_BUCK10))
+		return 0;
+	else if (id >= MAX77802_BUCK2 && id <= MAX77802_BUCK4)
+		return MAX77802_OPMODE_BUCK234_SHIFT;
+	else
+		return -EINVAL;
+}
+
+/*
+ * Some BUCKS supports Normal[ON/OFF] mode during suspend
+ *
+ * BUCK 1, 6, 2-4, 5, 7-10 (all)
+ *
+ * The other mode (0x02) will make PWRREQ switch between normal
+ * and low power.
+ */
+static int max77802_buck_set_suspend_disable(struct regulator_dev *rdev)
+{
+	unsigned int val = MAX77802_OPMODE_STANDBY;
+	struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	int shift = max77802_get_opmode_shift(id);
+
+	max77802->opmode[id] = val;
+	return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+				  rdev->desc->enable_mask, val << shift);
+}
+
+/*
+ * Some LDOs supports LPM-ON/OFF/Normal-ON mode during suspend state
+ * (Enable Control Logic1 by PWRREQ)
+ *
+ * LDOs 2, 4-19, 22-35.
+ *
+ */
+static int max77802_ldo_set_suspend_mode_logic1(struct regulator_dev *rdev,
+						unsigned int mode)
+{
+	struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	unsigned int val;
+	int shift = max77802_get_opmode_shift(id);
+
+	switch (mode) {
+	case REGULATOR_MODE_IDLE:			/* ON in LP Mode */
+		val = MAX77802_OPMODE_LP;
+		break;
+	case REGULATOR_MODE_NORMAL:			/* ON in Normal Mode */
+		val = MAX77802_OPMODE_NORMAL;
+		break;
+	case REGULATOR_MODE_STANDBY:			/* ON/OFF by PWRREQ */
+		val = MAX77802_OPMODE_STANDBY;
+		break;
+	default:
+		pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n",
+			rdev->desc->name, mode);
+		return -EINVAL;
+	}
+
+	max77802->opmode[rdev_get_id(rdev)] = val;
+	return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+				  rdev->desc->enable_mask, val << shift);
+}
+
+/*
+ * Mode 1 (Output[ON/OFF] by PWRREQ) is not supported on some LDOs
+ * (Enable Control Logic2 by PWRREQ)
+ *
+ * LDOs 1, 20, 21, and 3,
+ *
+ */
+static int max77802_ldo_set_suspend_mode_logic2(struct regulator_dev *rdev,
+						unsigned int mode)
+{
+	struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	unsigned int val;
+	int shift = max77802_get_opmode_shift(id);
+
+	switch (mode) {
+	case REGULATOR_MODE_IDLE:			/* ON in LP Mode */
+		val = MAX77802_OPMODE_LP;
+		break;
+	case REGULATOR_MODE_NORMAL:			/* ON in Normal Mode */
+		val = MAX77802_OPMODE_NORMAL;
+		break;
+	default:
+		pr_warn("%s: regulator_suspend_mode : 0x%x not supported\n",
+			rdev->desc->name, mode);
+		return -EINVAL;
+	}
+
+	max77802->opmode[rdev_get_id(rdev)] = val;
+	return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+				  rdev->desc->enable_mask, val << shift);
+}
+
+static int max77802_enable(struct regulator_dev *rdev)
+{
+	struct max77802_regulator_prv *max77802 = rdev_get_drvdata(rdev);
+	int id = rdev_get_id(rdev);
+	int shift = max77802_get_opmode_shift(id);
+
+	return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+				  rdev->desc->enable_mask,
+				  max77802->opmode[id] << shift);
+}
+
+static int max77802_find_ramp_value(struct regulator_dev *rdev,
+				    const unsigned int limits[], int size,
+				    unsigned int ramp_delay)
+{
+	int i;
+
+	for (i = 0; i < size; i++) {
+		if (ramp_delay <= limits[i])
+			return i;
+	}
+
+	/* Use maximum value for no ramp control */
+	pr_warn("%s: ramp_delay: %d not supported, setting 100000\n",
+		rdev->desc->name, ramp_delay);
+	return size - 1;
+}
+
+/* Used for BUCKs 2-4 */
+static int max77802_set_ramp_delay_2bit(struct regulator_dev *rdev,
+					int ramp_delay)
+{
+	int id = rdev_get_id(rdev);
+	unsigned int ramp_value;
+
+	if (id > MAX77802_BUCK4) {
+		pr_warn("%s: regulator_suspend_mode: ramp delay not supported\n",
+			rdev->desc->name);
+		return -EINVAL;
+	}
+	ramp_value = max77802_find_ramp_value(rdev, ramp_table_77802_2bit,
+				ARRAY_SIZE(ramp_table_77802_2bit), ramp_delay);
+
+	return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+				  MAX77802_RAMP_RATE_MASK_2BIT,
+				  ramp_value << MAX77802_RAMP_RATE_SHIFT_2BIT);
+}
+
+/* For BUCK1, 6 */
+static int max77802_set_ramp_delay_4bit(struct regulator_dev *rdev,
+					    int ramp_delay)
+{
+	unsigned int ramp_value;
+
+	ramp_value = max77802_find_ramp_value(rdev, ramp_table_77802_4bit,
+				ARRAY_SIZE(ramp_table_77802_4bit), ramp_delay);
+
+	return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg,
+				  MAX77802_RAMP_RATE_MASK_4BIT,
+				  ramp_value << MAX77802_RAMP_RATE_SHIFT_4BIT);
+}
+
+/*
+ * LDOs 2, 4-19, 22-35
+ */
+static struct regulator_ops max77802_ldo_ops_logic1 = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.map_voltage		= regulator_map_voltage_linear,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.enable			= max77802_enable,
+	.disable		= regulator_disable_regmap,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
+	.set_voltage_time_sel	= regulator_set_voltage_time_sel,
+	.set_suspend_mode	= max77802_ldo_set_suspend_mode_logic1,
+};
+
+/*
+ * LDOs 1, 20, 21, 3
+ */
+static struct regulator_ops max77802_ldo_ops_logic2 = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.map_voltage		= regulator_map_voltage_linear,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.enable			= max77802_enable,
+	.disable		= regulator_disable_regmap,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
+	.set_voltage_time_sel	= regulator_set_voltage_time_sel,
+	.set_suspend_mode	= max77802_ldo_set_suspend_mode_logic2,
+};
+
+/* BUCKS 1, 6 */
+static struct regulator_ops max77802_buck_16_dvs_ops = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.map_voltage		= regulator_map_voltage_linear,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.enable			= max77802_enable,
+	.disable		= regulator_disable_regmap,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
+	.set_voltage_time_sel	= regulator_set_voltage_time_sel,
+	.set_ramp_delay		= max77802_set_ramp_delay_4bit,
+	.set_suspend_disable	= max77802_buck_set_suspend_disable,
+};
+
+/* BUCKs 2-4, 5, 7-10 */
+static struct regulator_ops max77802_buck_dvs_ops = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.map_voltage		= regulator_map_voltage_linear,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.enable			= max77802_enable,
+	.disable		= regulator_disable_regmap,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
+	.set_voltage_time_sel	= regulator_set_voltage_time_sel,
+	.set_ramp_delay		= max77802_set_ramp_delay_2bit,
+	.set_suspend_disable	= max77802_buck_set_suspend_disable,
+};
+
+/* LDOs 3-7, 9-14, 18-26, 28, 29, 32-34 */
+#define regulator_77802_desc_p_ldo(num, log)	{			\
+	.name		= "LDO"#num,					\
+	.id		= MAX77802_LDO##num,				\
+	.ops		= &max77802_ldo_ops_logic##log,			\
+	.type		= REGULATOR_VOLTAGE,				\
+	.owner		= THIS_MODULE,					\
+	.min_uV		= 800000,					\
+	.uV_step	= 50000,					\
+	.ramp_delay	= MAX77802_RAMP_DELAY,				\
+	.n_voltages	= 1 << 6,					\
+	.vsel_reg	= MAX77802_REG_LDO1CTRL1 + num - 1,		\
+	.vsel_mask	= MAX77802_VSEL_MASK,				\
+	.enable_reg	= MAX77802_REG_LDO1CTRL1 + num - 1,		\
+	.enable_mask	= MAX77802_OPMODE_MASK << MAX77802_OPMODE_SHIFT_LDO, \
+}
+
+/* LDOs 1, 2, 8, 15, 17, 27, 30, 35 */
+#define regulator_77802_desc_n_ldo(num, log)   {			\
+	.name		= "LDO"#num,					\
+	.id		= MAX77802_LDO##num,				\
+	.ops		= &max77802_ldo_ops_logic##log,			\
+	.type		= REGULATOR_VOLTAGE,				\
+	.owner		= THIS_MODULE,					\
+	.min_uV		= 800000,					\
+	.uV_step	= 25000,					\
+	.ramp_delay	= MAX77802_RAMP_DELAY,				\
+	.n_voltages	= 1 << 6,					\
+	.vsel_reg	= MAX77802_REG_LDO1CTRL1 + num - 1,		\
+	.vsel_mask	= MAX77802_VSEL_MASK,				\
+	.enable_reg	= MAX77802_REG_LDO1CTRL1 + num - 1,		\
+	.enable_mask	= MAX77802_OPMODE_MASK << MAX77802_OPMODE_SHIFT_LDO, \
+}
+
+/* BUCKs 1, 6 */
+#define regulator_77802_desc_16_buck(num)	{			\
+	.name		= "BUCK"#num,					\
+	.id		= MAX77802_BUCK##num,				\
+	.ops		= &max77802_buck_16_dvs_ops,			\
+	.type		= REGULATOR_VOLTAGE,				\
+	.owner		= THIS_MODULE,					\
+	.min_uV		= 612500,					\
+	.uV_step	= 6250,						\
+	.ramp_delay	= MAX77802_RAMP_DELAY,				\
+	.n_voltages	= 1 << 8,					\
+	.vsel_reg	= MAX77802_REG_BUCK ## num ## DVS1,		\
+	.vsel_mask	= MAX77802_DVS_VSEL_MASK,			\
+	.enable_reg	= MAX77802_REG_BUCK ## num ## CTRL,		\
+	.enable_mask	= MAX77802_OPMODE_MASK,				\
+}
+
+/* BUCKS 2-4 */
+#define regulator_77802_desc_234_buck(num)	{			\
+	.name		= "BUCK"#num,					\
+	.id		= MAX77802_BUCK##num,				\
+	.ops		= &max77802_buck_dvs_ops,			\
+	.type		= REGULATOR_VOLTAGE,				\
+	.owner		= THIS_MODULE,					\
+	.min_uV		= 600000,					\
+	.uV_step	= 6250,						\
+	.ramp_delay	= MAX77802_RAMP_DELAY,				\
+	.n_voltages	= 0x91,						\
+	.vsel_reg	= MAX77802_REG_BUCK ## num ## DVS1,		\
+	.vsel_mask	= MAX77802_DVS_VSEL_MASK,			\
+	.enable_reg	= MAX77802_REG_BUCK ## num ## CTRL1,		\
+	.enable_mask	= MAX77802_OPMODE_MASK <<			\
+				MAX77802_OPMODE_BUCK234_SHIFT,		\
+}
+
+/* BUCK 5 */
+#define regulator_77802_desc_buck5(num)		{			\
+	.name		= "BUCK"#num,					\
+	.id		= MAX77802_BUCK##num,				\
+	.ops		= &max77802_buck_dvs_ops,			\
+	.type		= REGULATOR_VOLTAGE,				\
+	.owner		= THIS_MODULE,					\
+	.min_uV		= 750000,					\
+	.uV_step	= 50000,					\
+	.ramp_delay	= MAX77802_RAMP_DELAY,				\
+	.n_voltages	= 1 << 6,					\
+	.vsel_reg	= MAX77802_REG_BUCK5OUT,			\
+	.vsel_mask	= MAX77802_VSEL_MASK,				\
+	.enable_reg	= MAX77802_REG_BUCK5CTRL,			\
+	.enable_mask	= MAX77802_OPMODE_MASK,				\
+}
+
+/* BUCKs 7-10 */
+#define regulator_77802_desc_buck7_10(num)	{			\
+	.name		= "BUCK"#num,					\
+	.id		= MAX77802_BUCK##num,				\
+	.ops		= &max77802_buck_dvs_ops,			\
+	.type		= REGULATOR_VOLTAGE,				\
+	.owner		= THIS_MODULE,					\
+	.min_uV		= 750000,					\
+	.uV_step	= 50000,					\
+	.ramp_delay	= MAX77802_RAMP_DELAY,				\
+	.n_voltages	= 1 << 6,					\
+	.vsel_reg	= MAX77802_REG_BUCK7OUT + (num - 7) * 3,	\
+	.vsel_mask	= MAX77802_VSEL_MASK,				\
+	.enable_reg	= MAX77802_REG_BUCK7CTRL + (num - 7) * 3,	\
+	.enable_mask	= MAX77802_OPMODE_MASK,				\
+}
+
+static struct regulator_desc regulators[] = {
+	regulator_77802_desc_n_ldo(1, 2),
+	regulator_77802_desc_n_ldo(2, 1),
+	regulator_77802_desc_p_ldo(3, 2),
+	regulator_77802_desc_p_ldo(4, 1),
+	regulator_77802_desc_p_ldo(5, 1),
+	regulator_77802_desc_p_ldo(6, 1),
+	regulator_77802_desc_p_ldo(7, 1),
+	regulator_77802_desc_n_ldo(8, 1),
+	regulator_77802_desc_p_ldo(9, 1),
+	regulator_77802_desc_p_ldo(10, 1),
+	regulator_77802_desc_p_ldo(11, 1),
+	regulator_77802_desc_p_ldo(12, 1),
+	regulator_77802_desc_p_ldo(13, 1),
+	regulator_77802_desc_p_ldo(14, 1),
+	regulator_77802_desc_n_ldo(15, 1),
+	regulator_77802_desc_n_ldo(17, 1),
+	regulator_77802_desc_p_ldo(18, 1),
+	regulator_77802_desc_p_ldo(19, 1),
+	regulator_77802_desc_p_ldo(20, 2),
+	regulator_77802_desc_p_ldo(21, 2),
+	regulator_77802_desc_p_ldo(23, 1),
+	regulator_77802_desc_p_ldo(24, 1),
+	regulator_77802_desc_p_ldo(25, 1),
+	regulator_77802_desc_p_ldo(26, 1),
+	regulator_77802_desc_n_ldo(27, 1),
+	regulator_77802_desc_p_ldo(28, 1),
+	regulator_77802_desc_p_ldo(29, 1),
+	regulator_77802_desc_n_ldo(30, 1),
+	regulator_77802_desc_p_ldo(32, 1),
+	regulator_77802_desc_p_ldo(33, 1),
+	regulator_77802_desc_p_ldo(34, 1),
+	regulator_77802_desc_n_ldo(35, 1),
+	regulator_77802_desc_16_buck(1),
+	regulator_77802_desc_234_buck(2),
+	regulator_77802_desc_234_buck(3),
+	regulator_77802_desc_234_buck(4),
+	regulator_77802_desc_buck5(5),
+	regulator_77802_desc_16_buck(6),
+	regulator_77802_desc_buck7_10(7),
+	regulator_77802_desc_buck7_10(8),
+	regulator_77802_desc_buck7_10(9),
+	regulator_77802_desc_buck7_10(10),
+};
+
+#ifdef CONFIG_OF
+static int max77802_pmic_dt_parse_pdata(struct platform_device *pdev,
+					struct max77802_platform_data *pdata)
+{
+	struct max77802_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+	struct device_node *pmic_np, *regulators_np;
+	struct max77802_regulator_data *rdata;
+	struct of_regulator_match rmatch;
+	unsigned int i;
+
+	pmic_np = iodev->dev->of_node;
+	regulators_np = of_get_child_by_name(pmic_np, "regulators");
+	if (!regulators_np) {
+		dev_err(&pdev->dev, "could not find regulators sub-node\n");
+		return -EINVAL;
+	}
+
+	pdata->num_regulators = ARRAY_SIZE(regulators);
+	rdata = devm_kzalloc(&pdev->dev, sizeof(*rdata) *
+			     pdata->num_regulators, GFP_KERNEL);
+	if (!rdata) {
+		of_node_put(regulators_np);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < pdata->num_regulators; i++) {
+		rmatch.name = regulators[i].name;
+		rmatch.init_data = NULL;
+		rmatch.of_node = NULL;
+		if (of_regulator_match(&pdev->dev, regulators_np, &rmatch,
+				       1) != 1) {
+			dev_warn(&pdev->dev, "No matching regulator for '%s'\n",
+				 rmatch.name);
+			continue;
+		}
+		rdata[i].initdata = rmatch.init_data;
+		rdata[i].of_node = rmatch.of_node;
+		rdata[i].id = regulators[i].id;
+	}
+
+	pdata->regulators = rdata;
+	of_node_put(regulators_np);
+
+	return 0;
+}
+#else
+static int max77802_pmic_dt_parse_pdata(struct platform_device *pdev,
+					struct max77802_platform_data *pdata)
+{
+	return 0;
+}
+#endif /* CONFIG_OF */
+
+/**
+ * max77802_setup_gpios - init DVS-related GPIOs
+ *
+ * This function claims / initalizations GPIOs related to DVS if they are
+ * defined. This may have the effect of switching voltages if the
+ * pdata->buck_default_idx does not match the boot time state of pins.
+ */
+static int max77802_setup_gpios(struct device *dev,
+				struct max77802_platform_data *pdata)
+{
+	int buck_default_idx = pdata->buck_default_idx;
+	int ret;
+	int i;
+
+	/* Set all SELB high to avoid glitching while DVS is changing */
+	for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_selb); i++) {
+		struct gpio_desc *gpio = pdata->buck_gpio_selb[i];
+
+		/* OK if some GPIOs aren't defined */
+		if (IS_ERR(gpio))
+			continue;
+
+		ret = gpiod_direction_output_raw(gpio, 1);
+		if (ret) {
+			dev_err(dev, "can't set gpio[%d] dir: %d\n", i, ret);
+			return ret;
+		}
+	}
+
+	/* Set our initial setting */
+	for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_dvs); i++) {
+		struct gpio_desc *gpio = pdata->buck_gpio_dvs[i];
+
+		/* OK if some GPIOs aren't defined */
+		if (IS_ERR(gpio))
+			continue;
+
+		/* If a GPIO is valid, set it */
+		gpiod_direction_output(gpio, (buck_default_idx >> i) & 1);
+		if (ret) {
+			dev_err(dev, "can't set gpio[%d]: dir %d\n", i, ret);
+			return ret;
+		}
+	}
+
+	/* Now set SELB low to take effect */
+	for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_selb); i++) {
+		struct gpio_desc *gpio = pdata->buck_gpio_selb[i];
+
+		if (!IS_ERR(gpio))
+			gpiod_set_value(gpio, 0);
+	}
+
+	return 0;
+}
+
+/**
+ * max77802_read_gpios - read the current state of the dvs GPIOs
+ *
+ * We call this function at bootup to detect what slot the firmware was
+ * using for the DVS GPIOs.  That way we can properly preserve the firmware's
+ * voltage settings
+ */
+static int max77802_read_gpios(struct max77802_platform_data *pdata)
+{
+	int buck_default_idx = pdata->buck_default_idx;
+	int result = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pdata->buck_gpio_dvs); i++) {
+		struct gpio_desc *gpio = pdata->buck_gpio_dvs[i];
+
+		/* OK if some GPIOs aren't defined; we'll use default */
+		if (IS_ERR(gpio)) {
+			result |= buck_default_idx & (1 << i);
+			continue;
+		}
+
+		if (gpiod_get_value_cansleep(gpio))
+			result |= 1 << i;
+	}
+
+	return result;
+}
+
+static inline bool max77802_is_dvs_buck(int id)
+{
+	/* on 77802 bucks 1-4, 6 are */
+	return ((id >= MAX77802_BUCK1 && id <= MAX77802_BUCK4) ||
+		(id == MAX77802_BUCK6));
+}
+
+static void max77802_copy_reg(struct device *dev, struct regmap *regmap,
+			      int from_reg, int to_reg)
+{
+	int val;
+	int ret;
+
+	if (from_reg == to_reg)
+		return;
+
+	ret = regmap_read(regmap, from_reg, &val);
+	if (!ret)
+		ret = regmap_write(regmap, to_reg, val);
+
+	if (ret)
+		dev_warn(dev, "Copy err %d => %d (%d)\n",
+			 from_reg, to_reg, ret);
+}
+
+static int max77802_pmic_probe(struct platform_device *pdev)
+{
+	struct max77802_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+	struct max77802_platform_data *pdata = dev_get_platdata(iodev->dev);
+	struct max77802_regulator_prv *max77802;
+	int i, ret = 0;
+	struct regulator_config config = { };
+	unsigned int reg;
+	int buck_default_idx;
+	int buck_old_idx;
+
+	dev_dbg(&pdev->dev, "%s\n", __func__);
+
+	/* This is allocated by the MFD driver */
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data found for regulator\n");
+		return -ENODEV;
+	}
+
+	max77802 = devm_kzalloc(&pdev->dev,
+				sizeof(struct max77802_regulator_prv),
+				GFP_KERNEL);
+	if (!max77802)
+		return -ENOMEM;
+
+	if (iodev->dev->of_node) {
+		ret = max77802_pmic_dt_parse_pdata(pdev, pdata);
+		if (ret)
+			return ret;
+	}
+
+	config.dev = &pdev->dev;
+	config.regmap = iodev->regmap;
+	config.driver_data = max77802;
+	platform_set_drvdata(pdev, max77802);
+
+	buck_default_idx = pdata->buck_default_idx;
+	buck_old_idx = max77802_read_gpios(pdata);
+
+	for (i = 0; i < MAX77802_MAX_REGULATORS; i++) {
+		struct regulator_dev *rdev;
+		int id = pdata->regulators[i].id;
+
+		config.init_data = pdata->regulators[i].initdata;
+		config.of_node = pdata->regulators[i].of_node;
+
+		max77802->opmode[id] = MAX77802_OPMODE_NORMAL;
+
+		if (max77802_is_dvs_buck(id)) {
+			/* Try to copy over data so we keep firmware settings */
+			reg = regulators[i].vsel_reg;
+			max77802_copy_reg(&pdev->dev, iodev->regmap,
+					  reg + buck_old_idx,
+					  reg + buck_default_idx);
+			regulators[i].vsel_reg += buck_default_idx;
+		}
+
+		rdev = devm_regulator_register(&pdev->dev,
+						&regulators[i], &config);
+		if (IS_ERR(rdev)) {
+			dev_err(&pdev->dev,
+				"regulator init failed for %d\n", i);
+			return PTR_ERR(rdev);
+		}
+	}
+
+	ret = max77802_setup_gpios(&pdev->dev, pdata);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct platform_device_id max77802_pmic_id[] = {
+	{"max77802-pmic", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(platform, max77802_pmic_id);
+
+static struct platform_driver max77802_pmic_driver = {
+	.driver = {
+		.name = "max77802-pmic",
+		.owner = THIS_MODULE,
+	},
+	.probe = max77802_pmic_probe,
+	.id_table = max77802_pmic_id,
+};
+
+module_platform_driver(max77802_pmic_driver);
+
+MODULE_DESCRIPTION("MAXIM 77802 Regulator Driver");
+MODULE_AUTHOR("Simon Glass <sjg@chromium.org>");
+MODULE_LICENSE("GPL");