1d0055324SYixun Lan // SPDX-License-Identifier: GPL-2.0 OR MIT
2d0055324SYixun Lan /*
3d0055324SYixun Lan * Copyright (C) 2023-2025 SpacemiT (Hangzhou) Technology Co. Ltd
4d0055324SYixun Lan * Copyright (C) 2025 Yixun Lan <dlan@gentoo.org>
5d0055324SYixun Lan */
6d0055324SYixun Lan
7d0055324SYixun Lan #include <linux/clk.h>
8d0055324SYixun Lan #include <linux/gpio/driver.h>
9d0055324SYixun Lan #include <linux/init.h>
10d0055324SYixun Lan #include <linux/interrupt.h>
11d0055324SYixun Lan #include <linux/io.h>
12d0055324SYixun Lan #include <linux/irq.h>
13d0055324SYixun Lan #include <linux/module.h>
14d0055324SYixun Lan #include <linux/platform_device.h>
15d0055324SYixun Lan #include <linux/seq_file.h>
16d0055324SYixun Lan
17d0055324SYixun Lan /* register offset */
18d0055324SYixun Lan #define SPACEMIT_GPLR 0x00 /* port level - R */
19d0055324SYixun Lan #define SPACEMIT_GPDR 0x0c /* port direction - R/W */
20d0055324SYixun Lan #define SPACEMIT_GPSR 0x18 /* port set - W */
21d0055324SYixun Lan #define SPACEMIT_GPCR 0x24 /* port clear - W */
22d0055324SYixun Lan #define SPACEMIT_GRER 0x30 /* port rising edge R/W */
23d0055324SYixun Lan #define SPACEMIT_GFER 0x3c /* port falling edge R/W */
24d0055324SYixun Lan #define SPACEMIT_GEDR 0x48 /* edge detect status - R/W1C */
25d0055324SYixun Lan #define SPACEMIT_GSDR 0x54 /* (set) direction - W */
26d0055324SYixun Lan #define SPACEMIT_GCDR 0x60 /* (clear) direction - W */
27d0055324SYixun Lan #define SPACEMIT_GSRER 0x6c /* (set) rising edge detect enable - W */
28d0055324SYixun Lan #define SPACEMIT_GCRER 0x78 /* (clear) rising edge detect enable - W */
29d0055324SYixun Lan #define SPACEMIT_GSFER 0x84 /* (set) falling edge detect enable - W */
30d0055324SYixun Lan #define SPACEMIT_GCFER 0x90 /* (clear) falling edge detect enable - W */
31d0055324SYixun Lan #define SPACEMIT_GAPMASK 0x9c /* interrupt mask , 0 disable, 1 enable - R/W */
32d0055324SYixun Lan
33d0055324SYixun Lan #define SPACEMIT_NR_BANKS 4
34d0055324SYixun Lan #define SPACEMIT_NR_GPIOS_PER_BANK 32
35d0055324SYixun Lan
36d0055324SYixun Lan #define to_spacemit_gpio_bank(x) container_of((x), struct spacemit_gpio_bank, gc)
37d0055324SYixun Lan
38d0055324SYixun Lan struct spacemit_gpio;
39d0055324SYixun Lan
40d0055324SYixun Lan struct spacemit_gpio_bank {
41d0055324SYixun Lan struct gpio_chip gc;
42d0055324SYixun Lan struct spacemit_gpio *sg;
43d0055324SYixun Lan void __iomem *base;
44d0055324SYixun Lan u32 irq_mask;
45d0055324SYixun Lan u32 irq_rising_edge;
46d0055324SYixun Lan u32 irq_falling_edge;
47d0055324SYixun Lan };
48d0055324SYixun Lan
49d0055324SYixun Lan struct spacemit_gpio {
50d0055324SYixun Lan struct device *dev;
51d0055324SYixun Lan struct spacemit_gpio_bank sgb[SPACEMIT_NR_BANKS];
52d0055324SYixun Lan };
53d0055324SYixun Lan
spacemit_gpio_bank_index(struct spacemit_gpio_bank * gb)54d0055324SYixun Lan static u32 spacemit_gpio_bank_index(struct spacemit_gpio_bank *gb)
55d0055324SYixun Lan {
56d0055324SYixun Lan return (u32)(gb - gb->sg->sgb);
57d0055324SYixun Lan }
58d0055324SYixun Lan
spacemit_gpio_irq_handler(int irq,void * dev_id)59d0055324SYixun Lan static irqreturn_t spacemit_gpio_irq_handler(int irq, void *dev_id)
60d0055324SYixun Lan {
61d0055324SYixun Lan struct spacemit_gpio_bank *gb = dev_id;
62d0055324SYixun Lan unsigned long pending;
63d0055324SYixun Lan u32 n, gedr;
64d0055324SYixun Lan
65d0055324SYixun Lan gedr = readl(gb->base + SPACEMIT_GEDR);
66d0055324SYixun Lan if (!gedr)
67d0055324SYixun Lan return IRQ_NONE;
68d0055324SYixun Lan writel(gedr, gb->base + SPACEMIT_GEDR);
69d0055324SYixun Lan
70d0055324SYixun Lan pending = gedr & gb->irq_mask;
71d0055324SYixun Lan if (!pending)
72d0055324SYixun Lan return IRQ_NONE;
73d0055324SYixun Lan
74d0055324SYixun Lan for_each_set_bit(n, &pending, BITS_PER_LONG)
75d0055324SYixun Lan handle_nested_irq(irq_find_mapping(gb->gc.irq.domain, n));
76d0055324SYixun Lan
77d0055324SYixun Lan return IRQ_HANDLED;
78d0055324SYixun Lan }
79d0055324SYixun Lan
spacemit_gpio_irq_ack(struct irq_data * d)80d0055324SYixun Lan static void spacemit_gpio_irq_ack(struct irq_data *d)
81d0055324SYixun Lan {
82d0055324SYixun Lan struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
83d0055324SYixun Lan
84d0055324SYixun Lan writel(BIT(irqd_to_hwirq(d)), gb->base + SPACEMIT_GEDR);
85d0055324SYixun Lan }
86d0055324SYixun Lan
spacemit_gpio_irq_mask(struct irq_data * d)87d0055324SYixun Lan static void spacemit_gpio_irq_mask(struct irq_data *d)
88d0055324SYixun Lan {
89d0055324SYixun Lan struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
90d0055324SYixun Lan u32 bit = BIT(irqd_to_hwirq(d));
91d0055324SYixun Lan
92d0055324SYixun Lan gb->irq_mask &= ~bit;
93d0055324SYixun Lan writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK);
94d0055324SYixun Lan
95d0055324SYixun Lan if (bit & gb->irq_rising_edge)
96d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GCRER);
97d0055324SYixun Lan
98d0055324SYixun Lan if (bit & gb->irq_falling_edge)
99d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GCFER);
100d0055324SYixun Lan }
101d0055324SYixun Lan
spacemit_gpio_irq_unmask(struct irq_data * d)102d0055324SYixun Lan static void spacemit_gpio_irq_unmask(struct irq_data *d)
103d0055324SYixun Lan {
104d0055324SYixun Lan struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
105d0055324SYixun Lan u32 bit = BIT(irqd_to_hwirq(d));
106d0055324SYixun Lan
107d0055324SYixun Lan gb->irq_mask |= bit;
108d0055324SYixun Lan
109d0055324SYixun Lan if (bit & gb->irq_rising_edge)
110d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GSRER);
111d0055324SYixun Lan
112d0055324SYixun Lan if (bit & gb->irq_falling_edge)
113d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GSFER);
114d0055324SYixun Lan
115d0055324SYixun Lan writel(gb->irq_mask, gb->base + SPACEMIT_GAPMASK);
116d0055324SYixun Lan }
117d0055324SYixun Lan
spacemit_gpio_irq_set_type(struct irq_data * d,unsigned int type)118d0055324SYixun Lan static int spacemit_gpio_irq_set_type(struct irq_data *d, unsigned int type)
119d0055324SYixun Lan {
120d0055324SYixun Lan struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(d);
121d0055324SYixun Lan u32 bit = BIT(irqd_to_hwirq(d));
122d0055324SYixun Lan
123d0055324SYixun Lan if (type & IRQ_TYPE_EDGE_RISING) {
124d0055324SYixun Lan gb->irq_rising_edge |= bit;
125d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GSRER);
126d0055324SYixun Lan } else {
127d0055324SYixun Lan gb->irq_rising_edge &= ~bit;
128d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GCRER);
129d0055324SYixun Lan }
130d0055324SYixun Lan
131d0055324SYixun Lan if (type & IRQ_TYPE_EDGE_FALLING) {
132d0055324SYixun Lan gb->irq_falling_edge |= bit;
133d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GSFER);
134d0055324SYixun Lan } else {
135d0055324SYixun Lan gb->irq_falling_edge &= ~bit;
136d0055324SYixun Lan writel(bit, gb->base + SPACEMIT_GCFER);
137d0055324SYixun Lan }
138d0055324SYixun Lan
139d0055324SYixun Lan return 0;
140d0055324SYixun Lan }
141d0055324SYixun Lan
spacemit_gpio_irq_print_chip(struct irq_data * data,struct seq_file * p)142d0055324SYixun Lan static void spacemit_gpio_irq_print_chip(struct irq_data *data, struct seq_file *p)
143d0055324SYixun Lan {
144d0055324SYixun Lan struct spacemit_gpio_bank *gb = irq_data_get_irq_chip_data(data);
145d0055324SYixun Lan
146d0055324SYixun Lan seq_printf(p, "%s-%d", dev_name(gb->gc.parent), spacemit_gpio_bank_index(gb));
147d0055324SYixun Lan }
148d0055324SYixun Lan
149d0055324SYixun Lan static struct irq_chip spacemit_gpio_chip = {
150d0055324SYixun Lan .name = "k1-gpio-irqchip",
151d0055324SYixun Lan .irq_ack = spacemit_gpio_irq_ack,
152d0055324SYixun Lan .irq_mask = spacemit_gpio_irq_mask,
153d0055324SYixun Lan .irq_unmask = spacemit_gpio_irq_unmask,
154d0055324SYixun Lan .irq_set_type = spacemit_gpio_irq_set_type,
155d0055324SYixun Lan .irq_print_chip = spacemit_gpio_irq_print_chip,
156d0055324SYixun Lan .flags = IRQCHIP_IMMUTABLE | IRQCHIP_SKIP_SET_WAKE,
157d0055324SYixun Lan GPIOCHIP_IRQ_RESOURCE_HELPERS,
158d0055324SYixun Lan };
159d0055324SYixun Lan
spacemit_of_node_instance_match(struct gpio_chip * gc,unsigned int i)160d0055324SYixun Lan static bool spacemit_of_node_instance_match(struct gpio_chip *gc, unsigned int i)
161d0055324SYixun Lan {
162d0055324SYixun Lan struct spacemit_gpio_bank *gb = gpiochip_get_data(gc);
163d0055324SYixun Lan struct spacemit_gpio *sg = gb->sg;
164d0055324SYixun Lan
165d0055324SYixun Lan if (i >= SPACEMIT_NR_BANKS)
166d0055324SYixun Lan return false;
167d0055324SYixun Lan
168d0055324SYixun Lan return (gc == &sg->sgb[i].gc);
169d0055324SYixun Lan }
170d0055324SYixun Lan
spacemit_gpio_add_bank(struct spacemit_gpio * sg,void __iomem * regs,int index,int irq)171d0055324SYixun Lan static int spacemit_gpio_add_bank(struct spacemit_gpio *sg,
172d0055324SYixun Lan void __iomem *regs,
173d0055324SYixun Lan int index, int irq)
174d0055324SYixun Lan {
175d0055324SYixun Lan struct spacemit_gpio_bank *gb = &sg->sgb[index];
176d0055324SYixun Lan struct gpio_chip *gc = &gb->gc;
177d0055324SYixun Lan struct device *dev = sg->dev;
178d0055324SYixun Lan struct gpio_irq_chip *girq;
179d0055324SYixun Lan void __iomem *dat, *set, *clr, *dirin, *dirout;
180d0055324SYixun Lan int ret, bank_base[] = { 0x0, 0x4, 0x8, 0x100 };
181d0055324SYixun Lan
182d0055324SYixun Lan gb->base = regs + bank_base[index];
183d0055324SYixun Lan
184d0055324SYixun Lan dat = gb->base + SPACEMIT_GPLR;
185d0055324SYixun Lan set = gb->base + SPACEMIT_GPSR;
186d0055324SYixun Lan clr = gb->base + SPACEMIT_GPCR;
187d0055324SYixun Lan dirin = gb->base + SPACEMIT_GCDR;
188d0055324SYixun Lan dirout = gb->base + SPACEMIT_GSDR;
189d0055324SYixun Lan
190d0055324SYixun Lan /* This registers 32 GPIO lines per bank */
191d0055324SYixun Lan ret = bgpio_init(gc, dev, 4, dat, set, clr, dirout, dirin,
192d0055324SYixun Lan BGPIOF_UNREADABLE_REG_SET | BGPIOF_UNREADABLE_REG_DIR);
193d0055324SYixun Lan if (ret)
194d0055324SYixun Lan return dev_err_probe(dev, ret, "failed to init gpio chip\n");
195d0055324SYixun Lan
196d0055324SYixun Lan gb->sg = sg;
197d0055324SYixun Lan
198d0055324SYixun Lan gc->label = dev_name(dev);
199d0055324SYixun Lan gc->request = gpiochip_generic_request;
200d0055324SYixun Lan gc->free = gpiochip_generic_free;
201d0055324SYixun Lan gc->ngpio = SPACEMIT_NR_GPIOS_PER_BANK;
202d0055324SYixun Lan gc->base = -1;
203d0055324SYixun Lan gc->of_gpio_n_cells = 3;
204d0055324SYixun Lan gc->of_node_instance_match = spacemit_of_node_instance_match;
205d0055324SYixun Lan
206d0055324SYixun Lan girq = &gc->irq;
207d0055324SYixun Lan girq->threaded = true;
208d0055324SYixun Lan girq->handler = handle_simple_irq;
209d0055324SYixun Lan
210d0055324SYixun Lan gpio_irq_chip_set_chip(girq, &spacemit_gpio_chip);
211d0055324SYixun Lan
212d0055324SYixun Lan /* Disable Interrupt */
213d0055324SYixun Lan writel(0, gb->base + SPACEMIT_GAPMASK);
214d0055324SYixun Lan /* Disable Edge Detection Settings */
215d0055324SYixun Lan writel(0x0, gb->base + SPACEMIT_GRER);
216d0055324SYixun Lan writel(0x0, gb->base + SPACEMIT_GFER);
217d0055324SYixun Lan /* Clear Interrupt */
218d0055324SYixun Lan writel(0xffffffff, gb->base + SPACEMIT_GCRER);
219d0055324SYixun Lan writel(0xffffffff, gb->base + SPACEMIT_GCFER);
220d0055324SYixun Lan
221d0055324SYixun Lan ret = devm_request_threaded_irq(dev, irq, NULL,
222d0055324SYixun Lan spacemit_gpio_irq_handler,
223d0055324SYixun Lan IRQF_ONESHOT | IRQF_SHARED,
224d0055324SYixun Lan gb->gc.label, gb);
225d0055324SYixun Lan if (ret < 0)
226d0055324SYixun Lan return dev_err_probe(dev, ret, "failed to register IRQ\n");
227d0055324SYixun Lan
228d0055324SYixun Lan ret = devm_gpiochip_add_data(dev, gc, gb);
229d0055324SYixun Lan if (ret)
230d0055324SYixun Lan return ret;
231d0055324SYixun Lan
232d0055324SYixun Lan /* Distuingish IRQ domain, for selecting threecells mode */
233d0055324SYixun Lan irq_domain_update_bus_token(girq->domain, DOMAIN_BUS_WIRED);
234d0055324SYixun Lan
235d0055324SYixun Lan return 0;
236d0055324SYixun Lan }
237d0055324SYixun Lan
spacemit_gpio_probe(struct platform_device * pdev)238d0055324SYixun Lan static int spacemit_gpio_probe(struct platform_device *pdev)
239d0055324SYixun Lan {
240d0055324SYixun Lan struct device *dev = &pdev->dev;
241d0055324SYixun Lan struct spacemit_gpio *sg;
242d0055324SYixun Lan struct clk *core_clk, *bus_clk;
243d0055324SYixun Lan void __iomem *regs;
244d0055324SYixun Lan int i, irq, ret;
245d0055324SYixun Lan
246d0055324SYixun Lan sg = devm_kzalloc(dev, sizeof(*sg), GFP_KERNEL);
247d0055324SYixun Lan if (!sg)
248d0055324SYixun Lan return -ENOMEM;
249d0055324SYixun Lan
250d0055324SYixun Lan regs = devm_platform_ioremap_resource(pdev, 0);
251d0055324SYixun Lan if (IS_ERR(regs))
252d0055324SYixun Lan return PTR_ERR(regs);
253d0055324SYixun Lan
254d0055324SYixun Lan irq = platform_get_irq(pdev, 0);
255d0055324SYixun Lan if (irq < 0)
256d0055324SYixun Lan return irq;
257d0055324SYixun Lan
258d0055324SYixun Lan sg->dev = dev;
259d0055324SYixun Lan
260d0055324SYixun Lan core_clk = devm_clk_get_enabled(dev, "core");
261d0055324SYixun Lan if (IS_ERR(core_clk))
262d0055324SYixun Lan return dev_err_probe(dev, PTR_ERR(core_clk), "failed to get clock\n");
263d0055324SYixun Lan
264d0055324SYixun Lan bus_clk = devm_clk_get_enabled(dev, "bus");
265d0055324SYixun Lan if (IS_ERR(bus_clk))
266d0055324SYixun Lan return dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clock\n");
267d0055324SYixun Lan
268d0055324SYixun Lan for (i = 0; i < SPACEMIT_NR_BANKS; i++) {
269d0055324SYixun Lan ret = spacemit_gpio_add_bank(sg, regs, i, irq);
270d0055324SYixun Lan if (ret)
271d0055324SYixun Lan return ret;
272d0055324SYixun Lan }
273d0055324SYixun Lan
274d0055324SYixun Lan return 0;
275d0055324SYixun Lan }
276d0055324SYixun Lan
277d0055324SYixun Lan static const struct of_device_id spacemit_gpio_dt_ids[] = {
278d0055324SYixun Lan { .compatible = "spacemit,k1-gpio" },
279d0055324SYixun Lan { /* sentinel */ }
280d0055324SYixun Lan };
281*e6382fcfSVivian Wang MODULE_DEVICE_TABLE(of, spacemit_gpio_dt_ids);
282d0055324SYixun Lan
283d0055324SYixun Lan static struct platform_driver spacemit_gpio_driver = {
284d0055324SYixun Lan .probe = spacemit_gpio_probe,
285d0055324SYixun Lan .driver = {
286d0055324SYixun Lan .name = "k1-gpio",
287d0055324SYixun Lan .of_match_table = spacemit_gpio_dt_ids,
288d0055324SYixun Lan },
289d0055324SYixun Lan };
290d0055324SYixun Lan module_platform_driver(spacemit_gpio_driver);
291d0055324SYixun Lan
292d0055324SYixun Lan MODULE_AUTHOR("Yixun Lan <dlan@gentoo.org>");
293d0055324SYixun Lan MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC");
294d0055324SYixun Lan MODULE_LICENSE("GPL");
295