/*
 * PCI code for BCM7020-based TiVo devices
 *
 * Copyright (C) 2000 TiVo Inc. All Rights Reserved.
 */

#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <asm/bcm7020.h>

extern int fpgacore_init(void);

#define CF8_BSWAP(x)	cpu_to_le32(x)

static int configure_bus(int busno, int skip, u32 *iop, u32 *memp, int shift);

struct pci_fixup pcibios_fixups[] = {
	{ 0 }
};

// pciconf_lock ensures that nothing else modifies CF8 regs
spinlock_t pciconf_lock = SPIN_LOCK_UNLOCKED;

#define SETUP_PCI_CONFIG(busdevfn) \
	unsigned long flags;	\
	spin_lock_irqsave(&pciconf_lock, flags); \
	*BCM_PCI_CF8 = CF8_BSWAP(0x80000000+(busdevfn << 8)+(where & 0xfc)); \
	barrier();

#define RETURN_FROM_CONFIG	\
	barrier();		\
 	spin_unlock_irqrestore(&pciconf_lock, flags); \
	return PCIBIOS_SUCCESSFUL;

static int read_config_byte(int busdevfn, int where, u8 *value)
{
	SETUP_PCI_CONFIG(busdevfn)
	*value = *(volatile u8*)((long)BCM_PCI_CFC + (where & 3));
	RETURN_FROM_CONFIG
}

static int read_config_word(int busdevfn, int where, u16 *value)
{
	SETUP_PCI_CONFIG(busdevfn)
	*value = le16_to_cpu(*(volatile u16*)((long)BCM_PCI_CFC + (where & 2)));
	RETURN_FROM_CONFIG
}

static int read_config_dword(int busdevfn, int where, u32 *value)
{
	SETUP_PCI_CONFIG(busdevfn)
	*value = le32_to_cpu(*(volatile u32*)BCM_PCI_CFC);
	RETURN_FROM_CONFIG
}

static int write_config_byte(int busdevfn, int where, u8 value)
{
	SETUP_PCI_CONFIG(busdevfn)
	*(volatile u8*)((long)BCM_PCI_CFC + (where & 3)) = value;
	RETURN_FROM_CONFIG
}

static int write_config_word(int busdevfn, int where, u16 value)
{
	SETUP_PCI_CONFIG(busdevfn)
	*(volatile u16*)((long)BCM_PCI_CFC + (where & 2)) = cpu_to_le16(value);
	RETURN_FROM_CONFIG
}

static int write_config_dword(int busdevfn, int where, u32 value)
{
	SETUP_PCI_CONFIG(busdevfn)
	*(volatile u32*)BCM_PCI_CFC = cpu_to_le32(value);
	RETURN_FROM_CONFIG
}

static int pci_bcm_read_config_byte(struct pci_dev *dev, int where, u8 *value)
{
	int busdevfn = (dev->bus->number << 8) | dev->devfn;
	return read_config_byte(busdevfn, where, value);
}

static int pci_bcm_read_config_word(struct pci_dev *dev, int where, u16 *value)
{
	int busdevfn = (dev->bus->number << 8) | dev->devfn;
	return read_config_word(busdevfn, where, value);
}

static int pci_bcm_read_config_dword(struct pci_dev *dev, int where, u32 *value)
{
	int busdevfn = (dev->bus->number << 8) | dev->devfn;
	return read_config_dword(busdevfn, where, value);
}

static int pci_bcm_write_config_byte(struct pci_dev *dev, int where, u8 value)
{
	int busdevfn = (dev->bus->number << 8) | dev->devfn;
	return write_config_byte(busdevfn, where, value);
}

static int pci_bcm_write_config_word(struct pci_dev *dev, int where, u16 value)
{
	int busdevfn = (dev->bus->number << 8) | dev->devfn;
	return write_config_word(busdevfn, where, value);
}

static int pci_bcm_write_config_dword(struct pci_dev *dev, int where, u32 value)
{
	int busdevfn = (dev->bus->number << 8) | dev->devfn;
	return write_config_dword(busdevfn, where, value);
}

static struct pci_ops bcm_pci_conf = {
        pci_bcm_read_config_byte,
        pci_bcm_read_config_word,
        pci_bcm_read_config_dword,
        pci_bcm_write_config_byte,
        pci_bcm_write_config_word,
        pci_bcm_write_config_dword
};

