/* * Copyright 2001 MontaVista Software Inc. * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net * * Copyright (C) 2001 Ralf Baechle * * This file define the irq handler for MIPS CPU interrupts. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ /* * Almost all MIPS CPUs define 8 interrupt sources. They are typically * level triggered (i.e., cannot be cleared from CPU; must be cleared from * device). The first two are software interrupts which we don't really * use or support. The last one is usually the CPU timer interrupt if * counter register is present or, for CPUs with an external FPU, by * convention it's the FPU exception interrupt. * * Don't even think about using this on SMP. You have been warned. * * This file exports one global function: * void mips_cpu_irq_init(int irq_base); */ #include #include #include #include #include #include #include #include #include #include #include #include #include /*------------------------------------------------------------------------------------------*\ * Aufbau des Interrupt Systems * * CAUSEF_IP0 * CAUSEF_IP1 * CAUSEF_IP2 ohio hw interrupt 0 * CAUSEF_IP3 ohio hw interrupt 1 * CAUSEF_IP4 * CAUSEF_IP5 * CAUSEF_IP6 * CAUSEF_IP7 timer interrupt * * Setup the IRQ description array. These will be mapped * as flat interrupts numbers. The mapping is as follows * * 0 - 7 MIPS CPU Exceptions (HW/SW) * 8 - 47 Primary Interrupts (Avalanche) * 48- 79 Secondary Interrupts (Avalanche) * 80- 111 Low Vlynq Interrupts (Vlynq) * 112- 143 High Vlynq Interrupts(Vlynq) * \*------------------------------------------------------------------------------------------*/ /*--- #define DEBUG_OHIO_IRQ ---*/ #ifndef DEBUG_OHIO_IRQ #define DEBUG_PRINTK(...) #else #define DEBUG_PRINTK(...) printk(__VA_ARGS__) #endif /*--- #define FIND_FUNNY_IRQS ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static int mips_cpu_irq_base; static int mips_cpu_double_clear_mask[2]; static spinlock_t ohio_irq_lock = SPIN_LOCK_UNLOCKED; static struct { unsigned char used; unsigned char irq; } ohio_pacing_list[4]; /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static inline void unmask_mips_irq(unsigned int irq) { irq -= mips_cpu_irq_base; /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ if(irq < MIPS_EXCEPTION_OFFSET) { DEBUG_PRINTK("[unmask_mips_irq] MIPS irq=%u\n", irq); clear_c0_cause(0x100 << irq); set_c0_status(0x100 << irq); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_PRIMARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; DEBUG_PRINTK("[unmask_mips_irq] PRIMARY irq=%u real=%u\n", irq, irq - (MIPS_EXCEPTION_OFFSET - 1)); irq -= MIPS_EXCEPTION_OFFSET; IRQ->enable_set_reg[irq / 32] = (1 << (irq % 32)); DEBUG_PRINTK("[unmask_mips_irq] set IRQ->enable_set_reg[%u] = (1 << (%u));\n", irq / 32, irq % 32); DEBUG_PRINTK("[unmask_mips_irq] channel[%u] = %u\n", irq, IRQ->channel_number_reg[irq]); DEBUG_PRINTK("[unmask_mips_irq] raw status[%u] = 0x%x\n", irq / 32, IRQ->status_clear_reg[irq / 32]); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_SECONDARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; DEBUG_PRINTK("[unmask_mips_irq] SECONDARY irq=%u real=%u\n", irq, irq - OHIO_INT_END_PRIMARY); irq -= OHIO_INT_END_PRIMARY; IRQ->exception_enable_set_reg = (1 << irq); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ #if defined(CONFIG_MIPS_VIRTUAL_IRQ) } else if(irq < OHIO_INT_END_VIRTUAL) { irq -= OHIO_INT_END_SECONDARY; vlynq_irq_enable(0, irq); #endif /*--- #if defined(CONFIG_MIPS_VIRTUAL_IRQ) ---*/ } { #ifdef DEBUG_OHIO_IRQ struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; #endif DEBUG_PRINTK("[unmask_mips_irq]\n" "\tc0: mask 0x%x\n" "\tIRQ->enable_set_reg[0] = 0x%x\n" "\tIRQ->enable_set_reg[1] = 0x%x\n" "\tIRQ->exception_enable_set_reg = 0x%x\n", (read_c0_status() >> 10) & ((1 << 6) - 1), IRQ->enable_set_reg[0], IRQ->enable_set_reg[1], IRQ->exception_enable_set_reg); } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static inline void mask_mips_irq(unsigned int irq) { DEBUG_PRINTK("[mask_mips_irq] irq=%u\n", irq); irq -= mips_cpu_irq_base; /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ if(irq < MIPS_EXCEPTION_OFFSET) { clear_c0_status(0x100 << irq); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_PRIMARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; irq -= MIPS_EXCEPTION_OFFSET; IRQ->enable_clear_reg[irq / 32] = (1 << (irq % 32)); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_SECONDARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; irq -= OHIO_INT_END_PRIMARY; IRQ->exception_enable_clear_reg = (1 << irq); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ #if defined(CONFIG_MIPS_VIRTUAL_IRQ) } else if(irq < OHIO_INT_END_VIRTUAL) { irq -= OHIO_INT_END_SECONDARY; vlynq_irq_disable(0, irq); #endif /*--- #if defined(CONFIG_MIPS_VIRTUAL_IRQ) ---*/ } { #ifdef DEBUG_OHIO_IRQ struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; #endif DEBUG_PRINTK("[mask_mips_irq]\n" "\tc0: mask 0x%x\n" "\tIRQ->enable_set_reg[0] = 0x%x\n" "\tIRQ->enable_set_reg[1] = 0x%x\n" "\tIRQ->exception_enable_set_reg = 0x%x\n", (read_c0_status() >> 10) & ((1 << 6) - 1), IRQ->enable_set_reg[0], IRQ->enable_set_reg[1], IRQ->exception_enable_set_reg); } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static inline void mips_cpu_irq_enable(unsigned int irq) { unsigned long flags; DEBUG_PRINTK("[mips_cpu_irq_enable] irq=%u\n", irq); local_irq_save(flags); unmask_mips_irq(irq); local_irq_restore(flags); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void mips_cpu_irq_disable(unsigned int irq) { unsigned long flags; DEBUG_PRINTK("[mips_cpu_irq_disable] irq=%u\n", irq); local_irq_save(flags); mask_mips_irq(irq); local_irq_restore(flags); } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static unsigned int mips_cpu_irq_startup(unsigned int irq) { DEBUG_PRINTK("[mips_cpu_irq_startup] irq=%u\n", irq); mips_cpu_irq_enable(irq); return 0; } /*------------------------------------------------------------------------------------------*\ * Initialize the IRQ pacing administration * * * * Argument: prescale: Prescale-Value to define the intervals to count IRQs * \*------------------------------------------------------------------------------------------*/ void ohioint_ctrl_irq_pacing_setup(unsigned int prescale) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; unsigned int i; for(i = 0; i < 4; i++) { ohio_pacing_list[i].used = 0; ohio_pacing_list[i].irq = 0; } IRQ->pacing_prescale_reg.Bits.IPACEP = prescale; } /*------------------------------------------------------------------------------------------*\ * Register the usage of pacing for an IRQ * * * * Argument: irq - The IRQ that should be paced * * * * Return: >=0: Handle, with which to call the pacing related functions * * -1 : IRQ is already registered * * -2 : No more pacing registers available * * -3 : Thengiven IRQ can not be set and is therefor rejected * \*------------------------------------------------------------------------------------------*/ int ohioint_ctrl_irq_pacing_register(unsigned int irq) { unsigned int i, handle = 99; int flags; if((irq < MIPS_EXCEPTION_OFFSET) || (irq >= OHIO_INT_END_PRIMARY)) { printk(KERN_WARNING "[ohioint_ctrl_irq_pacing_register] This IRQ (%u) can not be set!\n", irq); return -3; } spin_lock_irqsave(&ohio_irq_lock, flags); /* First check, whether the IRQ got registered already */ for(i = 0; i < 4; i++) { if(ohio_pacing_list[i].used && (ohio_pacing_list[i].irq == irq)) { spin_unlock_irqrestore(&ohio_irq_lock, flags); printk(KERN_WARNING "[ohioint_ctrl_irq_pacing_register] IRQ %u already registered!\n", irq); return -1; } if(!ohio_pacing_list[i].used) handle = i; } if(handle == 9) { spin_unlock_irqrestore(&ohio_irq_lock, flags); printk(KERN_WARNING "[ohioint_ctrl_irq_pacing_register] All IRQ pacing registers already used!\n"); return -2; } ohio_pacing_list[handle].used = 1; ohio_pacing_list[handle].irq = irq; /* Configure pacing register */ ohioint_ctrl_irq_pacing_set(handle, 0); spin_unlock_irqrestore(&ohio_irq_lock, flags); return handle; } /*------------------------------------------------------------------------------------------*\ * Unregister the IRQ pacing for a given handle * * * * Argument: handle - Handle returned at register time * \*------------------------------------------------------------------------------------------*/ void ohioint_ctrl_irq_pacing_unregister(int handle) { if((handle < 0) || (handle > 3) || !ohio_pacing_list[handle].used) { printk(KERN_ERR "[ohioint_ctrl_irq_pacing_unregister] Handle %u is invalid!\n", handle); return; } /* Stop interrupt pacing for the given handle */ ohioint_ctrl_irq_pacing_set(handle, 0); ohio_pacing_list[handle].used = 0; } /*------------------------------------------------------------------------------------------*\ * Set the needed IRQ pacing * * * * Arguments: handle - Handle returned at registration time * * max - Summarize at most 'max' interrupts per time slot * * * * Return: 0 on success, -1 on invalid handle * \*------------------------------------------------------------------------------------------*/ int ohioint_ctrl_irq_pacing_set(int handle, unsigned int max) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; unsigned int irq; if((handle < 0) || (handle > 3) || !ohio_pacing_list[handle].used) { printk(KERN_ERR "[ohioint_ctrl_irq_pacing_set] Handle %u is invalid!\n", handle); return -1; } irq = ohio_pacing_list[handle].irq - MIPS_EXCEPTION_OFFSET; max &= (1 << 6) - 1; if(max == 1) max = 0; switch(handle) { case 0: if(max == 0) { IRQ->pacing_max_reg.Bits.IPACEN0 = 0; return 0; } IRQ->pacing_map_reg.Bits.IPACEMAP0 = irq; IRQ->pacing_max_reg.Bits.IPACEMAX0 = max; IRQ->pacing_max_reg.Bits.IPACEN0 = 1; break; case 1: if(max == 0) { IRQ->pacing_max_reg.Bits.IPACEN1 = 0; return 0; } IRQ->pacing_map_reg.Bits.IPACEMAP1 = irq; IRQ->pacing_max_reg.Bits.IPACEMAX1 = max; IRQ->pacing_max_reg.Bits.IPACEN1 = 1; break; case 2: if(max == 0) { IRQ->pacing_max_reg.Bits.IPACEN2 = 0; return 0; } IRQ->pacing_map_reg.Bits.IPACEMAP2 = irq; IRQ->pacing_max_reg.Bits.IPACEMAX2 = max; IRQ->pacing_max_reg.Bits.IPACEN2 = 1; break; case 3: if(max == 0) { IRQ->pacing_max_reg.Bits.IPACEN3 = 0; return 0; } IRQ->pacing_map_reg.Bits.IPACEMAP3 = irq; IRQ->pacing_max_reg.Bits.IPACEMAX3 = max; IRQ->pacing_max_reg.Bits.IPACEN3 = 1; break; } return 0; } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ unsigned int ohioint_set_type(unsigned int irq, enum _ohio_interrupt_type_trigger level, enum _ohio_interrupt_type_polarity polarity) { /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ if(irq < MIPS_EXCEPTION_OFFSET) { return 1; /*--- kein setzen möglich ---*/ /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_PRIMARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; irq -= MIPS_EXCEPTION_OFFSET; if(polarity == ohio_interrupt_type_active_low) { IRQ->polarity_reg[irq / 32] |= (1 << (irq % 32)); } else { IRQ->polarity_reg[irq / 32] &= ~(1 << (irq % 32)); } if(level == ohio_interrupt_type_level) { IRQ->type_reg[irq / 32] |= (1 << (irq % 32)); /*--- level trigger ---*/ } else { IRQ->type_reg[irq / 32] &= ~(1 << (irq % 32)); /*--- edge trigger ----*/ } if(level == ohio_interrupt_type_level) { mips_cpu_double_clear_mask[irq / 32] = (1 << (irq % 32)); } /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_SECONDARY) { return 1; /*--- kein setzen möglich ---*/ /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ #if defined(CONFIG_MIPS_VIRTUAL_IRQ) } else if(irq < OHIO_INT_END_VIRTUAL) { irq -= OHIO_INT_END_SECONDARY; vlynq_set_irq_type(irq, level, polarity); #endif /*--- #if defined(CONFIG_MIPS_VIRTUAL_IRQ) ---*/ } return 0; } EXPORT_SYMBOL(ohioint_set_type); #define mips_cpu_irq_shutdown mips_cpu_irq_disable /*------------------------------------------------------------------------------------------*\ * While we ack the interrupt interrupts are disabled and thus we don't need * to deal with concurrency issues. Same for mips_cpu_irq_end. \*------------------------------------------------------------------------------------------*/ static void mips_cpu_irq_ack(unsigned int irq) { DEBUG_PRINTK("[mips_cpu_irq_ack] irq=%u\n", irq); #if defined(FIND_FUNNY_IRQS) if(irq < MIPS_EXCEPTION_OFFSET) { } else if(irq < OHIO_INT_END_PRIMARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; unsigned int _irq = irq - (MIPS_EXCEPTION_OFFSET); if(!(IRQ->status_set_reg[_irq / 32] & (1 << (_irq % 32)))) { printk("[ack] irq %u %u set but not present\n", _irq, irq); } } #endif /*--- #if defined(FIND_FUNNY_IRQS) ---*/ mask_mips_irq(irq); irq -= mips_cpu_irq_base; /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ if(irq < MIPS_EXCEPTION_OFFSET) { /* Only necessary for soft interrupts */ clear_c0_cause(0x100 << irq); /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_PRIMARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; irq -= MIPS_EXCEPTION_OFFSET; clear_c0_cause(0x100 << 2); #if defined(FIND_FUNNY_IRQS) if(!(IRQ->status_clear_reg[irq / 32] & (1 << (irq % 32)))) { printk("[ack] irq %u set but not present in raw\n", irq); } #endif /*--- #if defined(FIND_FUNNY_IRQS) ---*/ IRQ->status_clear_reg[irq / 32] = (1 << (irq % 32)); #if defined(FIND_FUNNY_IRQS) if(IRQ->status_set_reg[irq / 32] & (1 << (irq % 32))) { printk("[ack] irq %u cleared but still present\n", irq); } if(irq == 2) { printk("[ack] irq %u raw status %x status %x\n", irq, IRQ->status_clear_reg[irq / 32], IRQ->status_set_reg[irq / 32] ); } #endif /*--- #if defined(FIND_FUNNY_IRQS) ---*/ /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ } else if(irq < OHIO_INT_END_SECONDARY) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; irq -= OHIO_INT_START_SECONDARY; IRQ->exception_clear_status_reg = 1 << irq; /*--------------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------------*/ #if defined(CONFIG_VLYNQ_SUPPORT) } else if(irq < OHIO_INT_END_VIRTUAL) { irq -= OHIO_INT_END_SECONDARY; vlynq_irq_ack(0, irq); #endif /*--- #if defined(CONFIG_VLYNQ_SUPPORT) ---*/ } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static void mips_cpu_irq_end(unsigned int irq) { DEBUG_PRINTK("[mips_cpu_irq_end] irq=%u\n", irq); irq -= mips_cpu_irq_base; if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS))) { if(irq < MIPS_EXCEPTION_OFFSET) { } else if(irq < OHIO_INT_END_PRIMARY) { unsigned int _irq = irq - (MIPS_EXCEPTION_OFFSET); #if defined(FIND_FUNNY_IRQS) struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; if(irq != 15) { if(IRQ->status_clear_reg[_irq / 32] & (1 << (_irq % 32))) { printk("[end] new raw interrupt %u %u present ? \n", _irq, irq); } } if(IRQ->status_set_reg[_irq / 32] & (1 << (_irq % 32))) { printk("[end] new real interrupt %u %u present ? \n", _irq, irq); } #endif /*--- #if defined(FIND_FUNNY_IRQS) ---*/ if(mips_cpu_double_clear_mask[_irq / 32] & (1 << (_irq % 32))) { struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; IRQ->status_clear_reg[_irq / 32] = (1 << (_irq % 32)); } } unmask_mips_irq(irq); } } /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ static hw_irq_controller mips_cpu_irq_controller_system = { "OHIO System", mips_cpu_irq_startup, mips_cpu_irq_shutdown, mips_cpu_irq_enable, mips_cpu_irq_disable, mips_cpu_irq_ack, mips_cpu_irq_end, NULL /* no affinity stuff for UP */ }; static hw_irq_controller mips_cpu_irq_controller_primary = { "OHIO primary", mips_cpu_irq_startup, mips_cpu_irq_shutdown, mips_cpu_irq_enable, mips_cpu_irq_disable, mips_cpu_irq_ack, mips_cpu_irq_end, NULL /* no affinity stuff for UP */ }; static hw_irq_controller mips_cpu_irq_controller_secondary = { "OHIO secondary", mips_cpu_irq_startup, mips_cpu_irq_shutdown, mips_cpu_irq_enable, mips_cpu_irq_disable, mips_cpu_irq_ack, mips_cpu_irq_end, NULL /* no affinity stuff for UP */ }; #if defined(CONFIG_VLYNQ_SUPPORT) static hw_irq_controller mips_cpu_irq_controller_vlynq = { "OHIO vlynq", mips_cpu_irq_startup, mips_cpu_irq_shutdown, mips_cpu_irq_enable, mips_cpu_irq_disable, mips_cpu_irq_ack, mips_cpu_irq_end, NULL /* no affinity stuff for UP */ }; #endif /*--- #if defined(CONFIG_VLYNQ_SUPPORT) ---*/ /*------------------------------------------------------------------------------------------*\ \*------------------------------------------------------------------------------------------*/ void __init mips_cpu_irq_init(int irq_base) { int i; #ifdef DEBUG_OHIO_IRQ struct _irq_hw *IRQ = (struct _irq_hw *)OHIO_IRQ_CTRL_BASE; #endif mips_cpu_double_clear_mask[0] = 0; mips_cpu_double_clear_mask[1] = 0; DEBUG_PRINTK("[mips_cpu_irq_init]\n"); for (i = irq_base; i < irq_base + 8 ; i++) { irq_desc[i].status = IRQ_DISABLED; irq_desc[i].action = NULL; irq_desc[i].depth = 1; irq_desc[i].handler = &mips_cpu_irq_controller_system; } for ( ; i < irq_base + OHIO_INT_END_PRIMARY ; i++) { irq_desc[i].status = IRQ_DISABLED; irq_desc[i].action = NULL; irq_desc[i].depth = 1; irq_desc[i].handler = &mips_cpu_irq_controller_primary; } for ( ; i < irq_base + OHIO_INT_END_SECONDARY ; i++) { irq_desc[i].status = IRQ_DISABLED; irq_desc[i].action = NULL; irq_desc[i].depth = 1; irq_desc[i].handler = &mips_cpu_irq_controller_secondary; } #if defined(CONFIG_VLYNQ_SUPPORT) for ( ; i < irq_base + OHIO_INT_END_VIRTUAL ; i++) { irq_desc[i].status = IRQ_DISABLED; irq_desc[i].action = NULL; irq_desc[i].depth = 1; irq_desc[i].handler = &mips_cpu_irq_controller_vlynq; } #endif /*--- #if defined(CONFIG_VLYNQ_SUPPORT) ---*/ mips_cpu_irq_base = irq_base; ohioint_ctrl_irq_pacing_setup((4 * ohio_get_clock(avm_clock_id_vbus)) / 1000000); }