/*
 * arch/arm/mach-cxd4115/pci.c
 *
 * PCI routines for CXD4115
 *
 * Copyright 2007,2008,2009 Sony Corporation
 *
 * This code is based on arch/arm/mach-ixp2000/pci.c.
 */
/*
 * arch/arm/mach-ixp2000/pci.c
 *
 * PCI routines for IXDP2400/IXDP2800 boards
 *
 * Original Author: Naeem Afzal <naeem.m.afzal@intel.com>
 * Maintained by: Deepak Saxena <dsaxena@plexity.net>
 *
 * Copyright 2002 Intel Corp.
 * Copyright (C) 2003-2004 MontaVista Software, Inc.
 *
 *  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.
 */

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/delay.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/system.h>
#include <asm/mach-types.h>
#include <asm/hardware.h>
#include <asm/arch/regs-pci.h>
#include <asm/arch/regs-clk.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/pci.h>
#include <asm/mach/pci.h>

//#define PCI_CFG_DEBUG

#if defined(CONFIG_CXD4115_PCIBRIDGE_INIT)
#define REG_DAT(val, mask, shf)  ((val & mask) << shf)

#define ARBITER_PCIREQ           REG_DAT(0x1F, 0xFF, 0) //use pcireq#0~#4
#define ARBITER_PCIBP_MODE       REG_DAT(0x0, 0x1, 12) //parking master
#define ARBITER_PARKING_TIMER    REG_DAT(0x0, 0xFF, 13) //no parking timer

#define reg_PCI_ARBITER_CTR \
          (ARBITER_PCIREQ | ARBITER_PCIBP_MODE | ARBITER_PARKING_TIMER)

#define AHBPCI_W1_ADDR           REG_DAT(AHBPCI_WINDOW1_BASE, 0xFFFFF800, 0)
#define AHBPCI_W1_ENDIAN         REG_DAT(0x0, 0x7, 6) //no endian translation
#define AHBPCI_W1_CFGTYPE        REG_DAT(0x0, 0x1, 4) //type 0
#define AHBPCI_W1_PCICMD         REG_DAT(0x5, 0x7, 1) //configuration cycle

#define reg_PCI_AHBPCI_WIN1_CTR \
          (AHBPCI_W1_ADDR | AHBPCI_W1_ENDIAN | AHBPCI_W1_CFGTYPE | AHBPCI_W1_PCICMD)

#define AHBPCI_W2_ADDR           REG_DAT(AHBPCI_WINDOW2_BASE, 0xFFFFF800, 0)
#define AHBPCI_W2_ENDIAN         REG_DAT(0x0, 0x7, 6) //no endian translation
#define AHBPCI_W2_BURST          REG_DAT(0x1, 0x1, 5) //burst enable
#define AHBPCI_W2_PCICMD         REG_DAT(0x3, 0x7, 1) //memory cycle
#define AHBPCI_W2_PREFETCH       REG_DAT(0x1, 0x1, 0) //prefetchable

#define reg_PCI_AHBPCI_WIN2_CTR \
          (AHBPCI_W2_ADDR | AHBPCI_W2_ENDIAN | AHBPCI_W2_PCICMD | AHBPCI_W2_BURST | AHBPCI_W2_PREFETCH)

#define AHBPCI_W3_ADDR           REG_DAT(AHBPCI_WINDOW3_BASE, 0xFFFFF800, 0)
#define AHBPCI_W3_ENDIAN         REG_DAT(0x0, 0x7, 6) //no endian translation
#define AHBPCI_W3_BURST          REG_DAT(0x1, 0x1, 5) //burst enable
#define AHBPCI_W3_PCICMD         REG_DAT(0x3, 0x7, 1) //memory cycle
#define AHBPCI_W3_PREFETCH       REG_DAT(0x0, 0x1, 0) //nonprefetchable

#define reg_PCI_AHBPCI_WIN3_CTR \
          (AHBPCI_W3_ADDR | AHBPCI_W3_ENDIAN | AHBPCI_W3_PCICMD | AHBPCI_W3_BURST | AHBPCI_W3_PREFETCH)

#define PCIAHB_W1_ADDR           REG_DAT(PCIAHB_WINDOW1_BASE, 0xFFFFF000, 0)
#define PCIAHB_W1_ENDIAN         REG_DAT(0x0, 0x7, 6) //no endian translation
#define PCIAHB_W1_PREFETCH       REG_DAT(0x3, 0x3, 0) //max 16bit

#define reg_PCI_PCIAHB_WIN1_CTR \
           (PCIAHB_W1_ADDR | PCIAHB_W1_ENDIAN | PCIAHB_W1_PREFETCH)

