1/*
2 * Copyright 2006, Broadcom Corporation
3 * All Rights Reserved.
4 *
5 * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
6 * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
7 * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
8 * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
9 *
10 * Broadcom HND BCM47xx chips interrupt dispatcher.
11 * Derived from ../generic/irq.c
12 *
13 *	MIPS IRQ	Source
14 *      --------        ------------------
15 *             0	Software
16 *             1        Software
17 *             2        Hardware (shared)
18 *             3        Hardware
19 *             4        Hardware
20 *             5        Hardware
21 *             6        Hardware
22 *             7        Hardware (r4k timer)
23 *
24 *      MIPS IRQ        Linux IRQ
25 *      --------        -----------
26 *         0 - 1        0 - 1
27 *             2        8 and above
28 *         3 - 7        3 - 7
29 *
30 * MIPS has 8 IRQs as indicated and assigned above. SB cores
31 * that use dedicated MIPS IRQ3 to IRQ6 are 1-to-1 mapped to
32 * linux IRQ3 to IRQ6. SB cores sharing MIPS IRQ2 are mapped
33 * to linux IRQ8 and above as virtual IRQs using the following
34 * mapping:
35 *
36 *   <linux-IRQ#> = <SB-core-flag> + <base-IRQ> + 2
37 *
38 * where <base-IRQ> is specified in setup.c when calling
39 * sb_mips_init(), 2 is to offset the two software IRQs.
40 *
41 * $Id: irq.c,v 1.1.1.1 2008/10/15 03:26:06 james26_jang Exp $
42 */
43
44#include <linux/config.h>
45
46#ifdef CONFIG_SMP
47/*
48 * This code is designed to work on Uniprocessor only.
49 *
50 * To support SMP we must know:
51 *   - interrupt architecture
52 *   - interrupt distribution machenism
53 */
54#error "This implementation does not support SMP"
55#endif
56
57#include <linux/init.h>
58#include <linux/irq.h>
59#include <linux/kernel_stat.h>
60
61#include <asm/mipsregs.h>
62
63#include <typedefs.h>
64#include <osl.h>
65#include <bcmutils.h>
66#include <sbconfig.h>
67#include <sbutils.h>
68#include <hndcpu.h>
69#include "bcm947xx.h"
70
71/* cp0 SR register IM field */
72#define SR_IM(irq)	(1 << ((irq) + STATUSB_IP0))
73
74/* cp0 CR register IP field */
75#define CR_IP(irq)	(1 << ((irq) + CAUSEB_IP0))
76
77/* other local constants */
78#define NUM_IRQS	32
79
80/* local variables and functions */
81static sbconfig_t *ccsbr = NULL;	/* Chipc core SB config regs */
82static sbconfig_t *mipssbr = NULL;	/* MIPS core SB config regs */
83static int mipsirq = -1;		/* MIPS core virtual IRQ number */
84static uint32 sbintvec = 0;		/* MIPS core sbintvec reg val */
85static int irq2en = 0;			/* MIPS IRQ2 enable count */
86
87/* global variables and functions */
88extern sb_t *bcm947xx_sbh;		/* defined in setup.c */
89
90extern asmlinkage void brcmIRQ(void);
91
92/* Control functions for MIPS IRQ0 to IRQ7 */
93static INLINE void
94enable_brcm_irq(unsigned int irq)
95{
96	set_c0_status(SR_IM(irq));
97}
98
99static INLINE void
100disable_brcm_irq(unsigned int irq)
101{
102	clear_c0_status(SR_IM(irq));
103}
104
105static unsigned int
106startup_brcm_irq(unsigned int irq)
107{
108	enable_brcm_irq(irq);
109	return 0;
110}
111
112static void
113shutdown_brcm_irq(unsigned int irq)
114{
115	disable_brcm_irq(irq);
116}
117
118static void
119ack_brcm_irq(unsigned int irq)
120{
121	/* Done in brcm_irq_dispatch()! */
122}
123
124static void
125end_brcm_irq(unsigned int irq)
126{
127	/* Done in brcm_irq_dispatch()! */
128}
129
130/* Control functions for linux IRQ8 and above */
131static INLINE void
132enable_brcm_irq2(unsigned int irq)
133{
134	ASSERT(irq2en >= 0);
135	if (irq2en++)
136		return;
137	enable_brcm_irq(2);
138}
139
140static INLINE void
141disable_brcm_irq2(unsigned int irq)
142{
143	ASSERT(irq2en > 0);
144	if (--irq2en)
145		return;
146	disable_brcm_irq(2);
147}
148
149static unsigned int
150startup_brcm_irq2(unsigned int irq)
151{
152	enable_brcm_irq2(irq);
153	return 0;
154}
155
156static void
157shutdown_brcm_irq2(unsigned int irq)
158{
159	disable_brcm_irq2(irq);
160}
161
162static void
163ack_brcm_irq2(unsigned int irq)
164{
165	/* Already done in brcm_irq_dispatch()! */
166}
167
168static void
169end_brcm_irq2(unsigned int irq)
170{
171	/* Already done in brcm_irq_dispatch()! */
172}
173
174/*
175 * Route interrupts to ISR(s).
176 *
177 * This function is entered with the IE disabled. It can be
178 * re-entered as soon as the IE is re-enabled in function
179 * handle_IRQ_envet().
180 */
181void
182brcm_irq_dispatch(struct pt_regs *regs)
183{
184	u32 pending, ipvec;
185	uint32 sbflagst = 0;
186	struct irqaction *action;
187	int cpu = smp_processor_id();
188	int irq;
189
190	/* Disable MIPS IRQs with pending interrupts */
191	pending = regs->cp0_cause & CAUSEF_IP;
192	pending &= regs->cp0_status;
193	clear_c0_status(pending);
194
195	/*
196	 * Build bitvec for pending interrupts. Start with
197	 * MIPS IRQ2 and add linux IRQs to higher bits to
198	 * make the interrupt processing uniform.
199	 */
200	ipvec = pending >> CAUSEB_IP2;
201	if (pending & CAUSEF_IP2) {
202		if (ccsbr && mipssbr) {
203			/* Make sure no one has changed IRQ assignments */
204			ASSERT(R_REG(NULL, &mipssbr->sbintvec) == sbintvec);
205			sbflagst = R_REG(NULL, &ccsbr->sbflagst);
206			sbflagst &= sbintvec;
207			ipvec += sbflagst << SBMIPS_VIRTIRQ_BASE;
208		}
209	}
210
211	/*
212	 * Handle MIPS timer interrupt. Re-enable MIPS IRQ7
213	 * immediately after servicing the interrupt so that
214	 * we can take this kind of interrupt again later
215	 * while servicing other interrupts.
216	 */
217	if (pending & CAUSEF_IP7) {
218		kstat.irqs[cpu][7]++;
219		action = irq_desc[7].action;
220		if (action)
221			handle_IRQ_event(7, regs, action);
222		ipvec &= ~(1 << 5);
223		pending &= ~CAUSEF_IP7;
224		set_c0_status(STATUSF_IP7);
225	}
226
227#ifdef CONFIG_HND_BMIPS3300_PROF
228	/*
229	 * Handle MIPS core interrupt. Re-enable the MIPS IRQ that
230	 * MIPS core is assigned to immediately after servicing the
231	 * interrupt so that we can take this kind of interrupt again
232	 * later while servicing other interrupts.
233	 *
234	 * mipsirq < 0 indicates MIPS core IRQ # is unknown.
235	 */
236	if (mipsirq >= 0 && (ipvec & (1 << mipsirq))) {
237		/*
238		 * MIPS core raised the interrupt on the shared MIPS IRQ2.
239		 * Make sure MIPS core is the only interrupt source before
240		 * re-enabling the IRQ.
241		 */
242		if (mipsirq >= SBMIPS_VIRTIRQ_BASE) {
243			if (sbflagst == (1 << (mipsirq-SBMIPS_VIRTIRQ_BASE))) {
244				irq = mipsirq + 2;
245				kstat.irqs[cpu][irq]++;
246				action = irq_desc[irq].action;
247				if (action)
248					handle_IRQ_event(irq, regs, action);
249				ipvec &= ~(1 << mipsirq);
250				pending &= ~CAUSEF_IP2;
251				set_c0_status(STATUSF_IP2);
252			}
253		}
254		/*
255		 * MIPS core raised the interrupt on a dedicated MIPS IRQ.
256		 * Re-enable the IRQ immediately.
257		 */
258		else {
259			irq = mipsirq + 2;
260			kstat.irqs[cpu][irq]++;
261			action = irq_desc[irq].action;
262			if (action)
263				handle_IRQ_event(irq, regs, action);
264			ipvec &= ~(1 << mipsirq);
265			pending &= ~CR_IP(irq);
266			set_c0_status(SR_IM(irq));
267		}
268	}
269#endif	/* CONFIG_HND_BMIPS3300_PROF */
270
271	/*
272	 * Handle all other interrupts. Re-enable disabled MIPS IRQs
273	 * after processing all pending interrupts.
274	 */
275	for (irq = 2; irq < NUM_IRQS && ipvec != 0; irq ++) {
276		if (ipvec & 1) {
277			kstat.irqs[cpu][irq]++;
278			if ((action = irq_desc[irq].action))
279				handle_IRQ_event(irq, regs, action);
280		}
281		ipvec >>= 1;
282	}
283	set_c0_status(pending);
284
285	/* Run pending bottom halves */
286	if (softirq_pending(cpu))
287		do_softirq();
288}
289
290/* MIPS IRQ0 to IRQ7 interrupt controller */
291static struct hw_interrupt_type brcm_irq_type = {
292	typename: "MIPS",
293	startup: startup_brcm_irq,
294	shutdown: shutdown_brcm_irq,
295	enable: enable_brcm_irq,
296	disable: disable_brcm_irq,
297	ack: ack_brcm_irq,
298	end: end_brcm_irq,
299	NULL
300};
301
302/* linux IRQ8 and above interrupt controller */
303static struct hw_interrupt_type brcm_irq2_type = {
304	typename: "IRQ2",
305	startup: startup_brcm_irq2,
306	shutdown: shutdown_brcm_irq2,
307	enable: enable_brcm_irq2,
308	disable: disable_brcm_irq2,
309	ack: ack_brcm_irq2,
310	end: end_brcm_irq2,
311	NULL
312};
313
314/*
315 * We utilize chipcommon configuration register SBFlagSt to implement a
316 * smart shared IRQ handling machenism through which only ISRs registered
317 * for the SB cores that raised the interrupt are invoked. This machenism
318 * relies on the SBFlagSt register's reliable recording of the SB cores
319 * that raised the interrupt.
320 */
321void __init
322init_IRQ(void)
323{
324	int i;
325	uint32 coreidx;
326	void *regs;
327
328	/* Cache chipc and mips33 config registers */
329	ASSERT(bcm947xx_sbh);
330	coreidx = sb_coreidx(bcm947xx_sbh);
331	if ((regs = sb_setcore(bcm947xx_sbh, SB_CC, 0)))
332		ccsbr = (sbconfig_t *)((ulong)regs + SBCONFIGOFF);
333	if ((regs = sb_setcore(bcm947xx_sbh, SB_MIPS33, 0))) {
334		mipssbr = (sbconfig_t *)((ulong)regs + SBCONFIGOFF);
335		mipsirq = sb_irq(bcm947xx_sbh);
336	}
337	sb_setcoreidx(bcm947xx_sbh, coreidx);
338
339	/* Cache mips33 sbintvec register */
340	if (mipssbr)
341		sbintvec = R_REG(NULL, &mipssbr->sbintvec);
342
343	/* Install interrupt controllers */
344	for (i = 0; i < NR_IRQS; i++) {
345		irq_desc[i].status = IRQ_DISABLED;
346		irq_desc[i].action = 0;
347		irq_desc[i].depth = 1;
348		irq_desc[i].handler = i < SBMIPS_NUMIRQS ?
349		        &brcm_irq_type :
350		        &brcm_irq2_type;
351	}
352    	set_except_vector(0, brcmIRQ);
353}
354