linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC] Add gpio based voltage switching regulator
@ 2011-09-26  6:52 Heiko Stübner
  2011-09-26 12:51 ` Mark Brown
  0 siblings, 1 reply; 5+ messages in thread
From: Heiko Stübner @ 2011-09-26  6:52 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood; +Cc: linux-kernel

This patch adds support for regulators that can switch between
two voltage levels by setting a gpio.

An example such a regulator is the TI-tps65024x regulator family.
They contain 4 fixed and 1 runtime-switchable voltage regulators.
The switch can be used by a cpufreq driver to set the core
voltage on some SoCs, for example the S3C2416/2450.

Handling of set_voltage calls with a range that fits neither the
low nor the high voltage is determined by the inbetween_high
option. When set to 1 the high voltage is used, on 0 the low
voltage is used and on -EINVAL an error is returned, disallowing
the usage of the voltage range.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>

---
I'm not hung up on the "inbetween handling", in fact at the moment it
seems to not belong there. But I'm not sure on how to handle
frequency tables like
	[0] = { 1000000, 1150000 },
	[1] = { 1150000, 1250000 },
	[2] = { 1250000, 1350000 },
I.e. the middle value should use the voltage in 2 for switch regulators,
but the defined value for more intelligent ones.

 drivers/regulator/Kconfig        |    8 +
 drivers/regulator/Makefile       |    1 +
 drivers/regulator/switch.c       |  320 ++++++++++++++++++++++++++++++++++++++
 include/linux/regulator/switch.h |   65 ++++++++
 4 files changed, 394 insertions(+), 0 deletions(-)

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index c7fd2c0..8510d8c 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -45,6 +45,14 @@ config REGULATOR_FIXED_VOLTAGE
 	  useful for systems which use a combination of software
 	  managed regulators and simple non-configurable regulators.
 
+config REGULATOR_SWITCHED_VOLTAGE
+	tristate "Switched voltage regulator support"
+	help
+	  This driver provides support for regulators that can switch
+	  between two voltages by setting a gpio,
+	  useful for systems which use a combination of software
+	  managed regulators and simple non-configurable regulators.
+
 config REGULATOR_VIRTUAL_CONSUMER
 	tristate "Virtual regulator consumer support"
 	help
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 040d5aa..5a8f4ee 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -5,6 +5,7 @@
 
 obj-$(CONFIG_REGULATOR) += core.o dummy.o
 obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
+obj-$(CONFIG_REGULATOR_SWITCHED_VOLTAGE) += switch.o
 obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
 obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o
 
diff --git a/drivers/regulator/switch.c b/drivers/regulator/switch.c
new file mode 100644
index 0000000..012e3e9
--- /dev/null
+++ b/drivers/regulator/switch.c
@@ -0,0 +1,320 @@
+/*
+ * switched.c
+ *
+ * Copyright 2011 Heiko Stuebner <heiko@sntech.de>
+ *
+ * based on fixed.c
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Roger Quadros <ext-roger.quadros@nokia.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 is useful for systems with mixed controllable and
+ * non-controllable regulators, as well as for allowing testing on
+ * systems with no controllable regulators.
+ */
+
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/switch.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+struct switched_voltage_data {
+	struct regulator_desc desc;
+	struct regulator_dev *dev;
+
+	int microvolts_high;
+	int microvolts_low;
+
+	unsigned startup_delay;
+	int inbetween_high;
+
+	int gpio_enable;
+	int gpio_switch;
+
+	bool switch_high;
+	bool enable_high;
+
+	bool is_switched;
+	bool is_enabled;
+};
+
+static int switched_voltage_is_enabled(struct regulator_dev *dev)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	return data->is_enabled;
+}
+
+static int switched_voltage_enable(struct regulator_dev *dev)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	if (gpio_is_valid(data->gpio_enable)) {
+		gpio_set_value_cansleep(data->gpio_enable, data->enable_high);
+		data->is_enabled = true;
+	}
+
+	return 0;
+}
+
+static int switched_voltage_disable(struct regulator_dev *dev)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	if (gpio_is_valid(data->gpio_enable)) {
+		gpio_set_value_cansleep(data->gpio_enable, !data->enable_high);
+		data->is_enabled = false;
+	}
+
+	return 0;
+}
+
+static int switched_voltage_enable_time(struct regulator_dev *dev)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	return data->startup_delay;
+}
+
+static int switched_voltage_get_voltage(struct regulator_dev *dev)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	return data->is_switched ? data->microvolts_high : data->microvolts_low;
+}
+
+static int switched_voltage_set_voltage(struct regulator_dev *dev,
+					int min_uV, int max_uV,
+					unsigned *selector)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	if (max_uV < data->microvolts_low || min_uV > data->microvolts_high)
+		return -EINVAL;
+
+	if (min_uV <= data->microvolts_low && max_uV >= data->microvolts_low) {
+		gpio_set_value_cansleep(data->gpio_switch, !data->switch_high);
+		data->is_switched = false;
+		return 0;
+	}
+
+	if (min_uV <= data->microvolts_high && max_uV >= data->microvolts_high) {
+		gpio_set_value_cansleep(data->gpio_switch, data->switch_high);
+		data->is_switched = true;
+		return 0;
+	}
+
+	/* target range does not match min_uV or max_uV, inbetween handling */
+	if (data->inbetween_high < 0)
+		return -EINVAL;
+
+	gpio_set_value_cansleep(data->gpio_switch,
+				data->inbetween_high ? data->switch_high
+						     : !data->switch_high);
+	data->is_switched = data->inbetween_high ? true : false;
+
+	return 0;
+}
+
+static int switched_voltage_list_voltage(struct regulator_dev *dev,
+				      unsigned selector)
+{
+	struct switched_voltage_data *data = rdev_get_drvdata(dev);
+
+	switch (selector) {
+	case 0:
+		return data->microvolts_low;
+		break;
+	case 1:
+		return data->microvolts_high;
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+}
+
+static struct regulator_ops switched_voltage_ops = {
+	.is_enabled = switched_voltage_is_enabled,
+	.enable = switched_voltage_enable,
+	.disable = switched_voltage_disable,
+	.enable_time = switched_voltage_enable_time,
+	.get_voltage = switched_voltage_get_voltage,
+	.set_voltage = switched_voltage_set_voltage,
+	.list_voltage = switched_voltage_list_voltage,
+};
+
+static int __devinit reg_switched_voltage_probe(struct platform_device *pdev)
+{
+	struct switched_voltage_config *config = pdev->dev.platform_data;
+	struct switched_voltage_data *drvdata;
+	int ret;
+
+	if (!config->init_data) {
+		dev_err(&pdev->dev, "regulator init_data missing\n");
+		return -EINVAL;
+	}
+
+	drvdata = kzalloc(sizeof(struct switched_voltage_data), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate device data\n");
+		return -ENOMEM;
+	}
+
+	drvdata->desc.name = kstrdup(config->supply_name, GFP_KERNEL);
+	if (drvdata->desc.name == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate supply name\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	drvdata->desc.type = REGULATOR_VOLTAGE;
+	drvdata->desc.owner = THIS_MODULE;
+	drvdata->desc.ops = &switched_voltage_ops;
+	drvdata->desc.n_voltages = 2;
+
+	drvdata->gpio_enable = config->gpio_enable;
+	drvdata->startup_delay = config->startup_delay;
+	drvdata->inbetween_high = config->inbetween_high;
+
+	drvdata->microvolts_low = config->init_data->constraints.min_uV;
+	drvdata->microvolts_high = config->init_data->constraints.max_uV;
+
+	if (gpio_is_valid(config->gpio_enable)) {
+		drvdata->enable_high = config->enable_high;
+
+		ret = gpio_request(config->gpio_enable, config->supply_name);
+		if (ret) {
+			dev_err(&pdev->dev,
+			   "Could not obtain regulator enable GPIO %d: %d\n",
+							config->gpio_enable, ret);
+			goto err_name;
+		}
+
+		/* set output direction without changing state
+		 * to prevent glitch
+		 */
+		drvdata->is_enabled = config->enabled_at_boot;
+		ret = drvdata->is_enabled ?
+				config->enable_high : !config->enable_high;
+
+		ret = gpio_direction_output(config->gpio_enable, ret);
+		if (ret) {
+			dev_err(&pdev->dev,
+			   "Could not configure regulator enable GPIO %d direction: %d\n",
+							config->gpio_enable, ret);
+			goto err_gpio;
+		}
+
+	} else {
+		/* Regulator without GPIO control is considered
+		 * always enabled
+		 */
+		drvdata->is_enabled = true;
+	}
+
+	drvdata->switch_high = config->switch_high;
+
+	ret = gpio_request(config->gpio_switch, config->supply_name);
+	if (ret) {
+		dev_err(&pdev->dev,
+		   "Could not obtain regulator switch GPIO %d: %d\n",
+						config->gpio_switch, ret);
+		goto err_gpio;
+	}
+
+	/* set output direction without changing state to prevent glitch */
+	drvdata->is_switched = config->switched_at_boot;
+	ret = drvdata->is_switched ?
+			config->switch_high : !config->switch_high;
+
+	ret = gpio_direction_output(config->gpio_switch, ret);
+	if (ret) {
+		dev_err(&pdev->dev,
+		   "Could not configure regulator switch GPIO %d direction: %d\n",
+						config->gpio_switch, ret);
+		goto err_switch;
+	}
+
+	drvdata->dev = regulator_register(&drvdata->desc, &pdev->dev,
+					  config->init_data, drvdata);
+	if (IS_ERR(drvdata->dev)) {
+		ret = PTR_ERR(drvdata->dev);
+		dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret);
+		goto err_switch;
+	}
+
+	platform_set_drvdata(pdev, drvdata);
+
+	dev_dbg(&pdev->dev, "%s supplying %d-%duV\n", drvdata->desc.name,
+		drvdata->microvolts_low, drvdata->microvolts_high);
+
+	return 0;
+
+err_switch:
+	gpio_free(config->gpio_switch);
+err_gpio:
+	if (gpio_is_valid(config->gpio_enable))
+		gpio_free(config->gpio_enable);
+err_name:
+	kfree(drvdata->desc.name);
+err:
+	kfree(drvdata);
+	return ret;
+}
+
+static int __devexit reg_switched_voltage_remove(struct platform_device *pdev)
+{
+	struct switched_voltage_data *drvdata = platform_get_drvdata(pdev);
+
+	regulator_unregister(drvdata->dev);
+
+	gpio_free(drvdata->gpio_switch);
+
+	if (gpio_is_valid(drvdata->gpio_enable))
+		gpio_free(drvdata->gpio_enable);
+
+	kfree(drvdata->desc.name);
+	kfree(drvdata);
+
+	return 0;
+}
+
+static struct platform_driver regulator_switched_voltage_driver = {
+	.probe		= reg_switched_voltage_probe,
+	.remove		= __devexit_p(reg_switched_voltage_remove),
+	.driver		= {
+		.name		= "reg-switched-voltage",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init regulator_switched_voltage_init(void)
+{
+	return platform_driver_register(&regulator_switched_voltage_driver);
+}
+subsys_initcall(regulator_switched_voltage_init);
+
+static void __exit regulator_switched_voltage_exit(void)
+{
+	platform_driver_unregister(&regulator_switched_voltage_driver);
+}
+module_exit(regulator_switched_voltage_exit);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("switched voltage regulator");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:reg-switched-voltage");
diff --git a/include/linux/regulator/switch.h b/include/linux/regulator/switch.h
new file mode 100644
index 0000000..cd013e9
--- /dev/null
+++ b/include/linux/regulator/switch.h
@@ -0,0 +1,65 @@
+/*
+ * switch.h
+ *
+ * based on fixed.h
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Roger Quadros <ext-roger.quadros@nokia.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.
+ */
+
+#ifndef __REGULATOR_SWITCHED_H
+#define __REGULATOR_SWITCHED_H
+
+struct regulator_init_data;
+
+/**
+ * struct switched_voltage_config - fixed_voltage_config structure
+ * @supply_name:	Name of the regulator supply
+ * @gpio_enable:	GPIO to use for enable control
+ * 			set to -EINVAL if not used
+ * @gpio_switch:	GPIO to use for switching voltages
+ * @startup_delay:	Start-up time in microseconds
+ * @enable_high:	Polarity of enable GPIO
+ *			1 = Active high, 0 = Active low
+ * @switch_high:	Polarity of witch GPIO
+ *			1 = max_voltage as high, 0 = max_voltage as low
+ * @enabled_at_boot:	Whether regulator has been enabled at
+ * 			boot or not. 1 = Yes, 0 = No
+ * 			This is used to keep the regulator at
+ * 			the default state
+ * @switched_at_boot:	Whether voltage is high at boot or not.
+ *			1 = Yes, 0 = No
+ * 			This is used to keep the regulator at
+ * 			the default state
+ * @inbetween_high:	How to handle voltage request that match neither
+ * 			min_uV nor max_uV. Set to -EINVAL to return error or
+ *			1 = set to max_uV, 0 = set to min_uV
+ * @init_data:		regulator_init_data
+ *
+ * This structure contains fixed voltage regulator configuration
+ * information that must be passed by platform code to the fixed
+ * voltage regulator driver.
+ */
+struct switched_voltage_config {
+	const char *supply_name;
+	int gpio_enable;
+	int gpio_switch;
+	unsigned startup_delay;
+	unsigned enable_high:1;
+	unsigned switch_high:1;
+	unsigned enabled_at_boot:1;
+	unsigned switched_at_boot:1;
+	int inbetween_high;
+	struct regulator_init_data *init_data;
+};
+
+#endif
-- 
tg: (c6a389f..) topic/drivers/reg-switch (depends on: master)

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

* Re: [RFC] Add gpio based voltage switching regulator
  2011-09-26  6:52 [RFC] Add gpio based voltage switching regulator Heiko Stübner
@ 2011-09-26 12:51 ` Mark Brown
  2011-09-28  8:02   ` Heiko Stübner
  0 siblings, 1 reply; 5+ messages in thread
From: Mark Brown @ 2011-09-26 12:51 UTC (permalink / raw)
  To: Heiko Stübner; +Cc: Liam Girdwood, linux-kernel

On Mon, Sep 26, 2011 at 08:52:18AM +0200, Heiko Stübner wrote:

> This patch adds support for regulators that can switch between
> two voltage levels by setting a gpio.

This really should be scalable beyond two voltages, or at least prepared
for that possibility.

> Handling of set_voltage calls with a range that fits neither the
> low nor the high voltage is determined by the inbetween_high
> option. When set to 1 the high voltage is used, on 0 the low
> voltage is used and on -EINVAL an error is returned, disallowing
> the usage of the voltage range.

No, don't do this.  If you can't set the requested voltage then fail.
This is not in the least bit driver specific.

> I'm not hung up on the "inbetween handling", in fact at the moment it
> seems to not belong there. But I'm not sure on how to handle
> frequency tables like
> 	[0] = { 1000000, 1150000 },
> 	[1] = { 1150000, 1250000 },
> 	[2] = { 1250000, 1350000 },
> I.e. the middle value should use the voltage in 2 for switch regulators,
> but the defined value for more intelligent ones.

I'm afraid I can't parse this, sorry.

> @@ -0,0 +1,320 @@
> +/*
> + * switched.c

This needs a better name.

Otherwise this looks good, the main thing is the ability to support more
voltages.

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

* Re: [RFC] Add gpio based voltage switching regulator
  2011-09-26 12:51 ` Mark Brown