#define PCIAHB_W2_ADDR           REG_DAT(PCIAHB_WINDOW2_BASE, 0xFFFFF000, 0)
#define PCIAHB_W2_ENDIAN         REG_DAT(0x0, 0x7, 6) //no endian translation
#define PCIAHB_W2_PREFETCH       REG_DAT(0x3, 0x3, 0) //max 16bit 

#define reg_PCI_PCIAHB_WIN2_CTR \
           (PCIAHB_W2_ADDR | PCIAHB_W2_ENDIAN | PCIAHB_W2_PREFETCH)

#define SMODE_READY_CTR          REG_DAT(0x1, 0x1, 17) //HREADY for slave
#define SMODE_READY_BURST        REG_DAT(0x0, 0x1, 16) //
#define MMODE_HBUSREQ            REG_DAT(0x0, 0x1, 7)
#define MMODE_BOUNDARY           REG_DAT(0x0, 0x3, 5)
#define MMODE_BURST_WIDTH        REG_DAT(0x0, 0x3, 3)
#define MMODE_WRITE_INCR         REG_DAT(0x1, 0x1, 2)
#define MMODE_BYTE_BURST         REG_DAT(0x0, 0x1, 1)
#define MMODE_HTRANS             REG_DAT(0x1, 0x1, 0)

#define reg_PCI_AHB_BUS_CTR \
            (SMODE_READY_CTR | SMODE_READY_BURST | MMODE_HBUSREQ | MMODE_BOUNDARY | MMODE_BURST_WIDTH | MMODE_WRITE_INCR | MMODE_BYTE_BURST | MMODE_HTRANS)

#define reg_PCI_INT_ENABLE \
           (PCI_INT_INTA | PCI_INT_INTB | PCI_INT_INTC | PCI_INT_INTD | \
            PCI_INT_AHBPCIWIN | PCI_INT_PCIAHBWIN2 | PCI_INT_PCIAHBWIN1 | \
            PCI_INT_DMAAHBPCI | PCI_INT_DMAPCIAHB | PCI_INT_CLKRUN | \
	    PCI_INT_PM | PCI_INT_SERR | PCI_INT_PERR | PCI_INT_MABORT | \
            PCI_INT_TABORT | PCI_INT_SIGTABORT)

#define reg_PCI_COMMAND (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER)

#define reg_PCI_BASE_ADDRESS_0 (AHBPCI_WINDOW1_BASE)
#define reg_PCI_BASE_ADDRESS_1 (PCIAHB_WINDOW1_BASE)
#define reg_PCI_BASE_ADDRESS_2 (PCIAHB_WINDOW2_BASE)

static void __init cxd4115_pci_reset(int assert)
{
	writel((assert)?CLKBLK_RESET_ASSERT:CLKBLK_RESET_NEGATE,
	       VA_HSTCTRL + CLKBLK_PCI_RST);
}

static void __init cxd4115_pci_hw_init(void)
{
	cxd4115_pci_reset(1); /* assert PCI RESET */
	/* select Function */
	writel(0xffffffff, VA_GPIO(5)+GPIO_PORTSEL_CLR);
	writel(0x0001ffff, VA_GPIO(6)+GPIO_PORTSEL_CLR);
	/* Enable XRESET,XGNT,CLKOUT */
	writel(OCTRL324_PCI_XRESET_OE|OCTRL324_PCI_XGNTTS_OE|OCTRL324_PCI_CLKOUT_OE, VA_OCTRL_SET(324));

	/* Enable PCI CLK */
	writel(CLKRST_XPS3_PCI, VA_CLKRST_SET(CLKRST_XPS3));
	writel(CLKBLK_CLOCK_ON, VA_HSTCTRL + CLKBLK_PCI_OCLK);
}

static int set_cfgreg(unsigned int bus, unsigned int slot);

