2e31e4cea2
SVN-Revision: 31379
344 lines
8.2 KiB
Diff
344 lines
8.2 KiB
Diff
--- /dev/null
|
|
+++ b/arch/arm/plat-fa/gpio.c
|
|
@@ -0,0 +1,283 @@
|
|
+/*
|
|
+ * Gpiochip and interrupt routines for Faraday FA526 based SoCs
|
|
+ *
|
|
+ * Copyright (C) 2008-2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
|
|
+ * Copyright (C) 2010-2012 Gabor Juhos <juhosg@openwrt.org>
|
|
+ *
|
|
+ * Based on plat-mxc/gpio.c:
|
|
+ * MXC GPIO supchip. (c) 2008 Daniel Mack <daniel@caiaq.de>
|
|
+ * Copyright 2008 Juergen Beisert, kernel@pengutronix.de
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ */
|
|
+
|
|
+#include <linux/spinlock.h>
|
|
+
|
|
+#include <plat/gpio.h>
|
|
+
|
|
+#define GPIO_DATA_OUT 0x0
|
|
+#define GPIO_DATA_IN 0x4
|
|
+#define GPIO_DIR 0x8
|
|
+#define GPIO_DATA_SET 0x10
|
|
+#define GPIO_DATA_CLR 0x14
|
|
+#define GPIO_PULL_EN 0x18
|
|
+#define GPIO_PULL_TYPE 0x1C
|
|
+#define GPIO_INT_EN 0x20
|
|
+#define GPIO_INT_STAT 0x24
|
|
+#define GPIO_INT_MASK 0x2C
|
|
+#define GPIO_INT_CLR 0x30
|
|
+#define GPIO_INT_TYPE 0x34
|
|
+#define GPIO_INT_BOTH_EDGE 0x38
|
|
+#define GPIO_INT_LEVEL 0x3C
|
|
+#define GPIO_DEBOUNCE_EN 0x40
|
|
+#define GPIO_DEBOUNCE_PRESCALE 0x44
|
|
+
|
|
+#define GPIO_REGS_SIZE 0x48
|
|
+
|
|
+static DEFINE_SPINLOCK(fa_gpio_lock);
|
|
+
|
|
+static inline struct fa_gpio_chip *to_fgc(struct gpio_chip *chip)
|
|
+{
|
|
+ return container_of(chip, struct fa_gpio_chip, gpio_chip);
|
|
+}
|
|
+
|
|
+static void _fa_gpio_irq_setenable(struct irq_data *d, int enable)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = irq_get_chip_data(d->irq);
|
|
+ void __iomem *base = fgc->mem_base;
|
|
+ unsigned int gpio = d->irq - fgc->irq_base;
|
|
+ unsigned int reg;
|
|
+
|
|
+ reg = __raw_readl(base + GPIO_INT_EN);
|
|
+ reg = (reg & (~(1 << gpio))) | (!!enable << gpio);
|
|
+ __raw_writel(reg, base + GPIO_INT_EN);
|
|
+}
|
|
+
|
|
+static void fa_gpio_irq_ack(struct irq_data *d)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = irq_get_chip_data(d->irq);
|
|
+ unsigned int gpio = d->irq - fgc->irq_base;
|
|
+
|
|
+ __raw_writel(1 << gpio, fgc->mem_base + GPIO_INT_CLR);
|
|
+}
|
|
+
|
|
+static void fa_gpio_irq_mask(struct irq_data *d)
|
|
+{
|
|
+ _fa_gpio_irq_setenable(d, 0);
|
|
+}
|
|
+
|
|
+static void fa_gpio_irq_unmask(struct irq_data *d)
|
|
+{
|
|
+ _fa_gpio_irq_setenable(d, 1);
|
|
+}
|
|
+
|
|
+static int fa_gpio_irq_set_type(struct irq_data *d, unsigned int type)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = irq_get_chip_data(d->irq);
|
|
+ void __iomem *base = fgc->mem_base;
|
|
+ unsigned int gpio = d->irq - fgc->irq_base;
|
|
+ unsigned int gpio_mask = 1 << gpio;
|
|
+ unsigned int reg_both, reg_level, reg_type;
|
|
+
|
|
+ reg_type = __raw_readl(base + GPIO_INT_TYPE);
|
|
+ reg_level = __raw_readl(base + GPIO_INT_LEVEL);
|
|
+ reg_both = __raw_readl(base + GPIO_INT_BOTH_EDGE);
|
|
+
|
|
+ switch (type) {
|
|
+ case IRQ_TYPE_EDGE_BOTH:
|
|
+ reg_type &= ~gpio_mask;
|
|
+ reg_both |= gpio_mask;
|
|
+ break;
|
|
+ case IRQ_TYPE_EDGE_RISING:
|
|
+ reg_type &= ~gpio_mask;
|
|
+ reg_both &= ~gpio_mask;
|
|
+ reg_level &= ~gpio_mask;
|
|
+ break;
|
|
+ case IRQ_TYPE_EDGE_FALLING:
|
|
+ reg_type &= ~gpio_mask;
|
|
+ reg_both &= ~gpio_mask;
|
|
+ reg_level |= gpio_mask;
|
|
+ break;
|
|
+ case IRQ_TYPE_LEVEL_HIGH:
|
|
+ reg_type |= gpio_mask;
|
|
+ reg_level &= ~gpio_mask;
|
|
+ break;
|
|
+ case IRQ_TYPE_LEVEL_LOW:
|
|
+ reg_type |= gpio_mask;
|
|
+ reg_level |= gpio_mask;
|
|
+ break;
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ __raw_writel(reg_type, base + GPIO_INT_TYPE);
|
|
+ __raw_writel(reg_level, base + GPIO_INT_LEVEL);
|
|
+ __raw_writel(reg_both, base + GPIO_INT_BOTH_EDGE);
|
|
+
|
|
+ fa_gpio_irq_ack(d);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void fa_gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
|
|
+{
|
|
+ struct fa_gpio_data *data = irq_get_handler_data(irq);
|
|
+ unsigned int chip;
|
|
+
|
|
+ for (chip = 0; chip < data->nchips; chip++) {
|
|
+ struct fa_gpio_chip *fgc = &data->chips[chip];
|
|
+ unsigned int status;
|
|
+ unsigned int i;
|
|
+
|
|
+ status = __raw_readl(fgc->mem_base + GPIO_INT_STAT);
|
|
+ for (i = fgc->irq_base; status != 0; status >>= 1, i++) {
|
|
+ if ((status & 1) == 0)
|
|
+ continue;
|
|
+
|
|
+ BUG_ON(!(irq_desc[i].handle_irq));
|
|
+ irq_desc[i].handle_irq(i, &irq_desc[i]);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static struct irq_chip fa_gpio_irq_chip = {
|
|
+ .name = "GPIO",
|
|
+ .irq_ack = fa_gpio_irq_ack,
|
|
+ .irq_mask = fa_gpio_irq_mask,
|
|
+ .irq_unmask = fa_gpio_irq_unmask,
|
|
+ .irq_set_type = fa_gpio_irq_set_type,
|
|
+};
|
|
+
|
|
+static void _fa_gpio_set_direction(struct fa_gpio_chip *fgc, unsigned offset,
|
|
+ int is_output)
|
|
+{
|
|
+ unsigned int reg;
|
|
+
|
|
+ reg = __raw_readl(fgc->mem_base + GPIO_DIR);
|
|
+ if (is_output)
|
|
+ reg |= 1 << offset;
|
|
+ else
|
|
+ reg &= ~(1 << offset);
|
|
+ __raw_writel(reg, fgc->mem_base + GPIO_DIR);
|
|
+}
|
|
+
|
|
+static void _fa_gpio_set(struct fa_gpio_chip *fgc, unsigned offset, int value)
|
|
+{
|
|
+ if (value)
|
|
+ __raw_writel(1 << offset, fgc->mem_base + GPIO_DATA_SET);
|
|
+ else
|
|
+ __raw_writel(1 << offset, fgc->mem_base + GPIO_DATA_CLR);
|
|
+}
|
|
+
|
|
+static void fa_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = to_fgc(chip);
|
|
+
|
|
+ _fa_gpio_set(fgc, offset, value);
|
|
+}
|
|
+
|
|
+static int fa_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = to_fgc(chip);
|
|
+
|
|
+ return (__raw_readl(fgc->mem_base + GPIO_DATA_IN) >> offset) & 1;
|
|
+}
|
|
+
|
|
+static int fa_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = to_fgc(chip);
|
|
+
|
|
+ return fgc->irq_base + offset;
|
|
+}
|
|
+
|
|
+static int fa_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = to_fgc(chip);
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&fa_gpio_lock, flags);
|
|
+
|
|
+ _fa_gpio_set_direction(fgc, offset, 0);
|
|
+
|
|
+ spin_unlock_irqrestore(&fa_gpio_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int fa_gpio_direction_output(struct gpio_chip *chip,
|
|
+ unsigned offset,
|
|
+ int value)
|
|
+{
|
|
+ struct fa_gpio_chip *fgc = to_fgc(chip);
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&fa_gpio_lock, flags);
|
|
+
|
|
+ _fa_gpio_set(fgc, offset, value);
|
|
+ _fa_gpio_set_direction(fgc, offset, 1);
|
|
+
|
|
+ spin_unlock_irqrestore(&fa_gpio_lock, flags);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int fa_gpio_init_chip(struct fa_gpio_chip *fgc)
|
|
+{
|
|
+ void __iomem *mem_base;
|
|
+ unsigned int i;
|
|
+ int err;
|
|
+
|
|
+ mem_base = ioremap(fgc->map_base, GPIO_REGS_SIZE);
|
|
+ if (!mem_base)
|
|
+ return -ENXIO;
|
|
+
|
|
+ fgc->mem_base = mem_base;
|
|
+
|
|
+ fgc->gpio_chip.direction_input = fa_gpio_direction_input;
|
|
+ fgc->gpio_chip.direction_output = fa_gpio_direction_output;
|
|
+ fgc->gpio_chip.get = fa_gpio_get;
|
|
+ fgc->gpio_chip.set = fa_gpio_set;
|
|
+ fgc->gpio_chip.to_irq = fa_gpio_to_irq;
|
|
+
|
|
+ /* disable, unmask and clear all interrupts */
|
|
+ __raw_writel(0x0, mem_base + GPIO_INT_EN);
|
|
+ __raw_writel(0x0, mem_base + GPIO_INT_MASK);
|
|
+ __raw_writel(~0x0, mem_base + GPIO_INT_CLR);
|
|
+
|
|
+ for (i = fgc->irq_base;
|
|
+ i < fgc->irq_base + fgc->gpio_chip.ngpio; i++) {
|
|
+ irq_set_chip(i, &fa_gpio_irq_chip);
|
|
+ irq_set_chip_data(i, fgc);
|
|
+ irq_set_handler(i, handle_edge_irq);
|
|
+ set_irq_flags(i, IRQF_VALID);
|
|
+ }
|
|
+
|
|
+ err = gpiochip_add(&fgc->gpio_chip);
|
|
+ if (err)
|
|
+ goto unmap;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ unmap:
|
|
+ iounmap(fgc->mem_base);
|
|
+ return err;
|
|
+}
|
|
+
|
|
+void __init fa_gpio_init(struct fa_gpio_data *data)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < data->nchips; i++) {
|
|
+ int err;
|
|
+
|
|
+ err = fa_gpio_init_chip(&data->chips[i]);
|
|
+ if (WARN(err, "GPIO init failed\n"))
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ irq_set_chained_handler(data->irq, fa_gpio_irq_handler);
|
|
+ irq_set_handler_data(data->irq, data);
|
|
+}
|
|
--- /dev/null
|
|
+++ b/arch/arm/plat-fa/include/plat/gpio.h
|
|
@@ -0,0 +1,33 @@
|
|
+/*
|
|
+ * Copyright (c) 2010-2012 Gabor Juhos <juhosg@openwrt.org>
|
|
+ *
|
|
+ * This file 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.
|
|
+ */
|
|
+
|
|
+#ifndef _FA_GPIO_H
|
|
+#define _FA_GPIO_H
|
|
+
|
|
+#include <linux/init.h>
|
|
+#include <linux/gpio.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/io.h>
|
|
+
|
|
+struct fa_gpio_chip {
|
|
+ struct gpio_chip gpio_chip;
|
|
+ unsigned int map_base;
|
|
+ unsigned int irq_base;
|
|
+
|
|
+ void __iomem *mem_base;
|
|
+};
|
|
+
|
|
+struct fa_gpio_data {
|
|
+ struct fa_gpio_chip *chips;
|
|
+ unsigned int nchips;
|
|
+ unsigned int irq;
|
|
+};
|
|
+
|
|
+void __init fa_gpio_init(struct fa_gpio_data *data);
|
|
+
|
|
+#endif /* _FA_GPIO_H */
|
|
--- a/arch/arm/plat-fa/Kconfig
|
|
+++ b/arch/arm/plat-fa/Kconfig
|
|
@@ -1,5 +1,8 @@
|
|
if PLAT_FA
|
|
|
|
+config PLAT_FA_GPIO
|
|
+ def_bool n
|
|
+
|
|
config PLAT_FA_TIME
|
|
def_bool n
|
|
|
|
--- a/arch/arm/plat-fa/Makefile
|
|
+++ b/arch/arm/plat-fa/Makefile
|
|
@@ -4,6 +4,7 @@
|
|
|
|
obj-y :=
|
|
|
|
+obj-$(CONFIG_PLAT_FA_GPIO) += gpio.o
|
|
obj-$(CONFIG_PLAT_FA_TIME) += time.o
|
|
|
|
obj-m :=
|