[RFC PATCH] gpio: Add a generic GPIO handling driver

Marc Kleine-Budde mkl at pengutronix.de
Mon Aug 6 08:27:20 EDT 2012


On 06/29/2012 11:21 AM, Lothar Waßmann wrote:
> This driver provides an interface for device drivers that need to
> control some external hardware (e.g. reset or enable signals) for the
> devices they serve.
> 
> Features:
>  - supports sharing a single GPIO between multiple devices.
>  - optionally sets the controlled GPIOs to a predefined state upon
>    suspend/resume.
>  - handles output polarity
> 
> Client API:
> 	cookie = request_gpio_switch(dev, id);
> 	gpio_switch_set(cookie, on|off);
> 	free_gpio_switch(cookie);
> 
> 	gpio_switch_set_suspend_state(cookie, on|off)
> 	gpio_switch_set_resume_state(cookie, on|off)
> 
> request_gpio_switch() always returns a valid cookie (that may be NULL
> if the requested pin is not registered on the current platform), so
> that client drivers do not need to check whether the current platform
> provides a certain pin or not.
> 
> The client drivers only cope with the logical pin states
> (active/inactive, on/off) while the gpio-switch driver takes care of
> the output polarity handling.
> 
> The typical use of this driver in a client driver (e.g. flexcan
> transceiver switch) would be like (with DT):

Adding linux-can on Cc.

In CAN drivers we have the following use cases:
a) more than one CAN devices share the same transceiver
b) transceiver has more than one GPIO that has to be switched

For the b) use case I think we currently never toggle the switches
independently from each other. This will become a use case, when we
consider to implement proper transceiver drivers.

What about pin-mux? Should the switch driver request pin-mux for the
gpios it handles?

> in the .dts file:
> 	flexcan_transceiver: gpio-switch at 0 {
> 		compatible = "linux,gpio-switch";
> 		gpio = <&gpio4 21 1>; /* GPIO is active low */
> 		label = "Flexcan Transceiver Enable";
> 		gpio-shared;
> 		init-state = <0>; /* Transceiver will be initially inactive */
> 	};
> 
> in flexcan_probe()
> 	struct gpio_sw *xcvr_switch = NULL;
> 	struct device_node *np = pdev->dev.of_node;
> 
> 	if (np) {
> 		ph = of_get_property(np, "transceiver-switch", NULL);
> 		if (ph) {
> 			xcvr_switch = request_gpio_switch(&pdev->dev,
> 					be32_to_cpu(*ph));
> 		}
> 	}

I don't like open coding this. What providing a devm capable helper
function.

