/* 2008-05-22: File added and changed by Sony Corporation */
/*
 * PB11MPCore PCIX Support
 *
 * Copyright (C) 2007 ARM
 *
 */
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/ptrace.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/mach/pci.h>

#define PCIX_READ_ACCESS		0x06000000
#define PCIX_WRITE_ACCESS		0x07000000


/* PCI-X Configuration registers */
#define PCI_VENID			0x00
#define PCI_DEVID			0x02
#define PCI_COMMAND			0x04
#define PCI_COMMAND_IO			0x1
#define PCI_COMMAND_MEMORY		0x2
#define PCI_COMMAND_MASTER		0x4
#define PCI_STATUS			0x06
#define PCI_REVID			0x08
#define PCI_CLCODE			0x09
#define PCI_CALISIZE			0x0C
#define PCI_LTTIMER			0x0D
#define PCI_HDTYPE			0x0E
#define PCI_PTBADDR0L			0x10
#define PCI_PTBADDR0M			0x14
#define PCI_PTBADDR1L			0x18
#define PCI_PCIPBNO			0x18
#define PCI_PCISBNO			0x19
#define PCI_PCISUBNO			0x1A
#define PCI_PCISLTR			0x1B
#define PCI_PTBADDR1M			0x1C
#define PCI_PCIIOBAR			0x1C
#define PCI_PCIIOLMT			0x1D
#define PCI_PCISSR			0x1E
#define PCI_PCIMBAR			0x20
#define PCI_PCIMLMT			0x22
#define PCI_PCIPMBAR			0x24
#define PCI_PCIPMLMT			0x26
#define PCI_PCIPMBARU32			0x28
#define PCI_SUBSYSVENID			0x2C
#define PCI_PCIPMLMTU32			0x2C
#define PCI_SUBSYSID			0x2E
#define PCI_PCIIOBARU16			0x30
#define PCI_PCIIOLMTU16			0x32
#define PCI_CAPPOINT			0x34
#define PCI_INTLINE			0x3C
#define PCI_INTPIN			0x3D
#define PCI_BCNTRL			0x3E
#define PCI_CFGADDR			0x40
#define PCI_CFGDATA			0x44
#define PCI_CFGATTR			0x48
#define PCI_PINTACNT			0x50
#define PCI_PINTXSTAT			0x54
#define PCI_PINTXMASK			0x58
#define PCI_SERRSTAT			0x5C
#define PCI_PERRADDRL			0x60
#define PCI_PERRADDRH			0x64
#define PCI_PERRSTAT			0x68
#define PCI_PERRMASK			0x6C
#define PCI_OCBERRSTAT			0x78
#define PCI_OCBERRMASK			0x7C
#define PCI_MSICAPID			0x80
#define PCI_MSINXTCAP			0x81
#define PCI_MSGCNT			0x82
#define PCI_MSGADDR			0x84
#define PCI_MSGUPADDR			0x88
#define PCI_MSGDATA			0x8C
#define PCI_PCIXCAPID			0x90
#define PCI_PCIXNXTCAP			0x91
#define PCI_PCIXCOMMAND			0x92
#define PCI_PCIXSTAT			0x94
#define PCI_SPCMPERRMSG			0x98
#define PCI_PTMEMSEG0			0xA0
#define PCI_PTMEMMSK0			0xA4
#define PCI_PTMEMENSW0			0xA8
#define PCI_PTMEMSEG1			0xB0
#define PCI_PTMEMMSK1			0xB4
#define PCI_PTMEMENSW1			0xB8
#define PCI_PMSEGL			0xC0
#define PCI_PMSEGH			0xC4
#define PCI_DRSTCNT			0xEC
#define PCI_PRST			0xF0
#define PCI_UNITCNT			0xF4
#define PCI_CNTOTIMER			0xF8
#define PCI_PSLADAFLASH			0xFC


/*
 * Software is exposed to downstream half of the PCI-X bridge registers
 * rather than a real PCI interface. Therefore there's no config memory
 * region as such.
 *
 * 	PCI Regions:
 * 	MEM		0xA0000000 - 0xBFFFFFFF
 * 	IO   		0x00000000 - 0x0000FFFF
 * 	CONFIG		-
 */

