[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1468994313-13538-7-git-send-email-andrew@aj.id.au>
Date: Wed, 20 Jul 2016 15:28:27 +0930
From: Andrew Jeffery <andrew@...id.au>
To: Linus Walleij <linus.walleij@...aro.org>
Cc: Alexandre Courbot <gnurou@...il.com>,
Joel Stanley <joel@....id.au>,
Mark Rutland <mark.rutland@....com>,
Rob Herring <robh+dt@...nel.org>,
Russell King <linux@...linux.org.uk>,
Benjamin Herrenschmidt <benh@...nel.crashing.org>,
Jeremy Kerr <jk@...abs.org>, devicetree@...r.kernel.org,
linux-arm-kernel@...ts.infradead.org, linux-gpio@...r.kernel.org,
linux-kernel@...r.kernel.org,
Alistair Popple <alistair@...ple.id.au>,
Andrew Jeffery <andrew@...id.au>
Subject: [PATCH 06/12] gpio: Add Aspeed driver
From: Joel Stanley <joel@....id.au>
The Aspeed SoCs contain GPIOs grouped by letter, where each letter group
contains 8 pins. The GPIO letter groups are then banked in sets of four
in the register layout.
The implementation exposes multiple banks through the one driver, and
requests and releases pins via the pinctrl subsystem. The hardware
supports generation of interrupts with per-pin triggers, and exposes this
capability through an irqchip and devicetree.
A number of supported features are not yet implemented: Configuration of
interrupt direction (ARM or LPC), debouncing, and provides WDT reset
tolerance for output ports.
Signed-off-by: Joel Stanley <joel@....id.au>
Signed-off-by: Alistair Popple <alistair@...ple.id.au>
Signed-off-by: Jeremy Kerr <jk@...abs.org>
Signed-off-by: Andrew Jeffery <andrew@...id.au>
---
arch/arm/mach-aspeed/Kconfig | 4 +
drivers/gpio/Kconfig | 8 +-
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-aspeed.c | 456 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 468 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpio/gpio-aspeed.c
diff --git a/arch/arm/mach-aspeed/Kconfig b/arch/arm/mach-aspeed/Kconfig
index 25a0ae01429e..a52de9d3adfb 100644
--- a/arch/arm/mach-aspeed/Kconfig
+++ b/arch/arm/mach-aspeed/Kconfig
@@ -6,6 +6,10 @@ menuconfig ARCH_ASPEED
select ASPEED_WATCHDOG
select MOXART_TIMER
select PINCTRL
+ select GPIOLIB
+ select GPIO_ASPEED
+ select GPIO_SYSFS
+
help
Say Y here if you want to run your kernel on an ASpeed BMC SoC.
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 536112fd2466..2c21b5db09cd 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -137,6 +137,13 @@ config GPIO_ATH79
Select this option to enable GPIO driver for
Atheros AR71XX/AR724X/AR913X SoC devices.
+config GPIO_ASPEED
+ bool "Aspeed GPIO support"
+ depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+ select GENERIC_IRQ_CHIP
+ help
+ Say Y here to support Aspeed AST2400 and AST2500 GPIO controllers.
+
config GPIO_BCM_KONA
bool "Broadcom Kona GPIO"
depends on OF_GPIO && (ARCH_BCM_MOBILE || COMPILE_TEST)
@@ -1072,7 +1079,6 @@ config GPIO_SODAVILLE
select GENERIC_IRQ_CHIP
help
Say Y here to support Intel Sodaville GPIO.
-
endmenu
menu "SPI GPIO expanders"
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 991598ea3fba..a2b18beb8af5 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o
obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o
obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
obj-$(CONFIG_GPIO_ATH79) += gpio-ath79.o
+obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o
obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c
new file mode 100644
index 000000000000..7885adc1332a
--- /dev/null
+++ b/drivers/gpio/gpio-aspeed.c
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2015 IBM Corp.
+ *
+ * Joel Stanley <joel@....id.au>
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+#include <linux/pinctrl/consumer.h>
+
+struct aspeed_gpio {
+ struct gpio_chip chip;
+ spinlock_t lock;
+ void __iomem *base;
+ int irq;
+ struct irq_chip irq_chip;
+ struct irq_domain *irq_domain;
+};
+
+struct aspeed_gpio_bank {
+ uint16_t val_regs;
+ uint16_t irq_regs;
+ const char names[4];
+};
+
+static const struct aspeed_gpio_bank aspeed_gpio_banks[] = {
+ {
+ .val_regs = 0x0000,
+ .irq_regs = 0x0008,
+ .names = { 'A', 'B', 'C', 'D' },
+ },
+ {
+ .val_regs = 0x0020,
+ .irq_regs = 0x0028,
+ .names = { 'E', 'F', 'G', 'H' },
+ },
+ {
+ .val_regs = 0x0070,
+ .irq_regs = 0x0098,
+ .names = { 'I', 'J', 'K', 'L' },
+ },
+ {
+ .val_regs = 0x0078,
+ .irq_regs = 0x00e8,
+ .names = { 'M', 'N', 'O', 'P' },
+ },
+ {
+ .val_regs = 0x0080,
+ .irq_regs = 0x0118,
+ .names = { 'Q', 'R', 'S', 'T' },
+ },
+ {
+ .val_regs = 0x0088,
+ .irq_regs = 0x0148,
+ .names = { 'U', 'V', 'W', 'X' },
+ },
+ /*
+ * A bank exists for { 'Y', 'Z', "AA", "AB" }, but is not implemented.
+ * Only half of GPIOs Y support interrupt configuration, and none of Z,
+ * AA or AB do as they are output only.
+ */
+};
+
+#define GPIO_BANK(x) ((x) >> 5)
+#define GPIO_OFFSET(x) ((x) & 0x1f)
+#define GPIO_BIT(x) BIT(GPIO_OFFSET(x))
+
+#define GPIO_DATA 0x00
+#define GPIO_DIR 0x04
+
+#define GPIO_IRQ_ENABLE 0x00
+#define GPIO_IRQ_TYPE0 0x04
+#define GPIO_IRQ_TYPE1 0x08
+#define GPIO_IRQ_TYPE2 0x0c
+#define GPIO_IRQ_STATUS 0x10
+
+static inline struct aspeed_gpio *to_aspeed_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct aspeed_gpio, chip);
+}
+
+static const struct aspeed_gpio_bank *to_bank(unsigned int offset)
+{
+ unsigned int bank = GPIO_BANK(offset);
+
+ WARN_ON(bank > ARRAY_SIZE(aspeed_gpio_banks));
+ return &aspeed_gpio_banks[bank];
+}
+
+static void *bank_val_reg(struct aspeed_gpio *gpio,
+ const struct aspeed_gpio_bank *bank,
+ unsigned int reg)
+{
+ return gpio->base + bank->val_regs + reg;
+}
+
+static void *bank_irq_reg(struct aspeed_gpio *gpio,
+ const struct aspeed_gpio_bank *bank,
+ unsigned int reg)
+{
+ return gpio->base + bank->irq_regs + reg;
+}
+
+static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+
+ return !!(ioread32(bank_val_reg(gpio, bank, GPIO_DATA))
+ & GPIO_BIT(offset));
+}
+
+static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(bank_val_reg(gpio, bank, GPIO_DATA));
+ if (val)
+ reg |= GPIO_BIT(offset);
+ else
+ reg &= ~GPIO_BIT(offset);
+
+ iowrite32(reg, bank_val_reg(gpio, bank, GPIO_DATA));
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR));
+ iowrite32(reg & ~GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR));
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_gpio_dir_out(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR));
+ iowrite32(reg | GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR));
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static inline int irqd_to_aspeed_gpio_data(struct irq_data *d,
+ struct aspeed_gpio **gpio,
+ const struct aspeed_gpio_bank **bank,
+ u32 *bit)
+{
+ int offset;
+
+ offset = irqd_to_hwirq(d);
+
+ *gpio = irq_data_get_irq_chip_data(d);
+ *bank = to_bank(offset);
+ *bit = GPIO_BIT(offset);
+
+ return 0;
+}
+
+static void aspeed_gpio_irq_ack(struct irq_data *d)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ void *status_addr;
+ u32 bit;
+ int rc;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
+ if (rc)
+ return;
+
+ status_addr = bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS);
+
+ spin_lock_irqsave(&gpio->lock, flags);
+ iowrite32(bit, status_addr);
+ spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void __aspeed_gpio_irq_set_mask(struct irq_data *d, bool set)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ u32 reg, bit;
+ void *addr;
+ int rc;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
+ if (rc)
+ return;
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_ENABLE);
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(addr);
+ if (set)
+ reg |= bit;
+ else
+ reg &= bit;
+ iowrite32(reg, addr);
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void aspeed_gpio_irq_mask(struct irq_data *d)
+{
+ __aspeed_gpio_irq_set_mask(d, false);
+}
+
+static void aspeed_gpio_irq_unmask(struct irq_data *d)
+{
+ __aspeed_gpio_irq_set_mask(d, true);
+}
+
+static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 type0, type1, type2, bit, reg;
+ const struct aspeed_gpio_bank *bank;
+ irq_flow_handler_t handler;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ void *addr;
+ int rc;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
+ if (rc)
+ return -EINVAL;
+
+ type0 = type1 = type2 = 0;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ type2 |= bit;
+ case IRQ_TYPE_EDGE_RISING:
+ type0 |= bit;
+ case IRQ_TYPE_EDGE_FALLING:
+ handler = handle_edge_irq;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ type0 |= bit;
+ case IRQ_TYPE_LEVEL_LOW:
+ type1 |= bit;
+ handler = handle_level_irq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type0;
+ iowrite32(reg, addr);
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type1;
+ iowrite32(reg, addr);
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type2;
+ iowrite32(reg, addr);
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+
+ irq_set_handler_locked(d, handler);
+
+ return 0;
+}
+
+static void aspeed_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct aspeed_gpio *gpio = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int i, p, girq;
+ unsigned long reg;
+
+ chained_irq_enter(chip, desc);
+
+ for (i = 0; i < ARRAY_SIZE(aspeed_gpio_banks); i++) {
+ const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i];
+
+ reg = ioread32(bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS));
+
+ for_each_set_bit(p, ®, 32) {
+ girq = irq_find_mapping(gpio->irq_domain, i * 32 + p);
+ generic_handle_irq(girq);
+ }
+
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static struct irq_chip aspeed_gpio_irqchip = {
+ .name = "aspeed-gpio",
+ .irq_ack = aspeed_gpio_irq_ack,
+ .irq_mask = aspeed_gpio_irq_mask,
+ .irq_unmask = aspeed_gpio_irq_unmask,
+ .irq_set_type = aspeed_gpio_set_type,
+};
+
+static int aspeed_gpio_to_irq(struct gpio_chip *chip, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(chip);
+
+ return irq_find_mapping(gpio->irq_domain, offset);
+}
+
+static void aspeed_gpio_setup_irqs(struct aspeed_gpio *gpio,
+ struct platform_device *pdev)
+{
+ int i, irq;
+
+ /* request our upstream IRQ */
+ gpio->irq = platform_get_irq(pdev, 0);
+ if (gpio->irq < 0)
+ return;
+
+ /* establish our irq domain to provide IRQs for each extended bank */
+ gpio->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+ gpio->chip.ngpio, &irq_domain_simple_ops, NULL);
+ if (!gpio->irq_domain)
+ return;
+
+ for (i = 0; i < gpio->chip.ngpio; i++) {
+ irq = irq_create_mapping(gpio->irq_domain, i);
+ irq_set_chip_data(irq, gpio);
+ irq_set_chip_and_handler(irq, &aspeed_gpio_irqchip,
+ handle_simple_irq);
+ irq_set_probe(irq);
+ }
+
+ irq_set_chained_handler_and_data(gpio->irq,
+ aspeed_gpio_irq_handler, gpio);
+}
+
+static int aspeed_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ return pinctrl_request_gpio(chip->base + offset);
+}
+
+static void aspeed_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ pinctrl_free_gpio(chip->base + offset);
+}
+
+static int __init aspeed_gpio_probe(struct platform_device *pdev)
+{
+ struct aspeed_gpio *gpio;
+ struct resource *res;
+ int rc;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+
+ gpio->base = devm_ioremap_resource(&pdev->dev, res);
+ if (!gpio->base)
+ return -ENOMEM;
+
+ spin_lock_init(&gpio->lock);
+
+ gpio->chip.ngpio = ARRAY_SIZE(aspeed_gpio_banks) * 32;
+
+ gpio->chip.parent = &pdev->dev;
+ gpio->chip.direction_input = aspeed_gpio_dir_in;
+ gpio->chip.direction_output = aspeed_gpio_dir_out;
+ gpio->chip.request = aspeed_gpio_request;
+ gpio->chip.free = aspeed_gpio_free;
+ gpio->chip.get = aspeed_gpio_get;
+ gpio->chip.set = aspeed_gpio_set;
+ gpio->chip.to_irq = aspeed_gpio_to_irq;
+ gpio->chip.label = dev_name(&pdev->dev);
+ gpio->chip.base = -1;
+
+ platform_set_drvdata(pdev, gpio);
+
+ rc = gpiochip_add(&gpio->chip);
+ if (rc < 0)
+ return rc;
+
+ aspeed_gpio_setup_irqs(gpio, pdev);
+
+ return 0;
+}
+
+static int aspeed_gpio_remove(struct platform_device *pdev)
+{
+ struct aspeed_gpio *gpio = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&gpio->chip);
+ return 0;
+}
+
+static const struct of_device_id aspeed_gpio_of_table[] = {
+ { .compatible = "aspeed,ast2400-gpio" },
+ { .compatible = "aspeed,ast2500-gpio" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table);
+
+static struct platform_driver aspeed_gpio_driver = {
+ .remove = aspeed_gpio_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_gpio_of_table,
+ },
+};
+
+module_platform_driver_probe(aspeed_gpio_driver, aspeed_gpio_probe);
+
+MODULE_DESCRIPTION("Aspeed GPIO Driver");
--
2.7.4
Powered by blists - more mailing lists