|  | // SPDX-License-Identifier: GPL-2.0 OR MIT | 
|  | /* | 
|  | * Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd | 
|  | * Copyright (C) 2025 Yixun Lan <dlan@gentoo.org> | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/gpio/driver.h> | 
|  | #include <linux/gpio/generic.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/irq.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/seq_file.h> | 
|  |  | 
|  | /* register offset */ | 
|  | #define SPACEMIT_GPLR		0x00 /* port level - R */ | 
|  | #define SPACEMIT_GPDR		0x0c /* port direction - R/W */ | 
|  | #define SPACEMIT_GPSR		0x18 /* port set - W */ | 
|  | #define SPACEMIT_GPCR		0x24 /* port clear - W */ | 
|  | #define SPACEMIT_GRER		0x30 /* port rising edge R/W */ | 
|  | #define SPACEMIT_GFER		0x3c /* port falling edge R/W */ | 
|  | #define SPACEMIT_GEDR		0x48 /* edge detect status - R/W1C */ | 
|  | #define SPACEMIT_GSDR		0x54 /* (set) direction - W */ | 
|  | #define SPACEMIT_GCDR		0x60 /* (clear) direction - W */ | 
|  | #define SPACEMIT_GSRER		0x6c /* (set) rising edge detect enable - W */ | 
|  | #define SPACEMIT_GCRER		0x78 /* (clear) rising edge detect enable - W */ | 
|  | #define SPACEMIT_GSFER		0x84 /* (set) falling edge detect enable - W */ | 
|  | #define SPACEMIT_GCFER		0x90 /* (clear) falling edge detect enable - W */ | 
|  | #define SPACEMIT_GAPMASK	0x9c /* interrupt mask , 0 disable, 1 enable - R/W */ | 
|  |  | 
|  | #define SPACEMIT_NR_BANKS		4 | 
|  | #define SPACEMIT_NR_GPIOS_PER_BANK	32 | 
|  |  | 
|  | #define to_spacemit_gpio_bank(x) container_of((x), struct spacemit_gpio_bank, gc) | 
|  |  | 
|  | struct spacemit_gpio; | 
|  |  | 
|  | struct spacemit_gpio_bank { | 
|  | struct gpio_generic_chip chip; | 
|  | struct spacemit_gpio *sg; | 
|  | void __iomem *base; | 
|  | u32 irq_mask; | 
|  | u32 irq_rising_edge; | 
|  | u32 irq_falling_edge; | 
|  | }; | 
|  |  | 
|  | struct spacemit_gpio { | 
|  | struct device *dev; | 
|  | struct spacemit_gpio_bank sgb[SPACEMIT_NR_BANKS]; | 
|  | }; | 
|  |  | 
|  | static u32 spacemit_gpio_bank_index(struct spacemit_gpio_bank *gb) | 
|  | { | 
|  | return (u32)(gb - gb->sg->sgb); | 
|  | } | 
|  |  | 
|  | static irqreturn_t spacemit_gpio_irq_handler(int irq, void *dev_id) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = dev_id; | 
|  | unsigned long pending; | 
|  | u32 n, gedr; | 
|  |  | 
|  | gedr = readl(gb->base + SPACEMIT_GEDR); | 
|  | if (!gedr) | 
|  | return IRQ_NONE; | 
|  | writel(gedr, gb->base + SPACEMIT_GEDR); | 
|  |  | 
|  | pending = gedr & gb->irq_mask; | 
|  | if (!pending) | 
|  | return IRQ_NONE; | 
|  |  | 
|  | for_each_set_bit(n, &pending, BITS_PER_LONG) | 
|  | handle_nested_irq(irq_find_mapping(gb->chip.gc.irq.domain, n)); | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static void spacemit_gpio_irq_ack(struct irq_data *d) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); | 
|  |  | 
|  | writel(BIT(irqd_to_hwirq(d)), gb->base + SPACEMIT_GEDR); | 
|  | } | 
|  |  | 
|  | static void spacemit_gpio_irq_mask(struct irq_data *d) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); | 
|  | u32 bit = BIT(irqd_to_hwirq(d)); | 
|  |  | 
|  | gb->irq_mask &= ~bit; | 
|  | writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK); | 
|  |  | 
|  | if (bit & gb->irq_rising_edge) | 
|  | writel(bit, gb->base + SPACEMIT_GCRER); | 
|  |  | 
|  | if (bit & gb->irq_falling_edge) | 
|  | writel(bit, gb->base + SPACEMIT_GCFER); | 
|  | } | 
|  |  | 
|  | static void spacemit_gpio_irq_unmask(struct irq_data *d) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); | 
|  | u32 bit = BIT(irqd_to_hwirq(d)); | 
|  |  | 
|  | gb->irq_mask |= bit; | 
|  |  | 
|  | if (bit & gb->irq_rising_edge) | 
|  | writel(bit, gb->base + SPACEMIT_GSRER); | 
|  |  | 
|  | if (bit & gb->irq_falling_edge) | 
|  | writel(bit, gb->base + SPACEMIT_GSFER); | 
|  |  | 
|  | writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK); | 
|  | } | 
|  |  | 
|  | static int spacemit_gpio_irq_set_type(struct irq_data *d, unsigned int type) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d); | 
|  | u32 bit = BIT(irqd_to_hwirq(d)); | 
|  |  | 
|  | if (type & IRQ_TYPE_EDGE_RISING) { | 
|  | gb->irq_rising_edge |= bit; | 
|  | writel(bit, gb->base + SPACEMIT_GSRER); | 
|  | } else { | 
|  | gb->irq_rising_edge &= ~bit; | 
|  | writel(bit, gb->base + SPACEMIT_GCRER); | 
|  | } | 
|  |  | 
|  | if (type & IRQ_TYPE_EDGE_FALLING) { | 
|  | gb->irq_falling_edge |= bit; | 
|  | writel(bit, gb->base + SPACEMIT_GSFER); | 
|  | } else { | 
|  | gb->irq_falling_edge &= ~bit; | 
|  | writel(bit, gb->base + SPACEMIT_GCFER); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void spacemit_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(data); | 
|  |  | 
|  | seq_printf(p, "%s-%d", dev_name(gb->chip.gc.parent), spacemit_gpio_bank_index(gb)); | 
|  | } | 
|  |  | 
|  | static struct irq_chip spacemit_gpio_chip = { | 
|  | .name		= "k1-gpio-irqchip", | 
|  | .irq_ack	= spacemit_gpio_irq_ack, | 
|  | .irq_mask	= spacemit_gpio_irq_mask, | 
|  | .irq_unmask	= spacemit_gpio_irq_unmask, | 
|  | .irq_set_type	= spacemit_gpio_irq_set_type, | 
|  | .irq_print_chip	= spacemit_gpio_irq_print_chip, | 
|  | .flags		= IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE, | 
|  | GPIOCHIP_IRQ_RESOURCE_HELPERS, | 
|  | }; | 
|  |  | 
|  | static bool spacemit_of_node_instance_match(struct gpio_chip *gc, unsigned int i) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = gpiochip_get_data(gc); | 
|  | struct spacemit_gpio *sg = gb->sg; | 
|  |  | 
|  | if (i >= SPACEMIT_NR_BANKS) | 
|  | return false; | 
|  |  | 
|  | return (gc == &sg->sgb[i].chip.gc); | 
|  | } | 
|  |  | 
|  | static int spacemit_gpio_add_bank(struct spacemit_gpio *sg, | 
|  | void __iomem *regs, | 
|  | int index, int irq) | 
|  | { | 
|  | struct spacemit_gpio_bank *gb = &sg->sgb[index]; | 
|  | struct gpio_generic_chip_config config; | 
|  | struct gpio_chip *gc = &gb->chip.gc; | 
|  | struct device *dev = sg->dev; | 
|  | struct gpio_irq_chip *girq; | 
|  | void __iomem *dat, *set, *clr, *dirin, *dirout; | 
|  | int ret, bank_base[] = { 0x0, 0x4, 0x8, 0x100 }; | 
|  |  | 
|  | gb->base = regs + bank_base[index]; | 
|  |  | 
|  | dat	= gb->base + SPACEMIT_GPLR; | 
|  | set	= gb->base + SPACEMIT_GPSR; | 
|  | clr	= gb->base + SPACEMIT_GPCR; | 
|  | dirin	= gb->base + SPACEMIT_GCDR; | 
|  | dirout	= gb->base + SPACEMIT_GSDR; | 
|  |  | 
|  | config = (struct gpio_generic_chip_config) { | 
|  | .dev = dev, | 
|  | .sz = 4, | 
|  | .dat = dat, | 
|  | .set = set, | 
|  | .clr = clr, | 
|  | .dirout = dirout, | 
|  | .dirin = dirin, | 
|  | .flags = GPIO_GENERIC_UNREADABLE_REG_SET | | 
|  | GPIO_GENERIC_UNREADABLE_REG_DIR, | 
|  | }; | 
|  |  | 
|  | /* This registers 32 GPIO lines per bank */ | 
|  | ret = gpio_generic_chip_init(&gb->chip, &config); | 
|  | if (ret) | 
|  | return dev_err_probe(dev, ret, "failed to init gpio chip\n"); | 
|  |  | 
|  | gb->sg = sg; | 
|  |  | 
|  | gc->label		= dev_name(dev); | 
|  | gc->request		= gpiochip_generic_request; | 
|  | gc->free		= gpiochip_generic_free; | 
|  | gc->ngpio		= SPACEMIT_NR_GPIOS_PER_BANK; | 
|  | gc->base		= -1; | 
|  | gc->of_gpio_n_cells	= 3; | 
|  | gc->of_node_instance_match = spacemit_of_node_instance_match; | 
|  |  | 
|  | girq			= &gc->irq; | 
|  | girq->threaded		= true; | 
|  | girq->handler		= handle_simple_irq; | 
|  |  | 
|  | gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip); | 
|  |  | 
|  | /* Disable Interrupt */ | 
|  | writel(0, gb->base + SPACEMIT_GAPMASK); | 
|  | /* Disable Edge Detection Settings */ | 
|  | writel(0x0, gb->base + SPACEMIT_GRER); | 
|  | writel(0x0, gb->base + SPACEMIT_GFER); | 
|  | /* Clear Interrupt */ | 
|  | writel(0xffffffff, gb->base + SPACEMIT_GCRER); | 
|  | writel(0xffffffff, gb->base + SPACEMIT_GCFER); | 
|  |  | 
|  | ret = devm_request_threaded_irq(dev, irq, NULL, | 
|  | spacemit_gpio_irq_handler, | 
|  | IRQF_ONESHOT | IRQF_SHARED, | 
|  | gb->chip.gc.label, gb); | 
|  | if (ret < 0) | 
|  | return dev_err_probe(dev, ret, "failed to register IRQ\n"); | 
|  |  | 
|  | ret = devm_gpiochip_add_data(dev, gc, gb); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* Distuingish IRQ domain, for selecting threecells mode */ | 
|  | irq_domain_update_bus_token(girq->domain, DOMAIN_BUS_WIRED); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int spacemit_gpio_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct spacemit_gpio *sg; | 
|  | struct clk *core_clk, *bus_clk; | 
|  | void __iomem *regs; | 
|  | int i, irq, ret; | 
|  |  | 
|  | sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL); | 
|  | if (!sg) | 
|  | return -ENOMEM; | 
|  |  | 
|  | regs = devm_platform_ioremap_resource(pdev, 0); | 
|  | if (IS_ERR(regs)) | 
|  | return PTR_ERR(regs); | 
|  |  | 
|  | irq = platform_get_irq(pdev, 0); | 
|  | if (irq < 0) | 
|  | return irq; | 
|  |  | 
|  | sg->dev	= dev; | 
|  |  | 
|  | core_clk = devm_clk_get_enabled(dev, "core"); | 
|  | if (IS_ERR(core_clk)) | 
|  | return dev_err_probe(dev, PTR_ERR(core_clk), "failed to get clock\n"); | 
|  |  | 
|  | bus_clk = devm_clk_get_enabled(dev, "bus"); | 
|  | if (IS_ERR(bus_clk)) | 
|  | return dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clock\n"); | 
|  |  | 
|  | for (i = 0; i < SPACEMIT_NR_BANKS; i++) { | 
|  | ret = spacemit_gpio_add_bank(sg, regs, i, irq); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct of_device_id spacemit_gpio_dt_ids[] = { | 
|  | { .compatible = "spacemit,k1-gpio" }, | 
|  | { /* sentinel */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, spacemit_gpio_dt_ids); | 
|  |  | 
|  | 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@gentoo.org>"); | 
|  | MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC"); | 
|  | MODULE_LICENSE("GPL"); |