1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
// 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/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_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 <dlan@gentoo.org>");
MODULE_DESCRIPTION("GPIO driver for SpacemiT K1 SoC");
MODULE_LICENSE("GPL");
|