summaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-csky-apb-intc.c
blob: 5a2ec43b7ddd49311be11d71fd8dc998e926934e (plain)
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
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/module.h>
#include <linux/irqdomain.h>
#include <linux/irqchip.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <asm/irq.h>

#define INTC_IRQS		64

#define CK_INTC_ICR		0x00
#define CK_INTC_PEN31_00	0x14
#define CK_INTC_PEN63_32	0x2c
#define CK_INTC_NEN31_00	0x10
#define CK_INTC_NEN63_32	0x28
#define CK_INTC_SOURCE		0x40
#define CK_INTC_DUAL_BASE	0x100

#define GX_INTC_PEN31_00	0x00
#define GX_INTC_PEN63_32	0x04
#define GX_INTC_NEN31_00	0x40
#define GX_INTC_NEN63_32	0x44
#define GX_INTC_NMASK31_00	0x50
#define GX_INTC_NMASK63_32	0x54
#define GX_INTC_SOURCE		0x60

static void __iomem *reg_base;
static struct irq_domain *root_domain;

static int nr_irq = INTC_IRQS;

/*
 * When controller support pulse signal, the PEN_reg will hold on signal
 * without software trigger.
 *
 * So, to support pulse signal we need to clear IFR_reg and the address of
 * IFR_offset is NEN_offset - 8.
 */
static void irq_ck_mask_set_bit(struct irq_data *d)
{
	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
	struct irq_chip_type *ct = irq_data_get_chip_type(d);
	unsigned long ifr = ct->regs.mask - 8;
	u32 mask = d->mask;

	irq_gc_lock(gc);
	*ct->mask_cache |= mask;
	irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask);
	irq_reg_writel(gc, irq_reg_readl(gc, ifr) & ~mask, ifr);
	irq_gc_unlock(gc);
}

static void __init ck_set_gc(struct device_node *node, void __iomem *reg_base,
			     u32 mask_reg, u32 irq_base)
{
	struct irq_chip_generic *gc;

	gc = irq_get_domain_generic_chip(root_domain, irq_base);
	gc->reg_base = reg_base;
	gc->chip_types[0].regs.mask = mask_reg;
	gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;

	if (of_find_property(node, "csky,support-pulse-signal", NULL))
		gc->chip_types[0].chip.irq_unmask = irq_ck_mask_set_bit;
}

static inline u32 build_channel_val(u32 idx, u32 magic)
{
	u32 res;

	/*
	 * Set the same index for each channel
	 */
	res = idx | (idx << 8) | (idx << 16) | (idx << 24);

	/*
	 * Set the channel magic number in descending order.
	 * The magic is 0x00010203 for ck-intc
	 * The magic is 0x03020100 for gx6605s-intc
	 */
	return res | magic;
}

static inline void setup_irq_channel(u32 magic, void __iomem *reg_addr)
{
	u32 i;

	/* Setup 64 channel slots */
	for (i = 0; i < INTC_IRQS; i += 4)
		writel(build_channel_val(i, magic), reg_addr + i);
}

static int __init
ck_intc_init_comm(struct device_node *node, struct device_node *parent)
{
	int ret;

	if (parent) {
		pr_err("C-SKY Intc not a root irq controller\n");
		return -EINVAL;
	}

	reg_base = of_iomap(node, 0);
	if (!reg_base) {
		pr_err("C-SKY Intc unable to map: %p.\n", node);
		return -EINVAL;
	}

	root_domain = irq_domain_add_linear(node, nr_irq,
					    &irq_generic_chip_ops, NULL);
	if (!root_domain) {
		pr_err("C-SKY Intc irq_domain_add failed.\n");
		return -ENOMEM;
	}

	ret = irq_alloc_domain_generic_chips(root_domain, 32, 1,
			"csky_intc", handle_level_irq,
			IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, 0, 0);
	if (ret) {
		pr_err("C-SKY Intc irq_alloc_gc failed.\n");
		return -ENOMEM;
	}

	return 0;
}

static inline bool handle_irq_perbit(struct pt_regs *regs, u32 hwirq,
				     u32 irq_base)
{
	if (hwirq == 0)
		return 0;

	handle_domain_irq(root_domain, irq_base + __fls(hwirq), regs);

	return 1;
}

