diff mbox

[RFC] gpio: Add a generic GPIO handling driver

Message ID 1340961676-25432-1-git-send-email-LW@KARO-electronics.de
State New
Headers show

Commit Message

Lothar Waßmann June 29, 2012, 9:21 a.m. UTC
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):

in the .dts file:
	flexcan_transceiver: gpio-switch@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));
		}
	}
...
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@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

Comments

Shawn Guo July 3, 2012, 7:14 a.m. UTC | #1
On Fri, Jun 29, 2012 at 11:21:16AM +0200, 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):
> 
> in the .dts file:
> 	flexcan_transceiver: gpio-switch@0 {
> 		compatible = "linux,gpio-switch";

Why not simply "gpio-switch"?

> 		gpio = <&gpio4 21 1>; /* GPIO is active low */
> 		label = "Flexcan Transceiver Enable";
> 		gpio-shared;
> 		init-state = <0>; /* Transceiver will be initially inactive */
> 	};
> 
It looks like the design intends to have every single gpio-switch
probed as a platform_device.  I'm not sure it's a good idea.

Have something like below in .dts:

gpio-switches {
 	compatible = "gpio-switch";

 	can_xcvr_en: can-xcvr-en {
 		label = "Flexcan Transceiver Enable";
 		gpio = <&gpio4 21 1>;
		...
 	};

 	usb_hub_en: usb-hub-en {
 		label = "USB Hub Enable";
 		gpio = <&gpio2 11 0>;
		...
 	};

	...
};
 
We will be able to probe gpio-switch device only once and have the
driver enumerate every sub-nodes as a gpio-switch provider.

Also I'm not sure it's preferable to have such shinny new stuff support
platform probing, which adds much unnecessary complexity, considering
we are on the way to device tree.  If we support device tree only,
a lot of codes like provider API will not be needed.

> 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);

I would have a fixed property name under client device node
representing the phandle to gpio-switch sub-node, like what we are
doing with clk binding.

> 		if (ph) {
> 			xcvr_switch = request_gpio_switch(&pdev->dev,
> 					be32_to_cpu(*ph));

Then, client driver does not need to look at the phandle property at
all, and gpio-switch driver will look for phandle using the fixed name.

Like clk API, we may need to change the second parameter of
request_gpio_switch to be the name of the switch requested to support
the client devices that have multiple switches.

> 		}
> 	}
> ...
> 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);

Can we have a devm_request_gpio_switch to ease the client driver
a little bit?

Regards,
Shawn

> 
> Signed-off-by: Lothar Waßmann <LW@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
Lothar Waßmann July 27, 2012, 5:25 a.m. UTC | #2
Hi Shawn,

sorry, I obviuosly missed your reply and had to pick it up from the
archives.

> On Fri, Jun 29, 2012 at 11:21:16AM +0200, Lothar Waßmann wrote:
>> in the .dts file:
>> 	flexcan_transceiver: gpio-switch at 0 {
>> 		compatible = "linux,gpio-switch";
> 
> Why not simply "gpio-switch"?
> 
OK.

>> 		gpio = <&gpio4 21 1>; /* GPIO is active low */
>> 		label = "Flexcan Transceiver Enable";
>> 		gpio-shared;
>> 		init-state = <0>; /* Transceiver will be initially inactive */
>> 	};
>> 
> It looks like the design intends to have every single gpio-switch
> probed as a platform_device.  I'm not sure it's a good idea.
> 
I considered that myself already.

> Also I'm not sure it's preferable to have such shinny new stuff support
> platform probing, which adds much unnecessary complexity, considering
> we are on the way to device tree.  If we support device tree only,
> a lot of codes like provider API will not be needed.
> 
There are still archs that do not use DT but could still benefit from
this driver.

>> 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);
> 
> I would have a fixed property name under client device node
> representing the phandle to gpio-switch sub-node, like what we are
> doing with clk binding.
> 
OK.

>> flexcan_remove()
>>     free_gpio_switch(xcvr_switch);
> 
> Can we have a devm_request_gpio_switch to ease the client driver
> a little bit?
> 
Sure.

I'll try to get a new version out over the weekend, if there is still
interest in this driver.


Lothar Waßmann
Linus Walleij Aug. 5, 2012, 9:53 a.m. UTC | #3
On Fri, Jun 29, 2012 at 11:21 AM, Lothar Waßmann <LW@karo-electronics.de> 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.

I sort of like the idea, but I want a solid use case, i.e. an indication
of platform using this, and patches in the works. So I want atleast
one more patch making real use of the mechanism, or we just
gain weight in the kernel for no use.

Also, the introduction message should be merged into
Documentation/gpio.txt so people can easily find it.

I'd ideally like Grant's feedback on this too, since it's a major thing.

Yours,
Linus Walleij
Marc Kleine-Budde Aug. 6, 2012, 12:27 p.m. UTC | #4
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@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@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@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@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@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
>
diff mbox

Patch

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@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@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@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