@ 2011-09-28  8:02   ` Heiko Stübner
  2011-09-28  9:03     ` Mark Brown
  0 siblings, 1 reply; 5+ messages in thread
From: Heiko Stübner @ 2011-09-28  8:02 UTC (permalink / raw)
  To: Mark Brown; +Cc: Liam Girdwood, linux-kernel

Hi Mark,

thanks for your comments.

Am Montag, 26. September 2011, 14:51:55 schrieb Mark Brown:
> On Mon, Sep 26, 2011 at 08:52:18AM +0200, Heiko Stübner wrote:
> > This patch adds support for regulators that can switch between
> > two voltage levels by setting a gpio.
> 
> This really should be scalable beyond two voltages, or at least prepared
> for that possibility.
I think I've come up with a solution for this but would like to make sure I'm
on the right track. So my general idea is to define the platform_data like

static struct switched_voltage_config es600_dcdc3 = {
[...]
	.switch_gpios = {
		GPIO1, /* bit 0 in matrix */
		GPIO2, /* bit 1 in matrix */
	},
	.nr_switch_gpios = 2,
	.switch_matrix = {
		{ .microvolts = 1000000, .gpio_state = (0 << 1) | (0 << 0) },
		{ .microvolts = 1100000, .gpio_state = (0 << 1) | (1 << 0) },
		{ .microvolts = 1200000, .gpio_state = (1 << 1) | (0 << 0) },
		{ .microvolts = 1300000, .gpio_state = (1 << 1) | (1 << 0) },
	},
	.nr_switch_matrix = 4,
[...]
};

