/*
 * arch/arm/mach-cxd4105/irq.c
 *
 * interrupt handler
 *
 * Copyright 2005,2006,2007 Sony Corporation
 *
 * This code is based on arch/arm/mach-integrator/irq.c.
 */
/*
 *  linux/arch/arm/mach-integrator/irq.c
 *
 *  Copyright (C) 1999 ARM Limited
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/interrupt.h>

#include <asm/hardware.h>
#include <asm/arch/regs-intctrl.h>
#include <asm/arch/regs-dmac.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-cf.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/mach/irq.h>

struct cxd_irqchip {
	struct irq_chip base;
	void (*demux)(int , struct irq_desc *);
	int irq_start;
	int irq_num;
	unsigned int allowmap;
	unsigned long io_base;
};

#define DECLARE_CHIP(name)   [name] = { \
		.base = { \
			.ack = default_mask_irq, \
			.mask = default_mask_irq, \
			.unmask = default_unmask_irq, \
		}, \
		.irq_start = name##_BASE,\
		.irq_num = name##_NUM, \
		.allowmap = name##_ALLOWMAP, \
		.io_base = INTC_IO_BASE(name), \
		.demux = default_demux \
	}

static void default_demux(int irq, struct irq_desc *desc);
static void default_mask_irq(unsigned int irq);
static void default_unmask_irq(unsigned int irq);
static void cxd4105_cfif_demux(int irq, struct irq_desc *desc);
static void cxd4105_cfif_mask_ack_irq(unsigned int irq);
static void cxd4105_cfif_mask_irq(unsigned int irq);
static void cxd4105_cfif_unmask_irq(unsigned int irq);
static void cxd4105_sysv_mask_ack_irq(unsigned int irq);
static void cxd4105_gpio_mask_ack_irq(unsigned int irq);
static void cxd4105_dma_demux(int irq, struct irq_desc *desc);
static void cxd4105_dma_mask_irq(unsigned int irq);
static void cxd4105_dma_unmask_irq(unsigned int irq);

static struct cxd_irqchip chips[MAXIRQNUM] = {
	DECLARE_CHIP(IRQ_UART),
	DECLARE_CHIP(IRQ_TIMER),
	[IRQ_DMAC] = {
		.base = {
			.ack = cxd4105_dma_mask_irq,
			.mask = cxd4105_dma_mask_irq,
			.unmask = cxd4105_dma_unmask_irq,
		},
		.irq_start = IRQ_DMAC_BASE,
		.irq_num = IRQ_DMAC_NUM,
		.allowmap = IRQ_DMAC_ALLOWMAP,
		.io_base = INTC_IO_BASE(IRQ_DMAC),
		.demux = cxd4105_dma_demux,
	},
	DECLARE_CHIP(IRQ_JOG),
	DECLARE_CHIP(IRQ_SIO),
#ifdef INT_SDIF
	DECLARE_CHIP(IRQ_SDIF),
#endif
	[IRQ_CF] = {
		.base = {
			.ack	= cxd4105_cfif_mask_ack_irq,
			.mask	= cxd4105_cfif_mask_irq,
			.unmask = cxd4105_cfif_unmask_irq,
		},
		.irq_start = IRQ_CF_BASE,
		.irq_num = IRQ_CF_NUM,
		.allowmap = IRQ_CF_ALLOWMAP,
		.io_base = INTC_IO_BASE(IRQ_CF),
		.demux = cxd4105_cfif_demux,
	},
#ifdef INT_PCI
	DECLARE_CHIP(IRQ_PCI),
#endif
	DECLARE_CHIP(IRQ_USB),
	DECLARE_CHIP(IRQ_ADC),
	[IRQ_GPIO] = {
		.base = {
			.ack = cxd4105_gpio_mask_ack_irq,
			.mask = default_mask_irq,
			.unmask = default_unmask_irq,
		},
		.irq_start = IRQ_GPIO_BASE,
		.irq_num = IRQ_GPIO_NUM,
		.allowmap = IRQ_GPIO_ALLOWMAP,
		.io_base = INTC_IO_BASE(IRQ_GPIO),
		.demux = default_demux,
	},
	[IRQ_SYSV] = {
		.base = {
			.ack = cxd4105_sysv_mask_ack_irq,
			.mask = default_mask_irq,
			.unmask = default_unmask_irq,
		},
		.irq_start = IRQ_SYSV_BASE,
		.irq_num = IRQ_SYSV_NUM,
		.allowmap = IRQ_SYSV_ALLOWMAP,
		.io_base = INTC_IO_BASE(IRQ_SYSV),
		.demux = default_demux,
	},

#ifdef CONFIG_CXD4105_EXTIO
	DECLARE_CHIP(IRQ_SIRCS),
	[IRQ_I2C] = {},
	[IRQ_SSP] = {},
# ifdef INT_I2S
	DECLARE_CHIP(IRQ_I2S),
# endif
	[IRQ_ATA] = {},
	[IRQ_CML] = {},
	[IRQ_MS] = {},
	[IRQ_MSIO] = {},
	DECLARE_CHIP(IRQ_TPU),
	[IRQ_TP] = {},
	[IRQ_MXX] = {},
	[IRQ_MBOX] = {},
	DECLARE_CHIP(IRQ_VIDEO),
	DECLARE_CHIP(IRQ_CODEC),
	[IRQ_WSX] = {},
	DECLARE_CHIP(IRQ_IMG),
	DECLARE_CHIP(IRQ_IMGMC),
	DECLARE_CHIP(IRQ_BUS),
	DECLARE_CHIP(IRQ_IMGV),
	DECLARE_CHIP(IRQ_AUDIO),
#endif
};

static void default_demux(int irq, struct irq_desc *desc)
{
	unsigned long sts;
	struct cxd_irqchip *chip = &chips[irq];

	sts = readl(INT_CTRL(chip->io_base + INTC_STATUS_OFFSET));
	sts &= ((1 << chip->irq_num) - 1);
	irq = chip->irq_start;
	desc = irq_desc + irq;
	while (sts) {
		if (sts & 1) {
			desc_handle_irq(irq, desc);
		}
		irq++;
		desc++;
		sts >>= 1;
	}
}

static void default_mask_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_desc + irq;
	struct cxd_irqchip *chip = container_of(desc->chip, struct cxd_irqchip, base);

	irq -= chip->irq_start;
	writel(1 << irq, INT_CTRL(chip->io_base + INTC_ENABLE_CLR_OFFSET));
}

static void default_unmask_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_desc + irq;
	struct cxd_irqchip *chip = container_of(desc->chip, struct cxd_irqchip, base);

	irq -= chip->irq_start;
	writel(1 << irq, INT_CTRL(chip->io_base + INTC_ENABLE_OFFSET));
}

/* CF */
static void cxd4105_cfif_demux(int irq_unused, struct irq_desc *desc)
{
	unsigned long sts;

	sts = readl(VA_CF_BASE + CF_IRQ_STAT);
	sts &= readl(VA_CF_BASE + CF_IRQ_EN);
	if (sts & CF_IRQ_STAT_INT_CF0_IRQ) {
		desc = irq_desc + IRQ_CF0;
		desc_handle_irq(IRQ_CF0, desc);
	}
	if (sts & CF_IRQ_STAT_INT_EXIO_IRQ) {
		desc = irq_desc + IRQ_EXIO;
		desc_handle_irq(IRQ_EXIO, desc);
	}
}

