Federico Fuga

Engineering, Tech, Informatics & science

Adding a user space "power switch" to your embedded linux

18 Jul 2019 08:25 +0000 Embedded Linux

Is is always amazing to see how some very obvious features are missing from the linux kernel.

Yesterday, for a project I am developing for a Dutch firm, I come across one of these “very obvious feature”: the option to switch on and off a device in the embedded linux board we’re using.

The problem seemed to be quite frequent: you have a device connected to the internal USB bus (a wifi module, maybe?) and you want to switch it off completely when not in use.

It is the only USB connection in that bus, a single power line is powering that device, and still you need to power the regulator on from the kernel land?

While discussing with a kernel developer of this feature, he replied that “such a feature should not be accessible from the user space”. This is a complete nonsense: in the embedded world it is perfectly acceptable that the upper layer application will decide weather a specific function must be accessed or not. And letting the driver decide is like the egg-and-chicken problem: the driver can power up the module during the probe phase, but the probe phase happens when the module is connected to the usb bus, and the module need to be powered-up to be discovered.

So, how to implement this? My first idea was to modify the regulator code to expose a special kernel object through sysfs, and call regulator_enable() or regulator_disable() on demand.

But of course this is a very specific solution, and it seems impossible that a more general solution is not already available on the official kernel.

The reality is: it exists. And it’s called “regulator userspace-consumer”.

It’s a small module that once loaded it implements exactly this functionality, but in a more elegant way, that is without requiring any modification to the regulator code.

If you search for examples on the net, you’ll find some post in various embedded linux forums, but none comes with the step-by-step solution, and they are pretty outdated.

Digging through the code, only brings to one place where the module is used, i.e. the old pxa270 SoM, and it is used as a platform device, so adding it to your platform does require some code modification.

A modern approach would be to use device trees. And indeed it is possible, many of the examples found in the forums explain how to do this. But the official kernel doesn’t implement the configuration of the userspace consumer through device tree.

Why?

Because the patch implementing this was lost or forgotten, or it was rejected, I’m not sure what is the best and most likely explanation.

Anyway. Here is the patch.

Apply it to your kernel source, enable CONFIG_REGULATOR_USERSPACE_CONSUMER in your config file, then add a device tree node like this

<code>wifi-switch {
        compatible = "reg-userspace-consumer";

        regulator-name = "wifi-consumer";
	#regulator-boot-on;
	regulator-supplies = "vcc";
        vcc-supply = <&reg_dldo1>;
        comment = "Usb vcc switch";
    };</code>

This will create a sysfs directory in /sys/module/platform/ named “wifi-switch”. There a file called “state” will allow you to read the state or change it by writing “enabled” or “disabled”.

Mission accomplished. One question remains, why it wasn’t accepted in the mainline kernel? Mysteries.

Here I attach the patch, from Laxman Dewangan.

One sidenote: pay attention to what regulator you attach the switch. It may be possible to totally switch-off the board if the regulator is powering the CPU or the SDRAM. Also, tweaking with the power supplies is not always safe. So use this at your own risk.

<code>From f003651c631a0a25832600e7446cd562f80c52a7 Mon Sep 17 00:00:00 2001
From: Laxman Dewangan <ldewangan@...dia.com>
Date: Wed, 30 Jul 2014 19:23:59 +0530
Subject: [PATCH 1/2] regulator: userspace-consumer: add DT support

Add DT support of the regulator driver userspace-consumer.
The supply names for this driver is provided through DT properties
so that proper regulator handle can be acquired.

Signed-off-by: Laxman Dewangan <ldewangan@...dia.com>
---
 drivers/regulator/userspace-consumer.c | 48 ++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/drivers/regulator/userspace-consumer.c b/drivers/regulator/userspace-consumer.c
index 765acc11c9c8..91d50a2770c0 100644
--- a/drivers/regulator/userspace-consumer.c
+++ b/drivers/regulator/userspace-consumer.c
@@ -23,6 +23,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/regulator/userspace-consumer.h>
 #include <linux/slab.h>
+#include <linux/of.h>
 
 struct userspace_consumer_data {
 	const char *name;
@@ -105,6 +106,41 @@ static const struct attribute_group attr_group = {
 	.attrs	= attributes,
 };
 
+static struct regulator_userspace_consumer_data *get_pdata_from_dt_node(
+		struct platform_device *pdev)
+{
+	struct regulator_userspace_consumer_data *pdata;
+	struct device_node *np = pdev->dev.of_node;
+	struct property *prop;
+	const char *supply;
+	int num_supplies;
+	int count = 0;
+
+	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+
+	pdata->name = of_get_property(np, "regulator-name", NULL);
+	pdata->init_on = of_property_read_bool(np, "regulator-boot-on");
+
+	num_supplies = of_property_count_strings(np, "regulator-supplies");
+	if (num_supplies < 0) {
+		dev_err(&pdev->dev,
+			"could not parse property regulator-supplies\n");
+		return ERR_PTR(-EINVAL);
+	}
+	pdata->num_supplies = num_supplies;
+	pdata->supplies = devm_kzalloc(&pdev->dev, num_supplies *
+				sizeof(*pdata->supplies), GFP_KERNEL);
+	if (!pdata->supplies)
+		return ERR_PTR(-ENOMEM);
+
+	of_property_for_each_string(np, "regulator-supplies", prop, supply)
+		pdata->supplies[count++].supply = supply;
+
+	return pdata;
+}
+
 static int regulator_userspace_consumer_probe(struct platform_device *pdev)
 {
 	struct regulator_userspace_consumer_data *pdata;
@@ -112,6 +148,11 @@ static int regulator_userspace_consumer_probe(struct platform_device *pdev)
 	int ret;
 
 	pdata = dev_get_platdata(&pdev->dev);
+	if (!pdata && pdev->dev.of_node) {
+		pdata = get_pdata_from_dt_node(pdev);
+		if (IS_ERR(pdata))
+			return PTR_ERR(pdata);
+	}
 	if (!pdata)
 		return -EINVAL;
 
@@ -171,11 +212,18 @@ static int regulator_userspace_consumer_remove(struct platform_device *pdev)
 	return 0;
 }
 
+static const struct of_device_id regulator_userspace_consumer_of_match[] = {
+	{ .compatible = "reg-userspace-consumer", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, regulator_userspace_consumer_of_match);
+
 static struct platform_driver regulator_userspace_consumer_driver = {
 	.probe		= regulator_userspace_consumer_probe,
 	.remove		= regulator_userspace_consumer_remove,
 	.driver		= {
 		.name		= "reg-userspace-consumer",
+		.of_match_table = regulator_userspace_consumer_of_match,
 	},
 };
 
-- 
2.17.1

</code>