// SPDX-License-Identifier: GPL-2.0 OR MIT /* * Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd * Copyright (C) 2025 Yixun Lan */ #include #include #include #include #include #include #include #include #include /* 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_chip gc; 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->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->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].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_chip *gc = &gb->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; /* This registers 32 GPIO lines per bank */ ret = bgpio_init(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\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->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 */ } }; 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 "); MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC"); MODULE_LICENSE("GPL");