void realview_pb11mp_pcix_sync(void)
{
	readl(PCIX_UNIT_BASE + PCI_VENID);
}

void realview_pb11mp_pcix_unit_init(void)
{
	u32 data = readl(PCIX_UNIT_BASE + PCI_UNITCNT);

	if (data & 0x10)
		writel(0x00000000, PCIX_UNIT_BASE + PCI_PRST); /* PCI reset */
	else {
		printk("Error: PCI-X unit not in PCI-X mode.\n");
		writel(data | 0x80000000, PCIX_UNIT_BASE + PCI_UNITCNT);
	}

	udelay(1500);

	writew(0x0006, PCIX_UNIT_BASE + PCI_COMMAND); /* Master-Memory enable */
	writew(0xfb30, PCIX_UNIT_BASE + PCI_STATUS);  /* Error bit clear */

	writeb(0x08, PCIX_UNIT_BASE + PCI_CALISIZE); /* Cache line size */
	writeb(0x40, PCIX_UNIT_BASE + PCI_LTTIMER);  /* Latency Timer */

	/* Segment Address[31:24] */
	writel(0x00000003, PCIX_UNIT_BASE + PCI_PMSEGL);
	/* Segment Address[63:32] */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PMSEGH);

	writel(0x00000003, PCIX_UNIT_BASE + PCI_PTMEMENSW0); /* no swap */
	writel(0x00000003, PCIX_UNIT_BASE + PCI_PTMEMENSW1); /* no swap */
	/* Window#0 256MB enable */
	writel(0x0fff0001, PCIX_UNIT_BASE + PCI_PTMEMMSK0);
	/* Window#1 Disable */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PTMEMMSK1);

	/* Window base address setting */
	/* Memory Base Address, Ptrefetch Disable, 64bit */
	writel(0x00000004, PCIX_UNIT_BASE + PCI_PTBADDR0L);
	/* Memory Base Address[63:32] */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PTBADDR0M);
	/* Memory Base Address, Ptrefetch Disable, 64bit */
	writel(0x00000004, PCIX_UNIT_BASE + PCI_PTBADDR1L);
	/* Memory Base Address[63:32] */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PTBADDR1M);
	/* OnChipBus Address#0[35:24] */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PTMEMSEG0);
	/* OnChipBus Address#1[35:24] */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PTMEMSEG1);
	/* 66MHz, 64bit device */
	writel(0x00010000, PCIX_UNIT_BASE + PCI_PCIXSTAT);
	/* Interrupt Mask */
	writel(0x00000000, PCIX_UNIT_BASE + PCI_PINTXMASK);
	/* Enable PCI error status */
	writel(0x000307f7, PCIX_UNIT_BASE + PCI_PERRMASK);
	/* Clear PCI error status */
	writel(0x000307f7, PCIX_UNIT_BASE + PCI_PERRSTAT);
	/* Enable count out */
	writel(0x10FFFFFF, PCIX_UNIT_BASE + PCI_CNTOTIMER);

	realview_pb11mp_pcix_sync();

	udelay(1500);
	/* Deassert PCI reset */
	writel(0x00000001, PCIX_UNIT_BASE + PCI_PRST);
	/* Initial end, Enable arbiter */
	writel(data | 0x80000020, PCIX_UNIT_BASE + PCI_UNITCNT);
	realview_pb11mp_pcix_sync();
	/* udelay(1500) is NOT enough for PCI-Express bridge */
	mdelay(15);
}

void realview_pb11mp_pcix_set_attr(unsigned int tag)
{
	/* Get Device and Funcion number */
	u32 data = readl(PCIX_UNIT_BASE + PCI_PCIXSTAT) & 0x0ff;
	/* attribute set */
	writel(tag | (data << 8), PCIX_UNIT_BASE + PCI_CFGATTR);
}

