summaryrefslogtreecommitdiffstats
path: root/arch/c6x/platforms/megamod-pic.c
blob: 56189e50728c0a075e2d37df92e0431c8d1e0018 (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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  Support for C64x+ Megamodule Interrupt Controller
 *
 *  Copyright (C) 2010, 2011 Texas Instruments Incorporated
 *  Contributed by: Mark Salter <msalter@redhat.com>
 */
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <asm/soc.h>
#include <asm/megamod-pic.h>

#define NR_COMBINERS	4
#define NR_MUX_OUTPUTS  12

#define IRQ_UNMAPPED 0xffff

/*
 * Megamodule Interrupt Controller register layout
 */
struct megamod_regs {
	u32	evtflag[8];
	u32	evtset[8];
	u32	evtclr[8];
	u32	reserved0[8];
	u32	evtmask[8];
	u32	mevtflag[8];
	u32	expmask[8];
	u32	mexpflag[8];
	u32	intmux_unused;
	u32	intmux[7];
	u32	reserved1[8];
	u32	aegmux[2];
	u32	reserved2[14];
	u32	intxstat;
	u32	intxclr;
	u32	intdmask;
	u32	reserved3[13];
	u32	evtasrt;
};

struct megamod_pic {
	struct irq_domain *irqhost;
	struct megamod_regs __iomem *regs;
	raw_spinlock_t lock;

	/* hw mux mapping */
	unsigned int output_to_irq[NR_MUX_OUTPUTS];
};

static struct megamod_pic *mm_pic;

struct megamod_cascade_data {
	struct megamod_pic *pic;
	int index;
};

static struct megamod_cascade_data cascade_data[NR_COMBINERS];

static void mask_megamod(struct irq_data *data)
{
	struct megamod_pic *pic = irq_data_get_irq_chip_data(data);
	irq_hw_number_t src = irqd_to_hwirq(data);
	u32 __iomem *evtmask = &pic->regs->evtmask[src / 32];

	raw_spin_lock(&pic->lock);
	soc_writel(soc_readl(evtmask) | (1 << (src & 31)), evtmask);
	raw_spin_unlock(&pic->lock);
}

static void unmask_megamod(struct irq_data *data)
{
	struct megamod_pic *pic = irq_data_get_irq_chip_data(data);
	irq_hw_number_t src = irqd_to_hwirq(data);
	u32 __iomem *evtmask = &pic->regs->evtmask[src / 32];

	raw_spin_lock(&pic->lock);
	soc_writel(soc_readl(evtmask) & ~(1 << (src & 31)), evtmask);
	raw_spin_unlock(&pic->lock);
}

static struct irq_chip megamod_chip = {
	.name		= "megamod",
	.irq_mask	= mask_megamod,
	.irq_unmask	= unmask_megamod,
};

static void megamod_irq_cascade(struct irq_desc *desc)
{
	struct megamod_cascade_data *cascade;
	struct megamod_pic *pic;
	unsigned int irq;
	u32 events;
	int n, idx;

	cascade = irq_desc_get_handler_data(desc);

	pic = cascade->pic;
	idx = cascade->index;

	while ((events = soc_readl(&pic->regs->mevtflag[idx])) != 0) {
		n = __ffs(events);

		irq = irq_linear_revmap(pic->irqhost, idx * 32 + n);

		soc_writel(1 << n, &pic->regs->evtclr[idx]);

		generic_handle_irq(irq);
	}
}

static int megamod_map(struct irq_domain *h, unsigned int virq,
		       irq_hw_number_t hw)
{
	struct megamod_pic *pic = h->host_data;
	int i;

	/* We shouldn't see a hwirq which is muxed to core controller */
	for (i = 0; i < NR_MUX_OUTPUTS; i++)
		if (pic->output_to_irq[i] == hw)
			return -1;

	irq_set_chip_data(virq, pic);
	irq_set_chip_and_handler(virq, &megamod_chip, handle_level_irq);

	/* Set default irq type */
	irq_set_irq_type(virq, IRQ_TYPE_NONE);

	return 0;
}

static const struct irq_domain_ops megamod_domain_ops = {
	.map	= megamod_map,
	.xlate	= irq_domain_xlate_onecell,
};

static void __init set_megamod_mux(struct megamod_pic *pic, int src, int output)
{
	int index, offset;
	u32 val;

	if (src < 0 || src >= (NR_COMBINERS * 32)) {
		pic->output_to_irq[output] = IRQ_UNMAPPED;
		return;
	}

	/* four mappings per mux register */
	index = output / 4;
	offset = (output & 3) * 8;

	val = soc_readl(&pic->regs->intmux[index]);
	val &= ~(0xff << offset);
	val |= src << offset;
	soc_writel(val, &pic->regs->intmux[index]);
}

/*
 * Parse the MUX mapping, if one exists.
 *
 * The MUX map is an array of up to 12 cells; one for each usable core priority
 * interrupt. The value of a given cell is the megamodule interrupt source
 * which is to me MUXed to the output corresponding to the cell position
 * withing the array. The first cell in the array corresponds to priority
 * 4 and the last (12th) cell corresponds to priority 15. The allowed
 * values are 4 - ((NR_COMBINERS * 32) - 1). Note that the combined interrupt
 * sources (0 - 3) are not allowed to be mapped through this property. They
 * are handled through the "interrupts" property. This allows us to use a
 * value of zero as a "do not map" placeholder.
 */
static void __init parse_priority_map(struct megamod_pic *pic,
				      int *mapping, int size)
{
	struct device_node *np = irq_domain_get_of_node(pic->irqhost);
	const __be32 *map;
	int i, maplen;
	u32 val;

	map = of_get_property(np, "ti,c64x+megamod-pic-mux", &maplen);
	if (map) {
		maplen /= 4;
		if (maplen > size)
			maplen = size;

		for (i = 0; i < maplen; i++) {
			val = be32_to_cpup(map);
			if (val && val >= 4)
				mapping[i] = val;
			++map;
		}
	}
}

static struct megamod_pic * __init init_megamod_pic(struct device_node *np)
{
	struct megamod_pic *pic;
	int i, irq;
	int mapping[NR_MUX_OUTPUTS];

	pr_info("Initializing C64x+ Megamodule PIC\n");

	pic = kzalloc(sizeof(struct megamod_pic), GFP_KERNEL);
	if (!pic) {
		pr_err("%pOF: Could not alloc PIC structure.\n", np);
		return NULL;
	}

	pic->irqhost = irq_domain_add_linear(np, NR_COMBINERS * 32,
					     &megamod_domain_ops, pic);
	if (!pic->irqhost) {
		pr_err("%pOF: Could not alloc host.\n", np);
		goto error_free;
	}

	pic->irqhost->host_data = pic;

	raw_spin_lock_init(&pic->lock);

	pic->regs = of_iomap(np, 0);
	if (!pic->regs) {
		pr_err("%pOF: Could not map registers.\n", np);
		goto error_free;
	}

	/* Initialize MUX map */
	for (i = 0; i < ARRAY_SIZE(mapping); i++)
		mapping[i] = IRQ_UNMAPPED;

	parse_priority_map(pic, mapping, ARRAY_SIZE(mapping));

	/*
	 * We can have up to 12 interrupts cascading to the core controller.
	 * These cascades can be from the combined interrupt sources or for
	 * individual interrupt sources. The "interrupts" property only
	 * deals with the cascaded combined interrupts. The individual
	 * interrupts muxed to the core controller use the core controller
	 * as their interrupt parent.
	 */
	for (i = 0; i < NR_COMBINERS; i++) {
		struct irq_data *irq_data;
		irq_hw_number_t hwirq;

		irq = irq_of_parse_and_map(np, i);
		if (irq == NO_IRQ)
			continue;

		irq_data = irq_get_irq_data(irq);
		if (!irq_data) {
			pr_err("%pOF: combiner-%d no irq_data for virq %d!\n",
			       np, i, irq);
			continue;
		}

		hwirq = irq_data->hwirq;

		/*
		 * Check that device tree provided something in the range
		 * of the core priority interrupts (4 - 15).
		 */
		if (hwirq < 4 || hwirq >= NR_PRIORITY_IRQS) {
			pr_err("%pOF: combiner-%d core irq %ld out of range!\n",
			       np, i, hwirq);
			continue;
		}

		/* record the mapping */
		mapping[hwirq - 4] = i;

		pr_debug("%pOF: combiner-%d cascading to hwirq %ld\n",
			 np, i, hwirq);

		cascade_data[i].pic = pic;
		cascade_data[i].index = i;

		/* mask and clear all events in combiner */
		soc_writel(~0, &pic->regs->evtmask[i]);
		soc_writel(~0, &pic->regs->evtclr[i]);

		irq_set_chained_handler_and_data(irq, megamod_irq_cascade,
						 &cascade_data[i]);
	}

	/* Finally, set up the MUX registers */
	for (i = 0; i < NR_MUX_OUTPUTS; i++) {
		if (mapping[i] != IRQ_UNMAPPED) {
			pr_debug("%pOF: setting mux %d to priority %d\n",
				 np, mapping[i], i + 4);
			set_megamod_mux(pic, mapping[i], i);
		}
	}

	return pic;

error_free:
	kfree(pic);

	return NULL;
}

/*
 * Return next active event after ACK'ing it.
 * Return -1 if no events active.
 */
static int get_exception(void)
{
	int i, bit;
	u32 mask;

	for (i = 0; i < NR_COMBINERS; i++) {
		mask = soc_readl(&mm_pic->regs->mexpflag[i]);
		if (mask) {
			bit = __ffs(mask);
			soc_writel(1 << bit, &mm_pic->regs->evtclr[i]);
			return (i * 32) + bit;
		}
	}
	return -1;
}

static void assert_event(unsigned int val)
{
	soc_writel(val, &mm_pic->regs->evtasrt);
}

void __init megamod_pic_init(void)
{
	struct device_node *np;

	np = of_find_compatible_node(NULL, NULL, "ti,c64x+megamod-pic");
	if (!np)
		return;

	mm_pic = init_megamod_pic(np);
	of_node_put(np);

	soc_ops.get_exception = get_exception;
	soc_ops.assert_event = assert_event;

	return;
}