/*
 * arch/ppc/platforms/alaska_pci.c
 *
 * Board setup routines for the Alaska (MPC8220i-RPC) board.
 *
 * Author: MontaVista Software, Inc.
 *         source@mvista.com
 *
 * Based on code from Motorola Inc.
 *
 * Copyright 2003 Motorola Inc.
 *
 * 2004 (c) MontaVista SoftOCware, 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.
 */

 // See alaska_pci_init() for changes to PCI mem size

#include <linux/module.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bootmem.h>

#include <asm/io.h>
#include <asm/prom.h>
#include <asm/pci-bridge.h>
#include <asm/machdep.h>
#include <asm/mpc8220.h>

#include <platforms/alaska.h>
#include <platforms/alaska_pci.h>

/*****************************************************************************/
#define cfg_read(val, addr, type, op)		*val = op((type)(addr))
#define cfg_write(val, addr, type, op)		op((type *)(addr), (val))

#define PCI_OP(rw, size, type, op, mask)					\
int alaska_pci_##rw##_config_##size(struct pci_dev *dev, int offset, type val)	\
{										\
	struct pci_controller *hose = dev->sysdata;				\
	u8 cfg_type = 0;							\
	u32 addr = 0;								\
										\
	if (ppc_md.pci_exclude_device)						\
		if (ppc_md.pci_exclude_device(dev->bus->number, dev->devfn))	\
			return PCIBIOS_DEVICE_NOT_FOUND;			\
										\
	if (hose->set_cfg_type)							\
		if (dev->bus->number != hose->first_busno)			\
			cfg_type = 1;						\
										\
	addr = (((offset & 0xfc) | cfg_type) | (dev->devfn << 8)		\
		| ((dev->bus->number - hose->bus_offset) << 16) | 0x80000000);	\
										\
	out_be32(hose->cfg_addr, addr);						\
	__asm__ __volatile__("sync");						\
	cfg_##rw(val, hose->cfg_data + (offset & mask), type, op);		\
										\
	out_be32(hose->cfg_addr, addr & 0x7fffffff);				\
	__asm__ __volatile__("sync");						\
										\
	return PCIBIOS_SUCCESSFUL;						\
}

PCI_OP(read, byte, u8 *, in_8, 3)
PCI_OP(read, word, u16 *, in_le16, 2)
PCI_OP(write, byte, u8, out_8, 3)
PCI_OP(write, word, u16, out_le16, 2)
PCI_OP(write, dword, u32, out_le32, 0)

/* for 8220 errata 1028 - PCI type 1 Config space reads of dwords don't work
 *  so this code does 2 word accesses and combines results.
 */