int realview_pb11mp_pcix_set_config(u32 bus, u32 dev, u32 func, int offset)
{
	u32 mode;
	u32 config_addr;

	writel(0x000307F7, PCIX_UNIT_BASE + PCI_PERRSTAT);   /* clear error bit */
	writew(0xfb30, PCIX_UNIT_BASE + PCI_STATUS);        /* error bit cear */

	if (!bus) {
		/* Type0 Configuration cycle */
		mode = readl(PCIX_UNIT_BASE + PCI_UNITCNT);
		if (mode & 0x1) { /* check PCI mode */
			printk(KERN_ERR "PCI mode detected during config cycle"
			       "attempt. Should really be in PCI-X mode.\n");
			BUG();
		} else {
			/* PCI-X Mode */
			if (dev < 16 || dev > 30)
				return -ENODEV;
			config_addr = (1 << dev) | ((dev & 0x1f) << 11) |
				      ((func & 0x7) << 8) | (offset & 0xfc);
			writel(config_addr, PCIX_UNIT_BASE + PCI_CFGADDR);
		}
	} else {
		config_addr = ((bus & 0xff) << 16) | ((dev & 0x1f) << 11) |
			      ((func & 0x7) << 8) | (offset & 0xfc) | 0x1;
		/* Type1 Configuration cycle */
		writel(config_addr, PCIX_UNIT_BASE + PCI_CFGADDR);
	}
	realview_pb11mp_pcix_sync();
	return 0;
}

int realview_pb11mp_pcix_check_error(void)
{
	u32 data;
	realview_pb11mp_pcix_sync();
	data = readl(PCIX_UNIT_BASE + PCI_PERRSTAT);
	if (data) {
		writel(data, PCIX_UNIT_BASE + PCI_PERRSTAT);
		return data;
	}
	return 0;
}

int realview_pb11mp_pci_read_config(struct pci_bus *pbus, u32 devfn,
				     int offset, int size, u32 *data)
{
	u32 bus = pbus->number;
	u32 slot = PCI_SLOT(devfn);
	u32 function = PCI_FUNC(devfn);

	realview_pb11mp_pcix_set_attr(PCIX_READ_ACCESS);
	if (realview_pb11mp_pcix_set_config(bus, slot, function, offset) < 0)
		goto out_err;
	switch (size) {
		case 1:
    			*data = (readl(PCIX_UNIT_BASE + PCI_CFGDATA) >>
				 (8 * (offset & 3))) & 0xff;
			break;
		case 2:
    			*data = (u32)readw(PCIX_UNIT_BASE + PCI_CFGDATA +
					   (offset & 3));
			break;
		default:
			*data = readl(PCIX_UNIT_BASE + PCI_CFGDATA);
			break;
	}
	/* Error codes from downstream bus don't mean much for software. */
	if (!realview_pb11mp_pcix_check_error())
		return PCIBIOS_SUCCESSFUL;
out_err:
	*data = ((size == 1) ? 0xFF : ((size == 2) ? 0xFFFF : 0xFFFFFFFF));
	return PCIBIOS_SUCCESSFUL;
}

int realview_pb11mp_pci_write_config(struct pci_bus *pbus, u32 devfn,
				      int offset, int size, u32 data)
{
	u32 slot = PCI_SLOT(devfn);
	u32 function = PCI_FUNC(devfn);
	u32 bus = pbus->number;

	realview_pb11mp_pcix_set_attr(PCIX_WRITE_ACCESS);
	if (realview_pb11mp_pcix_set_config(bus, slot, function, offset) < 0)
		return PCIBIOS_SUCCESSFUL;

	switch (size) {
		case 1:
			writeb(data, PCIX_UNIT_BASE + PCI_CFGDATA + (offset & 3));
			break;
		case 2:
			writew(data, PCIX_UNIT_BASE + PCI_CFGDATA + (offset & 3));
			break;
		default:
			writel(data, PCIX_UNIT_BASE + PCI_CFGDATA);
			break;
	}
	/* Write errors don't really matter */
	realview_pb11mp_pcix_check_error();
	realview_pb11mp_pcix_set_attr(PCIX_READ_ACCESS);
	return PCIBIOS_SUCCESSFUL;
}

