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@xxxxxxxxxx> --- 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@xxxxxxxxxx> + */ + +#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@xxxxxxxxxx>"); +MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC"); +MODULE_LICENSE("GPL"); -- 2.48.0