254 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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.
 | |
|  *
 | |
|  * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
 | |
|  * Copyright (C) 2008 Nicolas Schichan <nschichan@freebox.fr>
 | |
|  */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/module.h>
 | |
| #include <asm/irq_cpu.h>
 | |
| #include <asm/mipsregs.h>
 | |
| #include <bcm63xx_cpu.h>
 | |
| #include <bcm63xx_regs.h>
 | |
| #include <bcm63xx_io.h>
 | |
| #include <bcm63xx_irq.h>
 | |
| 
 | |
| /*
 | |
|  * dispatch internal devices IRQ (uart, enet, watchdog, ...). do not
 | |
|  * prioritize any interrupt relatively to another. the static counter
 | |
|  * will resume the loop where it ended the last time we left this
 | |
|  * function.
 | |
|  */
 | |
| static void bcm63xx_irq_dispatch_internal(void)
 | |
| {
 | |
| 	u32 pending;
 | |
| 	static int i;
 | |
| 
 | |
| 	pending = bcm_perf_readl(PERF_IRQMASK_REG) &
 | |
| 		bcm_perf_readl(PERF_IRQSTAT_REG);
 | |
| 
 | |
| 	if (!pending)
 | |
| 		return ;
 | |
| 
 | |
| 	while (1) {
 | |
| 		int to_call = i;
 | |
| 
 | |
| 		i = (i + 1) & 0x1f;
 | |
| 		if (pending & (1 << to_call)) {
 | |
| 			do_IRQ(to_call + IRQ_INTERNAL_BASE);
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| asmlinkage void plat_irq_dispatch(void)
 | |
| {
 | |
| 	u32 cause;
 | |
| 
 | |
| 	do {
 | |
| 		cause = read_c0_cause() & read_c0_status() & ST0_IM;
 | |
| 
 | |
| 		if (!cause)
 | |
| 			break;
 | |
| 
 | |
| 		if (cause & CAUSEF_IP7)
 | |
| 			do_IRQ(7);
 | |
| 		if (cause & CAUSEF_IP2)
 | |
| 			bcm63xx_irq_dispatch_internal();
 | |
| 		if (cause & CAUSEF_IP3)
 | |
| 			do_IRQ(IRQ_EXT_0);
 | |
| 		if (cause & CAUSEF_IP4)
 | |
| 			do_IRQ(IRQ_EXT_1);
 | |
| 		if (cause & CAUSEF_IP5)
 | |
| 			do_IRQ(IRQ_EXT_2);
 | |
| 		if (cause & CAUSEF_IP6)
 | |
| 			do_IRQ(IRQ_EXT_3);
 | |
| 	} while (1);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * internal IRQs operations: only mask/unmask on PERF irq mask
 | |
|  * register.
 | |
|  */
 | |
| static inline void bcm63xx_internal_irq_mask(unsigned int irq)
 | |
| {
 | |
| 	u32 mask;
 | |
| 
 | |
| 	irq -= IRQ_INTERNAL_BASE;
 | |
| 	mask = bcm_perf_readl(PERF_IRQMASK_REG);
 | |
| 	mask &= ~(1 << irq);
 | |
| 	bcm_perf_writel(mask, PERF_IRQMASK_REG);
 | |
| }
 | |
| 
 | |
| static void bcm63xx_internal_irq_unmask(unsigned int irq)
 | |
| {
 | |
| 	u32 mask;
 | |
| 
 | |
| 	irq -= IRQ_INTERNAL_BASE;
 | |
| 	mask = bcm_perf_readl(PERF_IRQMASK_REG);
 | |
| 	mask |= (1 << irq);
 | |
| 	bcm_perf_writel(mask, PERF_IRQMASK_REG);
 | |
| }
 | |
| 
 | |
| static unsigned int bcm63xx_internal_irq_startup(unsigned int irq)
 | |
| {
 | |
| 	bcm63xx_internal_irq_unmask(irq);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * external IRQs operations: mask/unmask and clear on PERF external
 | |
|  * irq control register.
 | |
|  */
 | |
| static void bcm63xx_external_irq_mask(unsigned int irq)
 | |
| {
 | |
| 	u32 reg;
 | |
| 
 | |
| 	irq -= IRQ_EXT_BASE;
 | |
| 	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
 | |
| 	reg &= ~EXTIRQ_CFG_MASK(irq);
 | |
| 	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
 | |
| }
 | |
| 
 | |
| static void bcm63xx_external_irq_unmask(unsigned int irq)
 | |
| {
 | |
| 	u32 reg;
 | |
| 
 | |
| 	irq -= IRQ_EXT_BASE;
 | |
| 	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
 | |
| 	reg |= EXTIRQ_CFG_MASK(irq);
 | |
| 	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
 | |
| }
 | |
| 
 | |
| static void bcm63xx_external_irq_clear(unsigned int irq)
 | |
| {
 | |
| 	u32 reg;
 | |
| 
 | |
| 	irq -= IRQ_EXT_BASE;
 | |
| 	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
 | |
| 	reg |= EXTIRQ_CFG_CLEAR(irq);
 | |
| 	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
 | |
| }
 | |
| 
 | |
| static unsigned int bcm63xx_external_irq_startup(unsigned int irq)
 | |
| {
 | |
| 	set_c0_status(0x100 << (irq - IRQ_MIPS_BASE));
 | |
| 	irq_enable_hazard();
 | |
| 	bcm63xx_external_irq_unmask(irq);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void bcm63xx_external_irq_shutdown(unsigned int irq)
 | |
| {
 | |
| 	bcm63xx_external_irq_mask(irq);
 | |
| 	clear_c0_status(0x100 << (irq - IRQ_MIPS_BASE));
 | |
| 	irq_disable_hazard();
 | |
| }
 | |
| 
 | |
| static int bcm63xx_external_irq_set_type(unsigned int irq,
 | |
| 					 unsigned int flow_type)
 | |
| {
 | |
| 	u32 reg;
 | |
| 	struct irq_desc *desc = irq_desc + irq;
 | |
| 
 | |
| 	irq -= IRQ_EXT_BASE;
 | |
| 
 | |
| 	flow_type &= IRQ_TYPE_SENSE_MASK;
 | |
| 
 | |
| 	if (flow_type == IRQ_TYPE_NONE)
 | |
| 		flow_type = IRQ_TYPE_LEVEL_LOW;
 | |
| 
 | |
| 	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
 | |
| 	switch (flow_type) {
 | |
| 	case IRQ_TYPE_EDGE_BOTH:
 | |
| 		reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
 | |
| 		reg |= EXTIRQ_CFG_BOTHEDGE(irq);
 | |
| 		break;
 | |
| 
 | |
| 	case IRQ_TYPE_EDGE_RISING:
 | |
| 		reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
 | |
| 		reg |= EXTIRQ_CFG_SENSE(irq);
 | |
| 		reg &= ~EXTIRQ_CFG_BOTHEDGE(irq);
 | |
| 		break;
 | |
| 
 | |
| 	case IRQ_TYPE_EDGE_FALLING:
 | |
| 		reg &= ~EXTIRQ_CFG_LEVELSENSE(irq);
 | |
| 		reg &= ~EXTIRQ_CFG_SENSE(irq);
 | |
| 		reg &= ~EXTIRQ_CFG_BOTHEDGE(irq);
 | |
| 		break;
 | |
| 
 | |
| 	case IRQ_TYPE_LEVEL_HIGH:
 | |
| 		reg |= EXTIRQ_CFG_LEVELSENSE(irq);
 | |
| 		reg |= EXTIRQ_CFG_SENSE(irq);
 | |
| 		break;
 | |
| 
 | |
| 	case IRQ_TYPE_LEVEL_LOW:
 | |
| 		reg |= EXTIRQ_CFG_LEVELSENSE(irq);
 | |
| 		reg &= ~EXTIRQ_CFG_SENSE(irq);
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		printk(KERN_ERR "bogus flow type combination given !\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
 | |
| 
 | |
| 	if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))  {
 | |
| 		desc->status |= IRQ_LEVEL;
 | |
| 		desc->handle_irq = handle_level_irq;
 | |
| 	} else {
 | |
| 		desc->handle_irq = handle_edge_irq;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct irq_chip bcm63xx_internal_irq_chip = {
 | |
| 	.name		= "bcm63xx_ipic",
 | |
| 	.startup	= bcm63xx_internal_irq_startup,
 | |
| 	.shutdown	= bcm63xx_internal_irq_mask,
 | |
| 
 | |
| 	.mask		= bcm63xx_internal_irq_mask,
 | |
| 	.mask_ack	= bcm63xx_internal_irq_mask,
 | |
| 	.unmask		= bcm63xx_internal_irq_unmask,
 | |
| };
 | |
| 
 | |
| static struct irq_chip bcm63xx_external_irq_chip = {
 | |
| 	.name		= "bcm63xx_epic",
 | |
| 	.startup	= bcm63xx_external_irq_startup,
 | |
| 	.shutdown	= bcm63xx_external_irq_shutdown,
 | |
| 
 | |
| 	.ack		= bcm63xx_external_irq_clear,
 | |
| 
 | |
| 	.mask		= bcm63xx_external_irq_mask,
 | |
| 	.unmask		= bcm63xx_external_irq_unmask,
 | |
| 
 | |
| 	.set_type	= bcm63xx_external_irq_set_type,
 | |
| };
 | |
| 
 | |
| static struct irqaction cpu_ip2_cascade_action = {
 | |
| 	.handler	= no_action,
 | |
| 	.name		= "cascade_ip2",
 | |
| };
 | |
| 
 | |
| void __init arch_init_irq(void)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	mips_cpu_irq_init();
 | |
| 	for (i = IRQ_INTERNAL_BASE; i < NR_IRQS; ++i)
 | |
| 		set_irq_chip_and_handler(i, &bcm63xx_internal_irq_chip,
 | |
| 					 handle_level_irq);
 | |
| 
 | |
| 	for (i = IRQ_EXT_BASE; i < IRQ_EXT_BASE + 4; ++i)
 | |
| 		set_irq_chip_and_handler(i, &bcm63xx_external_irq_chip,
 | |
| 					 handle_edge_irq);
 | |
| 
 | |
| 	setup_irq(IRQ_MIPS_BASE + 2, &cpu_ip2_cascade_action);
 | |
| }
 |