> ...
> static void flexcan_transceiver_switch(const struct flexcan_priv *priv, int on)
> {
> 	if (priv->pdata && priv->pdata->transceiver_switch)
> 		priv->pdata->transceiver_switch(on);
> 	else
> 		gpio_switch_set(priv->xcvr_switch, on);
> }
> ...
> flexcan_remove()
>     free_gpio_switch(xcvr_switch);
> 
> Signed-off-by: Lothar Waßmann <LW at KARO-electronics.de>
> ---
>  .../devicetree/bindings/gpio/gpio-switch.txt       |   36 ++
>  drivers/gpio/Kconfig                               |    9 +
>  drivers/gpio/Makefile                              |    3 +
>  drivers/gpio/gpio-switch.c                         |  389 ++++++++++++++++++++
>  include/linux/gpio-switch.h                        |   47 +++
>  5 files changed, 484 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/gpio/gpio-switch.txt
>  create mode 100644 drivers/gpio/gpio-switch.c
>  create mode 100644 include/linux/gpio-switch.h
> 
> diff --git a/Documentation/devicetree/bindings/gpio/gpio-switch.txt b/Documentation/devicetree/bindings/gpio/gpio-switch.txt
> new file mode 100644
> index 0000000..d91c628
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/gpio/gpio-switch.txt
> @@ -0,0 +1,36 @@
> +Device-Tree bindings for gpio/gpio-switch.c driver
> +
> +Required properties:
> +	- compatible = "linux,gpio-switch";
> +	- gpios: OF device-tree gpio specification
> +
> +Optional properties:
> +	- label: Human readable name for the gpio function
> +	- init-state: specifies whether the gpio should be switched
> +	  active or inactive on startup.
> +	  If not set, pin will be initialized to the inactive state.
> +	- suspend-state: specifies the state that the GPIO should be
> +	  set to when suspending.
> +	  If not set, pin state will not be changed upon suspend.
> +	- resume-state: specifies the state that the GPIO should be
> +	  set to when resuming.
> +	  If not set, pin state will not be changed upon resume.
> +	- gpio-shared: Boolean, Enable refcounted
> +	  assertion/deassertion of the GPIO for a pin that is shared
> +	  between multiple devices.
> +
> +Pin states are logical states. The actual pin output state is
> +determined by the active low flag of the referenced gpio.
> +
> +Example nodes:
> +
> +	gpio-switch {
> +		compatible = "linux,gpio-switch";
> +		flexcan_transceiver: gpio-switch at 0 {
> +			label = "Flexcan transceiver";
> +			gpios = <&gpio1 0 1>;
> +			init-state = <0>;
> +			gpio-shared;
> +		};
> +	};
> +	...
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index e03653d..17f2f4f 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -514,4 +514,13 @@ config GPIO_TPS65910
>  	help
>  	  Select this option to enable GPIO driver for the TPS65910
>  	  chip family.
> +
> +comment "Generic GPIO switch"
> +
> +config GENERIC_GPIO_SWITCH
> +       tristate "Generic GPIO switch"
> +       help
> +         Provide a generic GPIO interface for drivers that require some
> +	 external logic for the device they serve to operate.
> +
>  endif
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index 007f54b..b31f41a 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -64,3 +64,6 @@ obj-$(CONFIG_GPIO_WM831X)	+= gpio-wm831x.o
>  obj-$(CONFIG_GPIO_WM8350)	+= gpio-wm8350.o
>  obj-$(CONFIG_GPIO_WM8994)	+= gpio-wm8994.o
>  obj-$(CONFIG_GPIO_XILINX)	+= gpio-xilinx.o
> +
> +# Not a GPIO HW driver
> +obj-$(CONFIG_GENERIC_GPIO_SWITCH)	+= gpio-switch.o
> diff --git a/drivers/gpio/gpio-switch.c b/drivers/gpio/gpio-switch.c
> new file mode 100644
> index 0000000..ea0c390
> --- /dev/null
> +++ b/drivers/gpio/gpio-switch.c
> @@ -0,0 +1,389 @@
> +/*
> + * drivers/gpio/gpio-switch.c
> + *
> + * Copyright (C) 2012  Lothar Wassmann <LW at KARO-electronics.de>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * version 2 as published by the Free Software Foundation
> + *
> + * 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.
> + *
> + *
> + * Provide a generic interface for drivers that require to switch some
> + * external hardware such as transceivers, power enable or reset pins
> + * for the device they manage to work.
> + *
> + * Allows multiple devices to share a common switch.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/list.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/gpio-switch.h>
> +
> +static LIST_HEAD(gpio_switch_list);
> +static DEFINE_MUTEX(gpio_switch_list_lock);
> +static int gpio_switch_index;
> +
> +struct gpio_sw {
> +	struct list_head list;
> +	struct device *parent;
> +	int id;
> +	const char *label;
> +	int gpio;
> +	unsigned flags;
> +	int use_count;
> +	int enable_count;
> +	unsigned state:1;
> +};
> +
> +/* Helper functions; must be called with 'gpio_switch_list_lock' held */
> +static struct gpio_sw *gpio_switch_find_gpio(struct device *parent,
> +					int gpio)
> +{
> +	struct gpio_sw *sw;
> +
> +	list_for_each_entry(sw, &gpio_switch_list, list) {
> +		if (gpio_is_valid(gpio) && sw->gpio != gpio)
> +			continue;
> +		return sw;
> +	}
> +	return NULL;
> +}
> +
> +static struct gpio_sw *gpio_switch_find_by_phandle(struct device *parent,
> +					phandle ph)
> +{
> +	struct gpio_sw *sw;
> +	struct device_node *dp;
> +
> +	dp = of_find_node_by_phandle(ph);
> +	if (dp == NULL)
> +		return NULL;
> +
> +	list_for_each_entry(sw, &gpio_switch_list, list) {
> +		if (sw->parent && sw->parent->of_node == dp)
> +			return sw;
> +	}
> +	return NULL;
> +}
> +
> +static struct gpio_sw *gpio_switch_find_by_id(struct device *parent,
> +					int id)
> +{
> +	struct gpio_sw *sw;
> +
> +	list_for_each_entry(sw, &gpio_switch_list, list) {
> +		if (sw->parent && to_platform_device(sw->parent)->id == id)
> +			return sw;
> +	}
> +	return NULL;
> +}
> +
> +static void gpio_switch_delete(struct gpio_sw *sw)
> +{
> +	list_del_init(&sw->list);
> +	if (!(sw->flags & GPIO_SW_SHARED) ||
> +	    !gpio_switch_find_gpio(sw->parent, sw->gpio))
> +		gpio_free(sw->gpio);
> +}
> +
> +/* GPIO accessor */
> +static void gpio_switch_set_value(struct gpio_sw *sw, int on)
> +{
> +	gpio_set_value(sw->gpio, !on ^ !(sw->flags & GPIO_SW_ACTIVE_LOW));
> +}
> +
> +/* Provider API */
> +int gpio_switch_register(struct device *parent, const char *id, int gpio,
> +			 enum gpio_sw_flags flags)
> +{
> +	int ret;
> +	struct platform_device *pdev;
> +	struct gpio_sw_platform_data *pdata;
> +
> +	if (!gpio_is_valid(gpio)) {
> +		dev_err(parent, "Invalid GPIO %u\n", gpio);
> +		return -EINVAL;
> +	}
> +
> +	pdata = devm_kzalloc(parent, sizeof(*pdata), GFP_KERNEL);
> +	if (!pdata)
> +		return -ENOMEM;
> +
> +	pdata->gpio = gpio;
> +	pdata->flags = flags;
> +
> +	mutex_lock(&gpio_switch_list_lock);
> +	pdev = platform_device_alloc("gpio-switch", gpio_switch_index++);
> +	mutex_unlock(&gpio_switch_list_lock);
> +	if (pdev == NULL)
> +		return -ENOMEM;
> +
> +	pdev->dev.parent = parent;
> +	pdev->dev.platform_data = pdata;
> +
> +	ret = platform_device_add(pdev);
> +	if (ret)
> +		goto pdev_free;
> +
> +	return 0;
> +
> + pdev_free:
> +	platform_device_put(pdev);
> +	return ret;
> +}
> +EXPORT_SYMBOL(gpio_switch_register);
> +
> +int gpio_switch_unregister(struct gpio_sw *sw)
> +{
> +	int ret = -EINVAL;
> +	struct gpio_sw *ptr;
> +
> +	mutex_lock(&gpio_switch_list_lock);
> +	list_for_each_entry(ptr, &gpio_switch_list, list) {
> +		if (sw == ptr) {
> +			gpio_switch_delete(sw);
> +			ret = 0;
> +			break;
> +		}
> +	}
> +	mutex_unlock(&gpio_switch_list_lock);
> +	return ret;
> +}
> +EXPORT_SYMBOL(gpio_switch_unregister);
> +
> +/* Consumer API */
> +struct gpio_sw *request_gpio_switch(struct device *dev, u32 id)
> +{
> +	struct gpio_sw *sw;
> +	struct device_node *np = dev->of_node;
> +
> +	mutex_lock(&gpio_switch_list_lock);
> +	if (np)
> +		sw = gpio_switch_find_by_phandle(dev, id);
> +	else
> +		sw = gpio_switch_find_by_id(dev, id);
> +
> +	if (sw)
> +		sw->use_count++;
> +	mutex_unlock(&gpio_switch_list_lock);
> +
> +	if (sw)
> +		dev_dbg(dev, "Found gpio-switch %p\n", sw);
> +	else
> +		dev_dbg(dev, "No gpio found for ID %08x\n", id);
> +
> +	return sw;
> +}
> +EXPORT_SYMBOL(request_gpio_switch);
> +
> +void free_gpio_switch(struct gpio_sw *sw)
> +{
> +	if (!sw)
> +		return;
> +
> +	mutex_lock(&gpio_switch_list_lock);
> +	sw->use_count--;
> +	mutex_unlock(&gpio_switch_list_lock);
> +}
> +EXPORT_SYMBOL(free_gpio_switch);
> +
> +void __gpio_switch_set(struct gpio_sw *sw, int on)
> +{
> +	if (!(sw->flags & GPIO_SW_SHARED))
> +		gpio_switch_set_value(sw, on);
> +	else
> +		if ((on && (sw->enable_count++ == 0)) ||
> +			(!on && (--sw->enable_count == 0))) {
> +			WARN_ON(sw->enable_count < 0);
> +			gpio_switch_set_value(sw, on);
> +		}
> +}
> +EXPORT_SYMBOL(__gpio_switch_set);
> +
> +void gpio_switch_set_suspend_state(struct gpio_sw *sw, int suspend_state)
> +{
> +	switch (suspend_state) {
> +	case 0:
> +		sw->flags |= GPIO_SW_SUSPEND_OFF;
> +		break;
> +
> +	case 1:
> +		sw->flags |= GPIO_SW_SUSPEND_ON;
> +		break;
> +
> +	default:
> +		sw->flags &= ~(GPIO_SW_SUSPEND_ON | GPIO_SW_SUSPEND_OFF);
> +	}
> +}
> +EXPORT_SYMBOL(gpio_switch_set_suspend_state);
> +
> +void gpio_switch_set_resume_state(struct gpio_sw *sw, int resume_state)
> +{
> +	switch (resume_state) {
> +	case 0:
> +		sw->flags |= GPIO_SW_RESUME_OFF;
> +		break;
> +
> +	case 1:
> +		sw->flags |= GPIO_SW_RESUME_ON;
> +		break;
> +
> +	default:
> +		sw->flags &= ~(GPIO_SW_RESUME_ON | GPIO_SW_RESUME_OFF);
> +	}
> +}
> +EXPORT_SYMBOL(gpio_switch_set_resume_state);
> +
> +/* Driver boilerplate */
> +static int __devinit gpio_switch_platform_probe(struct platform_device *pdev,
> +						struct gpio_sw *sw)
> +{
> +	struct gpio_sw_platform_data *pdata = pdev->dev.platform_data;
> +
> +	sw->gpio = pdata->gpio;
> +	if (!gpio_is_valid(sw->gpio))
> +		return -EINVAL;
> +
> +	sw->flags = pdata->flags;
> +	sw->label = pdata->label;
> +	if (pdata->init_state) {
> +		if (pdata->init_state == GPIO_SW_INIT_ACTIVE)
> +			sw->enable_count++;
> +	}
> +	return 0;
> +}
> +
> +static int __devinit gpio_switch_dt_probe(struct platform_device *pdev,
> +					  struct gpio_sw *sw)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	int gpio;
> +	enum of_gpio_flags gpio_flags;
> +	const u32 *prop;
> +
> +	of_property_read_string(np, "label", &sw->label);
> +
> +	gpio = of_get_named_gpio_flags(np, "gpios", 0, &gpio_flags);
> +	if (!gpio_is_valid(gpio)) {
> +		dev_err(&pdev->dev, "No valid GPIO specified for '%s'\n",
> +			sw->label ?: "unknown");
> +		return -EINVAL;
> +	}
> +
> +	sw->gpio = gpio;
> +	if (gpio_flags & OF_GPIO_ACTIVE_LOW)
> +		sw->flags |= GPIO_SW_ACTIVE_LOW;
> +
> +	if (of_get_property(np, "shared-gpio", NULL))
> +		sw->flags |= GPIO_SW_SHARED;
> +
> +	prop = of_get_property(np, "suspend-state", NULL);
> +	if (prop)
> +		gpio_switch_set_suspend_state(sw, *prop);
> +
> +	prop = of_get_property(np, "resume-state", NULL);
> +	if (prop)
> +		gpio_switch_set_resume_state(sw, *prop);
> +
> +	prop = of_get_property(np, "init-state", NULL);
> +	if (prop) {
> +		if (*prop)
> +			sw->enable_count++;
> +	}
> +	return 0;
> +}
> +
> +static int __devinit gpio_switch_probe(struct platform_device *pdev)
> +{
> +	int ret;
> +	struct gpio_sw_platform_data *pdata = pdev->dev.platform_data;
> +	struct gpio_sw *sw;
> +
> +	sw = devm_kzalloc(&pdev->dev, sizeof(*sw), GFP_KERNEL);
> +	if (!sw)
> +		return -ENOMEM;
> +
> +	if (pdata)
> +		ret = gpio_switch_platform_probe(pdev, sw);
> +	else
> +		ret = gpio_switch_dt_probe(pdev, sw);
> +	if (ret)
> +		return ret;
> +
> +	INIT_LIST_HEAD(&sw->list);
> +	sw->parent = &pdev->dev;
> +
> +	if (!(sw->flags & GPIO_SW_SHARED) ||
> +	    !gpio_switch_find_gpio(sw->parent, sw->gpio)) {
> +		ret = gpio_request(sw->gpio, sw->label);
> +		if (ret) {
> +			dev_err(&pdev->dev, "Failed to request GPIO%d '%s'\n",
> +				sw->gpio, sw->label);
> +			return ret;
> +		}
> +		gpio_direction_output(sw->gpio, !!sw->enable_count ^
> +				!(sw->flags & GPIO_SW_ACTIVE_LOW));
> +	}
> +	platform_set_drvdata(pdev, sw);
> +
> +	mutex_lock(&gpio_switch_list_lock);
> +	list_add(&sw->list, &gpio_switch_list);
> +	mutex_unlock(&gpio_switch_list_lock);
> +
> +	dev_info(&pdev->dev, "GPIO%u registered for '%s'\n",
> +		sw->gpio, sw->label ?: "unknown");
> +	return 0;
> +}
> +
> +static int __devexit gpio_switch_remove(struct platform_device *pdev)
> +{
> +	struct gpio_sw *sw, *tmp;
> +
> +	list_for_each_entry_safe(sw, tmp, &gpio_switch_list, list) {
> +		gpio_switch_unregister(sw);
> +	}
> +	return 0;
> +}
> +
> +static struct of_device_id gpio_switch_dt_ids[] = {
> +	{ .compatible = "linux,gpio-switch", },
> +	{ /* sentinel */ }
> +};
> +
> +struct platform_driver gpio_switch_driver = {
> +	.driver = {
> +		.name = "gpio-switch",
> +		.owner = THIS_MODULE,
> +		.of_match_table = gpio_switch_dt_ids,
> +	},
> +	.probe = gpio_switch_probe,
> +	.remove = __devexit_p(gpio_switch_remove),
> +};
> +
> +static int __init gpio_switch_init(void)
> +{
> +	return platform_driver_register(&gpio_switch_driver);
> +}
> +arch_initcall(gpio_switch_init);
> +
> +static void __exit gpio_switch_exit(void)
> +{
> +	platform_driver_unregister(&gpio_switch_driver);
> +}
> +module_exit(gpio_switch_exit);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Lother Waßmann <LW at KARO-electronics.de>");
> +MODULE_DESCRIPTION("Generic GPIO switch driver");
> +MODULE_ALIAS("platform:gpio_switch");
> diff --git a/include/linux/gpio-switch.h b/include/linux/gpio-switch.h
> new file mode 100644
> index 0000000..23893ab
> --- /dev/null
> +++ b/include/linux/gpio-switch.h
> @@ -0,0 +1,47 @@
> +#ifndef LINUX_GPIO_SWITCH_H
> +#define LINUX_GPIO_SWITCH_H
> +
> +enum gpio_sw_flags {
> +	GPIO_SW_ACTIVE_LOW	= (1 << 0),
> +	GPIO_SW_SHARED		= (1 << 1),
> +	GPIO_SW_SUSPEND_ON	= (1 << 2),
> +	GPIO_SW_SUSPEND_OFF	= (1 << 3),
> +	GPIO_SW_RESUME_ON	= (1 << 4),
> +	GPIO_SW_RESUME_OFF	= (1 << 5),
> +};
> +
> +enum gpio_sw_initstate {
> +	GPIO_SW_INIT_NONE,
> +	GPIO_SW_INIT_ACTIVE,
> +	GPIO_SW_INIT_INACTIVE,
> +};
> +
> +struct gpio_sw_platform_data {
> +	const char *label;
> +	int gpio;
> +	enum gpio_sw_flags flags;
> +	int init_state;
> +};
> +
> +struct gpio_sw;
> +
> +extern int gpio_switch_register(struct device *parent, const char *id, int gpio,
> +			 enum gpio_sw_flags flags);
> +
> +extern int gpio_switch_unregister(struct gpio_sw *sw);
> +extern struct gpio_sw *request_gpio_switch(struct device *dev, u32 id);
> +extern void free_gpio_switch(struct gpio_sw *sw);
> +extern void __gpio_switch_set(struct gpio_sw *sw, int on);
> +
> +static inline void gpio_switch_set(struct gpio_sw *sw, int on)
> +{
> +	if (!sw)
> +		return;
> +
> +	__gpio_switch_set(sw, on);
> +}
> +
> +extern void gpio_switch_set_suspend_state(struct gpio_sw *sw, int suspend_state);
> +extern void gpio_switch_set_resume_state(struct gpio_sw *sw, int resume_state);
> +
> +#endif
> 


-- 
Pengutronix e.K.                  | Marc Kleine-Budde           |
Industrial Linux Solutions        | Phone: +49-231-2826-924     |
Vertretung West/Dortmund          | Fax:   +49-5121-206917-5555 |
Amtsgericht Hildesheim, HRA 2686  | http://www.pengutronix.de   |

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 262 bytes
Desc: OpenPGP digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120806/1a84a31c/attachment.sig>


More information about the linux-arm-kernel mailing list