lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <201109260852.19859.heiko@sntech.de>
Date:	Mon, 26 Sep 2011 08:52:18 +0200
From:	Heiko Stübner <heiko@...ech.de>
To:	Mark Brown <broonie@...nsource.wolfsonmicro.com>,
	Liam Girdwood <lrg@...com>
Cc:	linux-kernel@...r.kernel.org
Subject: [RFC] Add gpio based voltage switching regulator

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@...ech.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@...ech.de>
+ *
+ * based on fixed.c
+ *
+ * Copyright 2008 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@...nsource.wolfsonmicro.com>
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Roger Quadros <ext-roger.quadros@...ia.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@...ech.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@...nsource.wolfsonmicro.com>
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Roger Quadros <ext-roger.quadros@...ia.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)
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