* [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(®ulator_switched_voltage_driver);
+}
+subsys_initcall(regulator_switched_voltage_init);
+
+static void __exit regulator_switched_voltage_exit(void)
+{
+ platform_driver_unregister(®ulator_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