i.e. each voltage keeps the target gpio state in a bit-field which makes the
mapping current_state -> voltage in get_voltage very easy.

set_voltage would then look like

	gpio_state = get_gpio_state_for_voltage(microvolts);
	for(ptr = 0; ptr < nr_switch_gpios; ptr++) {
		state = ( gpio_state & (1 << ptr) ) >> ptr;
		gpio_set_value(switch_gpios[ptr], state);
	}

i.e. simply extracting the target setting from the bitfield for each gpio.

If using integers for the state, this would scale up to 16 gpios and
voltage-permutations thereof.

Reasonable?

> > Handling of set_voltage calls with a range that fits neither the
> > low nor the high voltage is determined by the inbetween_high
> > option. When set to 1 the high voltage is used, on 0 the low
> > voltage is used and on -EINVAL an error is returned, disallowing
> > the usage of the voltage range.
> 
> No, don't do this.  If you can't set the requested voltage then fail.
> This is not in the least bit driver specific.
ok

> > @@ -0,0 +1,320 @@
> > +/*
> > + * switched.c
> 
> This needs a better name.
"gpio-regulator"?

I'm quite open to suggestions :-)

> Otherwise this looks good, the main thing is the ability to support more
> voltages.

Heiko

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

* Re: [RFC] Add gpio based voltage switching regulator
  2011-09-28  8:02   ` Heiko Stübner
@ 2011-09-28  9:03     ` Mark Brown
  2011-09-30 10:59       ` [RFC v2] Add gpio-regulator driver Heiko Stübner
  0 siblings, 1 reply; 5+ messages in thread
From: Mark Brown @ 2011-09-28  9:03 UTC (permalink / raw)
  To: Heiko Stübner; +Cc: Liam Girdwood, linux-kernel

On Wed, Sep 28, 2011 at 10:02:54AM +0200, Heiko Stübner wrote:
> 	.switch_gpios = {
> 		GPIO1, /* bit 0 in matrix */
> 		GPIO2, /* bit 1 in matrix */
> 	},
> 	.nr_switch_gpios = 2,

