#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/irqchip/chained_irq.h>
/*
* GPIO unit register offsets.
#define GPIO_LEVEL_MASK_OFF 0x001c
/* The MV78200 has per-CPU registers for edge mask and level mask */
-#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
+#define GPIO_EDGE_MASK_MV78200_OFF(cpu) ((cpu) ? 0x30 : 0x18)
#define GPIO_LEVEL_MASK_MV78200_OFF(cpu) ((cpu) ? 0x34 : 0x1C)
/* The Armada XP has per-CPU registers for interrupt cause, interrupt
#define GPIO_EDGE_MASK_ARMADAXP_OFF(cpu) (0x10 + (cpu) * 0x4)
#define GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu) (0x20 + (cpu) * 0x4)
-#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1
-#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2
+#define MVEBU_GPIO_SOC_VARIANT_ORION 0x1
+#define MVEBU_GPIO_SOC_VARIANT_MV78200 0x2
#define MVEBU_GPIO_SOC_VARIANT_ARMADAXP 0x3
-#define MVEBU_MAX_GPIO_PER_BANK 32
+#define MVEBU_MAX_GPIO_PER_BANK 32
struct mvebu_gpio_chip {
struct gpio_chip chip;
spinlock_t lock;
void __iomem *membase;
void __iomem *percpu_membase;
- unsigned int irqbase;
+ int irqbase;
struct irq_domain *domain;
- int soc_variant;
+ int soc_variant;
+
+ /* Used to preserve GPIO registers across suspend/resume */
+ u32 out_reg;
+ u32 io_conf_reg;
+ u32 blink_en_reg;
+ u32 in_pol_reg;
+ u32 edge_mask_regs[4];
+ u32 level_mask_regs[4];
};
/*
return mvchip->membase + GPIO_BLINK_EN_OFF;
}
-static inline void __iomem *mvebu_gpioreg_io_conf(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_io_conf(struct mvebu_gpio_chip *mvchip)
{
return mvchip->membase + GPIO_IO_CONF_OFF;
}
return mvchip->membase + GPIO_IN_POL_OFF;
}
-static inline void __iomem *mvebu_gpioreg_data_in(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_data_in(struct mvebu_gpio_chip *mvchip)
{
return mvchip->membase + GPIO_DATA_IN_OFF;
}
-static inline void __iomem *mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_edge_cause(struct mvebu_gpio_chip *mvchip)
{
int cpu;
return mvchip->membase + GPIO_EDGE_CAUSE_OFF;
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
cpu = smp_processor_id();
- return mvchip->percpu_membase + GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu);
+ return mvchip->percpu_membase +
+ GPIO_EDGE_CAUSE_ARMADAXP_OFF(cpu);
default:
BUG();
}
}
-static inline void __iomem *mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip)
+static inline void __iomem *
+mvebu_gpioreg_edge_mask(struct mvebu_gpio_chip *mvchip)
{
int cpu;
return mvchip->membase + GPIO_EDGE_MASK_MV78200_OFF(cpu);
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
cpu = smp_processor_id();
- return mvchip->percpu_membase + GPIO_EDGE_MASK_ARMADAXP_OFF(cpu);
+ return mvchip->percpu_membase +
+ GPIO_EDGE_MASK_ARMADAXP_OFF(cpu);
default:
BUG();
}
return mvchip->membase + GPIO_LEVEL_MASK_MV78200_OFF(cpu);
case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
cpu = smp_processor_id();
- return mvchip->percpu_membase + GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu);
+ return mvchip->percpu_membase +
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(cpu);
default:
BUG();
}
* Functions implementing the gpio_chip methods
*/
-static int mvebu_gpio_request(struct gpio_chip *chip, unsigned pin)
-{
- return pinctrl_request_gpio(chip->base + pin);
-}
-
-static void mvebu_gpio_free(struct gpio_chip *chip, unsigned pin)
-{
- pinctrl_free_gpio(chip->base + pin);
-}
-
static void mvebu_gpio_set(struct gpio_chip *chip, unsigned pin, int value)
{
struct mvebu_gpio_chip *mvchip =
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
u32 mask = 1 << (d->irq - gc->irq_base);
irq_gc_lock(gc);
- gc->mask_cache &= ~mask;
- writel_relaxed(gc->mask_cache, mvebu_gpioreg_edge_mask(mvchip));
+ ct->mask_cache_priv &= ~mask;
+
+ writel_relaxed(ct->mask_cache_priv, mvebu_gpioreg_edge_mask(mvchip));
irq_gc_unlock(gc);
}
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+
u32 mask = 1 << (d->irq - gc->irq_base);
irq_gc_lock(gc);
- gc->mask_cache |= mask;
- writel_relaxed(gc->mask_cache, mvebu_gpioreg_edge_mask(mvchip));
+ ct->mask_cache_priv |= mask;
+ writel_relaxed(ct->mask_cache_priv, mvebu_gpioreg_edge_mask(mvchip));
irq_gc_unlock(gc);
}
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+
u32 mask = 1 << (d->irq - gc->irq_base);
irq_gc_lock(gc);
- gc->mask_cache &= ~mask;
- writel_relaxed(gc->mask_cache, mvebu_gpioreg_level_mask(mvchip));
+ ct->mask_cache_priv &= ~mask;
+ writel_relaxed(ct->mask_cache_priv, mvebu_gpioreg_level_mask(mvchip));
irq_gc_unlock(gc);
}
{
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
struct mvebu_gpio_chip *mvchip = gc->private;
+ struct irq_chip_type *ct = irq_data_get_chip_type(d);
+
u32 mask = 1 << (d->irq - gc->irq_base);
irq_gc_lock(gc);
- gc->mask_cache |= mask;
- writel_relaxed(gc->mask_cache, mvebu_gpioreg_level_mask(mvchip));
+ ct->mask_cache_priv |= mask;
+ writel_relaxed(ct->mask_cache_priv, mvebu_gpioreg_level_mask(mvchip));
irq_gc_unlock(gc);
}
* value of the line or the opposite value.
*
* Level IRQ handlers: DATA_IN is used directly as cause register.
- * Interrupt are masked by LEVEL_MASK registers.
+ * Interrupt are masked by LEVEL_MASK registers.
* Edge IRQ handlers: Change in DATA_IN are latched in EDGE_CAUSE.
- * Interrupt are masked by EDGE_MASK registers.
+ * Interrupt are masked by EDGE_MASK registers.
* Both-edge handlers: Similar to regular Edge handlers, but also swaps
- * the polarity to catch the next line transaction.
- * This is a race condition that might not perfectly
- * work on some use cases.
+ * the polarity to catch the next line transaction.
+ * This is a race condition that might not perfectly
+ * work on some use cases.
*
* Every eight GPIO lines are grouped (OR'ed) before going up to main
* cause register.
*
- * EDGE cause mask
- * data-in /--------| |-----| |----\
- * -----| |----- ---- to main cause reg
- * X \----------------| |----/
- * polarity LEVEL mask
+ * EDGE cause mask
+ * data-in /--------| |-----| |----\
+ * -----| |----- ---- to main cause reg
+ * X \----------------| |----/
+ * polarity LEVEL mask
*
****************************************************************************/
pin = d->hwirq;
u = readl_relaxed(mvebu_gpioreg_io_conf(mvchip)) & (1 << pin);
- if (!u) {
+ if (!u)
return -EINVAL;
- }
type &= IRQ_TYPE_SENSE_MASK;
if (type == IRQ_TYPE_NONE)
return 0;
}
-static void mvebu_gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
+static void mvebu_gpio_irq_handler(struct irq_desc *desc)
{
- struct mvebu_gpio_chip *mvchip = irq_get_handler_data(irq);
+ struct mvebu_gpio_chip *mvchip = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
u32 cause, type;
int i;
if (mvchip == NULL)
return;
+ chained_irq_enter(chip, desc);
+
cause = readl_relaxed(mvebu_gpioreg_data_in(mvchip)) &
readl_relaxed(mvebu_gpioreg_level_mask(mvchip));
cause |= readl_relaxed(mvebu_gpioreg_edge_cause(mvchip)) &
if (!(cause & (1 << i)))
continue;
- type = irqd_get_trigger_type(irq_get_irq_data(irq));
+ type = irq_get_trigger_type(irq);
if ((type & IRQ_TYPE_SENSE_MASK) == IRQ_TYPE_EDGE_BOTH) {
/* Swap polarity (race with GPIO line) */
u32 polarity;
polarity ^= 1 << i;
writel_relaxed(polarity, mvebu_gpioreg_in_pol(mvchip));
}
+
generic_handle_irq(irq);
}
+
+ chained_irq_exit(chip, desc);
}
#ifdef CONFIG_DEBUG_FS
(data_in ^ in_pol) & msk ? "hi" : "lo",
in_pol & msk ? "lo" : "hi");
if (!((edg_msk | lvl_msk) & msk)) {
- seq_printf(s, " disabled\n");
+ seq_puts(s, " disabled\n");
continue;
}
if (edg_msk & msk)
- seq_printf(s, " edge ");
+ seq_puts(s, " edge ");
if (lvl_msk & msk)
- seq_printf(s, " level");
+ seq_puts(s, " level");
seq_printf(s, " (%s)\n", cause & msk ? "pending" : "clear ");
}
}
#define mvebu_gpio_dbg_show NULL
#endif
-static struct of_device_id mvebu_gpio_of_match[] = {
+static const struct of_device_id mvebu_gpio_of_match[] = {
{
.compatible = "marvell,orion-gpio",
- .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ORION,
},
{
.compatible = "marvell,mv78200-gpio",
- .data = (void *) MVEBU_GPIO_SOC_VARIANT_MV78200,
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_MV78200,
},
{
.compatible = "marvell,armadaxp-gpio",
- .data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
+ .data = (void *) MVEBU_GPIO_SOC_VARIANT_ARMADAXP,
},
{
/* sentinel */
};
MODULE_DEVICE_TABLE(of, mvebu_gpio_of_match);
+static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
+ int i;
+
+ mvchip->out_reg = readl(mvebu_gpioreg_out(mvchip));
+ mvchip->io_conf_reg = readl(mvebu_gpioreg_io_conf(mvchip));
+ mvchip->blink_en_reg = readl(mvebu_gpioreg_blink(mvchip));
+ mvchip->in_pol_reg = readl(mvebu_gpioreg_in_pol(mvchip));
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ mvchip->edge_mask_regs[0] =
+ readl(mvchip->membase + GPIO_EDGE_MASK_OFF);
+ mvchip->level_mask_regs[0] =
+ readl(mvchip->membase + GPIO_LEVEL_MASK_OFF);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ for (i = 0; i < 2; i++) {
+ mvchip->edge_mask_regs[i] =
+ readl(mvchip->membase +
+ GPIO_EDGE_MASK_MV78200_OFF(i));
+ mvchip->level_mask_regs[i] =
+ readl(mvchip->membase +
+ GPIO_LEVEL_MASK_MV78200_OFF(i));
+ }
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ for (i = 0; i < 4; i++) {
+ mvchip->edge_mask_regs[i] =
+ readl(mvchip->membase +
+ GPIO_EDGE_MASK_ARMADAXP_OFF(i));
+ mvchip->level_mask_regs[i] =
+ readl(mvchip->membase +
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(i));
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ return 0;
+}
+
+static int mvebu_gpio_resume(struct platform_device *pdev)
+{
+ struct mvebu_gpio_chip *mvchip = platform_get_drvdata(pdev);
+ int i;
+
+ writel(mvchip->out_reg, mvebu_gpioreg_out(mvchip));
+ writel(mvchip->io_conf_reg, mvebu_gpioreg_io_conf(mvchip));
+ writel(mvchip->blink_en_reg, mvebu_gpioreg_blink(mvchip));
+ writel(mvchip->in_pol_reg, mvebu_gpioreg_in_pol(mvchip));
+
+ switch (mvchip->soc_variant) {
+ case MVEBU_GPIO_SOC_VARIANT_ORION:
+ writel(mvchip->edge_mask_regs[0],
+ mvchip->membase + GPIO_EDGE_MASK_OFF);
+ writel(mvchip->level_mask_regs[0],
+ mvchip->membase + GPIO_LEVEL_MASK_OFF);
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_MV78200:
+ for (i = 0; i < 2; i++) {
+ writel(mvchip->edge_mask_regs[i],
+ mvchip->membase + GPIO_EDGE_MASK_MV78200_OFF(i));
+ writel(mvchip->level_mask_regs[i],
+ mvchip->membase +
+ GPIO_LEVEL_MASK_MV78200_OFF(i));
+ }
+ break;
+ case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
+ for (i = 0; i < 4; i++) {
+ writel(mvchip->edge_mask_regs[i],
+ mvchip->membase +
+ GPIO_EDGE_MASK_ARMADAXP_OFF(i));
+ writel(mvchip->level_mask_regs[i],
+ mvchip->membase +
+ GPIO_LEVEL_MASK_ARMADAXP_OFF(i));
+ }
+ break;
+ default:
+ BUG();
+ }
+
+ return 0;
+}
+
static int mvebu_gpio_probe(struct platform_device *pdev)
{
struct mvebu_gpio_chip *mvchip;
unsigned int ngpios;
int soc_variant;
int i, cpu, id;
+ int err;
match = of_match_device(mvebu_gpio_of_match, &pdev->dev);
if (match)
else
soc_variant = MVEBU_GPIO_SOC_VARIANT_ORION;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(&pdev->dev, "Cannot get memory resource\n");
- return -ENODEV;
- }
-
- mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip), GFP_KERNEL);
- if (!mvchip) {
- dev_err(&pdev->dev, "Cannot allocate memory\n");
+ mvchip = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_gpio_chip),
+ GFP_KERNEL);
+ if (!mvchip)
return -ENOMEM;
- }
+
+ platform_set_drvdata(pdev, mvchip);
if (of_property_read_u32(pdev->dev.of_node, "ngpios", &ngpios)) {
dev_err(&pdev->dev, "Missing ngpios OF property\n");
mvchip->soc_variant = soc_variant;
mvchip->chip.label = dev_name(&pdev->dev);
mvchip->chip.dev = &pdev->dev;
- mvchip->chip.request = mvebu_gpio_request;
- mvchip->chip.free = mvebu_gpio_free;
+ mvchip->chip.request = gpiochip_generic_request;
+ mvchip->chip.free = gpiochip_generic_free;
mvchip->chip.direction_input = mvebu_gpio_direction_input;
mvchip->chip.get = mvebu_gpio_get;
mvchip->chip.direction_output = mvebu_gpio_direction_output;
mvchip->chip.to_irq = mvebu_gpio_to_irq;
mvchip->chip.base = id * MVEBU_MAX_GPIO_PER_BANK;
mvchip->chip.ngpio = ngpios;
- mvchip->chip.can_sleep = 0;
+ mvchip->chip.can_sleep = false;
mvchip->chip.of_node = np;
mvchip->chip.dbg_show = mvebu_gpio_dbg_show;
spin_lock_init(&mvchip->lock);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mvchip->membase = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(mvchip->membase))
return PTR_ERR(mvchip->membase);
* interrupt handlers, with each handler dealing with 8 GPIO
* pins. */
for (i = 0; i < 4; i++) {
- int irq;
- irq = platform_get_irq(pdev, i);
+ int irq = platform_get_irq(pdev, i);
+
if (irq < 0)
continue;
- irq_set_handler_data(irq, mvchip);
- irq_set_chained_handler(irq, mvebu_gpio_irq_handler);
+ irq_set_chained_handler_and_data(irq, mvebu_gpio_irq_handler,
+ mvchip);
}
mvchip->irqbase = irq_alloc_descs(-1, 0, ngpios, -1);
if (mvchip->irqbase < 0) {
dev_err(&pdev->dev, "no irqs\n");
- return -ENOMEM;
+ err = mvchip->irqbase;
+ goto err_gpiochip_add;
}
gc = irq_alloc_generic_chip("mvebu_gpio_irq", 2, mvchip->irqbase,
mvchip->membase, handle_level_irq);
if (!gc) {
dev_err(&pdev->dev, "Cannot allocate generic irq_chip\n");
- return -ENOMEM;
+ err = -ENOMEM;
+ goto err_gpiochip_add;
}
gc->private = mvchip;
if (!mvchip->domain) {
dev_err(&pdev->dev, "couldn't allocate irq domain %s (DT).\n",
mvchip->chip.label);
- irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
- IRQ_LEVEL | IRQ_NOPROBE);
- kfree(gc);
- return -ENODEV;
+ err = -ENODEV;
+ goto err_generic_chip;
}
return 0;
+
+err_generic_chip:
+ irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST,
+ IRQ_LEVEL | IRQ_NOPROBE);
+ kfree(gc);
+
+err_gpiochip_add:
+ gpiochip_remove(&mvchip->chip);
+
+ return err;
}
static struct platform_driver mvebu_gpio_driver = {
.driver = {
- .name = "mvebu-gpio",
- .owner = THIS_MODULE,
+ .name = "mvebu-gpio",
.of_match_table = mvebu_gpio_of_match,
},
.probe = mvebu_gpio_probe,
+ .suspend = mvebu_gpio_suspend,
+ .resume = mvebu_gpio_resume,
};
-
-static int __init mvebu_gpio_init(void)
-{
- return platform_driver_register(&mvebu_gpio_driver);
-}
-postcore_initcall(mvebu_gpio_init);
+module_platform_driver(mvebu_gpio_driver);