[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250121-03-k1-gpio-v4-2-4641c95c0194@gentoo.org>
Date: Tue, 21 Jan 2025 11:38:12 +0800
From: Yixun Lan <dlan@...too.org>
To: Linus Walleij <linus.walleij@...aro.org>,
Bartosz Golaszewski <brgl@...ev.pl>, Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>, Conor Dooley <conor@...nel.org>,
Paul Walmsley <paul.walmsley@...ive.com>,
Palmer Dabbelt <palmer@...belt.com>
Cc: Yangyu Chen <cyy@...self.name>, Jisheng Zhang <jszhang@...nel.org>,
Jesse Taube <mr.bossman075@...il.com>,
Inochi Amaoto <inochiama@...look.com>, Icenowy Zheng <uwu@...nowy.me>,
Meng Zhang <zhangmeng.kevin@...ux.spacemit.com>, linux-gpio@...r.kernel.org,
devicetree@...r.kernel.org, linux-kernel@...r.kernel.org,
linux-riscv@...ts.infradead.org, Yixun Lan <dlan@...too.org>
Subject: [PATCH v4 2/4] gpio: spacemit: add support for K1 SoC
Implement GPIO functionality which capable of setting pin as
input, output. Also, each pin can be used as interrupt which
support rising, failing, or both edge type trigger.
Signed-off-by: Yixun Lan <dlan@...too.org>
---
drivers/gpio/Kconfig | 7 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-spacemit-k1.c | 295 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 303 insertions(+)
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 56fee58e281e7cac7f287eb04e4c17a17f75ed04..c153f5439649da020ee42c38e88cb8df31a8e307 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -654,6 +654,13 @@ config GPIO_SNPS_CREG
where only several fields in register belong to GPIO lines and
each GPIO line owns a field with different length and on/off value.
+config GPIO_SPACEMIT_K1
+ bool "SPACEMIT K1 GPIO support"
+ depends on ARCH_SPACEMIT || COMPILE_TEST
+ select GPIOLIB_IRQCHIP
+ help
+ Say yes here to support the SpacemiT's K1 GPIO device.
+
config GPIO_SPEAR_SPICS
bool "ST SPEAr13xx SPI Chip Select as GPIO support"
depends on PLAT_SPEAR
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index af3ba4d81b583842893ea69e677fbe2abf31bc7b..6709ce511a0cf10310a94521c85a2d382dcfa696 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -156,6 +156,7 @@ obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o
obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o
obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o
obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o
+obj-$(CONFIG_GPIO_SPACEMIT_K1) += gpio-spacemit-k1.o
obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o
obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o
obj-$(CONFIG_GPIO_STMPE) += gpio-stmpe.o
diff --git a/drivers/gpio/gpio-spacemit-k1.c b/drivers/gpio/gpio-spacemit-k1.c
new file mode 100644
index 0000000000000000000000000000000000000000..de0491af494c10f528095efee9b3a140bdcc0b0d
--- /dev/null
+++ b/drivers/gpio/gpio-spacemit-k1.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd
+ * Copyright (c) 2025 Yixun Lan <dlan@...too.org>
+ */
+
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/driver.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/seq_file.h>
+#include <linux/module.h>
+
+/* register offset */
+#define GPLR 0x00
+#define GPDR 0x0c
+#define GPSR 0x18
+#define GPCR 0x24
+#define GRER 0x30
+#define GFER 0x3c
+#define GEDR 0x48
+#define GSDR 0x54
+#define GCDR 0x60
+#define GSRER 0x6c
+#define GCRER 0x78
+#define GSFER 0x84
+#define GCFER 0x90
+#define GAPMASK 0x9c
+#define GCPMASK 0xa8
+
+#define K1_BANK_GPIO_NUMBER (32)
+
+#define to_spacemit_gpio_port(x) container_of((x), struct spacemit_gpio_port, gc)
+
+struct spacemit_gpio;
+
+struct spacemit_gpio_port {
+ struct gpio_chip gc;
+ struct spacemit_gpio *gpio;
+ struct fwnode_handle *fwnode;
+ void __iomem *base;
+ int irq;
+ u32 irq_mask;
+ u32 irq_rising_edge;
+ u32 irq_falling_edge;
+ u32 index;
+};
+
+struct spacemit_gpio {
+ struct device *dev;
+ struct spacemit_gpio_port *ports;
+ u32 nr_ports;
+};
+
+static inline void spacemit_clear_edge_detection(struct spacemit_gpio_port *port, u32 bit)
+{
+ writel(bit, port->base + GCRER);
+ writel(bit, port->base + GCFER);
+}
+
+static inline void spacemit_set_edge_detection(struct spacemit_gpio_port *port, u32 bit)
+{
+ writel(bit & port->irq_rising_edge, port->base + GSRER);
+ writel(bit & port->irq_falling_edge, port->base + GSFER);
+}
+
+static inline void spacemit_reset_edge_detection(struct spacemit_gpio_port *port)
+{
+ writel(0xffffffff, port->base + GCFER);
+ writel(0xffffffff, port->base + GCRER);
+ writel(0xffffffff, port->base + GAPMASK);
+}
+
+static int spacemit_gpio_irq_type(struct irq_data *d, unsigned int type)
+{
+ struct spacemit_gpio_port *port = irq_data_get_irq_chip_data(d);
+ u32 bit = BIT(irqd_to_hwirq(d));
+
+ if (type & IRQ_TYPE_EDGE_RISING) {
+ port->irq_rising_edge |= bit;
+ writel(bit, port->base + GSRER);
+ } else {
+ port->irq_rising_edge &= ~bit;
+ writel(bit, port->base + GCRER);
+ }
+
+ if (type & IRQ_TYPE_EDGE_FALLING) {
+ port->irq_falling_edge |= bit;
+ writel(bit, port->base + GSFER);
+ } else {
+ port->irq_falling_edge &= ~bit;
+ writel(bit, port->base + GCFER);
+ }
+
+ return 0;
+}
+
+static irqreturn_t spacemit_gpio_irq_handler(int irq, void *dev_id)
+{
+ struct spacemit_gpio_port *port = dev_id;
+ unsigned long pending;
+ u32 n, gedr;
+
+ gedr = readl(port->base + GEDR);
+ if (!gedr)
+ return IRQ_NONE;
+
+ writel(gedr, port->base + GEDR);
+ gedr = gedr & port->irq_mask;
+
+ if (!gedr)
+ return IRQ_NONE;
+
+ pending = gedr;
+
+ for_each_set_bit(n, &pending, BITS_PER_LONG)
+ handle_nested_irq(irq_find_mapping(port->gc.irq.domain, n));
+
+ return IRQ_HANDLED;
+}
+
+static void spacemit_ack_muxed_gpio(struct irq_data *d)
+{
+ struct spacemit_gpio_port *port = irq_data_get_irq_chip_data(d);
+
+ writel(BIT(irqd_to_hwirq(d)), port->base + GEDR);
+}
+
+static void spacemit_mask_muxed_gpio(struct irq_data *d)
+{
+ struct spacemit_gpio_port *port = irq_data_get_irq_chip_data(d);
+ u32 bit = BIT(irqd_to_hwirq(d));
+
+ port->irq_mask &= ~bit;
+
+ spacemit_clear_edge_detection(port, bit);
+}
+
+static void spacemit_unmask_muxed_gpio(struct irq_data *d)
+{
+ struct spacemit_gpio_port *port = irq_data_get_irq_chip_data(d);
+ u32 bit = BIT(irqd_to_hwirq(d));
+
+ port->irq_mask |= bit;
+
+ spacemit_set_edge_detection(port, bit);
+}
+
+static void spacemit_irq_print_chip(struct irq_data *data, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+ struct spacemit_gpio_port *port = to_spacemit_gpio_port(gc);
+
+ seq_printf(p, "%s-%d", dev_name(gc->parent), port->index);
+}
+
+static struct irq_chip spacemit_muxed_gpio_chip = {
+ .name = "k1-gpio-irqchip",
+ .irq_ack = spacemit_ack_muxed_gpio,
+ .irq_mask = spacemit_mask_muxed_gpio,
+ .irq_unmask = spacemit_unmask_muxed_gpio,
+ .irq_set_type = spacemit_gpio_irq_type,
+ .irq_print_chip = spacemit_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int spacemit_gpio_get_ports(struct device *dev, struct spacemit_gpio *gpio,
+ void __iomem *regs)
+{
+ struct spacemit_gpio_port *port;
+ u32 i = 0, offset;
+
+ gpio->nr_ports = device_get_child_node_count(dev);
+ if (gpio->nr_ports == 0)
+ return -ENODEV;
+
+ gpio->ports = devm_kcalloc(dev, gpio->nr_ports, sizeof(*gpio->ports), GFP_KERNEL);
+ if (!gpio->ports)
+ return -ENOMEM;
+
+ device_for_each_child_node_scoped(dev, fwnode) {
+ port = &gpio->ports[i];
+ port->fwnode = fwnode;
+ port->index = i++;
+
+ if (fwnode_property_read_u32(fwnode, "reg", &offset))
+ return -EINVAL;
+
+ port->base = regs + offset;
+ port->irq = fwnode_irq_get(fwnode, 0);
+ }
+
+ return 0;
+}
+
+static int spacemit_gpio_add_port(struct spacemit_gpio *gpio, int index)
+{
+ struct spacemit_gpio_port *port;
+ struct device *dev = gpio->dev;
+ struct gpio_irq_chip *girq;
+ void __iomem *dat, *set, *clr, *dirin, *dirout;
+ int ret;
+
+ port = &gpio->ports[index];
+ port->gpio = gpio;
+
+ dat = port->base + GPLR;
+ set = port->base + GPSR;
+ clr = port->base + GPCR;
+ dirin = port->base + GCDR;
+ dirout = port->base + GSDR;
+
+ /* This registers 32 GPIO lines per port */
+ ret = bgpio_init(&port->gc, dev, 4, dat, set, clr, dirout, dirin,
+ BGPIOF_UNREADABLE_REG_SET | BGPIOF_UNREADABLE_REG_DIR);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to init gpio chip for port\n");
+
+ port->gc.label = dev_name(dev);
+ port->gc.fwnode = port->fwnode;
+ port->gc.request = gpiochip_generic_request;
+ port->gc.free = gpiochip_generic_free;
+ port->gc.ngpio = K1_BANK_GPIO_NUMBER;
+ port->gc.base = -1;
+
+ girq = &port->gc.irq;
+ girq->threaded = true;
+ girq->handler = handle_simple_irq;
+
+ gpio_irq_chip_set_chip(girq, &spacemit_muxed_gpio_chip);
+
+ spacemit_reset_edge_detection(port);
+
+ ret = devm_request_threaded_irq(dev, port->irq, NULL,
+ spacemit_gpio_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ port->gc.label, port);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to request IRQ\n");
+
+ return devm_gpiochip_add_data(dev, &port->gc, port);
+}
+
+static int spacemit_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct spacemit_gpio *gpio;
+ struct resource *res;
+ void __iomem *regs;
+ int i, ret;
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->dev = dev;
+
+ regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ ret = spacemit_gpio_get_ports(dev, gpio, regs);
+ if (ret)
+ return dev_err_probe(dev, ret, "fail to get gpio ports\n");
+
+ for (i = 0; i < gpio->nr_ports; i++) {
+ ret = spacemit_gpio_add_port(gpio, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id spacemit_gpio_dt_ids[] = {
+ { .compatible = "spacemit,k1-gpio" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver spacemit_gpio_driver = {
+ .probe = spacemit_gpio_probe,
+ .driver = {
+ .name = "k1-gpio",
+ .of_match_table = spacemit_gpio_dt_ids,
+ },
+};
+module_platform_driver(spacemit_gpio_driver);
+
+MODULE_AUTHOR("Yixun Lan <dlan@...too.org>");
+MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC");
+MODULE_LICENSE("GPL");
--
2.48.0
Powered by blists - more mailing lists