This seems like a good plan.

> > This needs a better name.
> "gpio-regulator"?

> I'm quite open to suggestions :-)

That would be better, yes.

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

* [RFC v2] Add gpio-regulator driver
  2011-09-28  9:03     ` Mark Brown
@ 2011-09-30 10:59       ` Heiko Stübner
  0 siblings, 0 replies; 5+ messages in thread
From: Heiko Stübner @ 2011-09-30 10:59 UTC (permalink / raw)
  To: Mark Brown; +Cc: Liam Girdwood, linux-kernel, heiko

As suggested by Mark Brown, I created a more generalised gpio-regulator driver.

It is definitly not finished - it just implements my idea and compiles - real world
testing and fixing follows this weekend. If possible I'd just like to make sure I'm
on the right track this time :-) .

As the mechanisms of setting gpio states don't change with the regulator type
it is also possible to use it as a current regulator.
Therefore it could replace i.e. the bq24022/bq2407x driver later on.

Platform data for the 3 gpio controlled regulators I know of could look like:
struct gpio_regulator_config bq24022 = {
	.supply_name = "bq24022",

	.gpios = {
		GPIO_ISET2,
	},
	.nr_gpios = 1,

	.gpio_states = {
		{ .value = 100000, gpios = (0 << 0) },
		{ .value = 500000, gpios = (1 << 0) },
	},
	.nr_gpio_states = 2,
	.state_at_boot = (0 << 0),
	.type = REGULATOR_CURRENT,
};