void __init pcibios_fixup_bus(struct pci_bus *b)
{
}

/*
 * Note that IDSEL on BCM7020 maps device# = IDSEL line - 16
 *
 * On P0, SLOT0 is device 11, SLOT1 is device 10, IDE is device 12
 */
static inline int __init map_irq(int devfn, u8 pin, int shift)
{
	switch (PCI_SLOT(devfn)) {
	case 15: /* ENC */
		return BCM_IRQ_PCI_A0;
	case 14: /* MS */
		return CPU_IRQ_INT0;
	case 13: /* USB */
		switch (pin) {
		default:
		case 1:
			return CPU_IRQ_INT3;
		case 2:
			return CPU_IRQ_INT1;
		case 3:
			return CPU_IRQ_INT4;
		}
	}
	return ((16 - (devfn >> 3) + pin + shift) & 3) + BCM_IRQ_PCI_A0;
}

/*
 * The boot firmware only does minimal configuration of the PCI bus,
 * so we completely redo it.  IO addresses are assigned starting at 0,
 * but skipping the first 0x1000, just in case there is an ISA bus
 * lurking about.
 *
 * This is not a very efficieant allocation scheme, and if PCI IO or
 * memory space starts to become scarce, it could be rewritten to
 * allocate address in size order.
 */
#define MIN_PCI_MEM_ALLOC BCM_PCIMEM_PHYS
#define MAX_PCI_MEM_ALLOC (BCM_PCIMEM_PHYS + BCM_PCIMEM_SIZE - 1)
#define MIN_PCI_IO_ALLOC 0x1000
#define MAX_PCI_IO_ALLOC (BCM_PCIIO_SIZE - 1)

