diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-10-23 08:45:05 +0100 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-10-23 08:45:05 +0100 |
commit | 114b5f8f7efc036dd7dd16efb0f218a88e6c6c02 (patch) | |
tree | ac02a222661b8b0bfb87f15773dc1d23220edfd9 /drivers/gpio/gpio-siox.c | |
parent | b0b6a28bc4b265aa56cbf4fa8fd27c0a4fa3a49c (diff) | |
parent | 40f5ff4f9f23a849ad135cb736d4d448d810ac17 (diff) |
Merge tag 'gpio-v4.20-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio
Pull GPIO updates from Linus Walleij:
"This is the bulk of GPIO changes for the v4.20 series:
Core changes:
- A patch series from Hans Verkuil to make it possible to
enable/disable IRQs on a GPIO line at runtime and drive GPIO lines
as output without having to put/get them from scratch.
The irqchip callbacks have been improved so that they can use only
the fastpatch callbacks to enable/disable irqs like any normal
irqchip, especially the gpiod_lock_as_irq() has been improved to be
callable in fastpath context.
A bunch of rework had to be done to achieve this but it is a big
win since I never liked to restrict this to slowpath. The only call
requireing slowpath was try_module_get() and this is kept at the
.request_resources() slowpath callback. In the GPIO CEC driver this
is a big win sine a single line is used for both outgoing and
incoming traffic, and this needs to use IRQs for incoming traffic
while actively driving the line for outgoing traffic.
- Janusz Krzysztofik improved the GPIO array API to pass a "cookie"
(struct gpio_array) and a bitmap for setting or getting multiple
GPIO lines at once.
This improvement orginated in a specific need to speed up an OMAP1
driver and has led to a much better API and real performance gains
when the state of the array can be used to bypass a lot of checks
and code when we want things to go really fast.
The previous code would minimize the number of calls down to the
driver callbacks assuming the CPU speed was orders of magnitude
faster than the I/O latency, but this assumption was wrong on
several platforms: what we needed to do was to profile and improve
the speed on the hot path of the array functions and this change is
now completed.
- Clean out the painful and hard to grasp BNF experiments from the
device tree bindings. Future approaches are looking into using JSON
schema for this purpose. (Rob Herring is floating a patch series.)
New drivers:
- The RCAR driver now supports r8a774a1 (RZ/G2M).
- Synopsys GPIO via CREGs driver.
Major improvements:
- Modernization of the EP93xx driver to use irqdomain and other
contemporary concepts.
- The ingenic driver has been merged into the Ingenic pin control
driver and removed from the GPIO subsystem.
- Debounce support in the ftgpio010 driver"
* tag 'gpio-v4.20-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (116 commits)
gpio: Clarify kerneldoc on gpiochip_set_chained_irqchip()
gpio: Remove unused 'irqchip' argument to gpiochip_set_cascaded_irqchip()
gpio: Drop parent irq assignment during cascade setup
mmc: pwrseq_simple: Fix incorrect handling of GPIO bitmap
gpio: fix SNPS_CREG kconfig dependency warning
gpiolib: Initialize gdev field before is used
gpio: fix kernel-doc after devres.c file rename
gpio: fix doc string for devm_gpiochip_add_data() to not talk about irq_chip
gpio: syscon: Fix possible NULL ptr usage
gpiolib: Show correct direction from the beginning
pinctrl: msm: Use init_valid_mask exported function
gpiolib: Add init_valid_mask exported function
GPIO: add single-register GPIO via CREG driver
dt-bindings: Document the Synopsys GPIO via CREG bindings
gpio: mockup: use device properties instead of platform_data
gpio: Slightly more helpful debugfs
gpio: omap: Remove set but not used variable 'dev'
gpio: omap: drop omap_gpio_list
Accept partial 'gpio-line-names' property.
gpio: omap: get rid of the conditional PM runtime calls
...
Diffstat (limited to 'drivers/gpio/gpio-siox.c')
-rw-r--r-- | drivers/gpio/gpio-siox.c | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-siox.c b/drivers/gpio/gpio-siox.c new file mode 100644 index 000000000000..571b2a81c6de --- /dev/null +++ b/drivers/gpio/gpio-siox.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2015-2018 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de> + */ + +#include <linux/module.h> +#include <linux/siox.h> +#include <linux/gpio/driver.h> +#include <linux/of.h> + +struct gpio_siox_ddata { + struct gpio_chip gchip; + struct irq_chip ichip; + struct mutex lock; + u8 setdata[1]; + u8 getdata[3]; + + spinlock_t irqlock; + u32 irq_enable; + u32 irq_status; + u32 irq_type[20]; +}; + +/* + * Note that this callback only sets the value that is clocked out in the next + * cycle. + */ +static int gpio_siox_set_data(struct siox_device *sdevice, u8 status, u8 buf[]) +{ + struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev); + + mutex_lock(&ddata->lock); + buf[0] = ddata->setdata[0]; + mutex_unlock(&ddata->lock); + + return 0; +} + +static int gpio_siox_get_data(struct siox_device *sdevice, const u8 buf[]) +{ + struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev); + size_t offset; + u32 trigger; + + mutex_lock(&ddata->lock); + + spin_lock_irq(&ddata->irqlock); + + for (offset = 0; offset < 12; ++offset) { + unsigned int bitpos = 11 - offset; + unsigned int gpiolevel = buf[bitpos / 8] & (1 << bitpos % 8); + unsigned int prev_level = + ddata->getdata[bitpos / 8] & (1 << (bitpos % 8)); + u32 irq_type = ddata->irq_type[offset]; + + if (gpiolevel) { + if ((irq_type & IRQ_TYPE_LEVEL_HIGH) || + ((irq_type & IRQ_TYPE_EDGE_RISING) && !prev_level)) + ddata->irq_status |= 1 << offset; + } else { + if ((irq_type & IRQ_TYPE_LEVEL_LOW) || + ((irq_type & IRQ_TYPE_EDGE_FALLING) && prev_level)) + ddata->irq_status |= 1 << offset; + } + } + + trigger = ddata->irq_status & ddata->irq_enable; + + spin_unlock_irq(&ddata->irqlock); + + ddata->getdata[0] = buf[0]; + ddata->getdata[1] = buf[1]; + ddata->getdata[2] = buf[2]; + + mutex_unlock(&ddata->lock); + + for (offset = 0; offset < 12; ++offset) { + if (trigger & (1 << offset)) { + struct irq_domain *irqdomain = ddata->gchip.irq.domain; + unsigned int irq = irq_find_mapping(irqdomain, offset); + + /* + * Conceptually handle_nested_irq should call the flow + * handler of the irq chip. But it doesn't, so we have + * to clean the irq_status here. + */ + spin_lock_irq(&ddata->irqlock); + ddata->irq_status &= ~(1 << offset); + spin_unlock_irq(&ddata->irqlock); + + handle_nested_irq(irq); + } + } + + return 0; +} + +static void gpio_siox_irq_ack(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct gpio_siox_ddata *ddata = + container_of(ic, struct gpio_siox_ddata, ichip); + + spin_lock_irq(&ddata->irqlock); + ddata->irq_status &= ~(1 << d->hwirq); + spin_unlock_irq(&ddata->irqlock); +} + +static void gpio_siox_irq_mask(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct gpio_siox_ddata *ddata = + container_of(ic, struct gpio_siox_ddata, ichip); + + spin_lock_irq(&ddata->irqlock); + ddata->irq_enable &= ~(1 << d->hwirq); + spin_unlock_irq(&ddata->irqlock); +} + +static void gpio_siox_irq_unmask(struct irq_data *d) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct gpio_siox_ddata *ddata = + container_of(ic, struct gpio_siox_ddata, ichip); + + spin_lock_irq(&ddata->irqlock); + ddata->irq_enable |= 1 << d->hwirq; + spin_unlock_irq(&ddata->irqlock); +} + +static int gpio_siox_irq_set_type(struct irq_data *d, u32 type) +{ + struct irq_chip *ic = irq_data_get_irq_chip(d); + struct gpio_siox_ddata *ddata = + container_of(ic, struct gpio_siox_ddata, ichip); + + spin_lock_irq(&ddata->irqlock); + ddata->irq_type[d->hwirq] = type; + spin_unlock_irq(&ddata->irqlock); + + return 0; +} + +static int gpio_siox_get(struct gpio_chip *chip, unsigned int offset) +{ + struct gpio_siox_ddata *ddata = + container_of(chip, struct gpio_siox_ddata, gchip); + int ret; + + mutex_lock(&ddata->lock); + + if (offset >= 12) { + unsigned int bitpos = 19 - offset; + + ret = ddata->setdata[0] & (1 << bitpos); + } else { + unsigned int bitpos = 11 - offset; + + ret = ddata->getdata[bitpos / 8] & (1 << (bitpos % 8)); + } + + mutex_unlock(&ddata->lock); + + return ret; +} + +static void gpio_siox_set(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct gpio_siox_ddata *ddata = + container_of(chip, struct gpio_siox_ddata, gchip); + u8 mask = 1 << (19 - offset); + + mutex_lock(&ddata->lock); + + if (value) + ddata->setdata[0] |= mask; + else + ddata->setdata[0] &= ~mask; + + mutex_unlock(&ddata->lock); +} + +static int gpio_siox_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + if (offset >= 12) + return -EINVAL; + + return 0; +} + +static int gpio_siox_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + if (offset < 12) + return -EINVAL; + + gpio_siox_set(chip, offset, value); + return 0; +} + +static int gpio_siox_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + if (offset < 12) + return 1; /* input */ + else + return 0; /* output */ +} + +static int gpio_siox_probe(struct siox_device *sdevice) +{ + struct gpio_siox_ddata *ddata; + int ret; + + ddata = devm_kzalloc(&sdevice->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + dev_set_drvdata(&sdevice->dev, ddata); + + mutex_init(&ddata->lock); + spin_lock_init(&ddata->irqlock); + + ddata->gchip.base = -1; + ddata->gchip.can_sleep = 1; + ddata->gchip.parent = &sdevice->dev; + ddata->gchip.owner = THIS_MODULE; + ddata->gchip.get = gpio_siox_get; + ddata->gchip.set = gpio_siox_set; + ddata->gchip.direction_input = gpio_siox_direction_input; + ddata->gchip.direction_output = gpio_siox_direction_output; + ddata->gchip.get_direction = gpio_siox_get_direction; + ddata->gchip.ngpio = 20; + + ddata->ichip.name = "siox-gpio"; + ddata->ichip.irq_ack = gpio_siox_irq_ack; + ddata->ichip.irq_mask = gpio_siox_irq_mask; + ddata->ichip.irq_unmask = gpio_siox_irq_unmask; + ddata->ichip.irq_set_type = gpio_siox_irq_set_type; + + ret = gpiochip_add(&ddata->gchip); + if (ret) { + dev_err(&sdevice->dev, + "Failed to register gpio chip (%d)\n", ret); + goto err_gpiochip; + } + + ret = gpiochip_irqchip_add(&ddata->gchip, &ddata->ichip, + 0, handle_level_irq, IRQ_TYPE_EDGE_RISING); + if (ret) { + dev_err(&sdevice->dev, + "Failed to register irq chip (%d)\n", ret); +err_gpiochip: + gpiochip_remove(&ddata->gchip); + } + + return ret; +} + +static int gpio_siox_remove(struct siox_device *sdevice) +{ + struct gpio_siox_ddata *ddata = dev_get_drvdata(&sdevice->dev); + + gpiochip_remove(&ddata->gchip); + return 0; +} + +static struct siox_driver gpio_siox_driver = { + .probe = gpio_siox_probe, + .remove = gpio_siox_remove, + .set_data = gpio_siox_set_data, + .get_data = gpio_siox_get_data, + .driver = { + .name = "gpio-siox", + }, +}; + +static int __init gpio_siox_init(void) +{ + return siox_driver_register(&gpio_siox_driver); +} +module_init(gpio_siox_init); + +static void __exit gpio_siox_exit(void) +{ + siox_driver_unregister(&gpio_siox_driver); +} +module_exit(gpio_siox_exit); + +MODULE_AUTHOR("Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>"); +MODULE_DESCRIPTION("SIOX gpio driver"); +MODULE_LICENSE("GPL v2"); |