static struct pci_ops pci_realview_pb11mp_ops = {
	.read	= realview_pb11mp_pci_read_config,
	.write	= realview_pb11mp_pci_write_config,
};

static struct resource io_mem = {
	.name	= "PCI non-prefetchable I/O space",
	.start	= REALVIEW_PB11MP_PCI_IO_BASE,
	.end	= REALVIEW_PB11MP_PCI_IO_BASE + REALVIEW_PB11MP_PCI_IO_SIZE - 1,
	.flags	= IORESOURCE_IO,
};

static struct resource nonpre_mem0 = {
	.name	= "PCI memory region 0",
	.start	= REALVIEW_PB11MP_PCI_MEM_BASE,
	.end	= REALVIEW_PB11MP_PCI_MEM_BASE + REALVIEW_PB11MP_PCI_MEM_SIZE - 1,
	.flags	= IORESOURCE_MEM,
};
/*
static struct resource pre_mem1 = {
	.name	= "PCI memory region 1",
	.start	= REALVIEW_PB11MP_PCI_MEM_BASE1,
	.end	= REALVIEW_PB11MP_PCI_MEM_BASE1+REALVIEW_PB11MP_PCI_MEM_BASE1_SIZE-1,
	.flags	= IORESOURCE_MEM | IORESOURCE_PREFETCH,
};
*/
static int __init pci_realview_pb11mp_setup_resources(struct resource **resource)
{
	int ret = 0;


	if ((ret = request_resource(&iomem_resource, &io_mem))) {
		printk(KERN_ERR "PCI: unable to allocate I/O "
		       "region 0 (Err = %d)\n", ret);
		goto out;
	}
	if ((ret = request_resource(&iomem_resource, &nonpre_mem0))) {
		printk(KERN_ERR "PCI: unable to allocate non-prefetcheble "
		       "memory region 0 (Err = %d)\n", ret);
		goto release_io_mem;
	}
	/*
	 * bus->resource[0] is the IO resource for this bus
	 * bus->resource[1] is the non-prefetchable mem resource 0 for this bus
	 */
	resource[0] = &io_mem;
	resource[1] = &nonpre_mem0;

	goto out;

 release_io_mem:
	release_resource(&io_mem);
 out:
	return ret;
}

int __init pci_realview_pb11mp_setup(int nr, struct pci_sys_data *sys)
{
	int ret;
	realview_pb11mp_pcix_unit_init();

	sys->mem_offset = 0;
	sys->io_offset = REALVIEW_PB11MP_PCI_IO_BASE;
	if ((ret = pci_realview_pb11mp_setup_resources(sys->resource)) < 0) {
		printk(KERN_ERR "Unable to allocate PCI regions.\n");
		return ret;
	} else
		return 1;
}


struct pci_bus *pci_realview_pb11mp_scan_bus(int nr, struct pci_sys_data *sys)
{
	return pci_scan_bus(sys->busnr, &pci_realview_pb11mp_ops, sys);
}

void __init pci_realview_pb11mp_preinit(void)
{
}

static struct pci_dev *pcie_bridge;

