| /* |
| * This file is subject to the terms and conditions of the GNU General Public |
| * License. See the file "COPYING" in the main directory of this archive |
| * for more details. |
| * |
| * arch/sh64/kernel/irq_cayman.c |
| * |
| * SH-5 Cayman Interrupt Support |
| * |
| * This file handles the board specific parts of the Cayman interrupt system |
| * |
| * Copyright (C) 2002 Stuart Menefy |
| */ |
| |
| #include <linux/config.h> |
| #include <asm/irq.h> |
| #include <asm/page.h> |
| #include <asm/io.h> |
| #include <linux/irq.h> |
| #include <linux/interrupt.h> |
| #include <linux/signal.h> |
| #include <asm/cayman.h> |
| |
| unsigned long epld_virt; |
| |
| #define EPLD_BASE 0x04002000 |
| #define EPLD_STATUS_BASE (epld_virt + 0x10) |
| #define EPLD_MASK_BASE (epld_virt + 0x20) |
| |
| /* Note the SMSC SuperIO chip and SMSC LAN chip interrupts are all muxed onto |
| the same SH-5 interrupt */ |
| |
| static irqreturn_t cayman_interrupt_smsc(int irq, void *dev_id, struct pt_regs *regs) |
| { |
| printk(KERN_INFO "CAYMAN: spurious SMSC interrupt\n"); |
| return IRQ_NONE; |
| } |
| |
| static irqreturn_t cayman_interrupt_pci2(int irq, void *dev_id, struct pt_regs *regs) |
| { |
| printk(KERN_INFO "CAYMAN: spurious PCI interrupt, IRQ %d\n", irq); |
| return IRQ_NONE; |
| } |
| |
| static struct irqaction cayman_action_smsc = { |
| .name = "Cayman SMSC Mux", |
| .handler = cayman_interrupt_smsc, |
| .flags = SA_INTERRUPT, |
| }; |
| |
| static struct irqaction cayman_action_pci2 = { |
| .name = "Cayman PCI2 Mux", |
| .handler = cayman_interrupt_pci2, |
| .flags = SA_INTERRUPT, |
| }; |
| |
| static void enable_cayman_irq(unsigned int irq) |
| { |
| unsigned long flags; |
| unsigned long mask; |
| unsigned int reg; |
| unsigned char bit; |
| |
| irq -= START_EXT_IRQS; |
| reg = EPLD_MASK_BASE + ((irq / 8) << 2); |
| bit = 1<<(irq % 8); |
| local_irq_save(flags); |
| mask = ctrl_inl(reg); |
| mask |= bit; |
| ctrl_outl(mask, reg); |
| local_irq_restore(flags); |
| } |
| |
| void disable_cayman_irq(unsigned int irq) |
| { |
| unsigned long flags; |
| unsigned long mask; |
| unsigned int reg; |
| unsigned char bit; |
| |
| irq -= START_EXT_IRQS; |
| reg = EPLD_MASK_BASE + ((irq / 8) << 2); |
| bit = 1<<(irq % 8); |
| local_irq_save(flags); |
| mask = ctrl_inl(reg); |
| mask &= ~bit; |
| ctrl_outl(mask, reg); |
| local_irq_restore(flags); |
| } |
| |
| static void ack_cayman_irq(unsigned int irq) |
| { |
| disable_cayman_irq(irq); |
| } |
| |
| static void end_cayman_irq(unsigned int irq) |
| { |
| if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS))) |
| enable_cayman_irq(irq); |
| } |
| |
| static unsigned int startup_cayman_irq(unsigned int irq) |
| { |
| enable_cayman_irq(irq); |
| return 0; /* never anything pending */ |
| } |
| |
| static void shutdown_cayman_irq(unsigned int irq) |
| { |
| disable_cayman_irq(irq); |
| } |
| |
| struct hw_interrupt_type cayman_irq_type = { |
| .typename = "Cayman-IRQ", |
| .startup = startup_cayman_irq, |
| .shutdown = shutdown_cayman_irq, |
| .enable = enable_cayman_irq, |
| .disable = disable_cayman_irq, |
| .ack = ack_cayman_irq, |
| .end = end_cayman_irq, |
| }; |
| |
| int cayman_irq_demux(int evt) |
| { |
| int irq = intc_evt_to_irq[evt]; |
| |
| if (irq == SMSC_IRQ) { |
| unsigned long status; |
| int i; |
| |
| status = ctrl_inl(EPLD_STATUS_BASE) & |
| ctrl_inl(EPLD_MASK_BASE) & 0xff; |
| if (status == 0) { |
| irq = -1; |
| } else { |
| for (i=0; i<8; i++) { |
| if (status & (1<<i)) |
| break; |
| } |
| irq = START_EXT_IRQS + i; |
| } |
| } |
| |
| if (irq == PCI2_IRQ) { |
| unsigned long status; |
| int i; |
| |
| status = ctrl_inl(EPLD_STATUS_BASE + 3 * sizeof(u32)) & |
| ctrl_inl(EPLD_MASK_BASE + 3 * sizeof(u32)) & 0xff; |
| if (status == 0) { |
| irq = -1; |
| } else { |
| for (i=0; i<8; i++) { |
| if (status & (1<<i)) |
| break; |
| } |
| irq = START_EXT_IRQS + (3 * 8) + i; |
| } |
| } |
| |
| return irq; |
| } |
| |
| #if defined(CONFIG_PROC_FS) && defined(CONFIG_SYSCTL) |
| int cayman_irq_describe(char* p, int irq) |
| { |
| if (irq < NR_INTC_IRQS) { |
| return intc_irq_describe(p, irq); |
| } else if (irq < NR_INTC_IRQS + 8) { |
| return sprintf(p, "(SMSC %d)", irq - NR_INTC_IRQS); |
| } else if ((irq >= NR_INTC_IRQS + 24) && (irq < NR_INTC_IRQS + 32)) { |
| return sprintf(p, "(PCI2 %d)", irq - (NR_INTC_IRQS + 24)); |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| void init_cayman_irq(void) |
| { |
| int i; |
| |
| epld_virt = onchip_remap(EPLD_BASE, 1024, "EPLD"); |
| if (!epld_virt) { |
| printk(KERN_ERR "Cayman IRQ: Unable to remap EPLD\n"); |
| return; |
| } |
| |
| for (i=0; i<NR_EXT_IRQS; i++) { |
| irq_desc[START_EXT_IRQS + i].handler = &cayman_irq_type; |
| } |
| |
| /* Setup the SMSC interrupt */ |
| setup_irq(SMSC_IRQ, &cayman_action_smsc); |
| setup_irq(PCI2_IRQ, &cayman_action_pci2); |
| } |