[RFC] GPIO User I/O

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

at the moment if a developer wishes to use a GPIO as output or input with a well
defined name from userspace via the sysfs interface he/she can use,
respectively, the gpio-leds or gpio-uinput devices. However, IMHO, this is not
the best nor a proper way to do it, that's why I'm here to propose this really
simple interface named GPIO_UIO.

That's why I wrote the attached patch (for kernel 4.19) which it's just a
proposal, and the purpose of this message is to collect feedback about this
solution. Of course I'll be happy to port it for latest kernel release and
complete it with all requested documentation and needed modifications, but I'll
do it only if this patch has some changes to be accepted by GPIO subsystem's
maintainers. :)

For the moment here is how it works:

1) The patch is activated by the configuration entry CONFIG_GPIO_UIO.

2) In the device-tree the developer defines all board's GPIO lines with their
names and mode of functioning:

+       gpio_uio {
+               compatible = "gpio-uio";
+
+               bypass0 {
+                       gpios = <&gpionb 10 GPIO_ACTIVE_HIGH>;
+                       mode = "out-low";
+               };
+
+               bypass1 {
+                       gpios = <&gpiosb 11 GPIO_ACTIVE_HIGH>;
+                       mode = "out-low";
+                       label = "bypass-1";
+               };
+        };

Property "mode" can be "asis", "input", "out-low", etc. and the property label
can be used in case the GPIO line's name should be different from the node's name.

3) At boot the GPIO lines are added:

[    2.398902] gpio-uio bypass0: line added
[    2.423558] gpio-uio bypass-1: line added

4) Then users will find a new class with entries, one for each new line:

# ls /sys/class/gpio-uio/
bypass-1  bypass0

5) By using the attribute "line" the users can get or set the line status

# cat /sys/class/gpio-uio/bypass-1/line
0
# echo 1 > /sys/class/gpio-uio/bypass-1/line
# cat /sys/class/gpio-uio/bypass-1/line
1

6) Developers can monitor the GPIO lines via debugfs as for kernel modules:

# cat /sys/kernel/debug/gpio
gpiochip1: GPIOs 446-475, parent: platform/d0018800.pinctrl, GPIO2:
 gpio-457 (                    |bypass-1            ) out lo

gpiochip0: GPIOs 476-511, parent: platform/d0013800.pinctrl, GPIO1:
 gpio-479 (                    |cd                  ) in  hi IRQ
 gpio-480 (                    |vcc_sd1             ) out lo
 gpio-486 (                    |bypass0             ) out lo


The End. :)

Ciao,

Rodolfo

-- 
GNU/Linux Solutions                  e-mail: giometti@xxxxxxxxxxxx
Linux Device Driver                          giometti@xxxxxxxx
Embedded Systems                     phone:  +39 349 2432127
UNIX programming                     skype:  rodolfo.giometti
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 4f52c3a8ec99..a072b45d7f20 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -73,6 +73,16 @@ config GPIO_SYSFS
 	  Kernel drivers may also request that a particular GPIO be
 	  exported to userspace; this can be useful when debugging.
 
+config GPIO_UIO
+	bool "/sys/class/gpio-uio/... (sysfs user I/O interface)"
+	depends on SYSFS
+	help
+	  Say Y here to add a sysfs interface for I/O activities from userspace.
+
+	  Instead of the GPIO_SYSFS support, by using this support, you'll be
+	  able to use GPIOs from userspace as stated in the device-tree
+	  for well defined pourposes and by using proper names.
+
 config GPIO_GENERIC
 	depends on HAS_IOMEM # Only for IOMEM drivers
 	tristate
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index c256aff66a65..2be35a9df6e0 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_GPIOLIB)		+= gpiolib-legacy.o
 obj-$(CONFIG_GPIOLIB)		+= gpiolib-devprop.o
 obj-$(CONFIG_OF_GPIO)		+= gpiolib-of.o
 obj-$(CONFIG_GPIO_SYSFS)	+= gpiolib-sysfs.o