static unsigned long cxd4105_get_cfirq_mask(unsigned int irq)
{
	unsigned long mask = 0;

	switch (irq) {
	case IRQ_CF0:
		mask = CF_IRQ_EN_INT_CF0_IRQ_EN;
		break;
	case IRQ_EXIO:
		mask = CF_IRQ_EN_INT_EXIO_IRQ_EN;
		break;
	}
	return mask;
}

static void cxd4105_cfif_mask_ack_irq(unsigned int irq)
{
	unsigned long tmp, mask;

	mask = cxd4105_get_cfirq_mask(irq);
	tmp = readl(VA_CF_BASE + CF_IRQ_EN);
	tmp &= ~mask;
	writel(tmp, VA_CF_BASE + CF_IRQ_EN);
	writel(mask & 0x07, VA_CF_BASE + CF_IRQ_CLR);
}

static void cxd4105_cfif_mask_irq(unsigned int irq)
{
	unsigned long tmp, mask;

	mask = cxd4105_get_cfirq_mask(irq);
	tmp = readl(VA_CF_BASE + CF_IRQ_EN);
	tmp &= ~mask;
	writel(tmp, VA_CF_BASE + CF_IRQ_EN);
}

static void cxd4105_cfif_unmask_irq(unsigned int irq)
{
	unsigned long tmp, mask;

	mask = cxd4105_get_cfirq_mask(irq);
	tmp = readl(VA_CF_BASE + CF_IRQ_EN);
	tmp |= mask;
	writel(tmp, VA_CF_BASE + CF_IRQ_EN);
}

/* SYSV */
static void cxd4105_sysv_mask_ack_irq(unsigned int irq)
{
	irq -= IRQ_SYSV0;
	writel(1 << irq, INT_CTRL(SYSV_ENABLE_CLEAR));
	/* clear interrupt status */
	writel(1 << (irq + 3), VA_AGPIOSYS + GPIO_INTCL);
}

