aboutsummaryrefslogtreecommitdiffstats
path: root/arch/sh/boards/bigsur/irq.c
blob: 6ddbcc77244da4d81653baa2c6bb2a6bb838debd (plain) (blame)
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
345
346
347
348
/*
 *
 * By Dustin McIntire (dustin@sensoria.com) (c)2001
 *
 * Setup and IRQ handling code for the HD64465 companion chip.
 * by Greg Banks <gbanks@pocketpenguins.com>
 * Copyright (c) 2000 PocketPenguins Inc
 *
 * Derived from setup_hd64465.c which bore the message:
 * Greg Banks <gbanks@pocketpenguins.com>
 * Copyright (c) 2000 PocketPenguins Inc and
 * Copyright (C) 2000 YAEGASHI Takeshi
 * and setup_cqreek.c which bore message:
 * Copyright (C) 2000  Niibe Yutaka
 *
 * May be copied or modified under the terms of the GNU General Public
 * License.  See linux/COPYING for more information.
 *
 * IRQ functions for a Hitachi Big Sur Evaluation Board.
 *
 */

#include <linux/config.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/bitops.h>

#include <asm/io.h>
#include <asm/irq.h>

#include <asm/bigsur/io.h>
#include <asm/hd64465/hd64465.h>
#include <asm/bigsur/bigsur.h>

//#define BIGSUR_DEBUG 3
#undef BIGSUR_DEBUG

#ifdef BIGSUR_DEBUG
#define DPRINTK(args...)        printk(args)
#define DIPRINTK(n, args...)    if (BIGSUR_DEBUG>(n)) printk(args)
#else
#define DPRINTK(args...)
#define DIPRINTK(n, args...)
#endif /* BIGSUR_DEBUG */

#ifdef CONFIG_HD64465
extern int hd64465_irq_demux(int irq);
#endif /* CONFIG_HD64465 */


/*===========================================================*/
//              Big Sur CPLD IRQ Routines
/*===========================================================*/

/* Level 1 IRQ routines */
static void disable_bigsur_l1irq(unsigned int irq)
{
        unsigned long flags;
        unsigned char mask;
        unsigned int mask_port = ((irq - BIGSUR_IRQ_LOW)/8) ? BIGSUR_IRLMR1 : BIGSUR_IRLMR0;
        unsigned char bit =  (1 << ((irq - MGATE_IRQ_LOW)%8) );

        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH) {
                DPRINTK("Disable L1 IRQ %d\n", irq);
                DIPRINTK(2,"disable_bigsur_l1irq: IMR=0x%08x mask=0x%x\n",
                        mask_port, bit);
                local_irq_save(flags);

                /* Disable IRQ - set mask bit */
                mask = inb(mask_port) | bit;
                outb(mask, mask_port);
                local_irq_restore(flags);
                return;
        }
        DPRINTK("disable_bigsur_l1irq: Invalid IRQ %d\n", irq);
}

static void enable_bigsur_l1irq(unsigned int irq)
{
        unsigned long flags;
        unsigned char mask;
        unsigned int mask_port = ((irq - BIGSUR_IRQ_LOW)/8) ? BIGSUR_IRLMR1 : BIGSUR_IRLMR0;
        unsigned char bit =  (1 << ((irq - MGATE_IRQ_LOW)%8) );

        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH) {
                DPRINTK("Enable L1 IRQ %d\n", irq);
                DIPRINTK(2,"enable_bigsur_l1irq: IMR=0x%08x mask=0x%x\n",
                        mask_port, bit);
                local_irq_save(flags);
                /* Enable L1 IRQ - clear mask bit */
                mask = inb(mask_port) & ~bit;
                outb(mask, mask_port);
                local_irq_restore(flags);
                return;
        }
        DPRINTK("enable_bigsur_l1irq: Invalid IRQ %d\n", irq);
}


/* Level 2 irq masks and registers for L2 decoding */
/* Level2 bitmasks for each level 1 IRQ */
const u32 bigsur_l2irq_mask[] =
    {0x40,0x80,0x08,0x01,0x01,0x3C,0x3E,0xFF,0x40,0x80,0x06,0x03};
/* Level2 to ISR[n] map for each level 1 IRQ */
const u32 bigsur_l2irq_reg[]  =
    {   2,   2,   3,   3,   1,   2,   1,   0,   1,   1,   3,   2};
/* Level2 to Level 1 IRQ map */
const u32 bigsur_l2_l1_map[]  =
    {7,7,7,7,7,7,7,7, 4,6,6,6,6,6,8,9, 11,11,5,5,5,5,0,1, 3,10,10,2,-1,-1,-1,-1};
/* IRQ inactive level (high or low) */
const u32 bigsur_l2_inactv_state[]  =   {0x00, 0xBE, 0xFC, 0xF7};

