mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
2ec98f5678
Core: - When a gpio_chip request GPIOs from itself, it can now fully control the line characteristics, both machine and consumer flags. This makes a lot of sense, but took some time before I figured out that this is how it has to work. - Several smallish documentation fixes. New drivers: - The PCA953x driver now supports the TI TCA9539. - The DaVinci driver now supports the K3 AM654 SoCs. Driver improvements: - Major overhaul and hardening of the OMAP driver by Russell King. - Starting to move some drivers to the new API passing irq_chip along with the gpio_chip when adding the gpio_chip instead of adding it separately. Unrelated: - Delete the FMC subsystem. -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEElDRnuGcz/wPCXQWMQRCzN7AZXXMFAl0i7gEACgkQQRCzN7AZ XXOeUA/+JKyI2zebTWBcgtxhn6VQCufMCtFmQl2JkEcy4pT7aBJcGWqFQCBW2Szf VTtqc8nNa90SZoOzsNbkeQgRjNKGZruMbh0ARUPcW4v3ZJHtUNUEDLTo8c3iyTgS 9k/FTeaTLt4WSZujeAO0O7G4KNnOOlTKLh58dr0PmXUR+0v+fbMhcJqJ9ABueV+V qENdpkTuG1ZcvzgLhBBEXdt3Plw9ICLWmPXtwY+784ewucVPbyQX7jV4+bBZ25fL DerCuMIgL5vRWWdiFO6/Jp603rHzZpTnjLJJocXUFiD6zA5rvU2jTWxsnUttjisg 8cTLMyQspsDvBxhEhCJVTuIKotbKH900TSaz+vx20W72/A1euy4y6uVi8FGZo4Ww KDkzB7anwHyEFKGnlYgHzDrfctgZrhQoyFz808DQRYg1JseZB5oGVDvScrPBD43j nbNDd8gwG4yp3tFnDx9xjIwQy3Ax4d510rAZyUN2801IlbA1bueq4t6Z2cCucWzX XA1gCKlXe4BUeitRAoZtqZNZG1ymEysW4jXy1V8xrwtAf8+QSN+xO98akz3VpnQL ae9q+HtF76fDBY1xFSXT37Ma3+4OR2vMF9QWuo4TCb9j1cL7llf8ZxtUq9LEHbDu erKLSSnwSFmqJNGSEA5SulGOCR/tRPkClngE9x0XEM6gOD+bs6E= =8zSV -----END PGP SIGNATURE----- Merge tag 'gpio-v5.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio Pull GPIO updates from Linus Walleij: "This is the big slew of GPIO changes for the v5.3 kernel cycle. This is mostly incremental work this time. Three important things: - The FMC subsystem is deleted through my tree. This happens through GPIO as its demise was discussed in relation to a patch decoupling its GPIO implementation from the standard way of handling GPIO. As it turns out, that is not the only subsystem it reimplements and the authors think it is better do scratch it and start over using the proper kernel subsystems than try to polish the rust shiny. See the commit (ACKed by the maintainers) for details. - Arnd made a small devres patch that was ACKed by Greg and goes into the device core. - SPDX header change colissions may happen, because at times I've seen that quite a lot changed during the -rc:s in regards to SPDX. (It is good stuff, tglx has me convinced, and it is worth the occasional pain.) Apart from this is is nothing controversial or problematic. Summary: Core: - When a gpio_chip request GPIOs from itself, it can now fully control the line characteristics, both machine and consumer flags. This makes a lot of sense, but took some time before I figured out that this is how it has to work. - Several smallish documentation fixes. New drivers: - The PCA953x driver now supports the TI TCA9539. - The DaVinci driver now supports the K3 AM654 SoCs. Driver improvements: - Major overhaul and hardening of the OMAP driver by Russell King. - Starting to move some drivers to the new API passing irq_chip along with the gpio_chip when adding the gpio_chip instead of adding it separately. Unrelated: - Delete the FMC subsystem" * tag 'gpio-v5.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio: (87 commits) Revert "gpio: tegra: Clean-up debugfs initialisation" gpiolib: Use spinlock_t instead of struct spinlock gpio: stp-xway: allow compile-testing gpio: stp-xway: get rid of the #include <lantiq_soc.h> dependency gpio: stp-xway: improve module clock error handling gpio: stp-xway: simplify error handling in xway_stp_probe() gpiolib: Clarify use of non-sleeping functions gpiolib: Fix references to gpiod_[gs]et_*value_cansleep() variants gpiolib: Document new gpio_chip.init_valid_mask field Documentation: gpio: Fix reference to gpiod_get_array() gpio: pl061: drop duplicate printing of device name gpio: altera: Pass irqchip when adding gpiochip gpio: siox: Use devm_ managed gpiochip gpio: siox: Add struct device *dev helper variable gpio: siox: Pass irqchip when adding gpiochip drivers: gpio: amd-fch: make resource struct const devres: allow const resource arguments gpio: ath79: Pass irqchip when adding gpiochip gpio: tegra: Clean-up debugfs initialisation gpio: siox: Switch to IRQ_TYPE_NONE ...
430 lines
11 KiB
C
430 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2008, 2009 Provigent Ltd.
|
|
*
|
|
* Author: Baruch Siach <baruch@tkos.co.il>
|
|
*
|
|
* Driver for the ARM PrimeCell(tm) General Purpose Input/Output (PL061)
|
|
*
|
|
* Data sheet: ARM DDI 0190B, September 2000
|
|
*/
|
|
#include <linux/spinlock.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/device.h>
|
|
#include <linux/amba/bus.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/pm.h>
|
|
|
|
#define GPIODIR 0x400
|
|
#define GPIOIS 0x404
|
|
#define GPIOIBE 0x408
|
|
#define GPIOIEV 0x40C
|
|
#define GPIOIE 0x410
|
|
#define GPIORIS 0x414
|
|
#define GPIOMIS 0x418
|
|
#define GPIOIC 0x41C
|
|
|
|
#define PL061_GPIO_NR 8
|
|
|
|
#ifdef CONFIG_PM
|
|
struct pl061_context_save_regs {
|
|
u8 gpio_data;
|
|
u8 gpio_dir;
|
|
u8 gpio_is;
|
|
u8 gpio_ibe;
|
|
u8 gpio_iev;
|
|
u8 gpio_ie;
|
|
};
|
|
#endif
|
|
|
|
struct pl061 {
|
|
raw_spinlock_t lock;
|
|
|
|
void __iomem *base;
|
|
struct gpio_chip gc;
|
|
struct irq_chip irq_chip;
|
|
int parent_irq;
|
|
|
|
#ifdef CONFIG_PM
|
|
struct pl061_context_save_regs csave_regs;
|
|
#endif
|
|
};
|
|
|
|
static int pl061_get_direction(struct gpio_chip *gc, unsigned offset)
|
|
{
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
|
|
return !(readb(pl061->base + GPIODIR) & BIT(offset));
|
|
}
|
|
|
|
static int pl061_direction_input(struct gpio_chip *gc, unsigned offset)
|
|
{
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
unsigned long flags;
|
|
unsigned char gpiodir;
|
|
|
|
raw_spin_lock_irqsave(&pl061->lock, flags);
|
|
gpiodir = readb(pl061->base + GPIODIR);
|
|
gpiodir &= ~(BIT(offset));
|
|
writeb(gpiodir, pl061->base + GPIODIR);
|
|
raw_spin_unlock_irqrestore(&pl061->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pl061_direction_output(struct gpio_chip *gc, unsigned offset,
|
|
int value)
|
|
{
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
unsigned long flags;
|
|
unsigned char gpiodir;
|
|
|
|
raw_spin_lock_irqsave(&pl061->lock, flags);
|
|
writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
|
|
gpiodir = readb(pl061->base + GPIODIR);
|
|
gpiodir |= BIT(offset);
|
|
writeb(gpiodir, pl061->base + GPIODIR);
|
|
|
|
/*
|
|
* gpio value is set again, because pl061 doesn't allow to set value of
|
|
* a gpio pin before configuring it in OUT mode.
|
|
*/
|
|
writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
|
|
raw_spin_unlock_irqrestore(&pl061->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pl061_get_value(struct gpio_chip *gc, unsigned offset)
|
|
{
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
|
|
return !!readb(pl061->base + (BIT(offset + 2)));
|
|
}
|
|
|
|
static void pl061_set_value(struct gpio_chip *gc, unsigned offset, int value)
|
|
{
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
|
|
writeb(!!value << offset, pl061->base + (BIT(offset + 2)));
|
|
}
|
|
|
|
static int pl061_irq_type(struct irq_data *d, unsigned trigger)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
int offset = irqd_to_hwirq(d);
|
|
unsigned long flags;
|
|
u8 gpiois, gpioibe, gpioiev;
|
|
u8 bit = BIT(offset);
|
|
|
|
if (offset < 0 || offset >= PL061_GPIO_NR)
|
|
return -EINVAL;
|
|
|
|
if ((trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) &&
|
|
(trigger & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)))
|
|
{
|
|
dev_err(gc->parent,
|
|
"trying to configure line %d for both level and edge "
|
|
"detection, choose one!\n",
|
|
offset);
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
raw_spin_lock_irqsave(&pl061->lock, flags);
|
|
|
|
gpioiev = readb(pl061->base + GPIOIEV);
|
|
gpiois = readb(pl061->base + GPIOIS);
|
|
gpioibe = readb(pl061->base + GPIOIBE);
|
|
|
|
if (trigger & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) {
|
|
bool polarity = trigger & IRQ_TYPE_LEVEL_HIGH;
|
|
|
|
/* Disable edge detection */
|
|
gpioibe &= ~bit;
|
|
/* Enable level detection */
|
|
gpiois |= bit;
|
|
/* Select polarity */
|
|
if (polarity)
|
|
gpioiev |= bit;
|
|
else
|
|
gpioiev &= ~bit;
|
|
irq_set_handler_locked(d, handle_level_irq);
|
|
dev_dbg(gc->parent, "line %d: IRQ on %s level\n",
|
|
offset,
|
|
polarity ? "HIGH" : "LOW");
|
|
} else if ((trigger & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) {
|
|
/* Disable level detection */
|
|
gpiois &= ~bit;
|
|
/* Select both edges, setting this makes GPIOEV be ignored */
|
|
gpioibe |= bit;
|
|
irq_set_handler_locked(d, handle_edge_irq);
|
|
dev_dbg(gc->parent, "line %d: IRQ on both edges\n", offset);
|
|
} else if ((trigger & IRQ_TYPE_EDGE_RISING) ||
|
|
(trigger & IRQ_TYPE_EDGE_FALLING)) {
|
|
bool rising = trigger & IRQ_TYPE_EDGE_RISING;
|
|
|
|
/* Disable level detection */
|
|
gpiois &= ~bit;
|
|
/* Clear detection on both edges */
|
|
gpioibe &= ~bit;
|
|
/* Select edge */
|
|
if (rising)
|
|
gpioiev |= bit;
|
|
else
|
|
gpioiev &= ~bit;
|
|
irq_set_handler_locked(d, handle_edge_irq);
|
|
dev_dbg(gc->parent, "line %d: IRQ on %s edge\n",
|
|
offset,
|
|
rising ? "RISING" : "FALLING");
|
|
} else {
|
|
/* No trigger: disable everything */
|
|
gpiois &= ~bit;
|
|
gpioibe &= ~bit;
|
|
gpioiev &= ~bit;
|
|
irq_set_handler_locked(d, handle_bad_irq);
|
|
dev_warn(gc->parent, "no trigger selected for line %d\n",
|
|
offset);
|
|
}
|
|
|
|
writeb(gpiois, pl061->base + GPIOIS);
|
|
writeb(gpioibe, pl061->base + GPIOIBE);
|
|
writeb(gpioiev, pl061->base + GPIOIEV);
|
|
|
|
raw_spin_unlock_irqrestore(&pl061->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pl061_irq_handler(struct irq_desc *desc)
|
|
{
|
|
unsigned long pending;
|
|
int offset;
|
|
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
struct irq_chip *irqchip = irq_desc_get_chip(desc);
|
|
|
|
chained_irq_enter(irqchip, desc);
|
|
|
|
pending = readb(pl061->base + GPIOMIS);
|
|
if (pending) {
|
|
for_each_set_bit(offset, &pending, PL061_GPIO_NR)
|
|
generic_handle_irq(irq_find_mapping(gc->irq.domain,
|
|
offset));
|
|
}
|
|
|
|
chained_irq_exit(irqchip, desc);
|
|
}
|
|
|
|
static void pl061_irq_mask(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
|
|
u8 gpioie;
|
|
|
|
raw_spin_lock(&pl061->lock);
|
|
gpioie = readb(pl061->base + GPIOIE) & ~mask;
|
|
writeb(gpioie, pl061->base + GPIOIE);
|
|
raw_spin_unlock(&pl061->lock);
|
|
}
|
|
|
|
static void pl061_irq_unmask(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
|
|
u8 gpioie;
|
|
|
|
raw_spin_lock(&pl061->lock);
|
|
gpioie = readb(pl061->base + GPIOIE) | mask;
|
|
writeb(gpioie, pl061->base + GPIOIE);
|
|
raw_spin_unlock(&pl061->lock);
|
|
}
|
|
|
|
/**
|
|
* pl061_irq_ack() - ACK an edge IRQ
|
|
* @d: IRQ data for this IRQ
|
|
*
|
|
* This gets called from the edge IRQ handler to ACK the edge IRQ
|
|
* in the GPIOIC (interrupt-clear) register. For level IRQs this is
|
|
* not needed: these go away when the level signal goes away.
|
|
*/
|
|
static void pl061_irq_ack(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
u8 mask = BIT(irqd_to_hwirq(d) % PL061_GPIO_NR);
|
|
|
|
raw_spin_lock(&pl061->lock);
|
|
writeb(mask, pl061->base + GPIOIC);
|
|
raw_spin_unlock(&pl061->lock);
|
|
}
|
|
|
|
static int pl061_irq_set_wake(struct irq_data *d, unsigned int state)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct pl061 *pl061 = gpiochip_get_data(gc);
|
|
|
|
return irq_set_irq_wake(pl061->parent_irq, state);
|
|
}
|
|
|
|
static int pl061_probe(struct amba_device *adev, const struct amba_id *id)
|
|
{
|
|
struct device *dev = &adev->dev;
|
|
struct pl061 *pl061;
|
|
struct gpio_irq_chip *girq;
|
|
int ret, irq;
|
|
|
|
pl061 = devm_kzalloc(dev, sizeof(*pl061), GFP_KERNEL);
|
|
if (pl061 == NULL)
|
|
return -ENOMEM;
|
|
|
|
pl061->base = devm_ioremap_resource(dev, &adev->res);
|
|
if (IS_ERR(pl061->base))
|
|
return PTR_ERR(pl061->base);
|
|
|
|
raw_spin_lock_init(&pl061->lock);
|
|
if (of_property_read_bool(dev->of_node, "gpio-ranges")) {
|
|
pl061->gc.request = gpiochip_generic_request;
|
|
pl061->gc.free = gpiochip_generic_free;
|
|
}
|
|
|
|
pl061->gc.base = -1;
|
|
pl061->gc.get_direction = pl061_get_direction;
|
|
pl061->gc.direction_input = pl061_direction_input;
|
|
pl061->gc.direction_output = pl061_direction_output;
|
|
pl061->gc.get = pl061_get_value;
|
|
pl061->gc.set = pl061_set_value;
|
|
pl061->gc.ngpio = PL061_GPIO_NR;
|
|
pl061->gc.label = dev_name(dev);
|
|
pl061->gc.parent = dev;
|
|
pl061->gc.owner = THIS_MODULE;
|
|
|
|
/*
|
|
* irq_chip support
|
|
*/
|
|
pl061->irq_chip.name = dev_name(dev);
|
|
pl061->irq_chip.irq_ack = pl061_irq_ack;
|
|
pl061->irq_chip.irq_mask = pl061_irq_mask;
|
|
pl061->irq_chip.irq_unmask = pl061_irq_unmask;
|
|
pl061->irq_chip.irq_set_type = pl061_irq_type;
|
|
pl061->irq_chip.irq_set_wake = pl061_irq_set_wake;
|
|
|
|
writeb(0, pl061->base + GPIOIE); /* disable irqs */
|
|
irq = adev->irq[0];
|
|
if (irq < 0) {
|
|
dev_err(&adev->dev, "invalid IRQ\n");
|
|
return -ENODEV;
|
|
}
|
|
pl061->parent_irq = irq;
|
|
|
|
girq = &pl061->gc.irq;
|
|
girq->chip = &pl061->irq_chip;
|
|
girq->parent_handler = pl061_irq_handler;
|
|
girq->num_parents = 1;
|
|
girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
|
|
GFP_KERNEL);
|
|
if (!girq->parents)
|
|
return -ENOMEM;
|
|
girq->parents[0] = irq;
|
|
girq->default_type = IRQ_TYPE_NONE;
|
|
girq->handler = handle_bad_irq;
|
|
|
|
ret = devm_gpiochip_add_data(dev, &pl061->gc, pl061);
|
|
if (ret)
|
|
return ret;
|
|
|
|
amba_set_drvdata(adev, pl061);
|
|
dev_info(dev, "PL061 GPIO chip registered\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int pl061_suspend(struct device *dev)
|
|
{
|
|
struct pl061 *pl061 = dev_get_drvdata(dev);
|
|
int offset;
|
|
|
|
pl061->csave_regs.gpio_data = 0;
|
|
pl061->csave_regs.gpio_dir = readb(pl061->base + GPIODIR);
|
|
pl061->csave_regs.gpio_is = readb(pl061->base + GPIOIS);
|
|
pl061->csave_regs.gpio_ibe = readb(pl061->base + GPIOIBE);
|
|
pl061->csave_regs.gpio_iev = readb(pl061->base + GPIOIEV);
|
|
pl061->csave_regs.gpio_ie = readb(pl061->base + GPIOIE);
|
|
|
|
for (offset = 0; offset < PL061_GPIO_NR; offset++) {
|
|
if (pl061->csave_regs.gpio_dir & (BIT(offset)))
|
|
pl061->csave_regs.gpio_data |=
|
|
pl061_get_value(&pl061->gc, offset) << offset;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pl061_resume(struct device *dev)
|
|
{
|
|
struct pl061 *pl061 = dev_get_drvdata(dev);
|
|
int offset;
|
|
|
|
for (offset = 0; offset < PL061_GPIO_NR; offset++) {
|
|
if (pl061->csave_regs.gpio_dir & (BIT(offset)))
|
|
pl061_direction_output(&pl061->gc, offset,
|
|
pl061->csave_regs.gpio_data &
|
|
(BIT(offset)));
|
|
else
|
|
pl061_direction_input(&pl061->gc, offset);
|
|
}
|
|
|
|
writeb(pl061->csave_regs.gpio_is, pl061->base + GPIOIS);
|
|
writeb(pl061->csave_regs.gpio_ibe, pl061->base + GPIOIBE);
|
|
writeb(pl061->csave_regs.gpio_iev, pl061->base + GPIOIEV);
|
|
writeb(pl061->csave_regs.gpio_ie, pl061->base + GPIOIE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops pl061_dev_pm_ops = {
|
|
.suspend = pl061_suspend,
|
|
.resume = pl061_resume,
|
|
.freeze = pl061_suspend,
|
|
.restore = pl061_resume,
|
|
};
|
|
#endif
|
|
|
|
static const struct amba_id pl061_ids[] = {
|
|
{
|
|
.id = 0x00041061,
|
|
.mask = 0x000fffff,
|
|
},
|
|
{ 0, 0 },
|
|
};
|
|
|
|
static struct amba_driver pl061_gpio_driver = {
|
|
.drv = {
|
|
.name = "pl061_gpio",
|
|
#ifdef CONFIG_PM
|
|
.pm = &pl061_dev_pm_ops,
|
|
#endif
|
|
},
|
|
.id_table = pl061_ids,
|
|
.probe = pl061_probe,
|
|
};
|
|
|
|
static int __init pl061_gpio_init(void)
|
|
{
|
|
return amba_driver_register(&pl061_gpio_driver);
|
|
}
|
|
device_initcall(pl061_gpio_init);
|