irq.c 7.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/*
 * 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>
14
#include <linux/irq.h>
15 16 17 18 19 20 21
#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>

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
static void __dispatch_internal(void) __maybe_unused;

#ifndef BCMCPU_RUNTIME_DETECT
#ifdef CONFIG_BCM63XX_CPU_6338
#define irq_stat_reg		PERF_IRQSTAT_6338_REG
#define irq_mask_reg		PERF_IRQMASK_6338_REG
#endif
#ifdef CONFIG_BCM63XX_CPU_6345
#define irq_stat_reg		PERF_IRQSTAT_6345_REG
#define irq_mask_reg		PERF_IRQMASK_6345_REG
#endif
#ifdef CONFIG_BCM63XX_CPU_6348
#define irq_stat_reg		PERF_IRQSTAT_6348_REG
#define irq_mask_reg		PERF_IRQMASK_6348_REG
#endif
#ifdef CONFIG_BCM63XX_CPU_6358
#define irq_stat_reg		PERF_IRQSTAT_6358_REG
#define irq_mask_reg		PERF_IRQMASK_6358_REG
#endif

#define dispatch_internal	__dispatch_internal

#define irq_stat_addr	(bcm63xx_regset_address(RSET_PERF) + irq_stat_reg)
#define irq_mask_addr	(bcm63xx_regset_address(RSET_PERF) + irq_mask_reg)

static inline void bcm63xx_init_irq(void)
{
}
#else /* ! BCMCPU_RUNTIME_DETECT */

static u32 irq_stat_addr, irq_mask_addr;
static void (*dispatch_internal)(void);

static void bcm63xx_init_irq(void)
{
	irq_stat_addr = bcm63xx_regset_address(RSET_PERF);
	irq_mask_addr = bcm63xx_regset_address(RSET_PERF);

	switch (bcm63xx_get_cpu_id()) {
	case BCM6338_CPU_ID:
		irq_stat_addr += PERF_IRQSTAT_6338_REG;
		irq_mask_addr += PERF_IRQMASK_6338_REG;
		break;
	case BCM6345_CPU_ID:
		irq_stat_addr += PERF_IRQSTAT_6345_REG;
		irq_mask_addr += PERF_IRQMASK_6345_REG;
		break;
	case BCM6348_CPU_ID:
		irq_stat_addr += PERF_IRQSTAT_6348_REG;
		irq_mask_addr += PERF_IRQMASK_6348_REG;
		break;
	case BCM6358_CPU_ID:
		irq_stat_addr += PERF_IRQSTAT_6358_REG;
		irq_mask_addr += PERF_IRQMASK_6358_REG;
		break;
	default:
		BUG();
	}

	dispatch_internal = __dispatch_internal;
}
#endif /* ! BCMCPU_RUNTIME_DETECT */

static inline void handle_internal(int intbit)
{
	do_IRQ(intbit + IRQ_INTERNAL_BASE);
}

90 91 92 93 94 95
/*
 * 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.
 */