struct gpio_regulator_config bq24075 = {
	.supply_name = "bq24075",

	.gpios = {
		GPIO_EN1,
		GPIO_EN2,
	},
	.nr_gpios = 2,

	.gpio_states = {
		{ .value = 100000, gpios = (0 << 1) | (0 << 0) },
		{ .value = 500000, gpios = (0 << 1) | (1 << 0) },
		{ .value = 975000, gpios = (1 << 1) | (0 << 0) },
	},
	.nr_gpio_states = 3,
	.state_at_boot = (0 << 1)|(0 << 0),
	.type = REGULATOR_CURRENT,
};

struct gpio_regulator_config tps650240 = {
	.supply_name = "tps650240-dcdc3",

	.gpios = {
		GPIO_DEFDCDC3,
	},
	.nr_gpios = 1,

	.gpio_states = {
		{ .value = 1000000, gpios = (0 << 0) },
		{ .value = 1300000, gpios = (1 << 0) },
	},
	.nr_gpio_states = 2,
	.state_at_boot = (1 << 0),
	.type = REGULATOR_VOLTAGE,
};

Comments?

---
 drivers/regulator/Kconfig                |    6 +
 drivers/regulator/Makefile               |    1 +
 drivers/regulator/gpio-regulator.c       |  361 ++++++++++++++++++++++++++++++
 include/linux/regulator/gpio-regulator.h |   84 +++++++
 4 files changed, 452 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/gpio-regulator.c
 create mode 100644 include/linux/regulator/gpio-regulator.h

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 8510d8c..57e2ccc 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -72,6 +72,12 @@ config REGULATOR_USERSPACE_CONSUMER
 
           If unsure, say no.
 
+config REGULATOR_GPIO
+	tristate "gpio regulator support"
+	help
+	  This driver provides support for regulators that can be
+	  controlled by setting gpios.
+
 config REGULATOR_BQ24022
 	tristate "TI bq24022 Dual Input 1-Cell Li-Ion Charger IC"
 	help
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 5a8f4ee..44dcac3 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_REGULATOR_SWITCHED_VOLTAGE) += switch.o
 obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
 obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o
 
+obj-$(CONFIG_REGULATOR_GPIO) += gpio-regulator.o
 obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o
 obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o
 obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o
