/** * Copyright (C) 2006-2014 Ikanos Communications. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * Info : VX185 interrupt routines. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined (CONFIG_CPU_MIPSR2_IRQ_VI) #include #endif #undef DEBUG_IRQ #ifdef DEBUG_IRQ /* note: prints function name for you */ #define DPRINTK(fmt, args...) printk("%s: " fmt, __FUNCTION__ , ## args) #else #define DPRINTK(fmt, args...) #endif #define ALL_INTS (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4 | IE_IRQ5) /* * IPC Source interrupt map table for all 64 source interrupts in the following format: * { * Reserved bit, Trigger_type (0: Level, 1: Edge), Sync_enable(0: Bypass, 1: Enable), * Polarity (0: Active low, 1: Active high), IPC Destination line (0..13) * } * Note that GPT0 should always be mapped to IPC line 6 (Host mips interrupt line 5) * since the low level assembly code hardwires CAUSEF_IP7 to go to the timer ISR. */ static const ipc_src_map_t ipc_src_table[64] = { /* 0 - 7 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 1, FUSIV_IPC_DEST_PIT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 1, FUSIV_IPC_DEST_PIT2}, /* 8 - 15 */ {0, 0, 0, 1, FUSIV_IPC_DEST_MAILBOX}, /* Mailbox Interrupt */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* GPT0 */ {0, 1, 0, 1, FUSIV_IPC_DEST_GPT1}, /* GPT1 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* GPT2 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* GPT3 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* 16 - 23 */ #ifdef CONFIG_FUSIV_MIPS_CMIPS_CORE {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP0 FL0 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP0 FL1 */ {0, 0, 0, 1, FUSIV_IPC_DEST_DSP0PF0}, /* DSP0 PF0 */ #else {0, 0, 0, 1, FUSIV_IPC_DEST_DSP0FL0}, /* DSP0 FL0 */ {0, 0, 0, 1, FUSIV_IPC_DEST_DSP0FL1}, /* DSP0 FL1 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP0 PF0 */ #endif {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP0 PF1 */ #ifdef CONFIG_FUSIV_MIPS_CMIPS_CORE {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP1 FL0 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP1 FL1 */ #else {0, 0, 0, 1, FUSIV_IPC_DEST_DSP1FL0}, /* DSP1 FL0 */ {0, 0, 0, 1, FUSIV_IPC_DEST_DSP1FL1}, /* DSP1 FL1 */ #endif {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP1 PF0 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* DSP1 PF1 */ /* 24 - 31 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, #ifdef CONFIG_FUSIV_MIPS_DUALCORE /* Dual Core */ #ifdef CONFIG_FUSIV_MIPS_CMIPS_CORE {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 1, FUSIV_IPC_DEST_SERIAL}, /* UART1 */ #else {0, 0, 0, 1, FUSIV_IPC_DEST_SERIAL}, /* UART0 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, #endif #else /* Single Core */ {0, 0, 0, 1, FUSIV_IPC_DEST_SERIAL}, /* UART0 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, #endif {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 1, FUSIV_IPC_DEST_SATA}, /* SATA */ /* 32 - 39 */ {0, 0, 0, 1, FUSIV_IPC_DEST_PCIE0}, /* PCIE 0 (priority may be changed later) */ {0, 0, 0, 1, FUSIV_IPC_DEST_PCIE1}, /* PCIE 1 (priority may be changed later) */ {0, 0, 0, 0, FUSIV_IPC_DEST_USBO}, /* USB - OHCI */ {0, 0, 0, 1, FUSIV_IPC_DEST_USBE}, /* USB - EHCI */ {0, 0, 0, 1, FUSIV_IPC_DEST_GIGE0}, {0, 0, 0, 1, FUSIV_IPC_DEST_GIGE1}, {0, 0, 0, 1, FUSIV_IPC_DEST_VAP}, {0, 0, 0, 1, FUSIV_IPC_DEST_CLSAP1}, /* 40 - 47 */ {0, 0, 0, 1, FUSIV_IPC_DEST_CLSAP2}, {0, 0, 0, 1, FUSIV_IPC_DEST_BMU}, {0, 0, 0, 1, FUSIV_IPC_DEST_SPA}, {0, 0, 0, 1, FUSIV_IPC_DEST_PERIAP}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, /* 48 - 55 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 1, FUSIV_IPC_DEST_SPI1}, /* 56 - 63 */ {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT}, {0, 0, 0, 0, FUSIV_IPC_DEST_DEFAULT} }; volatile ipc_t *ipc = (volatile ipc_t *)VX185_IPC_BASE_ADDR; #ifdef CONFIG_KGDB extern void breakpoint(void); #endif #if defined (CONFIG_CPU_MIPSR2_IRQ_VI) extern void __init mips_cpu_irq_init(void); #endif extern void set_debug_traps(void); extern irq_cpustat_t irq_stat[NR_CPUS]; unsigned int local_bh_count[NR_CPUS]; unsigned int local_irq_count[NR_CPUS]; unsigned int startup_ipc_src(struct irq_data *d); void shutdown_ipc_src(struct irq_data *d); static inline void enable_ipc_src(struct irq_data *d); static inline void disable_ipc_src(struct irq_data *d); void ack_ipc_src(struct irq_data *d); static inline void mask_ack_ipc_src(struct irq_data *d); void configure_ipc_src(unsigned int irq_nr); /* Need to revisit for 3.4.1 -CRS */ #if 0 static inline void end_ipc_src(unsigned int irq_nr); static inline void mask_ipc_src(unsigned int irq_nr); static inline void unmask_ipc_src(unsigned int irq_nr); static inline void eoi_ipc_src(unsigned int irq_nr); #endif /*________________________________SUPPORTING ROUTINES_______________________________*/ static inline void enable_ipc_src(struct irq_data *d) { unsigned char ipc_reg_select = d->irq / 8, ipc_src = d->irq % 8; unsigned int src_enable = ipc->src_ctrl[ipc_reg_select]; /* Enable the source */ src_enable |= (IPC_SRC_ENABLE << (ipc_src * 4)); ipc->src_ctrl[ipc_reg_select] = src_enable; /* Ensure the write before we go back */ // wbflush(); } static inline void disable_ipc_src(struct irq_data *d) { unsigned char ipc_reg_select = d->irq / 8, ipc_src = d->irq % 8; unsigned int src_enable = ipc->src_ctrl[ipc_reg_select]; /* Disable the source */ src_enable &= ~(IPC_SRC_ENABLE << (ipc_src * 4)); ipc->src_ctrl[ipc_reg_select] = src_enable; /* Ensure the write before we go back */ // wbflush(); } /* * NOTE: This is only needed for Edge detected source interrupts * to ack the IRQ in IPC if its configured as Edge detect */ void ack_ipc_src(struct irq_data *d) { unsigned char dest_line = ipc_src_table[d->irq].ipc_dst_line; unsigned int int_status = ipc->status_low[dest_line]; if (d->irq < 32) int_status |= (0x1 << d->irq); else int_status |= (0x1 << (d->irq - 32)); ipc->status_low[dest_line] = int_status; } static inline void mask_ack_ipc_src(struct irq_data *d) { /* We have already done it from our dispatcher code so nothing to do when kernel calls this . */ //local_disable_irq(irq_nr); } #if 0 static inline void mask_ipc_src(struct irq_data *d) { /* We have already done it from our dispatcher code so nothing to do when kernel calls this . */ //local_disable_irq(irq_nr); } static inline void unmask_ipc_src(struct irq_data *d) { /* We have already done it from our dispatcher code so nothing to do when kernel calls this . */ //local_disable_irq(irq_nr); } static inline void eoi_ipc_src(unsigned int irq_nr) { /* We have already done it from our dispatcher code so nothing to do when kernel calls this . */ //local_disable_irq(irq_nr); } static inline void end_ipc_src(unsigned int irq_nr) { if (!(irq_desc[irq_nr].status & (IRQ_DISABLED | IRQ_INPROGRESS))) enable_ipc_src(irq_nr); } #endif unsigned int startup_ipc_src(struct irq_data *d) { /* Configure source interrupt */ configure_ipc_src(d->irq); /* Enable the IRQ */ enable_ipc_src(d); return 0; } EXPORT_SYMBOL(startup_ipc_src); void shutdown_ipc_src(struct irq_data *d) { disable_ipc_src(d); return; } EXPORT_SYMBOL(shutdown_ipc_src); /* Set the following for the source interrupt:- 1.) trigger type - edge or level 2.) polarity - active-low or active-high detect 3.) source - IPC sync bypass or enable 4.) Destination Interrupt line (0..13) */ void configure_ipc_src(unsigned int irq_nr) { unsigned char ipc_reg_select = irq_nr / 8, ipc_src = irq_nr % 8; unsigned int src_enable = ipc->src_ctrl[ipc_reg_select]; unsigned int dest_enable = ipc->dest_select[ipc_reg_select]; unsigned int ipc_dst_line = 0; src_enable &= ~(IPC_SRC_CTRL_MASK << (ipc_src * 4)); /* Edge or level detect */ if (ipc_src_table[irq_nr].trigger_type) { src_enable |= (IPC_SRC_EDGE_DETECT << (ipc_src * 4)); } /* Sync enable or bypass */ if (ipc_src_table[irq_nr].sync_enable) { src_enable |= (IPC_SRC_SYNC_ENABLE << (ipc_src * 4)); } /* Active high or low detect */ if (ipc_src_table[irq_nr].polarity) { src_enable |= (IPC_SRC_ACT_HI_DETECT << (ipc_src * 4)); } ipc->src_ctrl[ipc_reg_select] = src_enable; /* Configure destination line */ dest_enable &= ~(IPC_DEFAULT_DEST_LINE << (ipc_src * 4)); ipc_dst_line = ipc_src_table[irq_nr].ipc_dst_line; #ifdef CONFIG_SMP /* patch to make kernel compiled with CONFIG_SMP to work with nosmp bootargument * * Interrupt Routing hack * * */ /* TODO : Need to understand this code -- VB */ if ((setup_max_cpus == 0) && (irq_nr != VPE1_MIPS_TIMER_INT)) { /* with nosmp=1 , make sure that all interrupts are routed to vpe0 */ //printk("nosmp option set..\n\r"); if (ipc_src_table[irq_nr].ipc_dst_line > FUSIV_IPC_DEST_LAST_VPE0) { ipc_dst_line -= MIPS_VPE1_OFFSET; printk (" Re-configuring IPC src line %d from ipc dest line %d to %d ipc destination", irq_nr, ipc_src_table[irq_nr].ipc_dst_line, ipc_dst_line); } } #endif dest_enable |= (ipc_dst_line << (ipc_src * 4)); ipc->dest_select[ipc_reg_select] = dest_enable; } static struct irq_chip vx185_ipc = { "Fusiv VX185", .irq_startup = startup_ipc_src, .irq_shutdown = shutdown_ipc_src, .irq_enable = enable_ipc_src, .irq_disable = disable_ipc_src, .irq_mask = disable_ipc_src, .irq_unmask = enable_ipc_src, .irq_ack = ack_ipc_src, .irq_mask_ack = mask_ack_ipc_src, }; void fusiv_dispatch_irq(int prio, struct pt_regs *regs) { /* Let us put some memory rather then few clocks. Memory is always cheaper then CPU time . */ unsigned char IRQCnt1 = 0; unsigned char IRQCnt2 = 0; unsigned char i; unsigned char IRQSet1[32]; unsigned char IRQSet2[32]; unsigned long stat; int irq; /* * Check if the interrupt line is owned by the periodic timer. * We dont expect this line to be shared. */ if (prio == PERIODIC_TIMER_INT_PRIORITY) { struct irq_desc *desc = irq_to_desc(VPE0_MIPS_TIMER_INT); struct irq_data *idata = irq_desc_get_irq_data(desc); disable_ipc_src(idata); do_IRQ(VPE0_MIPS_TIMER_INT); return; } /* Process interrupts on other interrupt lines */ stat = ipc->status_low[prio + 1]; if (likely(stat != 0)) { /* Very first thing to check if there is some thing to do */ while (stat) { irq = ffs(stat) - 1; stat &= ~(1 << irq); IRQSet1[IRQCnt1++] = irq; } } stat = ipc->status_hi[prio + 1]; if (likely(stat != 0)) { /* Very first thing to check if there is some thing to do */ while (stat) { irq = ffs(stat) - 1; stat &= ~(1 << irq); IRQSet2[IRQCnt2++] = irq + 32; } } /* We should not call do_IRQ before we acknowldege the Interupt Controller , one or more IRQ handler may not have SA_INTERRUPT bit set and kernel will enable the interrupts (which was set by processor ), which will again drag some body here . */ /* Let us acknowledge Interupt Controller that we got the message */ for (i = 0; i < IRQCnt1; i++) { struct irq_desc *desc = irq_to_desc(IRQSet1[i]); struct irq_data *idata = irq_desc_get_irq_data(desc); disable_ipc_src(idata); } /* Let us acknowledge Interupt Controller that we got the message */ for (i = 0; i < IRQCnt2; i++) { struct irq_desc *desc = irq_to_desc(IRQSet2[i]); struct irq_data *idata = irq_desc_get_irq_data(desc); disable_ipc_src(idata); } /* Now we are safe to enable the interrupts because we have told Interupt Controller that we are all set to go. */ for (i = 0; i < IRQCnt1; i++) { do_IRQ(IRQSet1[i]); } /* Now we are safe to enable the interrupts because we have told Interupt Controller that we are all set to go. */ for (i = 0; i < IRQCnt2; i++) { do_IRQ(IRQSet2[i]); } } #ifdef CONFIG_MIPS_MT_SMP #define MIPS_CPU_IPI_RESCHED_IRQ 0 /* SW int 0 for resched */ #define C_RESCHED C_SW0 #define MIPS_CPU_IPI_CALL_IRQ 1 /* SW int 1 for resched */ #define C_CALL C_SW1 static void ipi_resched_dispatch(void) { do_IRQ(MIPSCPU_INT_SW0); } static void ipi_call_dispatch(void) { do_IRQ(MIPSCPU_INT_SW1); } static irqreturn_t ipi_resched_interrupt(int irq, void *dev_id) { scheduler_ipi(); return IRQ_HANDLED; } static irqreturn_t ipi_call_interrupt(int irq, void *dev_id) { smp_call_function_interrupt(); return IRQ_HANDLED; } static struct irqaction irq_resched = { .handler = ipi_resched_interrupt, .flags = IRQF_DISABLED | IRQF_PERCPU, .name = "IPI_resched" }; static struct irqaction irq_call = { .handler = ipi_call_interrupt, .flags = IRQF_DISABLED | IRQF_PERCPU, .name = "IPI_call" }; void __init arch_init_ipiirq(int irq, struct irqaction *action) { setup_irq(irq, action); irq_set_handler(irq, handle_percpu_irq); } #endif /* CONFIG_MIPS_MT_SMP */ #if defined (CONFIG_CPU_MIPSR2_IRQ_VI) void mipscpu_int_hw5_dispatch(void) { do_IRQ(MIPSCPU_INT_HW5); } void mipscpu_int_hw4_dispatch(void) { do_IRQ(MIPSCPU_INT_HW4); } void mipscpu_int_hw3_dispatch(void) { do_IRQ(MIPSCPU_INT_HW3); } void mipscpu_int_hw2_dispatch(void) { do_IRQ(MIPSCPU_INT_HW2); } void mipscpu_int_hw1_dispatch(void) { do_IRQ(MIPSCPU_INT_HW1); } void mipscpu_int_hw0_dispatch(void) { do_IRQ(MIPSCPU_INT_HW0); } unsigned int status_ipc_src(unsigned int irq_nr) { unsigned char ipc_reg_select = irq_nr / 8, ipc_src = irq_nr % 8; unsigned int dest_line; unsigned long stat; /* get the status of the source */ dest_line = (IPC_DEFAULT_DEST_LINE << (ipc_src * 4)); /* get the destination interrupt line */ dest_line = (ipc->dest_select[ipc_reg_select] & dest_line) >> (ipc_src * 4); if (irq_nr < 32) { stat = ipc->status_low[dest_line]; return (stat & (1 << irq_nr) ? 1 : 0); } else { stat = ipc->status_hi[dest_line]; return (stat & (1 << (irq_nr - 32)) ? 1 : 0); } } EXPORT_SYMBOL(status_ipc_src); void setup_vint_handlers(void) { //The despatch for h/w int 5 is defined in plat_time.c file set_vi_handler(MIPSCPU_INT_HW4, mipscpu_int_hw4_dispatch); set_vi_handler(MIPSCPU_INT_HW3, mipscpu_int_hw3_dispatch); set_vi_handler(MIPSCPU_INT_HW2, mipscpu_int_hw2_dispatch); set_vi_handler(MIPSCPU_INT_HW1, mipscpu_int_hw1_dispatch); set_vi_handler(MIPSCPU_INT_HW0, mipscpu_int_hw0_dispatch); #if defined(CONFIG_MIPS_MT_SMP) set_vi_handler(MIPSCPU_INT_SW0, ipi_resched_dispatch); set_vi_handler(MIPSCPU_INT_SW1, ipi_call_dispatch); arch_init_ipiirq(MIPSCPU_INT_SW0, &irq_resched); arch_init_ipiirq(MIPSCPU_INT_SW1, &irq_call); #endif } #endif //CONFIG_CPU_MIPSR2_IRQ_VI void __init fusiv_init_IRQ(void) { int i; unsigned long cp0_status; cp0_status = read_c0_status(); printk("Initializing IPC..\r\n"); for (i = 0; i < 8; i++) ipc->src_ctrl[i] = 0x0; /* Route all sources by default to IPC dest line 15 which is unconncted */ for (i = 0; i < 8; i++) ipc->dest_select[i] = 0xFFFFFFFF; /* Clear all status registers */ for (i = 0; i < 15; i++) ipc->status_low[i] = 0xFFFFFFFF; for (i = 0; i < 15; i++) ipc->status_hi[i] = 0xFFFFFFFF; #if defined (CONFIG_CPU_MIPSR2_IRQ_VI) /* Initialize IRQ descriptor for six MIPS interrupt lines (2-7) */ if (!cpu_has_veic && cpu_has_vint) { mips_cpu_irq_init(); setup_vint_handlers(); change_c0_status(ST0_IM, STATUSF_IP0 | STATUSF_IP1 | STATUSF_IP2 | STATUSF_IP3 | STATUSF_IP4 | STATUSF_IP5 | STATUSF_IP6 | STATUSF_IP7); } else #endif { /* let us initialize all the IRQs Descriptors. */ for (i = 0; i < VX185_MAX_INTS; i++) { irq_set_chip_and_handler(i, &vx185_ipc, handle_level_irq); } clear_c0_status(ALL_INTS); set_c0_status(ALL_INTS); } #ifdef CONFIG_KGDB /* If local serial I/O used for debug port, enter kgdb at once */ puts("Waiting for kgdb to connect..."); set_debug_traps(); breakpoint(); #endif printk("Fusiv IPC INIT COMPLETe\n\r"); }