static void __init cxd4115_preinit(void)
{
	unsigned long agent;

	cxd4115_pci_hw_init();
	mdelay(1);		/* Trst */
	cxd4115_pci_reset(0);	/* negate PCI RESET */
	mdelay(1);

	//arbiter setting
	writel(reg_PCI_ARBITER_CTR, PCI_CTRL(PCI_ARBITER_CTR));
	
	//AHB->PCI Window 1
	writel(reg_PCI_AHBPCI_WIN1_CTR, PCI_CTRL(PCI_AHBPCI_WIN1_CTR));
	
        //AHB->PCI Window 2
	writel(reg_PCI_AHBPCI_WIN2_CTR, PCI_CTRL(PCI_AHBPCI_WIN2_CTR));
	//AHB->PCI Window 3
	writel(reg_PCI_AHBPCI_WIN3_CTR, PCI_CTRL(PCI_AHBPCI_WIN3_CTR));

	//PCI->AHB Window 1
	writel(reg_PCI_PCIAHB_WIN1_CTR, PCI_CTRL(PCI_PCIAHB_WIN1_CTR));
	//PCI->AHB Window 2
	writel(reg_PCI_PCIAHB_WIN2_CTR, PCI_CTRL(PCI_PCIAHB_WIN2_CTR));

	//PCI Bus control
	writel(reg_PCI_AHB_BUS_CTR, PCI_CTRL(PCI_AHB_BUS_CTR));
	/* Disable PCI Interrupts */
	writel(0x0, PCI_CTRL(PCI_INT_ENABLE));
	writel(~0x0, PCI_CTRL(PCI_INT_STATUS));

	//Host Configuration Register Setting
	for(agent = 0; agent < 1; agent ++) {
		unsigned long temp;
		
		set_cfgreg(0, agent);

		temp = readl(PCI_CFGREG(PCI_COMMAND)) & 0xFFFF0000;
		temp |= reg_PCI_COMMAND;

		writel(reg_PCI_COMMAND, PCI_CFGREG(PCI_COMMAND));

		//writel(reg_PCI_BASE_ADDRESS_0, PCI_CFGREG(PCI_BASE_ADDRESS_0));
		writel(reg_PCI_BASE_ADDRESS_1, PCI_CFGREG(PCI_BASE_ADDRESS_1));
		writel(reg_PCI_BASE_ADDRESS_2, PCI_CFGREG(PCI_BASE_ADDRESS_2));
	}
}
#endif /* CONFIG_CXD4115_PCIBRIDGE_INIT */

static int set_cfgreg(unsigned int bus, unsigned int slot)
{
	u32 cfg;

	if (bus == 0) {
		if (slot > 20)
			return -1;
		cfg = (1 << PCI_IDSEL(slot)) | PCI_CFG_TYPE0|(PCI_CFG_RW<<1);
	} else {
		cfg = (bus<<16) | (slot<<11) | PCI_CFG_TYPE1|(PCI_CFG_RW<<1);
	}

	writel(cfg, PCI_CTRL(PCI_AHBPCI_WIN1_CTR));

	return 0;
}

static int cxd4115_pci_read_config(struct pci_bus *bus, unsigned int devfn,
				   int where, int size, u32 *value)
{
	u32 val;

	if (bus->number == 0  &&  devfn == 0) { /* Host Bridge ? */
		if ( (0x10 <= where  &&  where < 0x2c) 
		    || (0x30 <= where  && where <  0x34)) {
			*value = 0x00000000;
			return PCIBIOS_SUCCESSFUL;
		}
	}

	if (set_cfgreg(bus->number, PCI_SLOT(devfn)) < 0)
		return PCIBIOS_DEVICE_NOT_FOUND;
	where |= PCI_FUNC(devfn) << 8;

	switch (size) {
	case 1:
		val = readb(PCI_CFGREG(where));
		break;
	case 2:
		where &= ~1; /* 16bit align */
		val = readw(PCI_CFGREG(where));
		break;
	default:
		where &= ~3; /* 32bit align */
		val = readl(PCI_CFGREG(where));
		break;
	}

	/* set AHBPCI_WIN1 to I/O space */
	writel(CXD4115_IO_SPACE, PCI_CTRL(PCI_AHBPCI_WIN1_CTR));

	if (readl(PCI_CTRL(PCI_INT_STATUS))
	    & (PCI_INT_AHBPCIWIN|PCI_INT_MABORT)) { /* ERROR? */
		/* clear */
		writel(0, PCI_CTRL(PCI_PCIERR_ADR));
		writel(0, PCI_CTRL(PCI_AHBERR_ADR));
		writel(PCI_INT_AHBPCIWIN|PCI_INT_MABORT, 
			     PCI_CTRL(PCI_INT_STATUS));
#ifdef PCI_CFG_DEBUG
		printk("device not found\n");
#endif /* PCI_CFG_DEBUG */
		*value = 0xffffffff;
		return PCIBIOS_DEVICE_NOT_FOUND;
	}

#ifdef PCI_CFG_DEBUG
	printk(KERN_ERR "pci_read_config:(%d,%d,0x%x)%d--> 0x%x\n",
	       bus->number, devfn, where, size, val);
#endif /* PCI_CFG_DEBUG */

	*value = val;
	return PCIBIOS_SUCCESSFUL;
}