diff --git a/drivers/regulator/gpio-regulator.c b/drivers/regulator/gpio-regulator.c
new file mode 100644
index 0000000..ed7ed40
--- /dev/null
+++ b/drivers/regulator/gpio-regulator.c
@@ -0,0 +1,363 @@
+/*
+ * gpio-regulator.c
+ *
+ * Copyright 2011 Heiko Stuebner <heiko@sntech.de>
+ *
+ * based on fixed.c
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Roger Quadros <ext-roger.quadros@nokia.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 is useful for systems with mixed controllable and
+ * non-controllable regulators, as well as for allowing testing on
+ * systems with no controllable regulators.
+ */
+
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/gpio-regulator.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+struct gpio_regulator_data {
+	struct regulator_desc desc;
+	struct regulator_dev *dev;
+
+	int enable_gpio;
+	bool enable_high;
+	bool is_enabled;
+	unsigned startup_delay;
+
+	int *gpios;
+	int nr_gpios;
+
+	struct gpio_regulator_state *gpio_states;
+	int nr_gpio_states;
+
+	int state;
+};
+
+static int gpio_regulator_is_enabled(struct regulator_dev *dev)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+
+	return data->is_enabled;
+}
+
+static int gpio_regulator_enable(struct regulator_dev *dev)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+
+	if (gpio_is_valid(data->enable_gpio)) {
+		gpio_set_value_cansleep(data->enable_gpio, data->enable_high);
+		data->is_enabled = true;
+	}
+
+	return 0;
+}
+
+static int gpio_regulator_disable(struct regulator_dev *dev)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+
+	if (gpio_is_valid(data->enable_gpio)) {
+		gpio_set_value_cansleep(data->enable_gpio, !data->enable_high);
+		data->is_enabled = false;
+	}
+
+	return 0;
+}
+
+static int gpio_regulator_enable_time(struct regulator_dev *dev)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+
+	return data->startup_delay;
+}
+
+static int gpio_regulator_get_value(struct regulator_dev *dev)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+	int ptr;
+
+	for (ptr = 0; ptr < data->nr_gpio_states; ptr++)
+		if (data->gpio_states[ptr].gpios == data->state)
+			return data->gpio_states[ptr].value;
+
+	return -EINVAL;
+}
+
+static int gpio_regulator_set_value(struct regulator_dev *dev,
+					int min, int max)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+	int ptr, target, state;
+
+	target = -1;
+	for (ptr = 0; ptr < data->nr_gpio_states; ptr++)
+		if (data->gpio_states[ptr].value >= min &&
+		    data->gpio_states[ptr].value <= max)
+			target = data->gpio_states[ptr].gpios;
+
+	if (target < 0)
+		return -EINVAL;
+
+	for (ptr = 0; ptr < data->nr_gpios; ptr++) {
+		state = (target & (1 << ptr)) >> ptr;
+		gpio_set_value(data->gpios[ptr], state);
+	}
+
+	data->state = target;
+
+	return 0;
+}
+
+static int gpio_regulator_set_voltage(struct regulator_dev *dev,
+					int min_uV, int max_uV,
+					unsigned *selector)
+{
+	return gpio_regulator_set_value(dev, min_uV, max_uV);
+}
+
+static int gpio_regulator_list_voltage(struct regulator_dev *dev,
+				      unsigned selector)
+{
+	struct gpio_regulator_data *data = rdev_get_drvdata(dev);
+
+	if (selector >= data->nr_gpio_states)
+		return -EINVAL;
+
+	return data->gpio_states[selector].value;
+}
+
+static int gpio_regulator_set_current_limit(struct regulator_dev *dev,
+					int min_uA, int max_uA)
+{
+	return gpio_regulator_set_value(dev, min_uA, max_uA);
+}
+
+static struct regulator_ops gpio_regulator_voltage_ops = {
+	.is_enabled = gpio_regulator_is_enabled,
+	.enable = gpio_regulator_enable,
+	.disable = gpio_regulator_disable,
+	.enable_time = gpio_regulator_enable_time,
+	.get_voltage = gpio_regulator_get_value,
+	.set_voltage = gpio_regulator_set_voltage,
+	.list_voltage = gpio_regulator_list_voltage,
+};
+
+static struct regulator_ops gpio_regulator_current_ops = {
+	.is_enabled = gpio_regulator_is_enabled,
+	.enable = gpio_regulator_enable,
+	.disable = gpio_regulator_disable,
+	.enable_time = gpio_regulator_enable_time,
+	.get_current_limit = gpio_regulator_get_value,
+	.set_current_limit = gpio_regulator_set_current_limit,
+};
+
+static int __devinit gpio_regulator_probe(struct platform_device *pdev)
+{
+	struct gpio_regulator_config *config = pdev->dev.platform_data;
+	struct gpio_regulator_data *drvdata;
+	int ptr, ret, state;
+
+	drvdata = kzalloc(sizeof(struct gpio_regulator_data), GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate device data\n");
+		return -ENOMEM;
+	}
+
+	drvdata->desc.name = kstrdup(config->supply_name, GFP_KERNEL);
+	if (drvdata->desc.name == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate supply name\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	drvdata->gpios = kmalloc(config->nr_gpios * sizeof(int), GFP_KERNEL);
+	if (drvdata->gpios == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate gpio data\n");
+		ret = -ENOMEM;
+		goto err_name;
+	}
+	/* init gpios negative; ability to deinitialize only the set ones */
+	for (ptr = 0; ptr < config->nr_gpios; ptr++)
+		drvdata->gpios[ptr] = -EINVAL;
+
+	drvdata->gpio_states = kmemdup(config->gpio_states,
+				       config->nr_gpio_states *
+					 sizeof(struct gpio_regulator_state),
+				       GFP_KERNEL);
+	if (drvdata == NULL) {
+		dev_err(&pdev->dev, "Failed to allocate state data\n");
+		ret = -ENOMEM;
+		goto err_memgpio;
+	}
+
+	drvdata->desc.type = REGULATOR_VOLTAGE;
+	drvdata->desc.owner = THIS_MODULE;
+
+	/* if we are a voltage regulator */
+	switch (config->type) {
+	case REGULATOR_VOLTAGE:
+		drvdata->desc.ops = &gpio_regulator_voltage_ops;
+		drvdata->desc.n_voltages = config->nr_gpio_states;
+		break;
+	case REGULATOR_CURRENT:
+		drvdata->desc.ops = &gpio_regulator_current_ops;
+		break;
+	default:
+		dev_err(&pdev->dev, "No regulator type set\n");
+		ret = -EINVAL;
+		goto err_memgpio;
+		break;
+	}
+
+	drvdata->enable_gpio = config->enable_gpio;
+	drvdata->startup_delay = config->startup_delay;
+
+	if (gpio_is_valid(config->enable_gpio)) {
+		drvdata->enable_high = config->enable_high;
+
+		ret = gpio_request(config->enable_gpio, config->supply_name);
+		if (ret) {
+			dev_err(&pdev->dev,
+			   "Could not obtain regulator enable GPIO %d: %d\n",
+							config->enable_gpio, ret);
+			goto err_memstate;
+		}
+
+		/* set output direction without changing state
+		 * to prevent glitch
+		 */
+		drvdata->is_enabled = config->enabled_at_boot;
+		ret = drvdata->is_enabled ?
+				config->enable_high : !config->enable_high;
+
+		ret = gpio_direction_output(config->enable_gpio, ret);
+		if (ret) {
+			dev_err(&pdev->dev,
+			   "Could not configure regulator enable GPIO %d direction: %d\n",
+							config->enable_gpio, ret);
+			goto err_enablegpio;
+		}
+	} else {
+		/* Regulator without GPIO control is considered
+		 * always enabled
+		 */
+		drvdata->is_enabled = true;
+	}
+
+	drvdata->nr_gpios = config->nr_gpios;
+	for (ptr = 0; ptr < drvdata->nr_gpios; ptr++) {
+		ret = gpio_request(config->gpios[ptr], config->supply_name);
+		if (ret) {
+			dev_err(&pdev->dev,
+			   "Could not obtain regulator setting GPIO %d: %d\n",
+							config->gpios[ptr], ret);
+			goto err_stategpio;
+		}
+
+		drvdata->gpios[ptr] = config->gpios[ptr];
+
+		state = (config->state_at_boot & (1 << ptr)) >> ptr;
+		ret = gpio_direction_output(drvdata->gpios[ptr], state);
+		if (ret) {
+			dev_err(&pdev->dev,
+			   "Could not configure regulator enable GPIO %d direction: %d\n",
+							drvdata->gpios[ptr], ret);
+			goto err_stategpio;
+		}
+	}
+	drvdata->state = config->state_at_boot;
+
+	drvdata->dev = regulator_register(&drvdata->desc, &pdev->dev,
+					  config->init_data, drvdata);
+	if (IS_ERR(drvdata->dev)) {
+		ret = PTR_ERR(drvdata->dev);
+		dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret);
+		goto err_stategpio;
+	}
+
+	platform_set_drvdata(pdev, drvdata);
+
+	return 0;
+
+err_stategpio:
+	for (ptr = 0; ptr < config->nr_gpios; ptr++)
+		if (gpio_is_valid(drvdata->gpios[ptr]))
+			gpio_free(drvdata->gpios[ptr]);
+err_enablegpio:
+	if (gpio_is_valid(config->enable_gpio))
+		gpio_free(config->enable_gpio);
+err_memstate:
+	kfree(drvdata->gpio_states);
+err_memgpio:
+	kfree(drvdata->gpios);
+err_name:
+	kfree(drvdata->desc.name);
+err:
+	kfree(drvdata);
+	return ret;
+}
+
+static int __devexit gpio_regulator_remove(struct platform_device *pdev)
+{
+	struct gpio_regulator_data *drvdata = platform_get_drvdata(pdev);
+	int ptr;
+
+	regulator_unregister(drvdata->dev);
+
+	for (ptr = 0; ptr < drvdata->nr_gpios; ptr++)
+		gpio_free(drvdata->gpios[ptr]);
+
+	kfree(drvdata->gpio_states);
+	kfree(drvdata->gpios);
+
+	if (gpio_is_valid(drvdata->enable_gpio))
+		gpio_free(drvdata->enable_gpio);
+
+	kfree(drvdata->desc.name);
+	kfree(drvdata);
+
+	return 0;
+}
+
+static struct platform_driver gpio_regulator_driver = {
+	.probe		= gpio_regulator_probe,
+	.remove		= __devexit_p(gpio_regulator_remove),
+	.driver		= {
+		.name		= "gpio-regulator",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init gpio_regulator_init(void)
+{
+	return platform_driver_register(&gpio_regulator_driver);
+}
+subsys_initcall(gpio_regulator_init);
+
+static void __exit gpio_regulator_exit(void)
+{
+	platform_driver_unregister(&gpio_regulator_driver);
+}
+module_exit(gpio_regulator_exit);
+
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("gpio voltage regulator");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-regulator");
diff --git a/include/linux/regulator/gpio-regulator.h b/include/linux/regulator/gpio-regulator.h
new file mode 100644
index 0000000..f85d1e5
--- /dev/null
+++ b/include/linux/regulator/gpio-regulator.h
@@ -0,0 +1,84 @@
+/*
+ * gpio-regulator.h
+ *
+ * Copyright 2011 Heiko Stuebner <heiko@sntech.de>
+ *
+ * based on fixed.h
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Roger Quadros <ext-roger.quadros@nokia.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.
+ */
+
+#ifndef __REGULATOR_GPIO_H
+#define __REGULATOR_GPIO_H
+
+struct regulator_init_data;
+
+/**
+ * struct gpio_regulator_state - state description
+ * @value:		microvolts or microampere
+ * @gpios:		bitfield of gpio target-states for the value
+ *
+ * This structure describes a supported setting of the regulator
+ * and the necessary gpio-state to achieve it.
+ */
+struct gpio_regulator_state {
+	int value;
+	int gpios;
+};
+
+/**
+ * struct gpio_regulator_config - config structure
+ * @supply_name:	Name of the regulator supply
+ * @enable_gpio:	GPIO to use for enable control
+ * 			set to -EINVAL if not used
+ * @enable_high:	Polarity of enable GPIO
+ *			1 = Active high, 0 = Active low
+ * @enabled_at_boot:	Whether regulator has been enabled at
+ * 			boot or not. 1 = Yes, 0 = No
+ * 			This is used to keep the regulator at
+ * 			the default state
+ * @startup_delay:	Start-up time in microseconds
+ * @gpios:		Array containing the gpios needed to control
+ * 			the setting of the regulator
+ * @nr_gpios:		Number of gpios
+ * @gpio_states:	Array of gpio_regulator_state entries describing
+ * 			the gpio state for specific voltages
+ * @nr_gpio_states:	Number of states available
+ * @state_at_boot:	State set at boot, default state for the gpios
+ * @init_data:		regulator_init_data
+ *
+ * This structure contains gpio-voltage regulator configuration
+ * information that must be passed by platform code to the
+ * gpio-voltage regulator driver.
+ */
+struct gpio_regulator_config {
+	const char *supply_name;
+
+	int enable_gpio;
+	unsigned enable_high:1;
+	unsigned enabled_at_boot:1;
+	unsigned startup_delay;
+
+	int *gpios;
+	int nr_gpios;
+
+	struct gpio_regulator_state *gpio_states;
+	int nr_gpio_states;
+
+	int state_at_boot;
+
+	enum regulator_type type;
+	struct regulator_init_data *init_data;
+};
+
+#endif
-- 
1.7.5.4


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

end of thread, other threads:[~2011-09-30 10:59 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-09-26  6:52 [RFC] Add gpio based voltage switching regulator Heiko Stübner
2011-09-26 12:51 ` Mark Brown
2011-09-28  8:02   ` Heiko Stübner
2011-09-28  9:03     ` Mark Brown
2011-09-30 10:59       ` [RFC v2] Add gpio-regulator driver Heiko Stübner

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