/* CPLD external status and mask registers base and offsets */
static const u32 isr_base = BIGSUR_IRQ0;
static const u32 isr_offset = BIGSUR_IRQ0 - BIGSUR_IRQ1;
static const u32 imr_base = BIGSUR_IMR0;
static const u32 imr_offset = BIGSUR_IMR0 - BIGSUR_IMR1;

#define REG_NUM(irq)  ((irq-BIGSUR_2NDLVL_IRQ_LOW)/8 )

/* Level 2 IRQ routines */
static void disable_bigsur_l2irq(unsigned int irq)
{
        unsigned long flags;
        unsigned char mask;
        unsigned char bit = 1 << ((irq-BIGSUR_2NDLVL_IRQ_LOW)%8);
        unsigned int mask_port = imr_base - REG_NUM(irq)*imr_offset;

    if(irq >= BIGSUR_2NDLVL_IRQ_LOW && irq < BIGSUR_2NDLVL_IRQ_HIGH) {
                DPRINTK("Disable L2 IRQ %d\n", irq);
                DIPRINTK(2,"disable_bigsur_l2irq: IMR=0x%08x mask=0x%x\n",
                        mask_port, bit);
                local_irq_save(flags);

                /* Disable L2 IRQ - set mask bit */
                mask = inb(mask_port) | bit;
                outb(mask, mask_port);
                local_irq_restore(flags);
                return;
        }
        DPRINTK("disable_bigsur_l2irq: Invalid IRQ %d\n", irq);
}

static void enable_bigsur_l2irq(unsigned int irq)
{
        unsigned long flags;
        unsigned char mask;
        unsigned char bit = 1 << ((irq-BIGSUR_2NDLVL_IRQ_LOW)%8);
        unsigned int mask_port = imr_base - REG_NUM(irq)*imr_offset;

    if(irq >= BIGSUR_2NDLVL_IRQ_LOW && irq < BIGSUR_2NDLVL_IRQ_HIGH) {
                DPRINTK("Enable L2 IRQ %d\n", irq);
                DIPRINTK(2,"enable_bigsur_l2irq: IMR=0x%08x mask=0x%x\n",
                        mask_port, bit);
                local_irq_save(flags);

                /* Enable L2 IRQ - clear mask bit */
                mask = inb(mask_port) & ~bit;
                outb(mask, mask_port);
                local_irq_restore(flags);
                return;
        }
        DPRINTK("enable_bigsur_l2irq: Invalid IRQ %d\n", irq);
}

static void mask_and_ack_bigsur(unsigned int irq)
{
        DPRINTK("mask_and_ack_bigsur IRQ %d\n", irq);
        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH)
                disable_bigsur_l1irq(irq);
        else
                disable_bigsur_l2irq(irq);
}

static void end_bigsur_irq(unsigned int irq)
{
        DPRINTK("end_bigsur_irq IRQ %d\n", irq);
        if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) {
                if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH)
                        enable_bigsur_l1irq(irq);
                else
                        enable_bigsur_l2irq(irq);
        }
}

static unsigned int startup_bigsur_irq(unsigned int irq)
{
        u8 mask;
        u32 reg;

        DPRINTK("startup_bigsur_irq IRQ %d\n", irq);

        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH) {
                /* Enable the L1 IRQ */
                enable_bigsur_l1irq(irq);
                /* Enable all L2 IRQs in this L1 IRQ */
                mask = ~(bigsur_l2irq_mask[irq-BIGSUR_IRQ_LOW]);
                reg = imr_base - bigsur_l2irq_reg[irq-BIGSUR_IRQ_LOW] * imr_offset;
                mask &= inb(reg);
                outb(mask,reg);
                DIPRINTK(2,"startup_bigsur_irq: IMR=0x%08x mask=0x%x\n",reg,inb(reg));
        }
        else {
                /* Enable the L2 IRQ - clear mask bit */
                enable_bigsur_l2irq(irq);
                /* Enable the L1 bit masking this L2 IRQ */
                enable_bigsur_l1irq(bigsur_l2_l1_map[irq-BIGSUR_2NDLVL_IRQ_LOW]);
                DIPRINTK(2,"startup_bigsur_irq: L1=%d L2=%d\n",
                        bigsur_l2_l1_map[irq-BIGSUR_2NDLVL_IRQ_LOW],irq);
        }
        return 0;
}

static void shutdown_bigsur_irq(unsigned int irq)
{
        DPRINTK("shutdown_bigsur_irq IRQ %d\n", irq);
        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH)
                disable_bigsur_l1irq(irq);
        else
                disable_bigsur_l2irq(irq);
}

/* Define the IRQ structures for the L1 and L2 IRQ types */
static struct hw_interrupt_type bigsur_l1irq_type = {
	.typename  = "BigSur-CPLD-Level1-IRQ",
	.startup = startup_bigsur_irq,
	.shutdown = shutdown_bigsur_irq,
	.enable = enable_bigsur_l1irq,
	.disable = disable_bigsur_l1irq,
	.ack = mask_and_ack_bigsur,
	.end = end_bigsur_irq
};

