mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-28 22:54:05 +08:00
bd0b9ac405
Most interrupt flow handlers do not use the irq argument. Those few which use it can retrieve the irq number from the irq descriptor. Remove the argument. Search and replace was done with coccinelle and some extra helper scripts around it. Thanks to Julia for her help! Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Cc: Julia Lawall <Julia.Lawall@lip6.fr> Cc: Jiang Liu <jiang.liu@linux.intel.com>
728 lines
18 KiB
C
728 lines
18 KiB
C
/*
|
|
* SPEAr platform PLGPIO driver
|
|
*
|
|
* Copyright (C) 2012 ST Microelectronics
|
|
* Viresh Kumar <viresh.kumar@linaro.org>
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
#define MAX_GPIO_PER_REG 32
|
|
#define PIN_OFFSET(pin) (pin % MAX_GPIO_PER_REG)
|
|
#define REG_OFFSET(base, reg, pin) (base + reg + (pin / MAX_GPIO_PER_REG) \
|
|
* sizeof(int *))
|
|
|
|
/*
|
|
* plgpio pins in all machines are not one to one mapped, bitwise with registers
|
|
* bits. These set of macros define register masks for which below functions
|
|
* (pin_to_offset and offset_to_pin) are required to be called.
|
|
*/
|
|
#define PTO_ENB_REG 0x001
|
|
#define PTO_WDATA_REG 0x002
|
|
#define PTO_DIR_REG 0x004
|
|
#define PTO_IE_REG 0x008
|
|
#define PTO_RDATA_REG 0x010
|
|
#define PTO_MIS_REG 0x020
|
|
|
|
struct plgpio_regs {
|
|
u32 enb; /* enable register */
|
|
u32 wdata; /* write data register */
|
|
u32 dir; /* direction set register */
|
|
u32 rdata; /* read data register */
|
|
u32 ie; /* interrupt enable register */
|
|
u32 mis; /* mask interrupt status register */
|
|
u32 eit; /* edge interrupt type */
|
|
};
|
|
|
|
/*
|
|
* struct plgpio: plgpio driver specific structure
|
|
*
|
|
* lock: lock for guarding gpio registers
|
|
* base: base address of plgpio block
|
|
* chip: gpio framework specific chip information structure
|
|
* p2o: function ptr for pin to offset conversion. This is required only for
|
|
* machines where mapping b/w pin and offset is not 1-to-1.
|
|
* o2p: function ptr for offset to pin conversion. This is required only for
|
|
* machines where mapping b/w pin and offset is not 1-to-1.
|
|
* p2o_regs: mask of registers for which p2o and o2p are applicable
|
|
* regs: register offsets
|
|
* csave_regs: context save registers for standby/sleep/hibernate cases
|
|
*/
|
|
struct plgpio {
|
|
spinlock_t lock;
|
|
void __iomem *base;
|
|
struct clk *clk;
|
|
struct gpio_chip chip;
|
|
int (*p2o)(int pin); /* pin_to_offset */
|
|
int (*o2p)(int offset); /* offset_to_pin */
|
|
u32 p2o_regs;
|
|
struct plgpio_regs regs;
|
|
#ifdef CONFIG_PM_SLEEP
|
|
struct plgpio_regs *csave_regs;
|
|
#endif
|
|
};
|
|
|
|
/* register manipulation inline functions */
|
|
static inline u32 is_plgpio_set(void __iomem *base, u32 pin, u32 reg)
|
|
{
|
|
u32 offset = PIN_OFFSET(pin);
|
|
void __iomem *reg_off = REG_OFFSET(base, reg, pin);
|
|
u32 val = readl_relaxed(reg_off);
|
|
|
|
return !!(val & (1 << offset));
|
|
}
|
|
|
|
static inline void plgpio_reg_set(void __iomem *base, u32 pin, u32 reg)
|
|
{
|
|
u32 offset = PIN_OFFSET(pin);
|
|
void __iomem *reg_off = REG_OFFSET(base, reg, pin);
|
|
u32 val = readl_relaxed(reg_off);
|
|
|
|
writel_relaxed(val | (1 << offset), reg_off);
|
|
}
|
|
|
|
static inline void plgpio_reg_reset(void __iomem *base, u32 pin, u32 reg)
|
|
{
|
|
u32 offset = PIN_OFFSET(pin);
|
|
void __iomem *reg_off = REG_OFFSET(base, reg, pin);
|
|
u32 val = readl_relaxed(reg_off);
|
|
|
|
writel_relaxed(val & ~(1 << offset), reg_off);
|
|
}
|
|
|
|
/* gpio framework specific routines */
|
|
static int plgpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
unsigned long flags;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_DIR_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.dir);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int plgpio_direction_output(struct gpio_chip *chip, unsigned offset,
|
|
int value)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
unsigned long flags;
|
|
unsigned dir_offset = offset, wdata_offset = offset, tmp;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & (PTO_DIR_REG | PTO_WDATA_REG))) {
|
|
tmp = plgpio->p2o(offset);
|
|
if (tmp == -1)
|
|
return -EINVAL;
|
|
|
|
if (plgpio->p2o_regs & PTO_DIR_REG)
|
|
dir_offset = tmp;
|
|
if (plgpio->p2o_regs & PTO_WDATA_REG)
|
|
wdata_offset = tmp;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
if (value)
|
|
plgpio_reg_set(plgpio->base, wdata_offset,
|
|
plgpio->regs.wdata);
|
|
else
|
|
plgpio_reg_reset(plgpio->base, wdata_offset,
|
|
plgpio->regs.wdata);
|
|
|
|
plgpio_reg_reset(plgpio->base, dir_offset, plgpio->regs.dir);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int plgpio_get_value(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
|
|
if (offset >= chip->ngpio)
|
|
return -EINVAL;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_RDATA_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return -EINVAL;
|
|
}
|
|
|
|
return is_plgpio_set(plgpio->base, offset, plgpio->regs.rdata);
|
|
}
|
|
|
|
static void plgpio_set_value(struct gpio_chip *chip, unsigned offset, int value)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
|
|
if (offset >= chip->ngpio)
|
|
return;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_WDATA_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
if (value)
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.wdata);
|
|
else
|
|
plgpio_reg_reset(plgpio->base, offset, plgpio->regs.wdata);
|
|
}
|
|
|
|
static int plgpio_request(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
int gpio = chip->base + offset;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if (offset >= chip->ngpio)
|
|
return -EINVAL;
|
|
|
|
ret = pinctrl_request_gpio(gpio);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!IS_ERR(plgpio->clk)) {
|
|
ret = clk_enable(plgpio->clk);
|
|
if (ret)
|
|
goto err0;
|
|
}
|
|
|
|
if (plgpio->regs.enb == -1)
|
|
return 0;
|
|
|
|
/*
|
|
* put gpio in IN mode before enabling it. This make enabling gpio safe
|
|
*/
|
|
ret = plgpio_direction_input(chip, offset);
|
|
if (ret)
|
|
goto err1;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_ENB_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1) {
|
|
ret = -EINVAL;
|
|
goto err1;
|
|
}
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.enb);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
return 0;
|
|
|
|
err1:
|
|
if (!IS_ERR(plgpio->clk))
|
|
clk_disable(plgpio->clk);
|
|
err0:
|
|
pinctrl_free_gpio(gpio);
|
|
return ret;
|
|
}
|
|
|
|
static void plgpio_free(struct gpio_chip *chip, unsigned offset)
|
|
{
|
|
struct plgpio *plgpio = container_of(chip, struct plgpio, chip);
|
|
int gpio = chip->base + offset;
|
|
unsigned long flags;
|
|
|
|
if (offset >= chip->ngpio)
|
|
return;
|
|
|
|
if (plgpio->regs.enb == -1)
|
|
goto disable_clk;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_ENB_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_reset(plgpio->base, offset, plgpio->regs.enb);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
|
|
disable_clk:
|
|
if (!IS_ERR(plgpio->clk))
|
|
clk_disable(plgpio->clk);
|
|
|
|
pinctrl_free_gpio(gpio);
|
|
}
|
|
|
|
/* PLGPIO IRQ */
|
|
static void plgpio_irq_disable(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
int offset = d->hwirq;
|
|
unsigned long flags;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_IE_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_set(plgpio->base, offset, plgpio->regs.ie);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
}
|
|
|
|
static void plgpio_irq_enable(struct irq_data *d)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
int offset = d->hwirq;
|
|
unsigned long flags;
|
|
|
|
/* get correct offset for "offset" pin */
|
|
if (plgpio->p2o && (plgpio->p2o_regs & PTO_IE_REG)) {
|
|
offset = plgpio->p2o(offset);
|
|
if (offset == -1)
|
|
return;
|
|
}
|
|
|
|
spin_lock_irqsave(&plgpio->lock, flags);
|
|
plgpio_reg_reset(plgpio->base, offset, plgpio->regs.ie);
|
|
spin_unlock_irqrestore(&plgpio->lock, flags);
|
|
}
|
|
|
|
static int plgpio_irq_set_type(struct irq_data *d, unsigned trigger)
|
|
{
|
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
int offset = d->hwirq;
|
|
void __iomem *reg_off;
|
|
unsigned int supported_type = 0, val;
|
|
|
|
if (offset >= plgpio->chip.ngpio)
|
|
return -EINVAL;
|
|
|
|
if (plgpio->regs.eit == -1)
|
|
supported_type = IRQ_TYPE_LEVEL_HIGH;
|
|
else
|
|
supported_type = IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING;
|
|
|
|
if (!(trigger & supported_type))
|
|
return -EINVAL;
|
|
|
|
if (plgpio->regs.eit == -1)
|
|
return 0;
|
|
|
|
reg_off = REG_OFFSET(plgpio->base, plgpio->regs.eit, offset);
|
|
val = readl_relaxed(reg_off);
|
|
|
|
offset = PIN_OFFSET(offset);
|
|
if (trigger & IRQ_TYPE_EDGE_RISING)
|
|
writel_relaxed(val | (1 << offset), reg_off);
|
|
else
|
|
writel_relaxed(val & ~(1 << offset), reg_off);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct irq_chip plgpio_irqchip = {
|
|
.name = "PLGPIO",
|
|
.irq_enable = plgpio_irq_enable,
|
|
.irq_disable = plgpio_irq_disable,
|
|
.irq_set_type = plgpio_irq_set_type,
|
|
};
|
|
|
|
static void plgpio_irq_handler(struct irq_desc *desc)
|
|
{
|
|
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
|
|
struct plgpio *plgpio = container_of(gc, struct plgpio, chip);
|
|
struct irq_chip *irqchip = irq_desc_get_chip(desc);
|
|
int regs_count, count, pin, offset, i = 0;
|
|
unsigned long pending;
|
|
|
|
count = plgpio->chip.ngpio;
|
|
regs_count = DIV_ROUND_UP(count, MAX_GPIO_PER_REG);
|
|
|
|
chained_irq_enter(irqchip, desc);
|
|
/* check all plgpio MIS registers for a possible interrupt */
|
|
for (; i < regs_count; i++) {
|
|
pending = readl_relaxed(plgpio->base + plgpio->regs.mis +
|
|
i * sizeof(int *));
|
|
if (!pending)
|
|
continue;
|
|
|
|
/* clear interrupts */
|
|
writel_relaxed(~pending, plgpio->base + plgpio->regs.mis +
|
|
i * sizeof(int *));
|
|
/*
|
|
* clear extra bits in last register having gpios < MAX/REG
|
|
* ex: Suppose there are max 102 plgpios. then last register
|
|
* must have only (102 - MAX_GPIO_PER_REG * 3) = 6 relevant bits
|
|
* so, we must not take other 28 bits into consideration for
|
|
* checking interrupt. so clear those bits.
|
|
*/
|
|
count = count - i * MAX_GPIO_PER_REG;
|
|
if (count < MAX_GPIO_PER_REG)
|
|
pending &= (1 << count) - 1;
|
|
|
|
for_each_set_bit(offset, &pending, MAX_GPIO_PER_REG) {
|
|
/* get correct pin for "offset" */
|
|
if (plgpio->o2p && (plgpio->p2o_regs & PTO_MIS_REG)) {
|
|
pin = plgpio->o2p(offset);
|
|
if (pin == -1)
|
|
continue;
|
|
} else
|
|
pin = offset;
|
|
|
|
/* get correct irq line number */
|
|
pin = i * MAX_GPIO_PER_REG + pin;
|
|
generic_handle_irq(
|
|
irq_find_mapping(gc->irqdomain, pin));
|
|
}
|
|
}
|
|
chained_irq_exit(irqchip, desc);
|
|
}
|
|
|
|
/*
|
|
* pin to offset and offset to pin converter functions
|
|
*
|
|
* In spear310 there is inconsistency among bit positions in plgpio regiseters,
|
|
* for different plgpio pins. For example: for pin 27, bit offset is 23, pin
|
|
* 28-33 are not supported, pin 95 has offset bit 95, bit 100 has offset bit 1
|
|
*/
|
|
static int spear310_p2o(int pin)
|
|
{
|
|
int offset = pin;
|
|
|
|
if (pin <= 27)
|
|
offset += 4;
|
|
else if (pin <= 33)
|
|
offset = -1;
|
|
else if (pin <= 97)
|
|
offset -= 2;
|
|
else if (pin <= 101)
|
|
offset = 101 - pin;
|
|
else
|
|
offset = -1;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static int spear310_o2p(int offset)
|
|
{
|
|
if (offset <= 3)
|
|
return 101 - offset;
|
|
else if (offset <= 31)
|
|
return offset - 4;
|
|
else
|
|
return offset + 2;
|
|
}
|
|
|
|
static int plgpio_probe_dt(struct platform_device *pdev, struct plgpio *plgpio)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int ret = -EINVAL;
|
|
u32 val;
|
|
|
|
if (of_machine_is_compatible("st,spear310")) {
|
|
plgpio->p2o = spear310_p2o;
|
|
plgpio->o2p = spear310_o2p;
|
|
plgpio->p2o_regs = PTO_WDATA_REG | PTO_DIR_REG | PTO_IE_REG |
|
|
PTO_RDATA_REG | PTO_MIS_REG;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,ngpio", &val)) {
|
|
plgpio->chip.ngpio = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid ngpio field\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,enb-reg", &val))
|
|
plgpio->regs.enb = val;
|
|
else
|
|
plgpio->regs.enb = -1;
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,wdata-reg", &val)) {
|
|
plgpio->regs.wdata = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid wdata reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,dir-reg", &val)) {
|
|
plgpio->regs.dir = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid dir reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,ie-reg", &val)) {
|
|
plgpio->regs.ie = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid ie reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,rdata-reg", &val)) {
|
|
plgpio->regs.rdata = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid rdata reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,mis-reg", &val)) {
|
|
plgpio->regs.mis = val;
|
|
} else {
|
|
dev_err(&pdev->dev, "DT: Invalid mis reg\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!of_property_read_u32(np, "st-plgpio,eit-reg", &val))
|
|
plgpio->regs.eit = val;
|
|
else
|
|
plgpio->regs.eit = -1;
|
|
|
|
return 0;
|
|
|
|
end:
|
|
return ret;
|
|
}
|
|
static int plgpio_probe(struct platform_device *pdev)
|
|
{
|
|
struct plgpio *plgpio;
|
|
struct resource *res;
|
|
int ret, irq;
|
|
|
|
plgpio = devm_kzalloc(&pdev->dev, sizeof(*plgpio), GFP_KERNEL);
|
|
if (!plgpio) {
|
|
dev_err(&pdev->dev, "memory allocation fail\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
plgpio->base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(plgpio->base))
|
|
return PTR_ERR(plgpio->base);
|
|
|
|
ret = plgpio_probe_dt(pdev, plgpio);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "DT probe failed\n");
|
|
return ret;
|
|
}
|
|
|
|
plgpio->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(plgpio->clk))
|
|
dev_warn(&pdev->dev, "clk_get() failed, work without it\n");
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
plgpio->csave_regs = devm_kzalloc(&pdev->dev,
|
|
sizeof(*plgpio->csave_regs) *
|
|
DIV_ROUND_UP(plgpio->chip.ngpio, MAX_GPIO_PER_REG),
|
|
GFP_KERNEL);
|
|
if (!plgpio->csave_regs) {
|
|
dev_err(&pdev->dev, "csave registers memory allocation fail\n");
|
|
return -ENOMEM;
|
|
}
|
|
#endif
|
|
|
|
platform_set_drvdata(pdev, plgpio);
|
|
spin_lock_init(&plgpio->lock);
|
|
|
|
plgpio->chip.base = -1;
|
|
plgpio->chip.request = plgpio_request;
|
|
plgpio->chip.free = plgpio_free;
|
|
plgpio->chip.direction_input = plgpio_direction_input;
|
|
plgpio->chip.direction_output = plgpio_direction_output;
|
|
plgpio->chip.get = plgpio_get_value;
|
|
plgpio->chip.set = plgpio_set_value;
|
|
plgpio->chip.label = dev_name(&pdev->dev);
|
|
plgpio->chip.dev = &pdev->dev;
|
|
plgpio->chip.owner = THIS_MODULE;
|
|
plgpio->chip.of_node = pdev->dev.of_node;
|
|
|
|
if (!IS_ERR(plgpio->clk)) {
|
|
ret = clk_prepare(plgpio->clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "clk prepare failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = gpiochip_add(&plgpio->chip);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "unable to add gpio chip\n");
|
|
goto unprepare_clk;
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
dev_info(&pdev->dev, "PLGPIO registered without IRQs\n");
|
|
return 0;
|
|
}
|
|
|
|
ret = gpiochip_irqchip_add(&plgpio->chip,
|
|
&plgpio_irqchip,
|
|
0,
|
|
handle_simple_irq,
|
|
IRQ_TYPE_NONE);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to add irqchip to gpiochip\n");
|
|
goto remove_gpiochip;
|
|
}
|
|
|
|
gpiochip_set_chained_irqchip(&plgpio->chip,
|
|
&plgpio_irqchip,
|
|
irq,
|
|
plgpio_irq_handler);
|
|
|
|
dev_info(&pdev->dev, "PLGPIO registered with IRQs\n");
|
|
|
|
return 0;
|
|
|
|
remove_gpiochip:
|
|
dev_info(&pdev->dev, "Remove gpiochip\n");
|
|
gpiochip_remove(&plgpio->chip);
|
|
unprepare_clk:
|
|
if (!IS_ERR(plgpio->clk))
|
|
clk_unprepare(plgpio->clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int plgpio_suspend(struct device *dev)
|
|
{
|
|
struct plgpio *plgpio = dev_get_drvdata(dev);
|
|
int i, reg_count = DIV_ROUND_UP(plgpio->chip.ngpio, MAX_GPIO_PER_REG);
|
|
void __iomem *off;
|
|
|
|
for (i = 0; i < reg_count; i++) {
|
|
off = plgpio->base + i * sizeof(int *);
|
|
|
|
if (plgpio->regs.enb != -1)
|
|
plgpio->csave_regs[i].enb =
|
|
readl_relaxed(plgpio->regs.enb + off);
|
|
if (plgpio->regs.eit != -1)
|
|
plgpio->csave_regs[i].eit =
|
|
readl_relaxed(plgpio->regs.eit + off);
|
|
plgpio->csave_regs[i].wdata = readl_relaxed(plgpio->regs.wdata +
|
|
off);
|
|
plgpio->csave_regs[i].dir = readl_relaxed(plgpio->regs.dir +
|
|
off);
|
|
plgpio->csave_regs[i].ie = readl_relaxed(plgpio->regs.ie + off);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is used to correct the values in end registers. End registers contain
|
|
* extra bits that might be used for other purpose in platform. So, we shouldn't
|
|
* overwrite these bits. This macro, reads given register again, preserves other
|
|
* bit values (non-plgpio bits), and retain captured value (plgpio bits).
|
|
*/
|
|
#define plgpio_prepare_reg(__reg, _off, _mask, _tmp) \
|
|
{ \
|
|
_tmp = readl_relaxed(plgpio->regs.__reg + _off); \
|
|
_tmp &= ~_mask; \
|
|
plgpio->csave_regs[i].__reg = \
|
|
_tmp | (plgpio->csave_regs[i].__reg & _mask); \
|
|
}
|
|
|
|
static int plgpio_resume(struct device *dev)
|
|
{
|
|
struct plgpio *plgpio = dev_get_drvdata(dev);
|
|
int i, reg_count = DIV_ROUND_UP(plgpio->chip.ngpio, MAX_GPIO_PER_REG);
|
|
void __iomem *off;
|
|
u32 mask, tmp;
|
|
|
|
for (i = 0; i < reg_count; i++) {
|
|
off = plgpio->base + i * sizeof(int *);
|
|
|
|
if (i == reg_count - 1) {
|
|
mask = (1 << (plgpio->chip.ngpio - i *
|
|
MAX_GPIO_PER_REG)) - 1;
|
|
|
|
if (plgpio->regs.enb != -1)
|
|
plgpio_prepare_reg(enb, off, mask, tmp);
|
|
|
|
if (plgpio->regs.eit != -1)
|
|
plgpio_prepare_reg(eit, off, mask, tmp);
|
|
|
|
plgpio_prepare_reg(wdata, off, mask, tmp);
|
|
plgpio_prepare_reg(dir, off, mask, tmp);
|
|
plgpio_prepare_reg(ie, off, mask, tmp);
|
|
}
|
|
|
|
writel_relaxed(plgpio->csave_regs[i].wdata, plgpio->regs.wdata +
|
|
off);
|
|
writel_relaxed(plgpio->csave_regs[i].dir, plgpio->regs.dir +
|
|
off);
|
|
|
|
if (plgpio->regs.eit != -1)
|
|
writel_relaxed(plgpio->csave_regs[i].eit,
|
|
plgpio->regs.eit + off);
|
|
|
|
writel_relaxed(plgpio->csave_regs[i].ie, plgpio->regs.ie + off);
|
|
|
|
if (plgpio->regs.enb != -1)
|
|
writel_relaxed(plgpio->csave_regs[i].enb,
|
|
plgpio->regs.enb + off);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(plgpio_dev_pm_ops, plgpio_suspend, plgpio_resume);
|
|
|
|
static const struct of_device_id plgpio_of_match[] = {
|
|
{ .compatible = "st,spear-plgpio" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, plgpio_of_match);
|
|
|
|
static struct platform_driver plgpio_driver = {
|
|
.probe = plgpio_probe,
|
|
.driver = {
|
|
.name = "spear-plgpio",
|
|
.pm = &plgpio_dev_pm_ops,
|
|
.of_match_table = plgpio_of_match,
|
|
},
|
|
};
|
|
|
|
static int __init plgpio_init(void)
|
|
{
|
|
return platform_driver_register(&plgpio_driver);
|
|
}
|
|
subsys_initcall(plgpio_init);
|
|
|
|
MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>");
|
|
MODULE_DESCRIPTION("STMicroelectronics SPEAr PLGPIO driver");
|
|
MODULE_LICENSE("GPL");
|