| /* |
| * Copyright (C) 2000 David J. Mckay (david.mckay@st.com) |
| * |
| * May be copied or modified under the terms of the GNU General Public |
| * License. See linux/COPYING for more information. |
| * |
| * Looks after interrupts on the overdrive board. |
| * |
| * Bases on the IPR irq system |
| */ |
| |
| #include <linux/config.h> |
| #include <linux/init.h> |
| #include <linux/irq.h> |
| |
| #include <asm/system.h> |
| #include <asm/io.h> |
| |
| #include <asm/overdrive/overdrive.h> |
| |
| struct od_data { |
| int overdrive_irq; |
| int irq_mask; |
| }; |
| |
| #define NUM_EXTERNAL_IRQS 16 |
| #define EXTERNAL_IRQ_NOT_IN_USE (-1) |
| #define EXTERNAL_IRQ_NOT_ASSIGNED (-1) |
| |
| /* |
| * This table is used to determine what to program into the FPGA's CT register |
| * for the specified Linux IRQ. |
| * |
| * The irq_mask gives the interrupt number from the PCI board (PCI_Int(6:0)) |
| * but is one greater than that because the because the FPGA treats 0 |
| * as disabled, a value of 1 asserts PCI_Int0, and so on. |
| * |
| * The overdrive_irq specifies which of the eight interrupt sources generates |
| * that interrupt, and but is multiplied by four to give the bit offset into |
| * the CT register. |
| * |
| * The seven interrupts levels (SH4 IRL's) we have available here is hardwired |
| * by the EPLD. The assignments here of which PCI interrupt generates each |
| * level is arbitary. |
| */ |
| static struct od_data od_data_table[NUM_EXTERNAL_IRQS] = { |
| /* overdrive_irq , irq_mask */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 0 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 7}, /* 1 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 6}, /* 2 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 3 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 5}, /* 4 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 5 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 6 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 4}, /* 7 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 8 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 9 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 3}, /* 10 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 2}, /* 11 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 12 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, 1}, /* 13 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE}, /* 14 */ |
| {EXTERNAL_IRQ_NOT_ASSIGNED, EXTERNAL_IRQ_NOT_IN_USE} /* 15 */ |
| }; |
| |
| static void set_od_data(int overdrive_irq, int irq) |
| { |
| if (irq >= NUM_EXTERNAL_IRQS || irq < 0) |
| return; |
| od_data_table[irq].overdrive_irq = overdrive_irq << 2; |
| } |
| |
| static void enable_od_irq(unsigned int irq); |
| void disable_od_irq(unsigned int irq); |
| |
| /* shutdown is same as "disable" */ |
| #define shutdown_od_irq disable_od_irq |
| |
| static void mask_and_ack_od(unsigned int); |
| static void end_od_irq(unsigned int irq); |
| |
| static unsigned int startup_od_irq(unsigned int irq) |
| { |
| enable_od_irq(irq); |
| return 0; /* never anything pending */ |
| } |
| |
| static struct hw_interrupt_type od_irq_type = { |
| .typename = "Overdrive-IRQ", |
| .startup = startup_od_irq, |
| .shutdown = shutdown_od_irq, |
| .enable = enable_od_irq, |
| .disable = disable_od_irq, |
| .ack = mask_and_ack_od, |
| .end = end_od_irq |
| }; |
| |
| static void disable_od_irq(unsigned int irq) |
| { |
| unsigned val, flags; |
| int overdrive_irq; |
| unsigned mask; |
| |
| /* Not a valid interrupt */ |
| if (irq < 0 || irq >= NUM_EXTERNAL_IRQS) |
| return; |
| |
| /* Is is necessary to use a cli here? Would a spinlock not be |
| * mroe efficient? |
| */ |
| local_irq_save(flags); |
| overdrive_irq = od_data_table[irq].overdrive_irq; |
| if (overdrive_irq != EXTERNAL_IRQ_NOT_ASSIGNED) { |
| mask = ~(0x7 << overdrive_irq); |
| val = ctrl_inl(OVERDRIVE_INT_CT); |
| val &= mask; |
| ctrl_outl(val, OVERDRIVE_INT_CT); |
| } |
| local_irq_restore(flags); |
| } |
| |
| static void enable_od_irq(unsigned int irq) |
| { |
| unsigned val, flags; |
| int overdrive_irq; |
| unsigned mask; |
| |
| /* Not a valid interrupt */ |
| if (irq < 0 || irq >= NUM_EXTERNAL_IRQS) |
| return; |
| |
| /* Set priority in OD back to original value */ |
| local_irq_save(flags); |
| /* This one is not in use currently */ |
| overdrive_irq = od_data_table[irq].overdrive_irq; |
| if (overdrive_irq != EXTERNAL_IRQ_NOT_ASSIGNED) { |
| val = ctrl_inl(OVERDRIVE_INT_CT); |
| mask = ~(0x7 << overdrive_irq); |
| val &= mask; |
| mask = od_data_table[irq].irq_mask << overdrive_irq; |
| val |= mask; |
| ctrl_outl(val, OVERDRIVE_INT_CT); |
| } |
| local_irq_restore(flags); |
| } |
| |
| |
| |
| /* this functions sets the desired irq handler to be an overdrive type */ |
| static void __init make_od_irq(unsigned int irq) |
| { |
| disable_irq_nosync(irq); |
| irq_desc[irq].handler = &od_irq_type; |
| disable_od_irq(irq); |
| } |
| |
| |
| static void mask_and_ack_od(unsigned int irq) |
| { |
| disable_od_irq(irq); |
| } |
| |
| static void end_od_irq(unsigned int irq) |
| { |
| enable_od_irq(irq); |
| } |
| |
| void __init init_overdrive_irq(void) |
| { |
| int i; |
| |
| /* Disable all interrupts */ |
| ctrl_outl(0, OVERDRIVE_INT_CT); |
| |
| /* Update interrupt pin mode to use encoded interrupts */ |
| i = ctrl_inw(INTC_ICR); |
| i &= ~INTC_ICR_IRLM; |
| ctrl_outw(i, INTC_ICR); |
| |
| for (i = 0; i < NUM_EXTERNAL_IRQS; i++) { |
| if (od_data_table[i].irq_mask != EXTERNAL_IRQ_NOT_IN_USE) { |
| make_od_irq(i); |
| } else if (i != 15) { // Cannot use imask on level 15 |
| make_imask_irq(i); |
| } |
| } |
| |
| /* Set up the interrupts */ |
| set_od_data(OVERDRIVE_PCI_INTA, OVERDRIVE_PCI_IRQ1); |
| set_od_data(OVERDRIVE_PCI_INTB, OVERDRIVE_PCI_IRQ2); |
| set_od_data(OVERDRIVE_AUDIO_INT, OVERDRIVE_ESS_IRQ); |
| } |