static struct hw_interrupt_type bigsur_l2irq_type = {
	.typename  = "BigSur-CPLD-Level2-IRQ",
	.startup = startup_bigsur_irq,
	.shutdown  =shutdown_bigsur_irq,
	.enable = enable_bigsur_l2irq,
	.disable = disable_bigsur_l2irq,
	.ack = mask_and_ack_bigsur,
	.end = end_bigsur_irq
};


static void make_bigsur_l1isr(unsigned int irq) {

        /* sanity check first */
        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH) {
                /* save the handler in the main description table */
                irq_desc[irq].handler = &bigsur_l1irq_type;
                irq_desc[irq].status = IRQ_DISABLED;
                irq_desc[irq].action = 0;
                irq_desc[irq].depth = 1;

                disable_bigsur_l1irq(irq);
                return;
        }
        DPRINTK("make_bigsur_l1isr: bad irq, %d\n", irq);
        return;
}

static void make_bigsur_l2isr(unsigned int irq) {

        /* sanity check first */
        if(irq >= BIGSUR_2NDLVL_IRQ_LOW && irq < BIGSUR_2NDLVL_IRQ_HIGH) {
                /* save the handler in the main description table */
                irq_desc[irq].handler = &bigsur_l2irq_type;
                irq_desc[irq].status = IRQ_DISABLED;
                irq_desc[irq].action = 0;
                irq_desc[irq].depth = 1;

                disable_bigsur_l2irq(irq);
                return;
        }
        DPRINTK("make_bigsur_l2isr: bad irq, %d\n", irq);
        return;
}

/* The IRQ's will be decoded as follows:
 * If a level 2 handler exists and there is an unmasked active
 * IRQ, the 2nd level handler will be called.
 * If a level 2 handler does not exist for the active IRQ
 * the 1st level handler will be called.
 */

int bigsur_irq_demux(int irq)
{
        int dmux_irq = irq;
        u8 mask, actv_irqs;
        u32 reg_num;

        DIPRINTK(3,"bigsur_irq_demux, irq=%d\n", irq);
        /* decode the 1st level IRQ */
        if(irq >= BIGSUR_IRQ_LOW && irq < BIGSUR_IRQ_HIGH) {
                /* Get corresponding L2 ISR bitmask and ISR number */
                mask = bigsur_l2irq_mask[irq-BIGSUR_IRQ_LOW];
                reg_num = bigsur_l2irq_reg[irq-BIGSUR_IRQ_LOW];
                /* find the active IRQ's (XOR with inactive level)*/
                actv_irqs = inb(isr_base-reg_num*isr_offset) ^
                                        bigsur_l2_inactv_state[reg_num];
                /* decode active IRQ's */
                actv_irqs = actv_irqs & mask & ~(inb(imr_base-reg_num*imr_offset));
                /* if NEZ then we have an active L2 IRQ */
                if(actv_irqs) dmux_irq = ffz(~actv_irqs) + reg_num*8+BIGSUR_2NDLVL_IRQ_LOW;
                /* if no 2nd level IRQ action, but has 1st level, use 1st level handler */
                if(!irq_desc[dmux_irq].action && irq_desc[irq].action)
                        dmux_irq = irq;
                DIPRINTK(1,"bigsur_irq_demux: irq=%d dmux_irq=%d mask=0x%04x reg=%d\n",
                        irq, dmux_irq, mask, reg_num);
        }
#ifdef CONFIG_HD64465
        dmux_irq = hd64465_irq_demux(dmux_irq);
#endif /* CONFIG_HD64465 */
        DIPRINTK(3,"bigsur_irq_demux, demux_irq=%d\n", dmux_irq);

        return dmux_irq;
}

/*===========================================================*/
//              Big Sur Init Routines
/*===========================================================*/
void __init init_bigsur_IRQ(void)
{
        int i;

        if (!MACH_BIGSUR) return;

        /* Create ISR's for Big Sur CPLD IRQ's */
        /*==============================================================*/
        for(i=BIGSUR_IRQ_LOW;i<BIGSUR_IRQ_HIGH;i++)
                make_bigsur_l1isr(i);

        printk(KERN_INFO "Big Sur CPLD L1 interrupts %d to %d.\n",
                BIGSUR_IRQ_LOW,BIGSUR_IRQ_HIGH);

        for(i=BIGSUR_2NDLVL_IRQ_LOW;i<BIGSUR_2NDLVL_IRQ_HIGH;i++)
                make_bigsur_l2isr(i);

        printk(KERN_INFO "Big Sur CPLD L2 interrupts %d to %d.\n",
                BIGSUR_2NDLVL_IRQ_LOW,BIGSUR_2NDLVL_IRQ_HIGH);

}