diff options
Diffstat (limited to 'arch/powerpc/sysdev/cpm2_pic.c')
| -rw-r--r-- | arch/powerpc/sysdev/cpm2_pic.c | 256 | 
1 files changed, 256 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/cpm2_pic.c b/arch/powerpc/sysdev/cpm2_pic.c new file mode 100644 index 00000000000..c804475c07d --- /dev/null +++ b/arch/powerpc/sysdev/cpm2_pic.c @@ -0,0 +1,256 @@ +/* + * Platform information definitions. + * + * Copied from arch/ppc/syslib/cpm2_pic.c with minor subsequent updates + * to make in work in arch/powerpc/. Original (c) belongs to Dan Malek. + * + * Author:  Vitaly Bordug <vbordug@ru.mvista.com> + * + * 1999-2001 (c) Dan Malek <dan@embeddedalley.com> + * 2006 (c) MontaVista Software, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +/* The CPM2 internal interrupt controller.  It is usually + * the only interrupt controller. + * There are two 32-bit registers (high/low) for up to 64 + * possible interrupts. + * + * Now, the fun starts.....Interrupt Numbers DO NOT MAP + * in a simple arithmetic fashion to mask or pending registers. + * That is, interrupt 4 does not map to bit position 4. + * We create two tables, indexed by vector number, to indicate + * which register to use and which bit in the register to use. + */ + +#include <linux/stddef.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/irq.h> + +#include <asm/immap_cpm2.h> +#include <asm/mpc8260.h> +#include <asm/io.h> +#include <asm/prom.h> + +#include "cpm2_pic.h" + +static struct device_node *cpm2_pic_node; +static struct irq_host *cpm2_pic_host; +#define NR_MASK_WORDS   ((NR_IRQS + 31) / 32) +static unsigned long ppc_cached_irq_mask[NR_MASK_WORDS]; + +static const u_char irq_to_siureg[] = { +	1, 1, 1, 1, 1, 1, 1, 1, +	1, 1, 1, 1, 1, 1, 1, 1, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	1, 1, 1, 1, 1, 1, 1, 1, +	1, 1, 1, 1, 1, 1, 1, 1, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* bit numbers do not match the docs, these are precomputed so the bit for + * a given irq is (1 << irq_to_siubit[irq]) */ +static const u_char irq_to_siubit[] = { +	 0, 15, 14, 13, 12, 11, 10,  9, +	 8,  7,  6,  5,  4,  3,  2,  1, +	 2,  1,  0, 14, 13, 12, 11, 10, +	 9,  8,  7,  6,  5,  4,  3,  0, +	31, 30, 29, 28, 27, 26, 25, 24, +	23, 22, 21, 20, 19, 18, 17, 16, +	16, 17, 18, 19, 20, 21, 22, 23, +	24, 25, 26, 27, 28, 29, 30, 31, +}; + +static void cpm2_mask_irq(unsigned int irq_nr) +{ +	int	bit, word; +	volatile uint	*simr; + +	irq_nr -= CPM_IRQ_OFFSET; + +	bit = irq_to_siubit[irq_nr]; +	word = irq_to_siureg[irq_nr]; + +	simr = &(cpm2_immr->im_intctl.ic_simrh); +	ppc_cached_irq_mask[word] &= ~(1 << bit); +	simr[word] = ppc_cached_irq_mask[word]; +} + +static void cpm2_unmask_irq(unsigned int irq_nr) +{ +	int	bit, word; +	volatile uint	*simr; + +	irq_nr -= CPM_IRQ_OFFSET; + +	bit = irq_to_siubit[irq_nr]; +	word = irq_to_siureg[irq_nr]; + +	simr = &(cpm2_immr->im_intctl.ic_simrh); +	ppc_cached_irq_mask[word] |= 1 << bit; +	simr[word] = ppc_cached_irq_mask[word]; +} + +static void cpm2_mask_and_ack(unsigned int irq_nr) +{ +	int	bit, word; +	volatile uint	*simr, *sipnr; + +	irq_nr -= CPM_IRQ_OFFSET; + +	bit = irq_to_siubit[irq_nr]; +	word = irq_to_siureg[irq_nr]; + +	simr = &(cpm2_immr->im_intctl.ic_simrh); +	sipnr = &(cpm2_immr->im_intctl.ic_sipnrh); +	ppc_cached_irq_mask[word] &= ~(1 << bit); +	simr[word] = ppc_cached_irq_mask[word]; +	sipnr[word] = 1 << bit; +} + +static void cpm2_end_irq(unsigned int irq_nr) +{ +	int	bit, word; +	volatile uint	*simr; + +	if (!(irq_desc[irq_nr].status & (IRQ_DISABLED|IRQ_INPROGRESS)) +			&& irq_desc[irq_nr].action) { + +		irq_nr -= CPM_IRQ_OFFSET; +		bit = irq_to_siubit[irq_nr]; +		word = irq_to_siureg[irq_nr]; + +		simr = &(cpm2_immr->im_intctl.ic_simrh); +		ppc_cached_irq_mask[word] |= 1 << bit; +		simr[word] = ppc_cached_irq_mask[word]; +		/* +		 * Work around large numbers of spurious IRQs on PowerPC 82xx +		 * systems. +		 */ +		mb(); +	} +} + +static struct irq_chip cpm2_pic = { +	.typename = " CPM2 SIU ", +	.enable = cpm2_unmask_irq, +	.disable = cpm2_mask_irq, +	.unmask = cpm2_unmask_irq, +	.mask_ack = cpm2_mask_and_ack, +	.end = cpm2_end_irq, +}; + +int cpm2_get_irq(struct pt_regs *regs) +{ +	int irq; +	unsigned long bits; + +	/* For CPM2, read the SIVEC register and shift the bits down +	 * to get the irq number.*/ +	bits = cpm2_immr->im_intctl.ic_sivec; +	irq = bits >> 26; + +	if (irq == 0) +		return(-1); +	return irq+CPM_IRQ_OFFSET; +} + +static int cpm2_pic_host_match(struct irq_host *h, struct device_node *node) +{ +	return cpm2_pic_node == NULL || cpm2_pic_node == node; +} + +static int cpm2_pic_host_map(struct irq_host *h, unsigned int virq, +			  irq_hw_number_t hw) +{ +	pr_debug("cpm2_pic_host_map(%d, 0x%lx)\n", virq, hw); + +	get_irq_desc(virq)->status |= IRQ_LEVEL; +	set_irq_chip_and_handler(virq, &cpm2_pic, handle_level_irq); +	return 0; +} + +static void cpm2_host_unmap(struct irq_host *h, unsigned int virq) +{ +	/* Make sure irq is masked in hardware */ +	cpm2_mask_irq(virq); + +	/* remove chip and handler */ +	set_irq_chip_and_handler(virq, NULL, NULL); +} + +static int cpm2_pic_host_xlate(struct irq_host *h, struct device_node *ct, +			    u32 *intspec, unsigned int intsize, +			    irq_hw_number_t *out_hwirq, unsigned int *out_flags) +{ +	static const unsigned char map_cpm2_senses[4] = { +		IRQ_TYPE_LEVEL_LOW, +		IRQ_TYPE_LEVEL_HIGH, +		IRQ_TYPE_EDGE_FALLING, +		IRQ_TYPE_EDGE_RISING, +	}; + +	*out_hwirq = intspec[0]; +	if (intsize > 1 && intspec[1] < 4) +		*out_flags = map_cpm2_senses[intspec[1]]; +	else +		*out_flags = IRQ_TYPE_NONE; + +	return 0; +} + +static struct irq_host_ops cpm2_pic_host_ops = { +	.match = cpm2_pic_host_match, +	.map = cpm2_pic_host_map, +	.unmap = cpm2_host_unmap, +	.xlate = cpm2_pic_host_xlate, +}; + +void cpm2_pic_init(struct device_node *node) +{ +	int i; + +	/* Clear the CPM IRQ controller, in case it has any bits set +	 * from the bootloader +	 */ + +	/* Mask out everything */ + +	cpm2_immr->im_intctl.ic_simrh = 0x00000000; +	cpm2_immr->im_intctl.ic_simrl = 0x00000000; + +	wmb(); + +	/* Ack everything */ +	cpm2_immr->im_intctl.ic_sipnrh = 0xffffffff; +	cpm2_immr->im_intctl.ic_sipnrl = 0xffffffff; +	wmb(); + +	/* Dummy read of the vector */ +	i = cpm2_immr->im_intctl.ic_sivec; +	rmb(); + +	/* Initialize the default interrupt mapping priorities, +	 * in case the boot rom changed something on us. +	 */ +	cpm2_immr->im_intctl.ic_sicr = 0; +	cpm2_immr->im_intctl.ic_scprrh = 0x05309770; +	cpm2_immr->im_intctl.ic_scprrl = 0x05309770; + +	/* create a legacy host */ +	if (node) +		cpm2_pic_node = of_node_get(node); + +	cpm2_pic_host = irq_alloc_host(IRQ_HOST_MAP_LINEAR, 64, &cpm2_pic_host_ops, 64); +	if (cpm2_pic_host == NULL) { +		printk(KERN_ERR "CPM2 PIC: failed to allocate irq host!\n"); +		return; +	} +}  |