Message ID | 1340961676-25432-1-git-send-email-LW@KARO-electronics.de |
---|---|
State | New |
Headers | show |
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
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
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
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 --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
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