static void do_config_device(int busdevfn, int maxbar, u32 *iop, u32 *memp)
{
	u16 command = PCI_COMMAND_FAST_BACK;
	int i;

	for (i = 0; i < maxbar; i++) {
		int reg = PCI_BASE_ADDRESS_0 + i * 4;
		u32 size, aligned;

		write_config_dword(busdevfn, reg, 0xffffffff);
		read_config_dword(busdevfn, reg, &size);
		// if the top bit is not set, this BAR is either 0 (no region
		// requested), or hardcoded, so skip it
		if (!(size & 0x80000000) || size == 0xffffffff)
			continue;
		if ((size & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY) {
			size = ~(size & PCI_BASE_ADDRESS_MEM_MASK);
			aligned = (*memp + size) & ~size;
			// hopefully, drivers will know 0 is not a valid
			// base address (as per PCI spec)...
			if (size + aligned > MAX_PCI_MEM_ALLOC) {
				printk(KERN_ERR "PCI: Unable to allocate "
				       "memory space to device %d on bus %d\n",
				       busdevfn & 0xff, busdevfn >> 8);
				write_config_dword(busdevfn, reg, 0);
				continue;
			}
			command |= PCI_COMMAND_MEMORY;
			write_config_dword(busdevfn, reg, aligned);
			*memp = aligned + size + 1;
		} else {
			size = ~(size & PCI_BASE_ADDRESS_IO_MASK);
			aligned = (*iop + size) & ~size;
			// same as above, but for IO...
			if (size + aligned > MAX_PCI_IO_ALLOC) {
				printk(KERN_ERR "PCI: Unable to allocate "
				       "IO space to device %d on bus %d\n",
				       busdevfn & 0xff, busdevfn >> 8);
				write_config_dword(busdevfn, reg, 0);
				continue;
			}
			command |= PCI_COMMAND_IO;
			write_config_dword(busdevfn, reg, aligned);
			*iop = aligned + size + 1;
		}
	}
	write_config_byte(busdevfn, PCI_CACHE_LINE_SIZE, 0);
	// XXX: this needs to be computed for real:
	write_config_byte(busdevfn, PCI_LATENCY_TIMER, 64);
	write_config_word(busdevfn, PCI_COMMAND, command);
}

#define ALIGN(x,a) ((x + a - 1) & ~(a - 1))

static inline void do_config_bridge(int busdevfn, int shift, u32 *iop, u32 *memp, int *subbus)
{
	u32 oldio, oldmem, oldaio, oldamem;
	u16 command = PCI_COMMAND_MASTER | PCI_COMMAND_FAST_BACK;

	write_config_byte(busdevfn, PCI_PRIMARY_BUS, busdevfn >> 16);
	write_config_byte(busdevfn, PCI_SECONDARY_BUS, *subbus);
	write_config_byte(busdevfn, PCI_SUBORDINATE_BUS, *subbus);
	oldio = *iop;
	oldmem = *memp;
	// PCI-PCI bridges need IO address ranges aligned to 4K
	oldaio = *iop = ALIGN(*iop, 0x1000);
	//  and memory address ranges aligned to 1M
	oldamem = *memp = ALIGN(*memp, 0x100000);
	*subbus = configure_bus(*subbus, (shift + (busdevfn >> 3)) & 3 , iop, memp, 0);
	write_config_byte(busdevfn, PCI_SUBORDINATE_BUS, *subbus);
	write_config_dword(busdevfn, PCI_IO_BASE_UPPER16, 0);
	// if no IO address space allocated, disable IO and forget alignment
	if (*iop == oldaio) {
		write_config_word(busdevfn, PCI_IO_BASE, 0x00ff);
		*iop = oldio;
	} else {
		*iop = ALIGN(*iop, 0x1000);
		write_config_byte(busdevfn, PCI_IO_BASE, (oldaio >> 8) & 0xf0);
		write_config_byte(busdevfn, PCI_IO_LIMIT, ((*iop - 1) >> 8) & 0xf0);
		write_config_word(busdevfn, PCI_IO_BASE_UPPER16, oldaio >> 16);
		write_config_word(busdevfn, PCI_IO_LIMIT_UPPER16, (*iop - 1) >> 16);
		command |= PCI_COMMAND_IO;
	}
	// same as above for PCI memory
	if (*memp == oldamem) {
		write_config_dword(busdevfn, PCI_MEMORY_BASE, 0x0000ffff);
		*memp = oldmem;
	} else {
		*memp = ALIGN(*memp, 0x100000);
		write_config_word(busdevfn, PCI_MEMORY_BASE, (oldamem >> 16) & 0xfff0);
		write_config_word(busdevfn, PCI_MEMORY_LIMIT, ((*memp - 1) >> 16) & 0xfff0);
		command |= PCI_COMMAND_MEMORY;
	}
	// just turn prefetchable memory off
	write_config_dword(busdevfn, PCI_PREF_MEMORY_BASE, 0x0000ffff);
	write_config_dword(busdevfn, PCI_PREF_BASE_UPPER32, 0);
	write_config_dword(busdevfn, PCI_PREF_LIMIT_UPPER32, 0);
	write_config_dword(busdevfn, PCI_ROM_ADDRESS1, 0);
	write_config_byte(busdevfn, PCI_BRIDGE_CONTROL, 0x80);
	write_config_word(busdevfn, PCI_COMMAND, command);
}

static int configure_bus(int busno, int shift, u32 *iop, u32 *memp, int skip)
{
	int devfn, multifn = 0, maxsub = busno;
	u16 vendor;
	u8 hdr_type, irq;

	for (devfn = skip << 3; devfn <= 0xff; devfn++) {
		int busdevfn = (busno << 8) | devfn;

		if (read_config_word(busdevfn, PCI_VENDOR_ID, &vendor))
			continue;
		if (vendor == 0xffff)
			continue;
		if (read_config_byte(busdevfn, PCI_HEADER_TYPE, &hdr_type))
			continue;
		if ((busdevfn & 0x0007) && !multifn)
			continue;
		if (!(busdevfn & 0x0007))
			multifn = (hdr_type & 0x80);
		write_config_word(busdevfn, PCI_COMMAND, 0);
		switch (hdr_type & 0x7f) {
		case PCI_HEADER_TYPE_NORMAL:
			do_config_device(busdevfn, 6, iop, memp);
			write_config_dword(busdevfn, PCI_ROM_ADDRESS, 0);
			break;
		case PCI_HEADER_TYPE_BRIDGE:
			maxsub++;
			do_config_bridge(busdevfn, shift, iop, memp, &maxsub);
			break;
		case PCI_HEADER_TYPE_CARDBUS:
			do_config_device(busdevfn, 1, iop, memp);
			write_config_byte(busdevfn, PCI_CB_PRIMARY_BUS, busno);
			write_config_word(busdevfn, PCI_CB_BRIDGE_CONTROL, 0);
			break;
		default:
			continue;
		}
		read_config_byte(busdevfn, PCI_INTERRUPT_PIN, &irq);
		irq = (irq && irq <= 4) ? map_irq(devfn , irq, shift) : 0xff;
		write_config_byte(busdevfn, PCI_INTERRUPT_LINE, irq);
	}
	return maxsub;
}

static void bcm_pcierr_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	printk(KERN_WARNING "PCI Error detected, IRQ %d @%08lx\n",
	       irq, regs->cp0_epc);
}

