Re: RFC v2 GPIO lines

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

 



Hello Rodolfo,

I might have had a similar problem, so if you don't mind me entering
the discussion …

Am Tue, Apr 27, 2021 at 05:30:34PM +0200 schrieb Rodolfo Giometti:
> 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_LINE.

What's the reason to use the sysfs interface instead of libgpiod and
the modern character device based interface?

> That's why I wrote the attached patch (for kernel 5.6) 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. :)
> 
> Note: This patch has been already discussed on linux-gpio@xxxxxxxxxxxxxxx
> mail-list (that's why RFC "v2") and, as suggested by Linus Walleij, the
> discussion has been moved here to see what you think about this proposal. You
> can see the discussion here: https://www.spinics.net/lists/linux-gpio/msg50482.html

Then, I think you should still Cc the linux-gpio list.

> For the moment here is how it works:
> 
> 1) The patch is activated by the configuration entry CONFIG_GPIO_LINE.
> 
> 2) In the device-tree the developer defines all board's GPIO lines with their
> names and mode of functioning:
> 
>         gpio_lines {
>                 compatible = "gpio-line";
> 
>                 iso_out2 {
>                         gpios = <&gpiosb 2 GPIO_ACTIVE_HIGH>;
>                         output-low;
>                 };
> 
>                 flag_ftdi {
>                         gpios = <&gpionb 12 GPIO_ACTIVE_HIGH>;
>                         input;
>                         line-name = "ftdi";
>                 };
>         };
> 
> 
> Property "line-name" is optional and it can be used in case the GPIO line's name
> should be different from the node's name. While to define the line type
> developers can use the keywords "input", "output-low", "output-high" and "as-is"
> in order to set the line as input, output (default low or high) or just using
> the line in its current configuration.

IIRC the led subsystem deprecated using the node name as label, this
is only a fallback for backwards compatibility nowadays.

> 3) At boot the added GPIO lines are shown:
> 
> [    2.880687] line iso_out2: GPIO448 added as output-low
> [    2.910249] line ftdi: GPIO488 added as input
> 
> 4) Then users will find a new class with entries, one for each new line as
> reported below:
> 
> # ls /sys/class/line/
> ftdi      iso_out2
> 
> 5) By using the attribute "state" the users can get or set the line status
> 
> # cat /sys/class/line/iso_out2/state
> 0
> # echo 1 > /sys/class/line/iso_out2/state
> # cat /sys/class/line/iso_out2/state
> 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-446 (                    |usb3-vbus           ) out hi
>  gpio-447 (                    |usb2-vbus           ) out hi
>  gpio-448 (                    |iso_out2            ) out hi
> 
> gpiochip0: GPIOs 476-511, parent: platform/d0013800.pinctrl, GPIO1:
>  gpio-488 (                    |ftdi                ) in  hi
> 
> The End. :)

Defining line names for GPIOs is already somewhat possible. We talked
about this few weeks ago, when I tried to set line names for a SAMA5D2
based board:
https://lore.kernel.org/linux-gpio/946021874.11132.1615900079722@xxxxxxxxxxxxxxxxx/

Greets
Alex