+obj-$(CONFIG_GPIO_UIO)		+= gpiolib-uio.o
 obj-$(CONFIG_GPIO_ACPI)		+= gpiolib-acpi.o
 
 # Device drivers. Generally keep list sorted alphabetically
diff --git a/drivers/gpio/gpiolib-uio.c b/drivers/gpio/gpiolib-uio.c
new file mode 100644
index 000000000000..8d6f584c710e
--- /dev/null
+++ b/drivers/gpio/gpiolib-uio.c
@@ -0,0 +1,256 @@
+/*
+ * GPIOlib - userspace I/O interface
+ *
+ *
+ * Copyright (C) 2020   Rodolfo Giometti <giometti@xxxxxxxx>
+ *
+ *   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 program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/kdev_t.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+
+#define GPIOUIO_MAX_SOURCES       128      /* should be enough... */
+
+/*
+ * Local variables
+ */
+
+static dev_t gpiouio_devt;
+static struct class *gpiouio_class;
+
+static DEFINE_MUTEX(gpiouio_idr_lock);
+static DEFINE_IDR(gpiouio_idr);
+
+struct gpiouio_device {
+	struct gpio_desc *gpiod;
+        const char *name;
+        unsigned int id;
+        struct device *dev;
+};
+
+/*
+ * sysfs methods
+ */
+
+static ssize_t line_store(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t count)
+{
+        struct gpiouio_device *gpiouio = dev_get_drvdata(dev);
+        int status, ret;
+
+        ret = sscanf(buf, "%d", &status);
+        if (ret != 1 && status != 0 && status != 1)
+                return -EINVAL;
+
+	gpiod_set_value_cansleep(gpiouio->gpiod, status);
+
+        return count;
+}
+
+static ssize_t line_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct gpiouio_device *gpiouio = dev_get_drvdata(dev);
+	int status = gpiod_get_value_cansleep(gpiouio->gpiod);
+
+	return sprintf(buf, "%d\n", status);
+}
+static DEVICE_ATTR_RW(line);
+
+/*
+ * Class attributes
+ */
+
+static struct attribute *gpiouio_attrs[] = {
+        &dev_attr_line.attr,
+        NULL,
+};
+
+static const struct attribute_group gpiouio_group = {
+        .attrs = gpiouio_attrs,
+};
+
+static const struct attribute_group *gpiouio_groups[] = {
+        &gpiouio_group,
+        NULL,
+};
+
+/*
+ * Driver stuff
+ */
+
+static int gpiouio_create_entry(const char *name,
+				struct gpio_desc *gpiod,
+				struct device *parent)
+{
+	struct gpiouio_device *gpiouio;
+	dev_t devt;
+	int ret;
+
+	/* First allocate a new gpiouio device */
+	gpiouio = kmalloc(sizeof(struct gpiouio_device), GFP_KERNEL);
+	if (!gpiouio)
+		return -ENOMEM;
+
+        mutex_lock(&gpiouio_idr_lock);
+        /*
+         * Get new ID for the new gpiouio source.  After idr_alloc() calling
+         * the new source will be freely available into the kernel.
+         */
+        ret = idr_alloc(&gpiouio_idr, gpiouio, 0,
+			GPIOUIO_MAX_SOURCES, GFP_KERNEL);
+        if (ret < 0) {
+                if (ret == -ENOSPC) {
+                        pr_err("%s: too many PPS sources in the system\n",
+                               name);
+                        ret = -EBUSY;
+                }
+                goto error_device_create;
+        }
+        gpiouio->id = ret;
+        mutex_unlock(&gpiouio_idr_lock);
+
+	/* Create the device and init the device's data */
+        devt = MKDEV(MAJOR(gpiouio_devt), gpiouio->id);
+	gpiouio->dev = device_create(gpiouio_class, parent, devt, gpiouio,
+				   "%s", name);
+	if (IS_ERR(gpiouio->dev)) {
+		dev_err(gpiouio->dev, "unable to create device %s\n", name);
+		ret = PTR_ERR(gpiouio->dev);
+		goto error_idr_remove;
+	}
+	dev_set_drvdata(gpiouio->dev, gpiouio);
+
+	/* Init the gpiouio data */
+	gpiouio->gpiod = gpiod;
+	gpiouio->name = name;
+
+	dev_info(gpiouio->dev, "line added\n");
+
+	return 0;
+
+error_idr_remove:
+	mutex_lock(&gpiouio_idr_lock);
+        idr_remove(&gpiouio_idr, gpiouio->id);
+
+error_device_create:
+	mutex_unlock(&gpiouio_idr_lock);
+	kfree(gpiouio);
+
+	return ret;
+}
+
+static int gpiouio_gpio_probe(struct platform_device *pdev)
+{
+        struct device *dev = &pdev->dev;
+        struct fwnode_handle *child;
+        int ret;
+
+        device_for_each_child_node(dev, child) {
+		struct device_node *np = to_of_node(child);
+                const char *label;
+		enum gpiod_flags flags = GPIOD_ASIS;
+                const char *mode = "as-is";
+		struct gpio_desc *gpiod;
+
+                ret = fwnode_property_read_string(child, "label", &label);
+                if (ret && IS_ENABLED(CONFIG_OF) && np)
+                        label = np->name;
+                if (!label) {
+                        dev_err(dev,
+				"label property not defined or invalid!\n");
+                        goto skip;
+                }
+
+		ret = fwnode_property_read_string(child, "mode", &mode);
+		if ((ret == 0) && mode) {
+			if (strcmp("as-is", mode) == 0)
+				flags = GPIOD_ASIS;
+			else if (strcmp("input", mode) == 0)
+				flags = GPIOD_IN;
+			else if (strcmp("out-low", mode) == 0)
+				flags = GPIOD_OUT_LOW;
+			else if (strcmp("out-high", mode) == 0)
+				flags = GPIOD_OUT_HIGH;
+			else if (strcmp("out-low-open-drain", mode) == 0)
+				flags = GPIOD_OUT_LOW_OPEN_DRAIN;
+			else if (strcmp("out-high-open-drain", mode) == 0)
+				flags = GPIOD_OUT_HIGH_OPEN_DRAIN;
+		}
+
+                gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
+                                                         flags, label);
+                if (IS_ERR(gpiod)) {
+                        dev_err(dev, "gpios property not defined!\n");
+                        goto skip;
+                }
+
+                ret = gpiouio_create_entry(label, gpiod, dev);
+                if (ret)
+                        goto skip;
+
+		/* Success, now go to the next child */
+		continue;
+
+skip:		/* Error, skip the child */
+		fwnode_handle_put(child);
+		dev_err(dev, "failed to register GPIO UIO interface\n");
+        }
+
+        return 0;
+}
+
+static const struct of_device_id of_gpio_gpiouio_match[] = {
+        { .compatible = "gpio-uio", },
+        { /* sentinel */ }
+};
+
+static struct platform_driver gpiouio_gpio_driver = {
+        .driver         = {
+                .name   = "gpio-uio",
+                .of_match_table = of_gpio_gpiouio_match,
+        },
+};
+
+builtin_platform_driver_probe(gpiouio_gpio_driver, gpiouio_gpio_probe);
+
+/*
+ * Module stuff
+ */
+
+static int __init gpiolib_uio_init(void)
+{
+	/* Create the new class */
+	gpiouio_class = class_create(THIS_MODULE, "gpio-uio");
+	if (!gpiouio_class) {
+		printk(KERN_ERR "gpiouio: failed to allocate class\n");
+		return -ENOMEM;
+	}
+	gpiouio_class->dev_groups = gpiouio_groups;
+
+	return 0;
+}
+
+postcore_initcall(gpiolib_uio_init);

[Index of Archives]     [Linux SPI]     [Linux Kernel]     [Linux ARM (vger)]     [Linux ARM MSM]     [Linux Omap]     [Linux Arm]     [Linux Tegra]     [Fedora ARM]     [Linux for Samsung SOC]     [eCos]     [Linux Fastboot]     [Gcc Help]     [Git]     [DCCP]     [IETF Announce]     [Security]     [Linux MIPS]     [Yosemite Campsites]

  Powered by Linux