All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.