/* Maps scrambled IRQ pins to IRQ lines. Scrambling depends on device slot. */
static int __init pci_realview_pb11mp_map_irq(struct pci_dev *dev, u8 slot, u8 pin)
{
	int devslot = PCI_SLOT(dev->devfn);
	int irq = 0;

	/* Upstream port of 8518 device */
	if (unlikely(dev->device == 0x8518 && dev->bus->primary == 0)) {
		BUG_ON(pcie_bridge);	/* Can't be already assigned */
		pcie_bridge = dev;
	}

	if (pcie_bridge) {
		if (dev->bus->primary >= pcie_bridge->bus->secondary &&
		    dev->bus->primary <= pcie_bridge->bus->subordinate)
			goto pcie;
	}

	/*
	 * slot	  pin    irq
	 * 0      #A(1)  53 + 32
	 * 0      #B(2)  50 + 32
	 * 0      #C(3)  51 + 32
	 * 0      #D(4)  52 + 32
	 * 1      #A(1)  52 + 32
	 * 1      #B(2)  53 + 32
	 * 1      #C(3)  50 + 32
	 * 1      #D(4)  51 + 32
	 * 2	  1      51
	 * 2      2      52
	 * 2      3      53
	 * 2      4      50
	 * 3	  1	 50
	 * 3	  2	 51
	 * 3	  3	 52
	 * 3	  4	 53
	 */
	switch (devslot & 3) {
		case 0:
			irq = ((pin + 2) & 3) + 50;
			break;
		case 1:
			irq = ((pin + 1) & 3) + 50;
			break;
		case 2:
			switch (pin) {	/* FIXME: I think this must be 51, 52, 53, 50 */
				case 1:
					irq = 51;
					break;
				case 2:
					irq = 50;
					break;
				case 3:
					irq = 53;
					break;
				case 4:
					irq = 52;
					break;
				default:
					printk("Warning, unsupported pci pin "
					       "%d\n", pin);
					irq = -1;
					break;
			}
			break;
		case 3:
			irq = pin + 49;
			break;
	}
	if (irq > 0)
		irq += 32;
	/* irq = ((devslot + 3 - pin + 1) & 3) + 50 + 32; */
	printk("PCI irq mapping: slot %d, pin %d, devslot %d, irq: %d\n",
	       slot, pin, devslot, irq);

	return irq + IRQ_PB11MP_GIC_START;

pcie:
	/*
	 * A *guess* example. The problem is that in PCIE in different slots
	 * a device could still get the same slot/pin/devslot triplet. Therefore
	 * we check the slot/pin/devslot triplet of an underlying PCIE port.
	 *
	 * slot	  pin    irq
	 * 0      #A(1)  56 + 32
	 * 0      #B(2)  57 + 32
	 * 0      #C(3)  54 + 32
	 * 0      #D(4)  55 + 32
	 * 1      #A(1)  55 + 32
	 * 1      #B(2)  56 + 32
	 * 1      #C(3)  57 + 32
	 * 1      #D(4)  54 + 32
	 * 2	  1      54
	 * 2      2      55
	 * 2      3      56
	 * 2      4      57
	 * 3	  1	 57
	 * 3	  2	 54
	 * 3	  3	 55
	 * 3	  4	 56
	 * 4	  1	 56
	 * 4	  2	 57
	 * 4	  3      54
	 * 4      4	 55
	 */
	if (dev->bus->self) {	/* We do have a bus above us */
		/* Device is right behind a PCIE hub port, and its not a PCIE
		 * hub port itself. */
		if (dev->bus->self->device == 0x8518 &&
		    dev->device != 0x8518) {
			/* Depend on devslot of 8518 port we're connected to */
			devslot = PCI_SLOT(dev->bus->self->devfn);
		}
	}
	/* These are the two cases for PCIE ports on PB11MPCore. Any other
	 * bus topology (e.g. with further PCIE bridges) may not work well. */
	switch (pin) {
		case 1:	/* Correct for Marvell Yukon2, Geforce G550. */
			irq = (devslot & 3) + 86;
			break;
		case 2:
		default:
			/* Bogus default. Covers some devices. */
			irq = (devslot & 3) + (pin - 1) + 86;
			break;
	}
	printk("PCI Express irq mapping: device: 0x%x, slot %d, pin %d, devslot %d, irq: %d\n",
	       dev->device, slot, pin, devslot, irq);

	return irq + IRQ_PB11MP_GIC_START;
}

static struct hw_pci realview_pb11mp_pci __initdata = {
	.swizzle		= NULL,
	.map_irq		= pci_realview_pb11mp_map_irq,
	.nr_controllers		= 1,
	.setup			= pci_realview_pb11mp_setup,
	.scan			= pci_realview_pb11mp_scan_bus,
	.preinit		= pci_realview_pb11mp_preinit
};

static int __init realview_pb11mp_pci_init(void)
{
	if (!machine_is_realview_pb11mp())
		return -ENODEV;

	pci_common_init(&realview_pb11mp_pci);
	return 0;
}

subsys_initcall(realview_pb11mp_pci_init);