/* gx6605s 64 irqs interrupt controller */
static void gx_irq_handler(struct pt_regs *regs)
{
	bool ret;

retry:
	ret = handle_irq_perbit(regs,
			readl(reg_base + GX_INTC_PEN63_32), 32);
	if (ret)
		goto retry;

	ret = handle_irq_perbit(regs,
			readl(reg_base + GX_INTC_PEN31_00), 0);
	if (ret)
		goto retry;
}

static int __init
gx_intc_init(struct device_node *node, struct device_node *parent)
{
	int ret;

	ret = ck_intc_init_comm(node, parent);
	if (ret)
		return ret;

	/*
	 * Initial enable reg to disable all interrupts
	 */
	writel(0x0, reg_base + GX_INTC_NEN31_00);
	writel(0x0, reg_base + GX_INTC_NEN63_32);

	/*
	 * Initial mask reg with all unmasked, because we only use enalbe reg
	 */
	writel(0x0, reg_base + GX_INTC_NMASK31_00);
	writel(0x0, reg_base + GX_INTC_NMASK63_32);

	setup_irq_channel(0x03020100, reg_base + GX_INTC_SOURCE);

	ck_set_gc(node, reg_base, GX_INTC_NEN31_00, 0);
	ck_set_gc(node, reg_base, GX_INTC_NEN63_32, 32);

	set_handle_irq(gx_irq_handler);

	return 0;
}
IRQCHIP_DECLARE(csky_gx6605s_intc, "csky,gx6605s-intc", gx_intc_init);

/*
 * C-SKY simple 64 irqs interrupt controller, dual-together could support 128
 * irqs.
 */
static void ck_irq_handler(struct pt_regs *regs)
{
	bool ret;
	void __iomem *reg_pen_lo = reg_base + CK_INTC_PEN31_00;
	void __iomem *reg_pen_hi = reg_base + CK_INTC_PEN63_32;

retry:
	/* handle 0 - 63 irqs */
	ret = handle_irq_perbit(regs, readl(reg_pen_hi), 32);
	if (ret)
		goto retry;

	ret = handle_irq_perbit(regs, readl(reg_pen_lo), 0);
	if (ret)
		goto retry;

	if (nr_irq == INTC_IRQS)
		return;

	/* handle 64 - 127 irqs */
	ret = handle_irq_perbit(regs,
			readl(reg_pen_hi + CK_INTC_DUAL_BASE), 96);
	if (ret)
		goto retry;

	ret = handle_irq_perbit(regs,
			readl(reg_pen_lo + CK_INTC_DUAL_BASE), 64);
	if (ret)
		goto retry;
}

static int __init
ck_intc_init(struct device_node *node, struct device_node *parent)
{
	int ret;

	ret = ck_intc_init_comm(node, parent);
	if (ret)
		return ret;

	/* Initial enable reg to disable all interrupts */
	writel(0, reg_base + CK_INTC_NEN31_00);
	writel(0, reg_base + CK_INTC_NEN63_32);

	/* Enable irq intc */
	writel(BIT(31), reg_base + CK_INTC_ICR);

	ck_set_gc(node, reg_base, CK_INTC_NEN31_00, 0);
	ck_set_gc(node, reg_base, CK_INTC_NEN63_32, 32);

	setup_irq_channel(0x00010203, reg_base + CK_INTC_SOURCE);

	set_handle_irq(ck_irq_handler);

	return 0;
}
IRQCHIP_DECLARE(ck_intc, "csky,apb-intc", ck_intc_init);

static int __init
ck_dual_intc_init(struct device_node *node, struct device_node *parent)
{
	int ret;

	/* dual-apb-intc up to 128 irq sources*/
	nr_irq = INTC_IRQS * 2;

	ret = ck_intc_init(node, parent);
	if (ret)
		return ret;

	/* Initial enable reg to disable all interrupts */
	writel(0, reg_base + CK_INTC_NEN31_00 + CK_INTC_DUAL_BASE);
	writel(0, reg_base + CK_INTC_NEN63_32 + CK_INTC_DUAL_BASE);

	ck_set_gc(node, reg_base + CK_INTC_DUAL_BASE, CK_INTC_NEN31_00, 64);
	ck_set_gc(node, reg_base + CK_INTC_DUAL_BASE, CK_INTC_NEN63_32, 96);

	setup_irq_channel(0x00010203,
			  reg_base + CK_INTC_SOURCE + CK_INTC_DUAL_BASE);

	return 0;
}
IRQCHIP_DECLARE(ck_dual_intc, "csky,dual-apb-intc", ck_dual_intc_init);
OpenPOWER on IntegriCloud