/*
 * The generic MIPS code assumes that the physical address of both local
 * memory (RAM) and peripheral memory (PCI memory space) is the same as
 * the bus address.  We accomodate that assumption by setting the BCM
 * registers such that PCI memory space accesses will use upper address
 * bits identical to the upper physical address bits of PCI memory space
 * (this is set up in bcm-setup.c), and that DRAM will be at PCI bus
 * address 0.
 */
void __init pcibios_init(void)
{
	u32 pciio = MIN_PCI_IO_ALLOC, pcimem = MIN_PCI_MEM_ALLOC;

	printk("PCI: Probing PCI hardware\n");

	ioport_resource.end = BCM_PCIIO_SIZE - 1;
	iomem_resource.start = BCM_PCIMEM_PHYS;
	iomem_resource.end = BCM_PCIMEM_PHYS + BCM_PCIMEM_SIZE - 1;

	configure_bus(0, 0, &pciio, &pcimem, 0);
	pci_scan_bus(0, &bcm_pci_conf, NULL);
	request_irq(BCM_IRQ_PCI_PERR, &bcm_pcierr_interrupt, SA_INTERRUPT,
	            "pci error", NULL);
	request_irq(BCM_IRQ_PCI_TABT, &bcm_pcierr_interrupt, SA_INTERRUPT,
	            "pci error", NULL);
	request_irq(BCM_IRQ_PCI_MABT, &bcm_pcierr_interrupt, SA_INTERRUPT,
	            "pci error", NULL);
	request_irq(BCM_IRQ_PCI_SERR, &bcm_pcierr_interrupt, SA_INTERRUPT,
	            "pci error", NULL);
	request_irq(BCM_IRQ_PCI_TPE, &bcm_pcierr_interrupt, SA_INTERRUPT,
		    "pci error", NULL);
	request_irq(BCM_IRQ_PCI_APE, &bcm_pcierr_interrupt, SA_INTERRUPT,
		    "pci error", NULL);

	fpgacore_init();
}

char * __init pcibios_setup(char *str)
{
	return str;
}

int pcibios_enable_device(struct pci_dev *dev, int mask)
{
        // Nothing to do, since we enable all devices at startup.
        return 0;
}

void pcibios_set_master(struct pci_dev *dev)
{
	u16 command;
	unsigned long flags;

	save_and_cli(flags);
	pci_read_config_word(dev, PCI_COMMAND, &command);
	command |= PCI_COMMAND_MASTER;
	pci_write_config_word(dev, PCI_COMMAND, command);
	restore_flags(flags);
}

// This was swiped from arch/alpha
void pcibios_update_resource(struct pci_dev *dev, struct resource *root,
			     struct resource *res, int resource)
{
	int where;
	u32 reg;

	where = PCI_BASE_ADDRESS_0 + (resource * 4);
	reg = (res->start - root->start) | (res->flags & 0xf);
	pci_write_config_dword(dev, where, reg);
	if ((res->flags & (PCI_BASE_ADDRESS_SPACE
			   | PCI_BASE_ADDRESS_MEM_TYPE_MASK))
	    == (PCI_BASE_ADDRESS_SPACE_MEMORY
		| PCI_BASE_ADDRESS_MEM_TYPE_64)) {
		pci_write_config_dword(dev, where+4, 0);
		printk(KERN_WARNING "PCI: dev %s type 64-bit\n", dev->name);
	}
}

void
pcibios_align_resource(void *data, struct resource *res, unsigned long size,
		       unsigned long something)
{
	// No special alignment necessary, since we run on sane hardware
}

unsigned __init int pcibios_assign_all_busses(void)
{
	return 0;
}