int alaska_pci_read_config_dword(struct pci_dev *dev, int offset, u32 *val)
{
	struct pci_controller *hose = dev->sysdata;
	u32 addr;
	u32 tmpv;
	u32 mask = 2;    /* word access */
	u8 cfg_type = 0;

	if (ppc_md.pci_exclude_device)
		if (ppc_md.pci_exclude_device(dev->bus->number, dev->devfn))
			return PCIBIOS_DEVICE_NOT_FOUND;
	if (hose->set_cfg_type)
		if (dev->bus->number != hose->first_busno)
			cfg_type = 1;

	/* Read lower 16 bits */
	addr = (((offset & 0xfc) | cfg_type) | (dev->devfn << 8)
		| ((dev->bus->number - hose->bus_offset) << 16) | 0x80000000);
	out_be32(hose->cfg_addr, addr);
	__asm__ __volatile__("sync");
	*val = (u32) in_le16((u16 *) (hose->cfg_data + (offset & mask)));
	out_be32(hose->cfg_addr, addr & 0x7fffffff);
	__asm__ __volatile__("sync");

	/* Read upper 16 bits */
	offset += 2;
	addr = (((offset & 0xfc) | cfg_type) | (dev->devfn << 8)
	    | ((dev->bus->number - hose->bus_offset) << 16) | 0x80000000);
	out_be32(hose->cfg_addr, addr);
	__asm__ __volatile__("sync");
	tmpv = (u32) in_le16((u16 *) (hose->cfg_data + (offset & mask)));
	out_be32(hose->cfg_addr, addr & 0x7fffffff);
	__asm__ __volatile__("sync");

	/* combine results into dword value */
	*val = (tmpv << 16) | *val;

	return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************/
struct pci_ops alaska_pci_ops =
{
	.read_byte	= alaska_pci_read_config_byte,
	.read_word	= alaska_pci_read_config_word,
	.read_dword	= alaska_pci_read_config_dword,
	.write_byte	= alaska_pci_write_config_byte,
	.write_word	= alaska_pci_write_config_word,
	.write_dword	= alaska_pci_write_config_dword
};


/*****************************************************************************/
void __init setup_alaska_pci(struct pci_controller *hose, u32 cfg_addr, u32 cfg_data)
{
	hose->ops = &alaska_pci_ops;
	hose->cfg_addr = (volatile u32 *) ioremap(cfg_addr, 4);
	hose->cfg_data = (volatile unsigned char *) ioremap(cfg_data, 4);
}

/*****************************************************************************/
void alaska_pci_init(void)
{
	struct mpc8220_xipci *xipci;
	u32 win0, win1, win2;
	xipci = (struct mpc8220_xipci *) ALASKA_PCI_REG_MMAP;

	win0 = (u32) ALASKA_PCI_SPACE_MEM;
	win1 = (u32) ALASKA_PCI_SPACE_IO;
	win2 = (u32) ALASKA_PCI_SPACE_CFG;

	/* assert pci reset */
	out_be32 (&xipci->glb_stat_ctl, 0x00000001);
	out_be32 (&xipci->target_ctrl, 0x00000000);	/* disable prefetching but read-multiples will still prefetch */

	printk (KERN_INFO "XLB_CONFIG = %08x\n", in_be32((volatile u32 *)MPC_XLB_CONFIG));
	/* Set the XLB arbiter master priority enable register (MBAR + 0x264) */
	out_be32((volatile u32 *)MPC_XLB_PRIEN, 0x000000ff);
	printk(KERN_ERR "Alaska XLB: Arbiter Master Priority Enable Register = %x\n",
		in_be32((volatile u32 *)MPC_XLB_PRIEN));
	/* Set all XLB masters to the same priority (MBAR + 0x268) */
	/* PCI is required to be atleast the same priority as the CPU */
	out_be32((volatile unsigned *)MPC_XLB_PRI, 0x00000000);
	udelay(300);
	printk(KERN_INFO "Alaska XLB: Arbiter Master Priority Register = %x\n",
		in_be32((volatile unsigned *)MPC_XLB_PRI));

/*********************************************************/
	/*
	 * Increase PCI Mem space for Video Path H/W
	 * Mfaraoni 1/17/05
	 */
	/* Looks for size mask in second byte = (0x[03]ffffff >> 1B) */
	// Uncomment size needed, must conform to alaska_pci.h
	//out_be32 (&xipci->init_win0,  (win0 >> 16) | win0 | 0x00030000);   // 64MB
	//out_be32 (&xipci->init_win0,  (win0 >> 16) | win0 | 0x000f0000);   // 256MB
	//out_be32 (&xipci->init_win0,  (win0 >> 16) | win0 | 0x001f0000);   // 512MB
	out_be32 (&xipci->init_win0,  (win0 >> 16) | win0 | 0x003f0000);   // 1GB
/*********************************************************/
	out_be32 (&xipci->init_win1, ((win1 >> 16) | win1));
	out_be32 (&xipci->init_win2, ((win2 >> 16) | win2));

	out_be32 (&xipci->init_win_cfg, 0x01090900);
	out_be32 (&xipci->init_ctrl, 0x00000000);


	/* enable bus master and mem access - mac_ */
	out_be32 (&xipci->stat_cmd_reg, 0x00000006);

	/* cache line size is ? - mac_*/
	out_be32 (&xipci->bist_htyp_lat_cshl, (in_be32(&xipci->bist_htyp_lat_cshl) | 0x0000f802));

	out_be32 (&xipci->base0, 0x40000000);  /* 256KB - starting at 1GB- mac_*/
	out_be32 (&xipci->base1, 0x00000000);  /* 1GB - starting at 0x00000000 - mac_*/

	out_be32 (&xipci->target_bar0, 0xf0000001); /* mbar space */
	out_be32 (&xipci->target_bar1, 0x00000001);

	/* Deassert reset bit */
	out_be32 (&xipci->glb_stat_ctl, 0x00000000);

	printk(KERN_INFO "Alaska FPGA PIC: Interrupt Enable Register = %x\n",
		in_be32((volatile unsigned *)FPGA_INT_ENABLE));
	printk(KERN_INFO "Alaska FPGA PIC: Interrupt Status Register = %x\n",
		in_be32((volatile unsigned *)FPGA_INT_STATUS));

	/* Enable PCI bus master support */
	/* Set PCIGNT1, PCIREQ1, PCIREQ0/PCIGNTIN, PCIGNT0/PCIREQOUT, PCIREQ2, PCIGNT2 */
	out_be32((volatile u32 *)MPC8220_PCFG3,
		(in_be32((volatile u32 *)MPC8220_PCFG3) & 0xFC3FCE7F));
	out_be32((volatile u32 *)MPC8220_PCFG3,
		(in_be32((volatile u32 *)MPC8220_PCFG3) | 0x01400180));

	/* Set FPGA bits 9:8 to 0b01 in USB status and control register for PCI slot 2 */
	out_be32((volatile u32*)FPGA_USB_CONTROL,
		(in_be32((volatile u32*)FPGA_USB_CONTROL) | 0x00100100));
}

/*****************************************************************************/
struct pci_dev *fake_pci_dev1(struct pci_controller *hose, int busnr, int devfn)
{
	static struct pci_dev dev;
	static struct pci_bus bus;

	if (hose == 0) {
		hose = pci_bus_to_hose(busnr);
		if (hose == 0)
		{
			printk(KERN_ERR "Can't find hose for PCI bus %d!\n", busnr);
		}
	}

	dev.bus = &bus;
	dev.sysdata = hose;
	dev.devfn = devfn;
	bus.number = busnr;
	bus.ops = hose->ops;

	return &dev;
}

/*****************************************************************************/
void alaska_find_bridges(void)
{
	struct pci_controller *hose;
	struct pci_dev *dev;

	pci_dram_offset = 0;

	/* Allocate memory for pci controller, aka hose */
	hose = pcibios_alloc_controller();
	if (!hose) {
		printk(KERN_WARNING "PCI: System does not support PCI\n");
		return;
	}

	/* Initialize PCI Windows and Registers */
	alaska_pci_init();

	/* Check if we get the hose right */
	setup_alaska_pci(hose, ALASKA_PCI_CFG_ADDRESS, ALASKA_PCI_CFG_DATA);
	printk(KERN_INFO "Setup Alaska PCI Controller:\nHose = 0x%08x, hose->cfg_addr=%p, hose->cfg_data=%p\n",
		(int)hose, hose->cfg_addr,  hose->cfg_data);

	dev = fake_pci_dev1(hose, 0, 0x80);

	/* Host alaska pci controller's device number is 16 */
	hose->first_busno = 0x00;
	hose->last_busno = 0xff;
	hose->pci_mem_offset = ALASKA_PCI_OFF_MEM_PHY;

	/* Setup bridge memory/IO ranges & resources */
	hose->io_space.start = ALASKA_PCI_IO_LOWER;
	hose->io_space.end = ALASKA_PCI_IO_UPPER;
	hose->mem_space.start = ALASKA_PCI_MEM_LOWER;
	hose->mem_space.end = ALASKA_PCI_MEM_UPPER;
	hose->io_base_phys = ALASKA_PCI_IO_PHY_START;

	/* Both get the same 81m */
	printk(KERN_INFO "Hose IO Base Physical = 0x%08x\n", (int)hose->io_base_phys);
	hose->io_base_virt = ioremap(hose->io_base_phys, 0x10000);
	printk(KERN_INFO "Hose IO Base Virtual = 0x%08x\n", (int)hose->io_base_virt);

	isa_io_base = (unsigned long)hose->io_base_virt;

	hose->io_resource.start = ALASKA_PCI_IO_LOWER;
	hose->io_resource.end = ALASKA_PCI_IO_UPPER;
	hose->io_resource.flags = IORESOURCE_IO;
	hose->io_resource.name = "PCI I/O";
	hose->mem_resources[0].start = ALASKA_PCI_MEM_LOWER;
	hose->mem_resources[0].end = ALASKA_PCI_MEM_UPPER;
	hose->mem_resources[0].flags = IORESOURCE_MEM;
	hose->mem_resources[0].name = "PCI Memory";

	/* Auto bus scan only finds one bus */
	ppc_md.pci_exclude_device = alaska_exclude_device;
	hose->last_busno = pciauto_bus_scan(hose, hose->first_busno);

	ppc_md.pcibios_fixup = NULL;
	ppc_md.pcibios_fixup_resources = NULL;
	ppc_md.pci_swizzle = common_swizzle;
	ppc_md.pci_map_irq = alaska_map_irq;
}

/*****************************************************************************/
int alaska_exclude_device(unsigned char bus, unsigned char devfn)
{
	/* We prevent us from seeing ourselves to avoid having
	 * the kernel try to remap our BAR #1 and mess up bus
	 * master from external PCI devices
	 */
	if ((bus == 0) && (PCI_SLOT(devfn) == ALASKA_HOST_BRIDGE_IDSEL))
		return PCIBIOS_DEVICE_NOT_FOUND;
	else
		return PCIBIOS_SUCCESSFUL;
}

/*****************************************************************************/
int alaska_map_irq(struct pci_dev *dev, unsigned char idsel, unsigned char pin)
{
	/*
	 *	PCI IDSEL/INTPIN->INTLINE
	 * 	   A | B | C | D
	 */
	static char pci_irq_table[][4] =
	{
		/* Alaska FPGA Interrupts */
		{ ALASKA_FPGA_INT__PCI1, ALASKA_FPGA_INT__PCI1, ALASKA_FPGA_INT__PCI1, ALASKA_FPGA_INT__PCI1 },     /* IDSEL 17 - PCI slot 1 */
		{ ALASKA_FPGA_INT__PCI2, ALASKA_FPGA_INT__PCI2, ALASKA_FPGA_INT__PCI2, ALASKA_FPGA_INT__PCI2 },     /* IDSEL 18 - PCI slot 2 */
		{ ALASKA_FPGA_INT__PCI3, ALASKA_FPGA_INT__PCI3, ALASKA_FPGA_INT__PCI3, ALASKA_FPGA_INT__PCI3 },     /* IDSEL 19 - PCI slot 3 */
		{ ALASKA_FPGA_INT__PCI4, ALASKA_FPGA_INT__PCI4, ALASKA_FPGA_INT__PCI4, ALASKA_FPGA_INT__PCI4 },     /* IDSEL 20 - PCI slot 4 */
	};

	const long min_idsel = 17, max_idsel = 20, irqs_per_slot = 4;

	return PCI_IRQ_TABLE_LOOKUP;
}

