/* * * Programmable Interrupt Controller functions for the Freescale MPC52xx. * * Copyright (C) 2006 bplan GmbH * * Based on the code from the 2.4 kernel by * Dale Farnsworth and Kent Borg. * * Copyright (C) 2004 Sylvain Munaut * Copyright (C) 2003 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. * */ #undef DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mpc52xx_pic.h" /* * */ static struct mpc52xx_intr __iomem *intr; static struct mpc52xx_sdma __iomem *sdma; static struct irq_host *mpc52xx_irqhost = NULL; static unsigned char mpc52xx_map_senses[4] = { IRQ_TYPE_LEVEL_HIGH, IRQ_TYPE_EDGE_RISING, IRQ_TYPE_EDGE_FALLING, IRQ_TYPE_LEVEL_LOW, }; /* * */ static inline void io_be_setbit(u32 __iomem * addr, int bitno) { out_be32(addr, in_be32(addr) | (1 << bitno)); } static inline void io_be_clrbit(u32 __iomem * addr, int bitno) { out_be32(addr, in_be32(addr) & ~(1 << bitno)); } /* * IRQ[0-3] interrupt irq_chip */ static void mpc52xx_extirq_mask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_clrbit(&intr->ctrl, 11 - l2irq); } static void mpc52xx_extirq_unmask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_setbit(&intr->ctrl, 11 - l2irq); } static void mpc52xx_extirq_ack(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_setbit(&intr->ctrl, 27 - l2irq); } static struct irq_chip mpc52xx_extirq_irqchip = { .typename = " MPC52xx IRQ[0-3] ", .mask = mpc52xx_extirq_mask, .unmask = mpc52xx_extirq_unmask, .ack = mpc52xx_extirq_ack, }; /* * Main interrupt irq_chip */ static void mpc52xx_main_mask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_setbit(&intr->main_mask, 15 - l2irq); } static void mpc52xx_main_unmask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_clrbit(&intr->main_mask, 15 - l2irq); } static struct irq_chip mpc52xx_main_irqchip = { .typename = "MPC52xx Main", .mask = mpc52xx_main_mask, .mask_ack = mpc52xx_main_mask, .unmask = mpc52xx_main_unmask, }; /* * Peripherals interrupt irq_chip */ static void mpc52xx_periph_mask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_setbit(&intr->per_mask, 31 - l2irq); } static void mpc52xx_periph_unmask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_clrbit(&intr->per_mask, 31 - l2irq); } static struct irq_chip mpc52xx_periph_irqchip = { .typename = "MPC52xx Peripherals", .mask = mpc52xx_periph_mask, .mask_ack = mpc52xx_periph_mask, .unmask = mpc52xx_periph_unmask, }; /* * SDMA interrupt irq_chip */ static void mpc52xx_sdma_mask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_setbit(&sdma->IntMask, l2irq); } static void mpc52xx_sdma_unmask(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); io_be_clrbit(&sdma->IntMask, l2irq); } static void mpc52xx_sdma_ack(unsigned int virq) { int irq; int l2irq; irq = irq_map[virq].hwirq; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; pr_debug("%s: irq=%x. l2=%d\n", __func__, irq, l2irq); out_be32(&sdma->IntPend, 1 << l2irq); } static struct irq_chip mpc52xx_sdma_irqchip = { .typename = "MPC52xx SDMA", .mask = mpc52xx_sdma_mask, .unmask = mpc52xx_sdma_unmask, .ack = mpc52xx_sdma_ack, }; /* * irq_host */ static int mpc52xx_irqhost_match(struct irq_host *h, struct device_node *node) { pr_debug("%s: node=%p\n", __func__, node); return mpc52xx_irqhost->host_data == node; } static int mpc52xx_irqhost_xlate(struct irq_host *h, struct device_node *ct, u32 * intspec, unsigned int intsize, irq_hw_number_t * out_hwirq, unsigned int *out_flags) { int intrvect_l1; int intrvect_l2; int intrvect_type; int intrvect_linux; if (intsize != 3) return -1; intrvect_l1 = (int)intspec[0]; intrvect_l2 = (int)intspec[1]; intrvect_type = (int)intspec[2]; intrvect_linux = (intrvect_l1 << MPC52xx_IRQ_L1_OFFSET) & MPC52xx_IRQ_L1_MASK; intrvect_linux |= (intrvect_l2 << MPC52xx_IRQ_L2_OFFSET) & MPC52xx_IRQ_L2_MASK; pr_debug("return %x, l1=%d, l2=%d\n", intrvect_linux, intrvect_l1, intrvect_l2); *out_hwirq = intrvect_linux; *out_flags = mpc52xx_map_senses[intrvect_type]; return 0; } /* * this function retrieves the correct IRQ type out * of the MPC regs * Only externals IRQs needs this */ static int mpc52xx_irqx_gettype(int irq) { int type; u32 ctrl_reg; ctrl_reg = in_be32(&intr->ctrl); type = (ctrl_reg >> (22 - irq * 2)) & 0x3; return mpc52xx_map_senses[type]; } static int mpc52xx_irqhost_map(struct irq_host *h, unsigned int virq, irq_hw_number_t irq) { int l1irq; int l2irq; struct irq_chip *good_irqchip; void *good_handle; int type; l1irq = (irq & MPC52xx_IRQ_L1_MASK) >> MPC52xx_IRQ_L1_OFFSET; l2irq = (irq & MPC52xx_IRQ_L2_MASK) >> MPC52xx_IRQ_L2_OFFSET; /* * Most of ours IRQs will be level low * Only external IRQs on some platform may be others */ type = IRQ_TYPE_LEVEL_LOW; switch (l1irq) { case MPC52xx_IRQ_L1_CRIT: pr_debug("%s: Critical. l2=%x\n", __func__, l2irq); BUG_ON(l2irq != 0); type = mpc52xx_irqx_gettype(l2irq); good_irqchip = &mpc52xx_extirq_irqchip; break; case MPC52xx_IRQ_L1_MAIN: pr_debug("%s: Main IRQ[1-3] l2=%x\n", __func__, l2irq); if ((l2irq >= 1) && (l2irq <= 3)) { type = mpc52xx_irqx_gettype(l2irq); good_irqchip = &mpc52xx_extirq_irqchip; } else { good_irqchip = &mpc52xx_main_irqchip; } break; case MPC52xx_IRQ_L1_PERP: pr_debug("%s: Peripherals. l2=%x\n", __func__, l2irq); good_irqchip = &mpc52xx_periph_irqchip; break; case MPC52xx_IRQ_L1_SDMA: pr_debug("%s: SDMA. l2=%x\n", __func__, l2irq); good_irqchip = &mpc52xx_sdma_irqchip; break; default: pr_debug("%s: Error, unknown L1 IRQ (0x%x)\n", __func__, l1irq); printk(KERN_ERR "Unknow IRQ!\n"); return -EINVAL; } switch (type) { case IRQ_TYPE_EDGE_FALLING: case IRQ_TYPE_EDGE_RISING: good_handle = handle_edge_irq; break; default: good_handle = handle_level_irq; } set_irq_chip_and_handler(virq, good_irqchip, good_handle); pr_debug("%s: virq=%x, hw=%x. type=%x\n", __func__, virq, (int)irq, type); return 0; } static struct irq_host_ops mpc52xx_irqhost_ops = { .match = mpc52xx_irqhost_match, .xlate = mpc52xx_irqhost_xlate, .map = mpc52xx_irqhost_map, }; /* * init (public) */ void __init mpc52xx_init_irq(void) { struct device_node *picnode = NULL; int picnode_regsize; u32 picnode_regoffset; struct device_node *sdmanode = NULL; int sdmanode_regsize; u32 sdmanode_regoffset; u64 size64; int flags; u32 intr_ctrl; picnode = of_find_compatible_node(NULL, "interrupt-controller", "mpc5200-pic"); if (picnode == NULL) { printk(KERN_ERR "MPC52xx PIC: " "Unable to find the interrupt controller " "in the OpenFirmware device tree\n"); goto end; } sdmanode = of_find_compatible_node(NULL, "dma-controller", "mpc5200-bestcomm"); if (sdmanode == NULL) { printk(KERN_ERR "MPC52xx PIC" "Unable to find the Bestcomm DMA controller device " "in the OpenFirmware device tree\n"); goto end; } /* Retrieve PIC ressources */ picnode_regoffset = (u32) of_get_address(picnode, 0, &size64, &flags); if (picnode_regoffset == 0) { printk(KERN_ERR "MPC52xx PIC" "Unable to get the interrupt controller address\n"); goto end; } picnode_regoffset = of_translate_address(picnode, (u32 *) picnode_regoffset); picnode_regsize = (int)size64; /* Retrieve SDMA ressources */ sdmanode_regoffset = (u32) of_get_address(sdmanode, 0, &size64, &flags); if (sdmanode_regoffset == 0) { printk(KERN_ERR "MPC52xx PIC: " "Unable to get the Bestcomm DMA controller address\n"); goto end; } sdmanode_regoffset = of_translate_address(sdmanode, (u32 *) sdmanode_regoffset); sdmanode_regsize = (int)size64; /* Remap the necessary zones */ intr = ioremap(picnode_regoffset, picnode_regsize); if (intr == NULL) { printk(KERN_ERR "MPC52xx PIC: " "Unable to ioremap interrupt controller registers!\n"); goto end; } sdma = ioremap(sdmanode_regoffset, sdmanode_regsize); if (sdma == NULL) { iounmap(intr); printk(KERN_ERR "MPC52xx PIC: " "Unable to ioremap Bestcomm DMA registers!\n"); goto end; } printk(KERN_INFO "MPC52xx PIC: MPC52xx PIC Remapped at 0x%8.8x\n", picnode_regoffset); printk(KERN_INFO "MPC52xx PIC: MPC52xx SDMA Remapped at 0x%8.8x\n", sdmanode_regoffset); /* Disable all interrupt sources. */ out_be32(&sdma->IntPend, 0xffffffff); /* 1 means clear pending */ out_be32(&sdma->IntMask, 0xffffffff); /* 1 means disabled */ out_be32(&intr->per_mask, 0x7ffffc00); /* 1 means disabled */ out_be32(&intr->main_mask, 0x00010fff); /* 1 means disabled */ intr_ctrl = in_be32(&intr->ctrl); intr_ctrl &= 0x00ff0000; /* Keeps IRQ[0-3] config */ intr_ctrl |= 0x0f000000 | /* clear IRQ 0-3 */ 0x00001000 | /* MEE master external enable */ 0x00000000 | /* 0 means disable IRQ 0-3 */ 0x00000001; /* CEb route critical normally */ out_be32(&intr->ctrl, intr_ctrl); /* Zero a bunch of the priority settings. */ out_be32(&intr->per_pri1, 0); out_be32(&intr->per_pri2, 0); out_be32(&intr->per_pri3, 0); out_be32(&intr->main_pri1, 0); out_be32(&intr->main_pri2, 0); /* * As last step, add an irq host to translate the real * hw irq information provided by the ofw to linux virq */ mpc52xx_irqhost = irq_alloc_host(IRQ_HOST_MAP_LINEAR, MPC52xx_IRQ_HIGHTESTHWIRQ, &mpc52xx_irqhost_ops, -1); if (mpc52xx_irqhost) { mpc52xx_irqhost->host_data = picnode; printk(KERN_INFO "MPC52xx PIC is up and running!\n"); } else { printk(KERN_ERR "MPC52xx PIC: Unable to allocate the IRQ host\n"); } end: of_node_put(picnode); of_node_put(sdmanode); } /* * get_irq (public) */ unsigned int mpc52xx_get_irq(void) { u32 status; int irq = NO_IRQ_IGNORE; status = in_be32(&intr->enc_status); if (status & 0x00000400) { /* critical */ irq = (status >> 8) & 0x3; if (irq == 2) /* high priority peripheral */ goto peripheral; irq |= (MPC52xx_IRQ_L1_CRIT << MPC52xx_IRQ_L1_OFFSET) & MPC52xx_IRQ_L1_MASK; } else if (status & 0x00200000) { /* main */ irq = (status >> 16) & 0x1f; if (irq == 4) /* low priority peripheral */ goto peripheral; irq |= (MPC52xx_IRQ_L1_MAIN << MPC52xx_IRQ_L1_OFFSET) & MPC52xx_IRQ_L1_MASK; } else if (status & 0x20000000) { /* peripheral */ peripheral: irq = (status >> 24) & 0x1f; if (irq == 0) { /* bestcomm */ status = in_be32(&sdma->IntPend); irq = ffs(status) - 1; irq |= (MPC52xx_IRQ_L1_SDMA << MPC52xx_IRQ_L1_OFFSET) & MPC52xx_IRQ_L1_MASK; } else irq |= (MPC52xx_IRQ_L1_PERP << MPC52xx_IRQ_L1_OFFSET) & MPC52xx_IRQ_L1_MASK; } pr_debug("%s: irq=%x. virq=%d\n", __func__, irq, irq_linear_revmap(mpc52xx_irqhost, irq)); return irq_linear_revmap(mpc52xx_irqhost, irq); }