96
static void __dispatch_internal(void)
97 98 99 100
{
	u32 pending;
	static int i;

101
	pending = bcm_readl(irq_stat_addr) & bcm_readl(irq_mask_addr);
102 103 104 105 106 107 108 109 110

	if (!pending)
		return ;

	while (1) {
		int to_call = i;

		i = (i + 1) & 0x1f;
		if (pending & (1 << to_call)) {
111
			handle_internal(to_call);
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
			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)
130
			dispatch_internal();
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
		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.
 */
146
static inline void bcm63xx_internal_irq_mask(struct irq_data *d)
147
{
148
	unsigned int irq = d->irq - IRQ_INTERNAL_BASE;
149 150
	u32 mask;

151
	mask = bcm_readl(irq_mask_addr);
152
	mask &= ~(1 << irq);
153
	bcm_writel(mask, irq_mask_addr);
154 155
}

156
static void bcm63xx_internal_irq_unmask(struct irq_data *d)
157
{
158
	unsigned int irq = d->irq - IRQ_INTERNAL_BASE;
159 160
	u32 mask;

161
	mask = bcm_readl(irq_mask_addr);
162
	mask |= (1 << irq);
163
	bcm_writel(mask, irq_mask_addr);
164 165 166 167 168 169
}

/*
 * external IRQs operations: mask/unmask and clear on PERF external
 * irq control register.
 */
170
static void bcm63xx_external_irq_mask(struct irq_data *d)
171
{
172
	unsigned int irq = d->irq - IRQ_EXT_BASE;
173 174 175 176 177 178 179
	u32 reg;

	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
	reg &= ~EXTIRQ_CFG_MASK(irq);
	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
}

180
static void bcm63xx_external_irq_unmask(struct irq_data *d)
181
{
182
	unsigned int irq = d->irq - IRQ_EXT_BASE;
183 184 185 186 187 188 189
	u32 reg;

	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
	reg |= EXTIRQ_CFG_MASK(irq);
	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
}

190
static void bcm63xx_external_irq_clear(struct irq_data *d)
191
{
192
	unsigned int irq = d->irq - IRQ_EXT_BASE;
193 194 195 196 197 198 199
	u32 reg;

	reg = bcm_perf_readl(PERF_EXTIRQ_CFG_REG);
	reg |= EXTIRQ_CFG_CLEAR(irq);
	bcm_perf_writel(reg, PERF_EXTIRQ_CFG_REG);
}

200
static unsigned int bcm63xx_external_irq_startup(struct irq_data *d)
201
{
202
	set_c0_status(0x100 << (d->irq - IRQ_MIPS_BASE));
203
	irq_enable_hazard();
204
	bcm63xx_external_irq_unmask(d);
205 206 207
	return 0;
}

208
static void bcm63xx_external_irq_shutdown(struct irq_data *d)
209
{
210 211
	bcm63xx_external_irq_mask(d);
	clear_c0_status(0x100 << (d->irq - IRQ_MIPS_BASE));
212 213 214
	irq_disable_hazard();
}

215
static int bcm63xx_external_irq_set_type(struct irq_data *d,
216 217
					 unsigned int flow_type)
{
218
	unsigned int irq = d->irq - IRQ_EXT_BASE;
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
	u32 reg;

	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);

261 262 263 264 265
	irqd_set_trigger_type(d, flow_type);
	if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
		__irq_set_handler_locked(d->irq, handle_level_irq);
	else
		__irq_set_handler_locked(d->irq, handle_edge_irq);
266

267
	return IRQ_SET_MASK_OK_NOCOPY;
268 269 270 271
}

static struct irq_chip bcm63xx_internal_irq_chip = {
	.name		= "bcm63xx_ipic",
272 273
	.irq_mask	= bcm63xx_internal_irq_mask,
	.irq_unmask	= bcm63xx_internal_irq_unmask,
274 275 276 277
};

static struct irq_chip bcm63xx_external_irq_chip = {
	.name		= "bcm63xx_epic",
278 279
	.irq_startup	= bcm63xx_external_irq_startup,
	.irq_shutdown	= bcm63xx_external_irq_shutdown,
280

281
	.irq_ack	= bcm63xx_external_irq_clear,
282

283 284
	.irq_mask	= bcm63xx_external_irq_mask,
	.irq_unmask	= bcm63xx_external_irq_unmask,
285

286
	.irq_set_type	= bcm63xx_external_irq_set_type,
287 288 289 290 291
};

static struct irqaction cpu_ip2_cascade_action = {
	.handler	= no_action,
	.name		= "cascade_ip2",
292
	.flags		= IRQF_NO_THREAD,
293 294 295 296 297 298
};

void __init arch_init_irq(void)
{
	int i;

299
	bcm63xx_init_irq();
300 301
	mips_cpu_irq_init();
	for (i = IRQ_INTERNAL_BASE; i < NR_IRQS; ++i)
302
		irq_set_chip_and_handler(i, &bcm63xx_internal_irq_chip,
303 304 305
					 handle_level_irq);

	for (i = IRQ_EXT_BASE; i < IRQ_EXT_BASE + 4; ++i)
306
		irq_set_chip_and_handler(i, &bcm63xx_external_irq_chip,
307 308 309 310
					 handle_edge_irq);

	setup_irq(IRQ_MIPS_BASE + 2, &cpu_ip2_cascade_action);
}