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

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.