/* GPIO */
static void cxd4105_gpio_mask_ack_irq(unsigned int irq)
{
	irq -= IRQ_GPIO0;
	writel(1 << irq, INT_CTRL(GPIO_ENABLE_CLEAR));
	/* clear interrupt status */
	writel(1 << irq, VA_GPIOSYS + GPIO_INTCL);
}

/* DMAC */
static void cxd4105_dma_demux(int irq_unused, struct irq_desc *desc)
{
	unsigned long sts, sts_l1;
	unsigned int irq;

	sts_l1 = readl(INT_CTRL(DMAC_STATUS));
	/* DMAC0 */
	if (sts_l1 & 0x01) {
		sts = readl(DMACINTSTATUS);
		sts &= DMAC_VALIDBITMASK;
		irq = IRQ_DMA0_0;
		desc = irq_desc + irq;
		while (sts) {
			if (sts & 1) {
				desc_handle_irq(irq, desc);
			}
			irq++;
			desc++;
			sts >>= 1;
		}
	}
	/* DMAC1 */
	if (sts_l1 & 0x02) {
		irq = IRQ_DMAC1;
		desc = irq_desc + irq;
		desc_handle_irq(irq, desc);
	}
}

static void cxd4105_dma_mask_irq(unsigned int irq)
{
	if (irq != IRQ_DMAC1)
		irq = IRQ_DMAC0;
	irq -= IRQ_DMAC0;
	writel(1 << irq, INT_CTRL(DMAC_ENABLE_CLEAR));
}

static void cxd4105_dma_unmask_irq(unsigned int irq)
{
	if (irq != IRQ_DMAC1)
		irq = IRQ_DMAC0;
	else
		printk("%s: for %d\n", __func__, irq);
	irq -= IRQ_DMAC0;
	writel(1 << irq, INT_CTRL(DMAC_ENABLE));
}

/* L1 */
static void sc_mask_irq(unsigned int irq)
{
	writel(1 << irq, INT_CTRL(IRQ_ENABLE_CLEAR));
}

static void sc_unmask_irq(unsigned int irq)
{
	writel(1 << irq, INT_CTRL(IRQ_ENABLE));
}
 
static struct irq_chip sc_chip = {
        .ack	= sc_mask_irq,
	.mask	= sc_mask_irq,
	.unmask = sc_unmask_irq,
};

void __init cxd4105_init_irq(void)
{
	unsigned int i, mask, flags;
	int irqno;
	struct cxd_irqchip *cp;

	/* select all interrupt as IRQ */
	writel(0x00000000, INT_CTRL(IRQ_SELECT));
	/* Disable all interrupts initially. */
	writel(~0, INT_CTRL(IRQ_ENABLE_CLEAR));

	/* Disable interrupts */
	writel(~0, INTC_ENABLE_CLR(INT_TIMER));
	writel(~0, INTC_ENABLE_CLR(INT_UART));
	writel(~0, INTC_ENABLE_CLR(INT_DMAC));
	writel(~0, INTC_ENABLE_CLR(INT_JOG));
	writel(~0, INTC_ENABLE_CLR(INT_SIO));
	writel(~0, INTC_ENABLE_CLR(INT_USB));
	writel(~0, INTC_ENABLE_CLR(INT_ADC));
	writel(~0, INTC_ENABLE_CLR(INT_GPIO));
#ifdef INT_PCI
	writel(~0, INTC_ENABLE_CLR(INT_PCI));
#endif
#ifdef INT_SDIF
	writel(~0, INTC_ENABLE_CLR(INT_SDIF));
#endif
	writel(~0, INTC_ENABLE_CLR(INT_SYSV));
	writel(0, VA_CF_BASE+CF_IRQ_EN);
	
	for (i = 0, cp = chips; i < MAXIRQNUM; i++, cp++) {
		if (((1 << i) && CXD4105_SC_VALID_INT) != 0) {
			set_irq_chip(i, &sc_chip);
			set_irq_handler(i, handle_level_irq);
			set_irq_flags(i, IRQF_VALID);

			/* register handler (includes reserved bit)
			   to handle spurious interrupt too */
			for (irqno = cp->irq_start, mask = 0x00000001;
			     irqno < cp->irq_start + cp->irq_num;
			     irqno++, mask <<= 1) {
				set_irq_chip(irqno, &cp->base);
				set_irq_handler(irqno, handle_level_irq);
				/* Only IRQ in allowmap: can_request_irq */
				flags = (cp->allowmap & mask) ? IRQF_VALID:0;
				set_irq_flags(irqno, flags);
			}
			if (cp->demux)
				set_irq_chained_handler(i, (irq_flow_handler_t)cp->demux);
		}
	}
}
