File Index
Files and directories
Sign in
FILE

gpio-spacemit-k1.c

File preview with syntax highlighting.

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