/*
 * arch/ppc/syslib/xilinx_pic.c
 *
 * Interrupt controller driver for Xilinx Virtex-II Pro.
 *
 * Author: MontaVista Software, Inc.
 *         source@mvista.com
 *
 * 2002-2004 (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.
 */

#include <linux/init.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/xparameters.h>
#include <asm/ibm4xx.h>

/* No one else should require these constants, so define them locally here. */
#define ISR 0			/* Interrupt Status Register */
#define IPR 1			/* Interrupt Pending Register */
#define IER 2			/* Interrupt Enable Register */
#define IAR 3			/* Interrupt Acknowledge Register */
#define SIE 4			/* Set Interrupt Enable bits */
#define CIE 5			/* Clear Interrupt Enable bits */
#define IVR 6			/* Interrupt Vector Register */
#define MER 7			/* Master Enable Register */

#if XPAR_XINTC_USE_DCR == 0
static volatile u32 *intc;
#define intc_out_be32(addr, mask)     out_be32((addr), (mask))
#define intc_in_be32(addr)            in_be32((addr))
#else
#define intc    XPAR_INTC_0_BASEADDR
#define intc_out_be32(addr, mask)     mtdcr((addr), (mask))
#define intc_in_be32(addr)            mfdcr((addr))
#endif

static void
xilinx_intc_enable(unsigned int irq)
{
	unsigned long mask = (0x00000001 << (irq & 31));
	pr_debug("enable: %d\n", irq);
	intc_out_be32(intc + SIE, mask);
}

static void
xilinx_intc_disable(unsigned int irq)
{
	unsigned long mask = (0x00000001 << (irq & 31));
	pr_debug("disable: %d\n", irq);
	intc_out_be32(intc + CIE, mask);
}

static void
xilinx_intc_disable_and_ack(unsigned int irq)
{
	unsigned long mask = (0x00000001 << (irq & 31));
	pr_debug("disable_and_ack: %d\n", irq);
	intc_out_be32(intc + CIE, mask);
	if (!(irq_desc[irq].status & IRQ_LEVEL))
		intc_out_be32(intc + IAR, mask);	/* ack edge triggered intr */
}

static void
xilinx_intc_end(unsigned int irq)
{
	unsigned long mask = (0x00000001 << (irq & 31));

	pr_debug("end: %d\n", irq);
	if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS))) {
		intc_out_be32(intc + SIE, mask);
		/* ack level sensitive intr */
		if (irq_desc[irq].status & IRQ_LEVEL)
			intc_out_be32(intc + IAR, mask);
	}
}

static struct hw_interrupt_type xilinx_intc = {
	"Xilinx Interrupt Controller",
	NULL,
	NULL,
	xilinx_intc_enable,
	xilinx_intc_disable,
	xilinx_intc_disable_and_ack,
	xilinx_intc_end,
	0
};

int
xilinx_pic_get_irq(struct pt_regs *regs)
{
	int irq;

	/*
	 * NOTE: This function is the one that needs to be improved in
	 * order to handle multiple interrupt controllers.  It currently
	 * is hardcoded to check for interrupts only on the first INTC.
	 */

	irq = intc_in_be32(intc + IVR);
	if (irq != -1)
		irq = irq;

	pr_debug("get_irq: %d\n", irq);

	return (irq);
}

void __init
ppc4xx_pic_init(void)
{
	int i;

	/*
	 * NOTE: The assumption here is that NR_IRQS is 32 or less
	 * (NR_IRQS is 32 for PowerPC 405 cores by default).
	 */
#if (NR_IRQS > 32)
#error NR_IRQS > 32 not supported
#endif

#if XPAR_XINTC_USE_DCR == 0
	intc = ioremap(XPAR_INTC_0_BASEADDR, 32);

	printk(KERN_INFO "Xilinx INTC #0 at 0x%08lX mapped to 0x%08lX\n",
	       (unsigned long) XPAR_INTC_0_BASEADDR, (unsigned long) intc);
#else
	printk(KERN_INFO "Xilinx INTC #0 at 0x%08lX (DCR)\n",
	       (unsigned long) XPAR_INTC_0_BASEADDR);
#endif

	/*
	 * Disable all external interrupts until they are
	 * explicity requested.
	 */
	intc_out_be32(intc + IER, 0);

	/* Acknowledge any pending interrupts just in case. */
	intc_out_be32(intc + IAR, ~(u32) 0);

	/* Turn on the Master Enable. */
	intc_out_be32(intc + MER, 0x3UL);

	ppc_md.get_irq = xilinx_pic_get_irq;

	for (i = 0; i < NR_IRQS; ++i) {
		irq_desc[i].handler = &xilinx_intc;

		if (XPAR_INTC_0_KIND_OF_INTR & (0x00000001 << i))
			irq_desc[i].status &= ~IRQ_LEVEL;
		else
			irq_desc[i].status |= IRQ_LEVEL;
	}
}
