/*
 * arch/arm/mach-cxd4115/pci_intr.c
 *
 * PCI bridge interrupt handler
 *
 * Copyright 2007,2008,2009 Sony Corporation
 * 
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 */
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/arch/regs-pci.h>
#include <linux/device.h>
#include <linux/amba/bus.h>

#define INTR_EN (PCI_INT_INTA|PCI_INT_INTB|PCI_INT_INTC|PCI_INT_INTD \
		 |PCI_INT_AHBPCIWIN|PCI_INT_PCIAHBWIN2|PCI_INT_PCIAHBWIN1 \
		 |PCI_INT_SERR|PCI_INT_PERR|PCI_INT_MABORT|PCI_INT_TABORT \
		 |PCI_INT_SIGTABORT)

static irqreturn_t pci_intr(int irq, void *devid)
{
	unsigned int stat, mask, pcierr, ahberr;

	stat = readl(PCI_CTRL(PCI_INT_STATUS));
	mask = readl(PCI_CTRL(PCI_INT_ENABLE));
	pcierr = readl(PCI_CTRL(PCI_PCIERR_ADR));
	ahberr = readl(PCI_CTRL(PCI_AHBERR_ADR));
	/* error report */
	printk(KERN_ERR "pcibridge: stat=0x%08x,mask=0x%08x,PCI=0x%08x,AHB=0x%08x\n", stat, mask, pcierr, ahberr);
	/* clear */
	writel(0, PCI_CTRL(PCI_PCIERR_ADR));
	writel(0, PCI_CTRL(PCI_AHBERR_ADR));
	writel(PCI_INT_BRIDGEMASK, PCI_CTRL(PCI_INT_STATUS));

	return IRQ_HANDLED;
}


static int pcibridge_probe(struct amba_device *dev, void *id)
{
	if (id == NULL) {
		return -ENODEV;
	}
	if (dev->res.start != CXD4115_PCI_BASE)
		return -ENODEV;
	if (!request_mem_region(CXD4115_PCI_BASE, SZ_1K, "pcibridge")) {
		printk(KERN_ERR "pcibridge: request_mem_region failed.\n");
		return -ENODEV;
	}
	if (request_irq(IRQ_PCI_BRIDGE, pci_intr, IRQF_DISABLED, "CXD4115 PCI bridge", NULL) != 0) {
		printk(KERN_ERR "pcibridge_probe: request_irq failed.\n");
		return -1;
	}
	writel(INTR_EN, PCI_CTRL(PCI_INT_ENABLE));

	return 0;
}

static int pcibridge_remove(struct amba_device *dev)
{
	unsigned long flags, mask;

	local_irq_save(flags);
	mask = readl(PCI_CTRL(PCI_INT_ENABLE));
	mask &= ~PCI_INT_BRIDGEMASK;
	writel(mask, PCI_CTRL(PCI_INT_ENABLE));
	local_irq_restore(flags);

	free_irq(IRQ_PCI_BRIDGE, NULL);
	return 0;
}

static int pcibridge_suspend(struct amba_device *dev, pm_message_t state)
{
	disable_irq(IRQ_PCI_BRIDGE);
	return 0;
}

static int pcibridge_resume(struct amba_device *dev)
{
	enable_irq(IRQ_PCI_BRIDGE);
	writel(INTR_EN, PCI_CTRL(PCI_INT_ENABLE));
	return 0;
}

static struct amba_id pcibridge_ids[] = {
	{
		.id	= CXD4115_PCI_BASE,
		.mask	= 0xffffffff,
	},
	{ 0, 0 },
};

static struct amba_driver pcibridge_driver = {
	.drv = {
		.name	= "pcibridge",
	},
	.id_table	= pcibridge_ids,
	.probe		= pcibridge_probe,
	.remove		= pcibridge_remove,

	.suspend	= pcibridge_suspend,
	.resume		= pcibridge_resume,
};

int __init cxd4115_pci_intr_init(void)
{
	return amba_driver_register(&pcibridge_driver);
}


#if !defined(CONFIG_PCI)
subsys_initcall(cxd4115_pci_intr_init);
#endif /* !CONFIG_PCI */