> From 0989c7e422cceb0a079e44cd20f29402e0894a79 Mon Sep 17 00:00:00 2001
> From: Rodolfo Giometti <giometti@xxxxxxxxxxxx>
> Date: Mon, 26 Apr 2021 14:06:49 +0200
> Subject: [PATCH] drivers gpio: add GPIO line support
> 
> Signed-off-by: Rodolfo Giometti <giometti@xxxxxxxxxxxx>
> ---
>  drivers/gpio/Kconfig        |  10 ++
>  drivers/gpio/Makefile       |   1 +
>  drivers/gpio/gpiolib-line.c | 251 ++++++++++++++++++++++++++++++++++++
>  3 files changed, 262 insertions(+)
>  create mode 100644 drivers/gpio/gpiolib-line.c
> 
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index e3607ec4c2e8..d9d8bc508b78 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -95,6 +95,16 @@ config GPIO_CDEV_V1
>  
>  	  If unsure, say Y.
>  
> +config GPIO_LINE
> +	bool "/sys/class/line/... (GPIO lines interface)"
> +	depends on SYSFS
> +	help
> +	  Say Y here to add a sysfs interface to manage system's GPIO lines.
> +
> +	  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 c58a90a3c3b1..d459ecd5ca18 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -9,6 +9,7 @@ obj-$(CONFIG_GPIOLIB)		+= gpiolib-legacy.o
>  obj-$(CONFIG_OF_GPIO)		+= gpiolib-of.o
>  obj-$(CONFIG_GPIO_CDEV)		+= gpiolib-cdev.o
>  obj-$(CONFIG_GPIO_SYSFS)	+= gpiolib-sysfs.o
> +obj-$(CONFIG_GPIO_LINE)		+= gpiolib-line.o
>  obj-$(CONFIG_GPIO_ACPI)		+= gpiolib-acpi.o
>  
>  # Device drivers. Generally keep list sorted alphabetically
> diff --git a/drivers/gpio/gpiolib-line.c b/drivers/gpio/gpiolib-line.c
> new file mode 100644
> index 000000000000..b7dae8c0ace4
> --- /dev/null
> +++ b/drivers/gpio/gpiolib-line.c
> @@ -0,0 +1,251 @@
> +/*
> + * GPIOlib - userspace I/O line interface
> + *
> + *
> + * Copyright (C) 2020-2021   Rodolfo Giometti <giometti@xxxxxxxxxxxx>
> + *
> + *   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 GPIO_LINE_MAX_SOURCES       128      /* should be enough... */
> +
> +/*
> + * Local variables
> + */
> +
> +static dev_t gpio_line_devt;
> +static struct class *gpio_line_class;
> +
> +static DEFINE_MUTEX(gpio_line_idr_lock);
> +static DEFINE_IDR(gpio_line_idr);
> +
> +struct gpio_line_device {
> +	struct gpio_desc *gpiod;
> +	const char *name;
> +	unsigned int id;
> +	struct device *dev;
> +};
> +
> +/*
> + * sysfs methods
> + */
> +
> +static ssize_t state_store(struct device *dev,
> +				struct device_attribute *attr,
> +				const char *buf, size_t count)
> +{
> +	struct gpio_line_device *gpio_line = 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(gpio_line->gpiod, status);
> +
> +	return count;
> +}
> +
> +static ssize_t state_show(struct device *dev,
> +				struct device_attribute *attr, char *buf)
> +{
> +	struct gpio_line_device *gpio_line = dev_get_drvdata(dev);
> +	int status = gpiod_get_value_cansleep(gpio_line->gpiod);
> +
> +	return sprintf(buf, "%d\n", status);
> +}
> +static DEVICE_ATTR_RW(state);
> +
> +/*
> + * Class attributes
> + */
> +
> +static struct attribute *gpio_line_attrs[] = {
> +	&dev_attr_state.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group gpio_line_group = {
> +	.attrs = gpio_line_attrs,
> +};
> +
> +static const struct attribute_group *gpio_line_groups[] = {
> +	&gpio_line_group,
> +	NULL,
> +};
> +
> +/*
> + * Driver stuff
> + */
> +
> +static struct gpio_line_device *gpio_line_create_entry(const char *name,
> +				struct gpio_desc *gpiod,
> +				struct device *parent)
> +{
> +	struct gpio_line_device *gpio_line;
> +	dev_t devt;
> +	int ret;
> +
> +	/* First allocate a new gpio_line device */
> +	gpio_line = kmalloc(sizeof(struct gpio_line_device), GFP_KERNEL);
> +	if (!gpio_line)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mutex_lock(&gpio_line_idr_lock);
> +	/*
> +	 * Get new ID for the new gpio_line source.  After idr_alloc() calling
> +	 * the new source will be freely available into the kernel.
> +	 */
> +	ret = idr_alloc(&gpio_line_idr, gpio_line, 0,
> +			GPIO_LINE_MAX_SOURCES, GFP_KERNEL);
> +	if (ret < 0) {
> +		if (ret == -ENOSPC) {
> +			pr_err("%s: too many GPIO lines in the system\n",
> +			       name);
> +			ret = -EBUSY;
> +		}
> +		goto error_device_create;
> +	}
> +	gpio_line->id = ret;
> +	mutex_unlock(&gpio_line_idr_lock);
> +
> +	/* Create the device and init the device's data */
> +	devt = MKDEV(MAJOR(gpio_line_devt), gpio_line->id);
> +	gpio_line->dev = device_create(gpio_line_class, parent, devt, gpio_line,
> +				   "%s", name);
> +	if (IS_ERR(gpio_line->dev)) {
> +		dev_err(gpio_line->dev, "unable to create device %s\n", name);
> +		ret = PTR_ERR(gpio_line->dev);
> +		goto error_idr_remove;
> +	}
> +	dev_set_drvdata(gpio_line->dev, gpio_line);
> +
> +	/* Init the gpio_line data */
> +	gpio_line->gpiod = gpiod;
> +	gpio_line->name = name;
> +
> +	return gpio_line;
> +
> +error_idr_remove:
> +	mutex_lock(&gpio_line_idr_lock);
> +	idr_remove(&gpio_line_idr, gpio_line->id);
> +
> +error_device_create:
> +	mutex_unlock(&gpio_line_idr_lock);
> +	kfree(gpio_line);
> +
> +	return ERR_PTR(ret);
> +}
> +
> +static int gpio_line_gpio_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct fwnode_handle *child;
> +	struct gpio_line_device *gpio_line;
> +	int ret;
> +
> +	device_for_each_child_node(dev, child) {
> +		struct device_node *np = to_of_node(child);
> +		const char *name;
> +		enum gpiod_flags flags;
> +		struct gpio_desc *gpiod;
> +
> +		ret = fwnode_property_read_string(child, "line-name", &name);
> +		if (ret && IS_ENABLED(CONFIG_OF) && np)
> +			name = np->name;
> +		if (!name) {
> +			dev_err(dev,
> +				"name property not defined or invalid!\n");
> +			goto skip;
> +		}
> +
> +		flags = GPIOD_ASIS;
> +		if (of_property_read_bool(np, "input"))
> +			flags = GPIOD_IN;
> +		else if (of_property_read_bool(np, "output-low"))
> +			flags = GPIOD_OUT_LOW;
> +		else if (of_property_read_bool(np, "output-high"))
> +			flags = GPIOD_OUT_HIGH;
> +		gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
> +							 flags, name);
> +		if (IS_ERR(gpiod)) {
> +			dev_err(dev, "gpios property not defined!\n");
> +			goto skip;
> +		}
> +
> +		gpio_line = gpio_line_create_entry(name, gpiod, dev);
> +		if (IS_ERR(gpio_line))
> +			goto skip;
> +
> +		/* Success, go to the next child */
> +		dev_info(gpio_line->dev, "GPIO%d added as %s\n",
> +			desc_to_gpio(gpiod),
> +			flags == GPIOD_ASIS ? "as-is" :
> +			  flags == GPIOD_OUT_HIGH ? "output-high" :
> +			    flags == GPIOD_OUT_LOW ? "output-low" :
> +			      flags == GPIOD_IN ? "input" : "unknow!");
> +		continue;
> +
> +skip:		/* Error, skip the child */
> +		fwnode_handle_put(child);
> +		dev_err(dev, "failed to register GPIO lines interface\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id of_gpio_gpio_line_match[] = {
> +	{ .compatible = "gpio-line", },
> +	{ /* sentinel */ }
> +};
> +
> +static struct platform_driver gpio_line_gpio_driver = {
> +	.driver	 = {
> +		.name   = "gpio-line",
> +		.of_match_table = of_gpio_gpio_line_match,
> +	},
> +};
> +
> +builtin_platform_driver_probe(gpio_line_gpio_driver, gpio_line_gpio_probe);
> +
> +/*
> + * Module stuff
> + */
> +
> +static int __init gpiolib_line_init(void)
> +{
> +	/* Create the new class */
> +	gpio_line_class = class_create(THIS_MODULE, "line");
> +	if (!gpio_line_class) {
> +		printk(KERN_ERR "gpio_line: failed to create class\n");
> +		return -ENOMEM;
> +	}
> +	gpio_line_class->dev_groups = gpio_line_groups;
> +
> +	return 0;
> +}
> +
> +postcore_initcall(gpiolib_line_init);
> -- 
> 2.25.1
> 




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux