diff options
Diffstat (limited to 'drivers/ssb/driver_pcicore.c')
-rw-r--r-- | drivers/ssb/driver_pcicore.c | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/drivers/ssb/driver_pcicore.c b/drivers/ssb/driver_pcicore.c new file mode 100644 index 0000000..2faaa90 --- /dev/null +++ b/drivers/ssb/driver_pcicore.c @@ -0,0 +1,576 @@ +/* + * Sonics Silicon Backplane + * Broadcom PCI-core driver + * + * Copyright 2005, Broadcom Corporation + * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de> + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include <linux/ssb/ssb.h> +#include <linux/pci.h> +#include <linux/delay.h> + +#include "ssb_private.h" + + +static inline +u32 pcicore_read32(struct ssb_pcicore *pc, u16 offset) +{ + return ssb_read32(pc->dev, offset); +} + +static inline +void pcicore_write32(struct ssb_pcicore *pc, u16 offset, u32 value) +{ + ssb_write32(pc->dev, offset, value); +} + +/************************************************** + * Code for hostmode operation. + **************************************************/ + +#ifdef CONFIG_SSB_PCICORE_HOSTMODE + +#include <asm/paccess.h> +/* Probe a 32bit value on the bus and catch bus exceptions. + * Returns nonzero on a bus exception. + * This is MIPS specific */ +#define mips_busprobe32(val, addr) get_dbe((val), ((u32 *)(addr))) + +/* Assume one-hot slot wiring */ +#define SSB_PCI_SLOT_MAX 16 + +/* Global lock is OK, as we won't have more than one extpci anyway. */ +static DEFINE_SPINLOCK(cfgspace_lock); +/* Core to access the external PCI config space. Can only have one. */ +static struct ssb_pcicore *extpci_core; + +static u32 ssb_pcicore_pcibus_iobase = 0x100; +static u32 ssb_pcicore_pcibus_membase = SSB_PCI_DMA; + +int pcibios_plat_dev_init(struct pci_dev *d) +{ + struct resource *res; + int pos, size; + u32 *base; + + ssb_printk(KERN_INFO "PCI: Fixing up device %s\n", + pci_name(d)); + + /* Fix up resource bases */ + for (pos = 0; pos < 6; pos++) { + res = &d->resource[pos]; + if (res->flags & IORESOURCE_IO) + base = &ssb_pcicore_pcibus_iobase; + else + base = &ssb_pcicore_pcibus_membase; + if (res->end) { + size = res->end - res->start + 1; + if (*base & (size - 1)) + *base = (*base + size) & ~(size - 1); + res->start = *base; + res->end = res->start + size - 1; + *base += size; + pci_write_config_dword(d, PCI_BASE_ADDRESS_0 + (pos << 2), res->start); + } + /* Fix up PCI bridge BAR0 only */ + if (d->bus->number == 0 && PCI_SLOT(d->devfn) == 0) + break; + } + /* Fix up interrupt lines */ + d->irq = ssb_mips_irq(extpci_core->dev) + 2; + pci_write_config_byte(d, PCI_INTERRUPT_LINE, d->irq); + + return 0; +} + +static void __init ssb_fixup_pcibridge(struct pci_dev *dev) +{ + if (dev->bus->number != 0 || PCI_SLOT(dev->devfn) != 0) + return; + + ssb_printk(KERN_INFO "PCI: fixing up bridge\n"); + + /* Enable PCI bridge bus mastering and memory space */ + pci_set_master(dev); + pcibios_enable_device(dev, ~0); + + /* Enable PCI bridge BAR1 prefetch and burst */ + pci_write_config_dword(dev, SSB_BAR1_CONTROL, 3); + + /* Make sure our latency is high enough to handle the devices behind us */ + pci_write_config_byte(dev, PCI_LATENCY_TIMER, 0xa8); +} +DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, ssb_fixup_pcibridge); + +int __init pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + return ssb_mips_irq(extpci_core->dev) + 2; +} + +static u32 get_cfgspace_addr(struct ssb_pcicore *pc, + unsigned int bus, unsigned int dev, + unsigned int func, unsigned int off) +{ + u32 addr = 0; + u32 tmp; + + if (unlikely(pc->cardbusmode && dev > 1)) + goto out; + if (bus == 0) { + /* Type 0 transaction */ + if (unlikely(dev >= SSB_PCI_SLOT_MAX)) + goto out; + /* Slide the window */ + tmp = SSB_PCICORE_SBTOPCI_CFG0; + tmp |= ((1 << (dev + 16)) & SSB_PCICORE_SBTOPCI1_MASK); + pcicore_write32(pc, SSB_PCICORE_SBTOPCI1, tmp); + /* Calculate the address */ + addr = SSB_PCI_CFG; + addr |= ((1 << (dev + 16)) & ~SSB_PCICORE_SBTOPCI1_MASK); + addr |= (func << 8); + addr |= (off & ~3); + } else { + /* Type 1 transaction */ + pcicore_write32(pc, SSB_PCICORE_SBTOPCI1, + SSB_PCICORE_SBTOPCI_CFG1); + /* Calculate the address */ + addr = SSB_PCI_CFG; + addr |= (bus << 16); + addr |= (dev << 11); + addr |= (func << 8); + addr |= (off & ~3); + } +out: + return addr; +} + +static int ssb_extpci_read_config(struct ssb_pcicore *pc, + unsigned int bus, unsigned int dev, + unsigned int func, unsigned int off, + void *buf, int len) +{ + int err = -EINVAL; + u32 addr, val; + void __iomem *mmio; + + SSB_WARN_ON(!pc->hostmode); + if (unlikely(len != 1 && len != 2 && len != 4)) + goto out; + addr = get_cfgspace_addr(pc, bus, dev, func, off); + if (unlikely(!addr)) + goto out; + err = -ENOMEM; + mmio = ioremap_nocache(addr, len); + if (!mmio) + goto out; + + if (mips_busprobe32(val, mmio)) { + val = 0xffffffff; + goto unmap; + } + + val = readl(mmio); + val >>= (8 * (off & 3)); + + switch (len) { + case 1: + *((u8 *)buf) = (u8)val; + break; + case 2: + *((u16 *)buf) = (u16)val; + break; + case 4: + *((u32 *)buf) = (u32)val; + break; + } + err = 0; +unmap: + iounmap(mmio); +out: + return err; +} + +static int ssb_extpci_write_config(struct ssb_pcicore *pc, + unsigned int bus, unsigned int dev, + unsigned int func, unsigned int off, + const void *buf, int len) +{ + int err = -EINVAL; + u32 addr, val = 0; + void __iomem *mmio; + + SSB_WARN_ON(!pc->hostmode); + if (unlikely(len != 1 && len != 2 && len != 4)) + goto out; + addr = get_cfgspace_addr(pc, bus, dev, func, off); + if (unlikely(!addr)) + goto out; + err = -ENOMEM; + mmio = ioremap_nocache(addr, len); + if (!mmio) + goto out; + + if (mips_busprobe32(val, mmio)) { + val = 0xffffffff; + goto unmap; + } + + switch (len) { + case 1: + val = readl(mmio); + val &= ~(0xFF << (8 * (off & 3))); + val |= *((const u8 *)buf) << (8 * (off & 3)); + break; + case 2: + val = readl(mmio); + val &= ~(0xFFFF << (8 * (off & 3))); + val |= *((const u16 *)buf) << (8 * (off & 3)); + break; + case 4: + val = *((const u32 *)buf); + break; + } + writel(val, mmio); + + err = 0; +unmap: + iounmap(mmio); +out: + return err; +} + +static int ssb_pcicore_read_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 *val) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&cfgspace_lock, flags); + err = ssb_extpci_read_config(extpci_core, bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), reg, val, size); + spin_unlock_irqrestore(&cfgspace_lock, flags); + + return err ? PCIBIOS_DEVICE_NOT_FOUND : PCIBIOS_SUCCESSFUL; +} + +static int ssb_pcicore_write_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 val) +{ + unsigned long flags; + int err; + + spin_lock_irqsave(&cfgspace_lock, flags); + err = ssb_extpci_write_config(extpci_core, bus->number, PCI_SLOT(devfn), + PCI_FUNC(devfn), reg, &val, size); + spin_unlock_irqrestore(&cfgspace_lock, flags); + + return err ? PCIBIOS_DEVICE_NOT_FOUND : PCIBIOS_SUCCESSFUL; +} + +static struct pci_ops ssb_pcicore_pciops = { + .read = ssb_pcicore_read_config, + .write = ssb_pcicore_write_config, +}; + +static struct resource ssb_pcicore_mem_resource = { + .name = "SSB PCIcore external memory", + .start = SSB_PCI_DMA, + .end = SSB_PCI_DMA + SSB_PCI_DMA_SZ - 1, + .flags = IORESOURCE_MEM, +}; + +static struct resource ssb_pcicore_io_resource = { + .name = "SSB PCIcore external I/O", + .start = 0x100, + .end = 0x7FF, + .flags = IORESOURCE_IO, +}; + +static struct pci_controller ssb_pcicore_controller = { + .pci_ops = &ssb_pcicore_pciops, + .io_resource = &ssb_pcicore_io_resource, + .mem_resource = &ssb_pcicore_mem_resource, + .mem_offset = 0x24000000, +}; + +static void ssb_pcicore_init_hostmode(struct ssb_pcicore *pc) +{ + u32 val; + + if (WARN_ON(extpci_core)) + return; + extpci_core = pc; + + ssb_dprintk(KERN_INFO PFX "PCIcore in host mode found\n"); + /* Reset devices on the external PCI bus */ + val = SSB_PCICORE_CTL_RST_OE; + val |= SSB_PCICORE_CTL_CLK_OE; + pcicore_write32(pc, SSB_PCICORE_CTL, val); + val |= SSB_PCICORE_CTL_CLK; /* Clock on */ + pcicore_write32(pc, SSB_PCICORE_CTL, val); + udelay(150); /* Assertion time demanded by the PCI standard */ + val |= SSB_PCICORE_CTL_RST; /* Deassert RST# */ + pcicore_write32(pc, SSB_PCICORE_CTL, val); + val = SSB_PCICORE_ARBCTL_INTERN; + pcicore_write32(pc, SSB_PCICORE_ARBCTL, val); + udelay(1); /* Assertion time demanded by the PCI standard */ + + /*TODO cardbus mode */ + + /* 64MB I/O window */ + pcicore_write32(pc, SSB_PCICORE_SBTOPCI0, + SSB_PCICORE_SBTOPCI_IO); + /* 64MB config space */ + pcicore_write32(pc, SSB_PCICORE_SBTOPCI1, + SSB_PCICORE_SBTOPCI_CFG0); + /* 1GB memory window */ + pcicore_write32(pc, SSB_PCICORE_SBTOPCI2, + SSB_PCICORE_SBTOPCI_MEM | SSB_PCI_DMA); + + /* Enable PCI bridge BAR0 prefetch and burst */ + val = PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + ssb_extpci_write_config(pc, 0, 0, 0, PCI_COMMAND, &val, 2); + /* Clear error conditions */ + val = 0; + ssb_extpci_write_config(pc, 0, 0, 0, PCI_STATUS, &val, 2); + + /* Enable PCI interrupts */ + pcicore_write32(pc, SSB_PCICORE_IMASK, + SSB_PCICORE_IMASK_INTA); + + /* Ok, ready to run, register it to the system. + * The following needs change, if we want to port hostmode + * to non-MIPS platform. */ + set_io_port_base((unsigned long)ioremap_nocache(SSB_PCI_MEM, 0x04000000)); + /* Give some time to the PCI controller to configure itself with the new + * values. Not waiting at this point causes crashes of the machine. */ + mdelay(10); + register_pci_controller(&ssb_pcicore_controller); +} + +static int pcicore_is_in_hostmode(struct ssb_pcicore *pc) +{ + struct ssb_bus *bus = pc->dev->bus; + u16 chipid_top; + u32 tmp; + + chipid_top = (bus->chip_id & 0xFF00); + if (chipid_top != 0x4700 && + chipid_top != 0x5300) + return 0; + + if (bus->sprom.r1.boardflags_lo & SSB_PCICORE_BFL_NOPCI) + return 0; + + /* The 200-pin BCM4712 package does not bond out PCI. Even when + * PCI is bonded out, some boards may leave the pins floating. */ + if (bus->chip_id == 0x4712) { + if (bus->chip_package == SSB_CHIPPACK_BCM4712S) + return 0; + if (bus->chip_package == SSB_CHIPPACK_BCM4712M) + return 0; + } + if (bus->chip_id == 0x5350) + return 0; + + return !mips_busprobe32(tmp, (bus->mmio + (pc->dev->core_index * SSB_CORE_SIZE))); +} +#endif /* CONFIG_SSB_PCICORE_HOSTMODE */ + + +/************************************************** + * Generic and Clientmode operation code. + **************************************************/ + +static void ssb_pcicore_init_clientmode(struct ssb_pcicore *pc) +{ + /* Disable PCI interrupts. */ + ssb_write32(pc->dev, SSB_INTVEC, 0); +} + +void ssb_pcicore_init(struct ssb_pcicore *pc) +{ + struct ssb_device *dev = pc->dev; + struct ssb_bus *bus; + + if (!dev) + return; + bus = dev->bus; + if (!ssb_device_is_enabled(dev)) + ssb_device_enable(dev, 0); + +#ifdef CONFIG_SSB_PCICORE_HOSTMODE + pc->hostmode = pcicore_is_in_hostmode(pc); + if (pc->hostmode) + ssb_pcicore_init_hostmode(pc); +#endif /* CONFIG_SSB_PCICORE_HOSTMODE */ + if (!pc->hostmode) + ssb_pcicore_init_clientmode(pc); +} + +static u32 ssb_pcie_read(struct ssb_pcicore *pc, u32 address) +{ + pcicore_write32(pc, 0x130, address); + return pcicore_read32(pc, 0x134); +} + +static void ssb_pcie_write(struct ssb_pcicore *pc, u32 address, u32 data) +{ + pcicore_write32(pc, 0x130, address); + pcicore_write32(pc, 0x134, data); +} + +static void ssb_pcie_mdio_write(struct ssb_pcicore *pc, u8 device, + u8 address, u16 data) +{ + const u16 mdio_control = 0x128; + const u16 mdio_data = 0x12C; + u32 v; + int i; + + v = 0x80; /* Enable Preamble Sequence */ + v |= 0x2; /* MDIO Clock Divisor */ + pcicore_write32(pc, mdio_control, v); + + v = (1 << 30); /* Start of Transaction */ + v |= (1 << 28); /* Write Transaction */ + v |= (1 << 17); /* Turnaround */ + v |= (u32)device << 22; + v |= (u32)address << 18; + v |= data; + pcicore_write32(pc, mdio_data, v); + /* Wait for the device to complete the transaction */ + udelay(10); + for (i = 0; i < 10; i++) { + v = pcicore_read32(pc, mdio_control); + if (v & 0x100 /* Trans complete */) + break; + msleep(1); + } + pcicore_write32(pc, mdio_control, 0); +} + +static void ssb_broadcast_value(struct ssb_device *dev, + u32 address, u32 data) +{ + /* This is used for both, PCI and ChipCommon core, so be careful. */ + BUILD_BUG_ON(SSB_PCICORE_BCAST_ADDR != SSB_CHIPCO_BCAST_ADDR); + BUILD_BUG_ON(SSB_PCICORE_BCAST_DATA != SSB_CHIPCO_BCAST_DATA); + + ssb_write32(dev, SSB_PCICORE_BCAST_ADDR, address); + ssb_read32(dev, SSB_PCICORE_BCAST_ADDR); /* flush */ + ssb_write32(dev, SSB_PCICORE_BCAST_DATA, data); + ssb_read32(dev, SSB_PCICORE_BCAST_DATA); /* flush */ +} + +static void ssb_commit_settings(struct ssb_bus *bus) +{ + struct ssb_device *dev; + + dev = bus->chipco.dev ? bus->chipco.dev : bus->pcicore.dev; + if (WARN_ON(!dev)) + return; + /* This forces an update of the cached registers. */ + ssb_broadcast_value(dev, 0xFD8, 0); +} + +int ssb_pcicore_dev_irqvecs_enable(struct ssb_pcicore *pc, + struct ssb_device *dev) +{ + struct ssb_device *pdev = pc->dev; + struct ssb_bus *bus; + int err = 0; + u32 tmp; + + might_sleep(); + + if (!pdev) + goto out; + bus = pdev->bus; + + /* Enable interrupts for this device. */ + if (bus->host_pci && + ((pdev->id.revision >= 6) || (pdev->id.coreid == SSB_DEV_PCIE))) { + u32 coremask; + + /* Calculate the "coremask" for the device. */ + coremask = (1 << dev->core_index); + + err = pci_read_config_dword(bus->host_pci, SSB_PCI_IRQMASK, &tmp); + if (err) + goto out; + tmp |= coremask << 8; + err = pci_write_config_dword(bus->host_pci, SSB_PCI_IRQMASK, tmp); + if (err) + goto out; + } else { + u32 intvec; + + intvec = ssb_read32(pdev, SSB_INTVEC); + if ((bus->chip_id & 0xFF00) == 0x4400) { + /* Workaround: On the BCM44XX the BPFLAG routing + * bit is wrong. Use a hardcoded constant. */ + intvec |= 0x00000002; + } else { + tmp = ssb_read32(dev, SSB_TPSFLAG); + tmp &= SSB_TPSFLAG_BPFLAG; + intvec |= tmp; + } + ssb_write32(pdev, SSB_INTVEC, intvec); + } + + /* Setup PCIcore operation. */ + if (pc->setup_done) + goto out; + if (pdev->id.coreid == SSB_DEV_PCI) { + tmp = pcicore_read32(pc, SSB_PCICORE_SBTOPCI2); + tmp |= SSB_PCICORE_SBTOPCI_PREF; + tmp |= SSB_PCICORE_SBTOPCI_BURST; + pcicore_write32(pc, SSB_PCICORE_SBTOPCI2, tmp); + + if (pdev->id.revision < 5) { + tmp = ssb_read32(pdev, SSB_IMCFGLO); + tmp &= ~SSB_IMCFGLO_SERTO; + tmp |= 2; + tmp &= ~SSB_IMCFGLO_REQTO; + tmp |= 3 << SSB_IMCFGLO_REQTO_SHIFT; + ssb_write32(pdev, SSB_IMCFGLO, tmp); + ssb_commit_settings(bus); + } else if (pdev->id.revision >= 11) { + tmp = pcicore_read32(pc, SSB_PCICORE_SBTOPCI2); + tmp |= SSB_PCICORE_SBTOPCI_MRM; + pcicore_write32(pc, SSB_PCICORE_SBTOPCI2, tmp); + } + } else { + WARN_ON(pdev->id.coreid != SSB_DEV_PCIE); + //TODO: Better make defines for all these magic PCIE values. + if ((pdev->id.revision == 0) || (pdev->id.revision == 1)) { + /* TLP Workaround register. */ + tmp = ssb_pcie_read(pc, 0x4); + tmp |= 0x8; + ssb_pcie_write(pc, 0x4, tmp); + } + if (pdev->id.revision == 0) { + const u8 serdes_rx_device = 0x1F; + + ssb_pcie_mdio_write(pc, serdes_rx_device, + 2 /* Timer */, 0x8128); + ssb_pcie_mdio_write(pc, serdes_rx_device, + 6 /* CDR */, 0x0100); + ssb_pcie_mdio_write(pc, serdes_rx_device, + 7 /* CDR BW */, 0x1466); + } else if (pdev->id.revision == 1) { + /* DLLP Link Control register. */ + tmp = ssb_pcie_read(pc, 0x100); + tmp |= 0x40; + ssb_pcie_write(pc, 0x100, tmp); + } + } + pc->setup_done = 1; +out: + return err; +} +EXPORT_SYMBOL(ssb_pcicore_dev_irqvecs_enable); |