static int cxd4115_pci_write_config(struct pci_bus *bus, unsigned int devfn, 
				    int where, int size, u32 value)
{
#ifdef PCI_CFG_DEBUG
	printk(KERN_ERR "pci_write_config:(%d,%d,0x%x)<--0x%x\n", bus->number, devfn, where, value);
#endif /* PCI_CFG_DEBUG */
	if (bus->number == 0  &&  devfn == 0) { /* Host Bridge ? */
		if ((0x10 <= where  &&  where < 0x2c) 
		    || (0x30 <= where  && where <  0x34)) {
			return PCIBIOS_SUCCESSFUL;
		}
	}

	if (set_cfgreg(bus->number, PCI_SLOT(devfn)) < 0)
		return PCIBIOS_DEVICE_NOT_FOUND;
	
	where |= PCI_FUNC(devfn) << 8;

	switch (size) {
	case 1:
		writeb(value, PCI_CFGREG(where));
		break;
	case 2:
		where &= ~1; /* 16bit align */
		writew(value, PCI_CFGREG(where));
		break;
	default:
		where &= ~3; /* 32bit align */
		writel(value, PCI_CFGREG(where));
		break;
	}

	/* set AHBPCI_WIN1 to I/O space */
	writel(CXD4115_IO_SPACE, PCI_CTRL(PCI_AHBPCI_WIN1_CTR));

	return PCIBIOS_SUCCESSFUL;
}

static struct pci_ops cxd4115_pci_ops = {
	.read	= cxd4115_pci_read_config,
	.write	= cxd4115_pci_write_config
};

static struct pci_bus *cxd4115_pci_scan_bus(int nr, struct pci_sys_data *sysdata)
{
	return pci_scan_bus(sysdata->busnr, &cxd4115_pci_ops, sysdata);
}

static void __init cxd4115_pci_postinit(void)
{
	cxd4115_pci_intr_init();
}

#define CXD4115_MAX_PCI_DEV 5
static int cxd4115_irqmap[CXD4115_MAX_PCI_DEV] = {
	-1,
	IRQ_PCI_INTA,
	IRQ_PCI_INTB,
	IRQ_PCI_INTC,
	IRQ_PCI_INTD,
};

static int __init cxd4115_pci_map_irq(struct pci_dev *dev, u8 slot, u8 pin)
{
	if (slot >= CXD4115_MAX_PCI_DEV)
		return -1;
	return cxd4115_irqmap[slot];
}

#ifdef CONFIG_PCI_EXCLUDE_CONFIGURED_DEV
#define MEM_RESOURCE_OFFSET (CXD4115_PCIWINDOW_SIZE - CONFIG_CXD4115_PCI_MEM_SIZE*SZ_1K)
#define IO_RESOURCE_OFFSET  (CXD4115_PCIWINDOW_IOSIZE - CONFIG_CXD4115_PCI_IO_SIZE*SZ_1K)
#else
#define MEM_RESOURCE_OFFSET 0
#define IO_RESOURCE_OFFSET  0
#endif

static struct resource cxd4115_pci_mem_space = {
	.start	= CXD4115_PCIWINDOW_BASE+MEM_RESOURCE_OFFSET,
	.end	= CXD4115_PCIWINDOW_BASE+CXD4115_PCIWINDOW_SIZE-1,
	.flags	= IORESOURCE_MEM,
	.name	= "PCI Mem Space"
};

static struct resource cxd4115_pci_io_space = {
	.start	= CXD4115_PCIWINDOW_IOBASE+IO_RESOURCE_OFFSET,
	.end	= CXD4115_PCIWINDOW_IOBASE+CXD4115_PCIWINDOW_IOSIZE-1,
	.flags	= IORESOURCE_IO,
	.name	= "PCI I/O Space"
};

static int __init cxd4115_pci_setup(int nr, struct pci_sys_data *sys)
{
	if (nr == 0) {
		sys->mem_offset  = 0;
		sys->resource[0] = &cxd4115_pci_io_space;
		sys->resource[1] = &cxd4115_pci_mem_space;
		sys->resource[2] = NULL;
		return 1;
	}
	return 0;
}

static struct hw_pci cxd4115_pci __initdata = {
	.nr_controllers = 1,
#if defined(CONFIG_CXD4115_PCIBRIDGE_INIT)
	.preinit        = cxd4115_preinit,
#endif	
	.setup		= cxd4115_pci_setup,
	.scan		= cxd4115_pci_scan_bus,
	.postinit	= cxd4115_pci_postinit,
	.map_irq	= cxd4115_pci_map_irq,
	.swizzle        = pci_std_swizzle,
};

static int __init cxd4115_pci_init(void)
{
	unsigned int octrl64;

	octrl64 = readl(VA_OCTRL_DATA(64));
	if ((octrl64 & (OCTRL64_PIN_ATA|OCTRL64_RCV_ATA|OCTRL64_DRV_ATA))
	    != 0x0) {
		printk(KERN_ERR "[PCI] not PCI mode.\n");
		return 0;
	}

#if !defined(CONFIG_CXD4115_PCIBRIDGE_INIT)
	mdelay(150); /* wait for initialize done. */
#endif
	pci_common_init(&cxd4115_pci);
	return 0;
}

subsys_initcall(cxd4115_pci_init);
