The Sonics Silicon Backplane is a mini-bus used on
various Broadcom chips and embedded devices.
Devices using the SSB include b44, bcm43xx and various
Broadcom based wireless routers.
A b44 and bcm43xx port and a SSB based OHCI driver is available.
Patch #2: This includes a compilation fix for nondebug build (d'oh)
and various small fixes based on comments.
Signed-off-by: Michael Buesch <mb@bu3sch.de>
Acked-by: John Linville <linville@tuxdriver.com>
diff --git a/drivers/Makefile b/drivers/Makefile
index a9e4c5f..2b079ed 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -87,3 +87,4 @@ obj-$(CONFIG_DMA_ENGINE) += dma/
obj-$(CONFIG_HID) += hid/
obj-$(CONFIG_PPC_PS3) += ps3/
obj-$(CONFIG_OF) += of/
+obj-$(CONFIG_SSB) += ssb/
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 3e1c442..7bdae47 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -58,6 +58,8 @@ source "drivers/power/Kconfig"
source "drivers/hwmon/Kconfig"
+source "drivers/ssb/Kconfig"
+
source "drivers/mfd/Kconfig"
source "drivers/media/Kconfig"
diff --git a/drivers/ssb/Makefile b/drivers/ssb/Makefile
new file mode 100644
index 0000000..045aff0
--- /dev/null
+++ b/drivers/ssb/Makefile
@@ -0,0 +1,13 @@
+# core
+ssb-y += main.o scan.o
+
+# host support
+ssb-$(CONFIG_SSB_PCIHOST) += pci.o pcihost_wrapper.o
+ssb-$(CONFIG_SSB_PCMCIAHOST) += pcmcia.o
+
+# built-in drivers
+ssb-y += driver_chipcommon.o
+ssb-$(CONFIG_SSB_DRIVER_MIPS) += driver_mipscore.o
+ssb-$(CONFIG_SSB_DRIVER_PCICORE) += driver_pcicore.o
+
+obj-$(CONFIG_SSB) += ssb.o
diff --git a/drivers/ssb/Kconfig b/drivers/ssb/Kconfig
new file mode 100644
index 0000000..34a9411
--- /dev/null
+++ b/drivers/ssb/Kconfig
@@ -0,0 +1,92 @@
+menu "Sonics Silicon Backplane"
+
+config SSB
+ tristate "Sonics Silicon Backplane support"
+ depends on EXPERIMENTAL
+ help
+ Support for the Sonics Silicon Backplane bus
+
+ The module will be called ssb
+
+ If unsure, say M
+
+config SSB_PCIHOST
+ bool "Support for SSB on PCI-bus host"
+ depends on SSB && PCI
+ default y
+ help
+ Support for a Sonics Silicon Backplane on top
+ of a PCI device.
+
+ If unsure, say Y
+
+config SSB_PCMCIAHOST
+ bool "Support for SSB on PCMCIA-bus host"
+ depends on SSB && PCMCIA
+ help
+ Support for a Sonics Silicon Backplane on top
+ of a PCMCIA device.
+
+ If unsure, say N
+
+config SSB_SILENT
+ bool "No SSB kernel messages"
+ depends on SSB
+ help
+ This option turns off all Sonics Silicon Backplane printks.
+ Note that you won't be able to identify problems, once
+ messages are turned off.
+ This might only be desired for production kernels on
+ embedded devices to reduce the kernel size.
+
+ Say N
+
+config SSB_DEBUG
+ bool "SSB debugging"
+ depends on SSB && !SSB_SILENT
+ help
+ This turns on additional runtime checks and debugging
+ messages. Turn this on for SSB troubleshooting.
+
+ If unsure, say N
+
+config SSB_SERIAL
+ bool
+ depends on SSB
+ # ChipCommon and ExtIf serial support routines.
+
+config SSB_DRIVER_PCICORE
+ bool "SSB PCI core driver"
+ depends on SSB && SSB_PCIHOST
+ help
+ Driver for the Sonics Silicon Backplane attached
+ Broadcom PCI core.
+
+ If unsure, say Y
+
+config SSB_PCICORE_HOSTMODE
+ bool "Hostmode support for SSB PCI core"
+ depends on SSB_DRIVER_PCICORE && SSB_DRIVER_MIPS
+ help
+ PCIcore hostmode operation (external PCI bus).
+
+config SSB_DRIVER_MIPS
+ bool "SSB Broadcom MIPS core driver"
+ depends on SSB && MIPS
+ select SSB_SERIAL
+ help
+ Driver for the Sonics Silicon Backplane attached
+ Broadcom MIPS core.
+
+ If unsure, say N
+
+config SSB_DRIVER_EXTIF
+ bool "SSB Broadcom EXTIF core driver"
+ depends on SSB_DRIVER_MIPS
+ help
+ Driver for the Sonics Silicon Backplane attached
+ Broadcom EXTIF core.
+
+ If unsure, say N
+
+endmenu
diff --git a/drivers/ssb/driver_chipcommon.c b/drivers/ssb/driver_chipcommon.c
new file mode 100644
index 0000000..8e5491c
--- /dev/null
+++ b/drivers/ssb/driver_chipcommon.c
@@ -0,0 +1,394 @@
+/*
+ * Sonics Silicon Backplane
+ * Broadcom ChipCommon 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/ssb/ssb_regs.h>
+#include <linux/pci.h>
+
+#include "ssb_private.h"
+
+
+/* Clock sources */
+enum ssb_clksrc {
+ /* PCI clock */
+ SSB_CHIPCO_CLKSRC_PCI,
+ /* Crystal slow clock oscillator */
+ SSB_CHIPCO_CLKSRC_XTALOS,
+ /* Low power oscillator */
+ SSB_CHIPCO_CLKSRC_LOPWROS,
+};
+
+
+static inline u32 chipco_read32(struct ssb_chipcommon *cc,
+ u16 offset)
+{
+ return ssb_read32(cc->dev, offset);
+}
+
+static inline void chipco_write32(struct ssb_chipcommon *cc,
+ u16 offset,
+ u32 value)
+{
+ ssb_write32(cc->dev, offset, value);
+}
+
+void ssb_chipco_set_clockmode(struct ssb_chipcommon *cc,
+ enum ssb_clkmode mode)
+{
+ struct ssb_device *ccdev = cc->dev;
+ struct ssb_bus *bus;
+ u32 tmp;
+
+ if (!ccdev)
+ return;
+ bus = ccdev->bus;
+ /* chipcommon cores prior to rev6 don't support dynamic clock control */
+ if (ccdev->id.revision < 6)
+ return;
+ /* chipcommon cores rev10 are a whole new ball game */
+ if (ccdev->id.revision >= 10)
+ return;
+ if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL))
+ return;
+
+ switch (mode) {
+ case SSB_CLKMODE_SLOW:
+ tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
+ tmp |= SSB_CHIPCO_SLOWCLKCTL_FSLOW;
+ chipco_write32(cc, SSB_CHIPCO_SLOWCLKCTL, tmp);
+ break;
+ case SSB_CLKMODE_FAST:
+ ssb_pci_xtal(bus, SSB_GPIO_XTAL, 1); /* Force crystal on */
+ tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
+ tmp &= ~SSB_CHIPCO_SLOWCLKCTL_FSLOW;
+ tmp |= SSB_CHIPCO_SLOWCLKCTL_IPLL;
+ chipco_write32(cc, SSB_CHIPCO_SLOWCLKCTL, tmp);
+ break;
+ case SSB_CLKMODE_DYNAMIC:
+ tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
+ tmp &= ~SSB_CHIPCO_SLOWCLKCTL_FSLOW;
+ tmp &= ~SSB_CHIPCO_SLOWCLKCTL_IPLL;
+ tmp &= ~SSB_CHIPCO_SLOWCLKCTL_ENXTAL;
+ if ((tmp & SSB_CHIPCO_SLOWCLKCTL_SRC) != SSB_CHIPCO_SLOWCLKCTL_SRC_XTAL)
+ tmp |= SSB_CHIPCO_SLOWCLKCTL_ENXTAL;
+ chipco_write32(cc, SSB_CHIPCO_SLOWCLKCTL, tmp);
+
+ /* for dynamic control, we have to release our xtal_pu "force on" */
+ if (tmp & SSB_CHIPCO_SLOWCLKCTL_ENXTAL)
+ ssb_pci_xtal(bus, SSB_GPIO_XTAL, 0);
+ break;
+ default:
+ SSB_WARN_ON(1);
+ }
+}
+
+/* Get the Slow Clock Source */
+static enum ssb_clksrc chipco_pctl_get_slowclksrc(struct ssb_chipcommon *cc)
+{
+ struct ssb_bus *bus = cc->dev->bus;
+ u32 uninitialized_var(tmp);
+
+ if (cc->dev->id.revision < 6) {
+ if (bus->bustype == SSB_BUSTYPE_SSB ||
+ bus->bustype == SSB_BUSTYPE_PCMCIA)
+ return SSB_CHIPCO_CLKSRC_XTALOS;
+ if (bus->bustype == SSB_BUSTYPE_PCI) {
+ pci_read_config_dword(bus->host_pci, SSB_GPIO_OUT, &tmp);
+ if (tmp & 0x10)
+ return SSB_CHIPCO_CLKSRC_PCI;
+ return SSB_CHIPCO_CLKSRC_XTALOS;
+ }
+ }
+ if (cc->dev->id.revision < 10) {
+ tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
+ tmp &= 0x7;
+ if (tmp == 0)
+ return SSB_CHIPCO_CLKSRC_LOPWROS;
+ if (tmp == 1)
+ return SSB_CHIPCO_CLKSRC_XTALOS;
+ if (tmp == 2)
+ return SSB_CHIPCO_CLKSRC_PCI;
+ }
+
+ return SSB_CHIPCO_CLKSRC_XTALOS;
+}
+
+/* Get maximum or minimum (depending on get_max flag) slowclock frequency. */
+static int chipco_pctl_clockfreqlimit(struct ssb_chipcommon *cc, int get_max)
+{
+ int uninitialized_var(limit);
+ enum ssb_clksrc clocksrc;
+ int divisor = 1;
+ u32 tmp;
+
+ clocksrc = chipco_pctl_get_slowclksrc(cc);
+ if (cc->dev->id.revision < 6) {
+ switch (clocksrc) {
+ case SSB_CHIPCO_CLKSRC_PCI:
+ divisor = 64;
+ break;
+ case SSB_CHIPCO_CLKSRC_XTALOS:
+ divisor = 32;
+ break;
+ default:
+ SSB_WARN_ON(1);
+ }
+ } else if (cc->dev->id.revision < 10) {
+ switch (clocksrc) {
+ case SSB_CHIPCO_CLKSRC_LOPWROS:
+ break;
+ case SSB_CHIPCO_CLKSRC_XTALOS:
+ case SSB_CHIPCO_CLKSRC_PCI:
+ tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
+ divisor = (tmp >> 16) + 1;
+ divisor *= 4;
+ break;
+ }
+ } else {
+ tmp = chipco_read32(cc, SSB_CHIPCO_SYSCLKCTL);
+ divisor = (tmp >> 16) + 1;
+ divisor *= 4;
+ }
+
+ switch (clocksrc) {
+ case SSB_CHIPCO_CLKSRC_LOPWROS:
+ if (get_max)
+ limit = 43000;
+ else
+ limit = 25000;
+ break;
+ case SSB_CHIPCO_CLKSRC_XTALOS:
+ if (get_max)
+ limit = 20200000;
+ else
+ limit = 19800000;
+ break;
+ case SSB_CHIPCO_CLKSRC_PCI:
+ if (get_max)
+ limit = 34000000;
+ else
+ limit = 25000000;
+ break;
+ }
+ limit /= divisor;
+
+ return limit;
+}
+
+static void chipco_powercontrol_init(struct ssb_chipcommon *cc)
+{
+ struct ssb_bus *bus = cc->dev->bus;
+
+ if (bus->chip_id == 0x4321) {
+ if (bus->chip_rev == 0)
+ chipco_write32(cc, SSB_CHIPCO_CHIPCTL, 0x3A4);
+ else if (bus->chip_rev == 1)
+ chipco_write32(cc, SSB_CHIPCO_CHIPCTL, 0xA4);
+ }
+
+ if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL))
+ return;
+
+ if (cc->dev->id.revision >= 10) {
+ /* Set Idle Power clock rate to 1Mhz */
+ chipco_write32(cc, SSB_CHIPCO_SYSCLKCTL,
+ (chipco_read32(cc, SSB_CHIPCO_SYSCLKCTL) &
+ 0x0000FFFF) | 0x00040000);
+ } else {
+ int maxfreq;
+
+ maxfreq = chipco_pctl_clockfreqlimit(cc, 1);
+ chipco_write32(cc, SSB_CHIPCO_PLLONDELAY,
+ (maxfreq * 150 + 999999) / 1000000);
+ chipco_write32(cc, SSB_CHIPCO_FREFSELDELAY,
+ (maxfreq * 15 + 999999) / 1000000);
+ }
+}
+
+static void calc_fast_powerup_delay(struct ssb_chipcommon *cc)
+{
+ struct ssb_bus *bus = cc->dev->bus;
+ int minfreq;
+ unsigned int tmp;
+ u32 pll_on_delay;
+
+ if (bus->bustype != SSB_BUSTYPE_PCI)
+ return;
+ if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL))
+ return;
+
+ minfreq = chipco_pctl_clockfreqlimit(cc, 0);
+ pll_on_delay = chipco_read32(cc, SSB_CHIPCO_PLLONDELAY);
+ tmp = (((pll_on_delay + 2) * 1000000) + (minfreq - 1)) / minfreq;
+ SSB_WARN_ON(tmp & ~0xFFFF);
+
+ cc->fast_pwrup_delay = tmp;
+}
+
+void ssb_chipcommon_init(struct ssb_chipcommon *cc)
+{
+ if (!cc->dev)
+ return; /* We don't have a ChipCommon */
+ chipco_powercontrol_init(cc);
+ ssb_chipco_set_clockmode(cc, SSB_CLKMODE_FAST);
+ calc_fast_powerup_delay(cc);
+}
+
+void ssb_chipco_suspend(struct ssb_chipcommon *cc, pm_message_t state)
+{
+ if (!cc->dev)
+ return;
+ ssb_chipco_set_clockmode(cc, SSB_CLKMODE_SLOW);
+}
+
+void ssb_chipco_resume(struct ssb_chipcommon *cc)
+{
+ if (!cc->dev)
+ return;
+ chipco_powercontrol_init(cc);
+ ssb_chipco_set_clockmode(cc, SSB_CLKMODE_FAST);
+}
+
+void ssb_chipco_get_clockcontrol(struct ssb_chipcommon *cc,
+ u32 *plltype, u32 *n, u32 *m)
+{
+ *n = chipco_read32(cc, SSB_CHIPCO_CLOCK_N);
+ *plltype = (cc->capabilities & SSB_CHIPCO_CAP_PLLT);
+ switch (*plltype) {
+ case SSB_PLLTYPE_6: /* 100/200 or 120/240 only */
+ *m = chipco_read32(cc, SSB_CHIPCO_CLOCK_MIPS);
+ break;
+ case SSB_PLLTYPE_3: /* 25Mhz, 2 dividers */
+ if (cc->dev->bus->chip_id != 0x5365) {
+ *m = chipco_read32(cc, SSB_CHIPCO_CLOCK_M2);
+ break;
+ }
+ /* Fallthough */
+ default:
+ *m = chipco_read32(cc, SSB_CHIPCO_CLOCK_SB);
+ }
+}
+
+void ssb_chipco_timing_init(struct ssb_chipcommon *cc,
+ unsigned long ns)
+{
+ struct ssb_device *dev = cc->dev;
+ struct ssb_bus *bus = dev->bus;
+ u32 tmp;
+
+ /* set register for external IO to control LED. */
+ chipco_write32(cc, SSB_CHIPCO_PROG_CFG, 0x11);
+ tmp = DIV_ROUND_UP(10, ns) << SSB_PROG_WCNT_3_SHIFT; /* Waitcount-3 = 10ns */
+ tmp |= DIV_ROUND_UP(40, ns) << SSB_PROG_WCNT_1_SHIFT; /* Waitcount-1 = 40ns */
+ tmp |= DIV_ROUND_UP(240, ns); /* Waitcount-0 = 240ns */
+ chipco_write32(cc, SSB_CHIPCO_PROG_WAITCNT, tmp); /* 0x01020a0c for a 100Mhz clock */
+
+ /* Set timing for the flash */
+ tmp = DIV_ROUND_UP(10, ns) << SSB_FLASH_WCNT_3_SHIFT; /* Waitcount-3 = 10nS */
+ tmp |= DIV_ROUND_UP(10, ns) << SSB_FLASH_WCNT_1_SHIFT; /* Waitcount-1 = 10nS */
+ tmp |= DIV_ROUND_UP(120, ns); /* Waitcount-0 = 120nS */
+ if ((bus->chip_id == 0x5365) ||
+ (dev->id.revision < 9))
+ chipco_write32(cc, SSB_CHIPCO_FLASH_WAITCNT, tmp);
+ if ((bus->chip_id == 0x5365) ||
+ (dev->id.revision < 9) ||
+ ((bus->chip_id == 0x5350) && (bus->chip_rev == 0)))
+ chipco_write32(cc, SSB_CHIPCO_PCMCIA_MEMWAIT, tmp);
+
+ if (bus->chip_id == 0x5350) {
+ /* Enable EXTIF */
+ tmp = DIV_ROUND_UP(10, ns) << SSB_PROG_WCNT_3_SHIFT; /* Waitcount-3 = 10ns */
+ tmp |= DIV_ROUND_UP(20, ns) << SSB_PROG_WCNT_2_SHIFT; /* Waitcount-2 = 20ns */
+ tmp |= DIV_ROUND_UP(100, ns) << SSB_PROG_WCNT_1_SHIFT; /* Waitcount-1 = 100ns */
+ tmp |= DIV_ROUND_UP(120, ns); /* Waitcount-0 = 120ns */
+ chipco_write32(cc, SSB_CHIPCO_PROG_WAITCNT, tmp); /* 0x01020a0c for a 100Mhz clock */
+ }
+}
+
+
+#ifdef CONFIG_SSB_SERIAL
+int ssb_chipco_serial_init(struct ssb_chipcommon *cc,
+ struct ssb_serial_port *ports)
+{
+ struct ssb_bus *bus = cc->dev->bus;
+ int nr_ports = 0;
+ u32 plltype;
+ unsigned int irq;
+ u32 baud_base, div;
+ u32 i, n;
+
+ plltype = (cc->capabilities & SSB_CHIPCO_CAP_PLLT);
+ irq = ssb_mips_irq(cc->dev);
+
+ if (plltype == SSB_PLLTYPE_1) {
+ /* PLL clock */
+ baud_base = ssb_calc_clock_rate(plltype,
+ chipco_read32(cc, SSB_CHIPCO_CLOCK_N),
+ chipco_read32(cc, SSB_CHIPCO_CLOCK_M2));
+ div = 1;
+ } else {
+ if (cc->dev->id.revision >= 11) {
+ /* Fixed ALP clock */
+ baud_base = 20000000;
+ div = 1;
+ /* Set the override bit so we don't divide it */
+ chipco_write32(cc, SSB_CHIPCO_CORECTL,
+ SSB_CHIPCO_CORECTL_UARTCLK0);
+ } else if (cc->dev->id.revision >= 3) {
+ /* Internal backplane clock */
+ baud_base = ssb_clockspeed(bus);
+ div = 2; /* Minimum divisor */
+ chipco_write32(cc, SSB_CHIPCO_CLKDIV,
+ (chipco_read32(cc, SSB_CHIPCO_CLKDIV)
+ & ~SSB_CHIPCO_CLKDIV_UART) | div);
+ } else {
+ /* Fixed internal backplane clock */
+ baud_base = 88000000;
+ div = 48;
+ }
+
+ /* Clock source depends on strapping if UartClkOverride is unset */
+ if ((cc->dev->id.revision > 0) &&
+ !(chipco_read32(cc, SSB_CHIPCO_CORECTL) & SSB_CHIPCO_CORECTL_UARTCLK0)) {
+ if ((cc->capabilities & SSB_CHIPCO_CAP_UARTCLK) ==
+ SSB_CHIPCO_CAP_UARTCLK_INT) {
+ /* Internal divided backplane clock */
+ baud_base /= div;
+ } else {
+ /* Assume external clock of 1.8432 MHz */
+ baud_base = 1843200;
+ }
+ }
+ }
+
+ /* Determine the registers of the UARTs */
+ n = (cc->capabilities & SSB_CHIPCO_CAP_NRUART);
+ for (i = 0; i < n; i++) {
+ void __iomem *cc_mmio;
+ void __iomem *uart_regs;
+
+ cc_mmio = cc->dev->bus->mmio + (cc->dev->core_index * SSB_CORE_SIZE);
+ uart_regs = cc_mmio + SSB_CHIPCO_UART0_DATA;
+ /* Offset changed at after rev 0 */
+ if (cc->dev->id.revision == 0)
+ uart_regs += (i * 8);
+ else
+ uart_regs += (i * 256);
+
+ nr_ports++;
+ ports[i].regs = uart_regs;
+ ports[i].irq = irq;
+ ports[i].baud_base = baud_base;
+ ports[i].reg_shift = 0;
+ }
+
+ return nr_ports;
+}
+#endif /* CONFIG_SSB_SERIAL */
diff --git a/drivers/ssb/driver_mipscore.c b/drivers/ssb/driver_mipscore.c
new file mode 100644
index 0000000..3f09598
--- /dev/null
+++ b/drivers/ssb/driver_mipscore.c
@@ -0,0 +1,258 @@
+/*
+ * Sonics Silicon Backplane
+ * Broadcom MIPS 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/serial.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+#include <linux/time.h>
+
+#include "ssb_private.h"
+
+
+static inline u32 mips_read32(struct ssb_mipscore *mcore,
+ u16 offset)
+{
+ return ssb_read32(mcore->dev, offset);
+}
+
+static inline void mips_write32(struct ssb_mipscore *mcore,
+ u16 offset,
+ u32 value)
+{
+ ssb_write32(mcore->dev, offset, value);
+}
+
+static const u32 ipsflag_irq_mask[] = {
+ 0,
+ SSB_IPSFLAG_IRQ1,
+ SSB_IPSFLAG_IRQ2,
+ SSB_IPSFLAG_IRQ3,
+ SSB_IPSFLAG_IRQ4,
+};
+
+static const u32 ipsflag_irq_shift[] = {
+ 0,
+ SSB_IPSFLAG_IRQ1_SHIFT,
+ SSB_IPSFLAG_IRQ2_SHIFT,
+ SSB_IPSFLAG_IRQ3_SHIFT,
+ SSB_IPSFLAG_IRQ4_SHIFT,
+};
+
+static inline u32 ssb_irqflag(struct ssb_device *dev)
+{
+ return ssb_read32(dev, SSB_TPSFLAG) & SSB_TPSFLAG_BPFLAG;
+}
+
+/* Get the MIPS IRQ assignment for a specified device.
+ * If unassigned, 0 is returned.
+ */
+unsigned int ssb_mips_irq(struct ssb_device *dev)
+{
+ struct ssb_bus *bus = dev->bus;
+ u32 irqflag;
+ u32 ipsflag;
+ u32 tmp;
+ unsigned int irq;
+
+ irqflag = ssb_irqflag(dev);
+ ipsflag = ssb_read32(bus->mipscore.dev, SSB_IPSFLAG);
+ for (irq = 1; irq <= 4; irq++) {
+ tmp = ((ipsflag & ipsflag_irq_mask[irq]) >> ipsflag_irq_shift[irq]);
+ if (tmp == irqflag)
+ break;
+ }
+ if (irq == 5)
+ irq = 0;
+
+ return irq;
+}
+
+static void clear_irq(struct ssb_bus *bus, unsigned int irq)
+{
+ struct ssb_device *dev = bus->mipscore.dev;
+
+ /* Clear the IRQ in the MIPScore backplane registers */
+ if (irq == 0) {
+ ssb_write32(dev, SSB_INTVEC, 0);
+ } else {
+ ssb_write32(dev, SSB_IPSFLAG,
+ ssb_read32(dev, SSB_IPSFLAG) |
+ ipsflag_irq_mask[irq]);
+ }
+}
+
+static void set_irq(struct ssb_device *dev, unsigned int irq)
+{
+ unsigned int oldirq = ssb_mips_irq(dev);
+ struct ssb_bus *bus = dev->bus;
+ struct ssb_device *mdev = bus->mipscore.dev;
+ u32 irqflag = ssb_irqflag(dev);
+
+ dev->irq = irq + 2;
+
+ ssb_dprintk(KERN_INFO PFX
+ "set_irq: core 0x%04x, irq %d => %d\n",
+ dev->id.coreid, oldirq, irq);
+ /* clear the old irq */
+ if (oldirq == 0)
+ ssb_write32(mdev, SSB_INTVEC, (~(1 << irqflag) & ssb_read32(mdev, SSB_INTVEC)));
+ else
+ clear_irq(bus, oldirq);
+
+ /* assign the new one */
+ if (irq == 0)
+ ssb_write32(mdev, SSB_INTVEC, ((1 << irqflag) & ssb_read32(mdev, SSB_INTVEC)));
+
+ irqflag <<= ipsflag_irq_shift[irq];
+ irqflag |= (ssb_read32(mdev, SSB_IPSFLAG) & ~ipsflag_irq_mask[irq]);
+ ssb_write32(mdev, SSB_IPSFLAG, irqflag);
+}
+
+/* XXX: leave here or move into separate extif driver? */
+static int ssb_extif_serial_init(struct ssb_device *dev, struct ssb_serial_ports *ports)
+{
+
+}
+
+
+static void ssb_mips_serial_init(struct ssb_mipscore *mcore)
+{
+ struct ssb_bus *bus = mcore->dev->bus;
+
+ //TODO if (EXTIF available
+#if 0
+ extifregs_t *eir = (extifregs_t *) regs;
+ sbconfig_t *sb;
+
+ /* Determine external UART register base */
+ sb = (sbconfig_t *)((ulong) eir + SBCONFIGOFF);
+ base = EXTIF_CFGIF_BASE(sb_base(R_REG(&sb->sbadmatch1)));
+
+ /* Determine IRQ */
+ irq = sb_irq(sbh);
+
+ /* Disable GPIO interrupt initially */
+ W_REG(&eir->gpiointpolarity, 0);
+ W_REG(&eir->gpiointmask, 0);
+
+ /* Search for external UARTs */
+ n = 2;
+ for (i = 0; i < 2; i++) {
+ regs = (void *) REG_MAP(base + (i * 8), 8);
+ if (BCMINIT(serial_exists)(regs)) {
+ /* Set GPIO 1 to be the external UART IRQ */
+ W_REG(&eir->gpiointmask, 2);
+ if (add)
+ add(regs, irq, 13500000, 0);
+ }
+ }
+
+ /* Add internal UART if enabled */
+ if (R_REG(&eir->corecontrol) & CC_UE)
+ if (add)
+ add((void *) &eir->uartdata, irq, sb_clock(sbh), 2);
+
+#endif
+ if (bus->extif.dev)
+ mcore->nr_serial_ports = ssb_extif_serial_init(&bus->extif, mcore->serial_ports);
+ else if (bus->chipco.dev)
+ mcore->nr_serial_ports = ssb_chipco_serial_init(&bus->chipco, mcore->serial_ports);
+ else
+ mcore->nr_serial_ports = 0;
+}
+
+static void ssb_mips_flash_detect(struct ssb_mipscore *mcore)
+{
+ struct ssb_bus *bus = mcore->dev->bus;
+
+ if (bus->chipco.dev) {
+ mcore->flash_window = 0x1c000000;
+ mcore->flash_window_size = 0x800000;
+ } else {
+ mcore->flash_window = 0x1fc00000;
+ mcore->flash_window_size = 0x400000;
+ }
+}
+
+
+static void ssb_cpu_clock(struct ssb_mipscore *mcore)
+{
+}
+
+void ssb_mipscore_init(struct ssb_mipscore *mcore)
+{
+ struct ssb_bus *bus = mcore->dev->bus;
+ struct ssb_device *dev;
+ unsigned long hz, ns;
+ unsigned int irq, i;
+
+ if (!mcore->dev)
+ return; /* We don't have a MIPS core */
+
+ ssb_dprintk(KERN_INFO PFX "Initializing MIPS core...\n");
+
+ hz = ssb_clockspeed(bus);
+ if (!hz)
+ hz = 100000000;
+ ns = 1000000000 / hz;
+
+//TODO
+#if 0
+ if (have EXTIF) {
+ /* Initialize extif so we can get to the LEDs and external UART */
+ W_REG(&eir->prog_config, CF_EN);
+
+ /* Set timing for the flash */
+ tmp = CEIL(10, ns) << FW_W3_SHIFT; /* W3 = 10nS */
+ tmp = tmp | (CEIL(40, ns) << FW_W1_SHIFT); /* W1 = 40nS */
+ tmp = tmp | CEIL(120, ns); /* W0 = 120nS */
+ W_REG(&eir->prog_waitcount, tmp); /* 0x01020a0c for a 100Mhz clock */
+
+ /* Set programmable interface timing for external uart */
+ tmp = CEIL(10, ns) << FW_W3_SHIFT; /* W3 = 10nS */
+ tmp = tmp | (CEIL(20, ns) << FW_W2_SHIFT); /* W2 = 20nS */
+ tmp = tmp | (CEIL(100, ns) << FW_W1_SHIFT); /* W1 = 100nS */
+ tmp = tmp | CEIL(120, ns); /* W0 = 120nS */
+ W_REG(&eir->prog_waitcount, tmp);
+ }
+#endif
+ if (bus->chipco.dev)
+ ssb_chipco_timing_init(&bus->chipco, ns);
+
+ /* Assign IRQs to all cores on the bus, start with irq line 2, because serial usually takes 1 */
+ for (irq = 2, i = 0; i < bus->nr_devices; i++) {
+ dev = &(bus->devices[i]);
+ dev->irq = ssb_mips_irq(dev) + 2;
+ switch (dev->id.coreid) {
+ case SSB_DEV_USB11_HOST:
+ /* shouldn't need a separate irq line for non-4710, most of them have a proper
+ * external usb controller on the pci */
+ if ((bus->chip_id == 0x4710) && (irq <= 4)) {
+ set_irq(dev, irq++);
+ break;
+ }
+ /* fallthrough */
+ case SSB_DEV_PCI:
+ case SSB_DEV_ETHERNET:
+ case SSB_DEV_80211:
+ case SSB_DEV_USB20_HOST:
+ /* These devices get their own IRQ line if available, the rest goes on IRQ0 */
+ if (irq <= 4) {
+ set_irq(dev, irq++);
+ break;
+ }
+ }
+ }
+
+ ssb_mips_serial_init(mcore);
+ ssb_mips_flash_detect(mcore);
+}
diff --git a/drivers/ssb/driver_pcicore.c b/drivers/ssb/driver_pcicore.c
new file mode 100644
index 0000000..e11b103
--- /dev/null
+++ b/drivers/ssb/driver_pcicore.c
@@ -0,0 +1,568 @@
+/*
+ * 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);
+}
+DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, ssb_fixup_pcibridge);
+
+int __init pcibios_map_irq(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(*((const u32 *)buf), 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);
+ 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));
+ 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);
diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c
new file mode 100644
index 0000000..cfe090f
--- /dev/null
+++ b/drivers/ssb/main.c
@@ -0,0 +1,1112 @@
+/*
+ * Sonics Silicon Backplane
+ * Subsystem core
+ *
+ * Copyright 2005, Broadcom Corporation
+ * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de>
+ *
+ * Licensed under the GNU/GPL. See COPYING for details.
+ */
+
+#include "ssb_private.h"
+
+#include <linux/delay.h>
+#include <linux/ssb/ssb.h>
+#include <linux/ssb/ssb_regs.h>
+
+#include <linux/pci.h>
+
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+
+MODULE_DESCRIPTION("Sonics Silicon Backplane driver");
+MODULE_LICENSE("GPL");
+
+
+/* Temporary list of yet-to-be-attached buses */
+static LIST_HEAD(attach_queue);
+/* List if running buses */
+static LIST_HEAD(buses);
+/* Software ID counter */
+static unsigned int next_busnumber;
+/* buses_mutes locks the two buslists and the next_busnumber.
+ * Don't lock this directly, but use ssb_buses_[un]lock() below. */
+static DEFINE_MUTEX(buses_mutex);
+
+/* There are differences in the codeflow, if the bus is
+ * initialized from early boot, as various needed services
+ * are not available early. This is a mechanism to delay
+ * these initializations to after early boot has finished.
+ * It's also used to avoid mutex locking, as that's not
+ * available and needed early. */
+static bool ssb_is_early_boot = 1;
+
+static void ssb_buses_lock(void);
+static void ssb_buses_unlock(void);
+
+
+#ifdef CONFIG_SSB_PCIHOST
+struct ssb_bus *ssb_pci_dev_to_bus(struct pci_dev *pdev)
+{
+ struct ssb_bus *bus;
+
+ ssb_buses_lock();
+ list_for_each_entry(bus, &buses, list) {
+ if (bus->bustype == SSB_BUSTYPE_PCI &&
+ bus->host_pci == pdev)
+ goto found;
+ }
+ bus = NULL;
+found:
+ ssb_buses_unlock();
+
+ return bus;
+}
+#endif /* CONFIG_SSB_PCIHOST */
+
+static struct ssb_device *ssb_device_get(struct ssb_device *dev)
+{
+ if (dev)
+ get_device(dev->dev);
+ return dev;
+}
+
+static void ssb_device_put(struct ssb_device *dev)
+{
+ if (dev)
+ put_device(dev->dev);
+}
+
+static int ssb_bus_resume(struct ssb_bus *bus)
+{
+ int err;
+
+ ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 1);
+ err = ssb_pcmcia_init(bus);
+ if (err) {
+ /* No need to disable XTAL, as we don't have one on PCMCIA. */
+ return err;
+ }
+ ssb_chipco_resume(&bus->chipco);
+
+ return 0;
+}
+
+static int ssb_device_resume(struct device *dev)
+{
+ struct ssb_device *ssb_dev = dev_to_ssb_dev(dev);
+ struct ssb_driver *ssb_drv;
+ struct ssb_bus *bus;
+ int err = 0;
+
+ bus = ssb_dev->bus;
+ if (bus->suspend_cnt == bus->nr_devices) {
+ err = ssb_bus_resume(bus);
+ if (err)
+ return err;
+ }
+ bus->suspend_cnt--;
+ if (dev->driver) {
+ ssb_drv = drv_to_ssb_drv(dev->driver);
+ if (ssb_drv && ssb_drv->resume)
+ err = ssb_drv->resume(ssb_dev);
+ if (err)
+ goto out;
+ }
+out:
+ return err;
+}
+
+static void ssb_bus_suspend(struct ssb_bus *bus, pm_message_t state)
+{
+ ssb_chipco_suspend(&bus->chipco, state);
+ ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0);
+
+ /* Reset HW state information in memory, so that HW is
+ * completely reinitialized on resume. */
+ bus->mapped_device = NULL;
+#ifdef CONFIG_SSB_DRIVER_PCICORE
+ bus->pcicore.setup_done = 0;
+#endif
+}
+
+static int ssb_device_suspend(struct device *dev, pm_message_t state)
+{
+ struct ssb_device *ssb_dev = dev_to_ssb_dev(dev);
+ struct ssb_driver *ssb_drv;
+ struct ssb_bus *bus;
+ int err = 0;
+
+ if (dev->driver) {
+ ssb_drv = drv_to_ssb_drv(dev->driver);
+ if (ssb_drv && ssb_drv->suspend)
+ err = ssb_drv->suspend(ssb_dev, state);
+ if (err)
+ goto out;
+ }
+
+ bus = ssb_dev->bus;
+ bus->suspend_cnt++;
+ if (bus->suspend_cnt == bus->nr_devices) {
+ /* All devices suspended. Shutdown the bus. */
+ ssb_bus_suspend(bus, state);
+ }
+
+out:
+ return err;
+}
+
+#ifdef CONFIG_SSB_PCIHOST
+int ssb_devices_freeze(struct ssb_bus *bus)
+{
+ struct ssb_device *dev;
+ struct ssb_driver *drv;
+ int err = 0;
+ int i;
+ pm_message_t state = PMSG_FREEZE;
+
+ /* First check that we are capable to freeze all devices. */
+ for (i = 0; i < bus->nr_devices; i++) {
+ dev = &(bus->devices[i]);
+ if (!dev->dev ||
+ !dev->dev->driver ||
+ !device_is_registered(dev->dev))
+ continue;
+ drv = drv_to_ssb_drv(dev->dev->driver);
+ if (!drv)
+ continue;
+ if (!drv->suspend) {
+ /* Nope, can't suspend this one. */
+ return -EOPNOTSUPP;
+ }
+ }
+ /* Now suspend all devices */
+ for (i = 0; i < bus->nr_devices; i++) {
+ dev = &(bus->devices[i]);
+ if (!dev->dev ||
+ !dev->dev->driver ||
+ !device_is_registered(dev->dev))
+ continue;
+ drv = drv_to_ssb_drv(dev->dev->driver);
+ if (!drv)
+ continue;
+ err = drv->suspend(dev, state);
+ if (err) {
+ ssb_printk(KERN_ERR PFX "Failed to freeze device %s\n",
+ dev->dev->bus_id);
+ goto err_unwind;
+ }
+ }
+
+ return 0;
+err_unwind:
+ for (i--; i >= 0; i--) {
+ dev = &(bus->devices[i]);
+ if (!dev->dev ||
+ !dev->dev->driver ||
+ !device_is_registered(dev->dev))
+ continue;
+ drv = drv_to_ssb_drv(dev->dev->driver);
+ if (!drv)
+ continue;
+ if (drv->resume)
+ drv->resume(dev);
+ }
+ return err;
+}
+
+int ssb_devices_thaw(struct ssb_bus *bus)
+{
+ struct ssb_device *dev;
+ struct ssb_driver *drv;
+ int err;
+ int i;
+
+ for (i = 0; i < bus->nr_devices; i++) {
+ dev = &(bus->devices[i]);
+ if (!dev->dev ||
+ !dev->dev->driver ||
+ !device_is_registered(dev->dev))
+ continue;
+ drv = drv_to_ssb_drv(dev->dev->driver);
+ if (!drv)
+ continue;
+ if (SSB_WARN_ON(!drv->resume))
+ continue;
+ err = drv->resume(dev);
+ if (err) {
+ ssb_printk(KERN_ERR PFX "Failed to thaw device %s\n",
+ dev->dev->bus_id);
+ }
+ }
+
+ return 0;
+}
+#endif /* CONFIG_SSB_PCIHOST */
+
+static void ssb_device_shutdown(struct device *dev)
+{
+ struct ssb_device *ssb_dev = dev_to_ssb_dev(dev);
+ struct ssb_driver *ssb_drv;
+
+ if (!dev->driver)
+ return;
+ ssb_drv = drv_to_ssb_drv(dev->driver);
+ if (ssb_drv && ssb_drv->shutdown)
+ ssb_drv->shutdown(ssb_dev);
+}
+
+static int ssb_device_remove(struct device *dev)
+{
+ struct ssb_device *ssb_dev = dev_to_ssb_dev(dev);
+ struct ssb_driver *ssb_drv = drv_to_ssb_drv(dev->driver);
+
+ if (ssb_drv && ssb_drv->remove)
+ ssb_drv->remove(ssb_dev);
+ ssb_device_put(ssb_dev);
+
+ return 0;
+}
+
+static int ssb_device_probe(struct device *dev)
+{
+ struct ssb_device *ssb_dev = dev_to_ssb_dev(dev);
+ struct ssb_driver *ssb_drv = drv_to_ssb_drv(dev->driver);
+ int err = 0;
+
+ ssb_device_get(ssb_dev);
+ if (ssb_drv && ssb_drv->probe)
+ err = ssb_drv->probe(ssb_dev, &ssb_dev->id);
+ if (err)
+ ssb_device_put(ssb_dev);
+
+ return err;
+}
+
+static int ssb_match_devid(const struct ssb_device_id *tabid,
+ const struct ssb_device_id *devid)
+{
+ if ((tabid->vendor != devid->vendor) &&
+ tabid->vendor != SSB_ANY_VENDOR)
+ return 0;
+ if ((tabid->coreid != devid->coreid) &&
+ tabid->coreid != SSB_ANY_ID)
+ return 0;
+ if ((tabid->revision != devid->revision) &&
+ tabid->revision != SSB_ANY_REV)
+ return 0;
+ return 1;
+}
+
+static int ssb_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct ssb_device *ssb_dev = dev_to_ssb_dev(dev);
+ struct ssb_driver *ssb_drv = drv_to_ssb_drv(drv);
+ const struct ssb_device_id *id;
+
+ for (id = ssb_drv->id_table;
+ id->vendor || id->coreid || id->revision;
+ id++) {
+ if (ssb_match_devid(id, &ssb_dev->id))
+ return 1; /* found */
+ }
+
+ return 0;
+}
+
+static struct bus_type ssb_bustype = {
+ .name = "ssb",
+ .match = ssb_bus_match,
+ .probe = ssb_device_probe,
+ .remove = ssb_device_remove,
+ .shutdown = ssb_device_shutdown,
+ .suspend = ssb_device_suspend,
+ .resume = ssb_device_resume,
+};
+
+static void ssb_buses_lock(void)
+{
+ /* See the comment at the ssb_is_early_boot definition */
+ if (!ssb_is_early_boot)
+ mutex_lock(&buses_mutex);
+}
+
+static void ssb_buses_unlock(void)
+{
+ /* See the comment at the ssb_is_early_boot definition */
+ if (!ssb_is_early_boot)
+ mutex_unlock(&buses_mutex);
+}
+
+static void ssb_devices_unregister(struct ssb_bus *bus)
+{
+ struct ssb_device *sdev;
+ int i;
+
+ for (i = bus->nr_devices - 1; i >= 0; i--) {
+ sdev = &(bus->devices[i]);
+ if (sdev->dev)
+ device_unregister(sdev->dev);
+ }
+}
+
+void ssb_bus_unregister(struct ssb_bus *bus)
+{
+ ssb_buses_lock();
+ ssb_devices_unregister(bus);
+ list_del(&bus->list);
+ ssb_buses_unlock();
+
+ /* ssb_pcmcia_exit(bus); */
+ ssb_pci_exit(bus);
+ ssb_iounmap(bus);
+}
+EXPORT_SYMBOL(ssb_bus_unregister);
+
+static void ssb_release_dev(struct device *dev)
+{
+ struct __ssb_dev_wrapper *devwrap;
+
+ devwrap = container_of(dev, struct __ssb_dev_wrapper, dev);
+ kfree(devwrap);
+}
+
+static int ssb_devices_register(struct ssb_bus *bus)
+{
+ struct ssb_device *sdev;
+ struct device *dev;
+ struct __ssb_dev_wrapper *devwrap;
+ int i, err = 0;
+ int dev_idx = 0;
+
+ for (i = 0; i < bus->nr_devices; i++) {
+ sdev = &(bus->devices[i]);
+
+ /* We don't register SSB-system devices to the kernel,
+ * as the drivers for them are built into SSB. */
+ switch (sdev->id.coreid) {
+ case SSB_DEV_CHIPCOMMON:
+ case SSB_DEV_PCI:
+ case SSB_DEV_PCIE:
+ case SSB_DEV_PCMCIA:
+ case SSB_DEV_MIPS:
+ case SSB_DEV_MIPS_3302:
+ case SSB_DEV_EXTIF:
+ continue;
+ }
+
+ devwrap = kzalloc(sizeof(*devwrap), GFP_KERNEL);
+ if (!devwrap) {
+ ssb_printk(KERN_ERR PFX
+ "Could not allocate device\n");
+ err = -ENOMEM;
+ goto error;
+ }
+ dev = &devwrap->dev;
+ devwrap->sdev = sdev;
+
+ dev->release = ssb_release_dev;
+ dev->bus = &ssb_bustype;
+ snprintf(dev->bus_id, sizeof(dev->bus_id),
+ "ssb%u:%d", bus->busnumber, dev_idx);
+
+ switch (bus->bustype) {
+ case SSB_BUSTYPE_PCI:
+#ifdef CONFIG_SSB_PCIHOST
+ sdev->irq = bus->host_pci->irq;
+ dev->parent = &bus->host_pci->dev;
+#endif
+ break;
+ case SSB_BUSTYPE_PCMCIA:
+#ifdef CONFIG_SSB_PCMCIAHOST
+ dev->parent = &bus->host_pcmcia->dev;
+#endif
+ break;
+ case SSB_BUSTYPE_SSB:
+ break;
+ }
+
+ sdev->dev = dev;
+ err = device_register(dev);
+ if (err) {
+ ssb_printk(KERN_ERR PFX
+ "Could not register %s\n",
+ dev->bus_id);
+ /* Set dev to NULL to not unregister
+ * dev on error unwinding. */
+ sdev->dev = NULL;
+ kfree(devwrap);
+ goto error;
+ }
+ dev_idx++;
+ }
+
+ return 0;
+error:
+ /* Unwind the already registered devices. */
+ ssb_devices_unregister(bus);
+ return err;
+}
+
+/* Needs ssb_buses_lock() */
+static int ssb_attach_queued_buses(void)
+{
+ struct ssb_bus *bus, *n;
+ int err = 0;
+ int drop_them_all = 0;
+
+ list_for_each_entry_safe(bus, n, &attach_queue, list) {
+ if (drop_them_all) {
+ list_del(&bus->list);
+ continue;
+ }
+ /* Can't init the PCIcore in ssb_bus_register(), as that
+ * is too early in boot for embedded systems
+ * (no udelay() available). So do it here in attach stage.
+ */
+ ssb_pcicore_init(&bus->pcicore);
+
+ err = ssb_devices_register(bus);
+ if (err) {
+ drop_them_all = 1;
+ list_del(&bus->list);
+ continue;
+ }
+ list_move_tail(&bus->list, &buses);
+ }
+
+ return err;
+}
+
+static u16 ssb_ssb_read16(struct ssb_device *dev, u16 offset)
+{
+ struct ssb_bus *bus = dev->bus;
+
+ offset += dev->core_index * SSB_CORE_SIZE;
+ return readw(bus->mmio + offset);
+}
+
+static u32 ssb_ssb_read32(struct ssb_device *dev, u16 offset)
+{
+ struct ssb_bus *bus = dev->bus;
+
+ offset += dev->core_index * SSB_CORE_SIZE;
+ return readl(bus->mmio + offset);
+}
+
+static void ssb_ssb_write16(struct ssb_device *dev, u16 offset, u16 value)
+{
+ struct ssb_bus *bus = dev->bus;
+
+ offset += dev->core_index * SSB_CORE_SIZE;
+ writew(value, bus->mmio + offset);
+}
+
+static void ssb_ssb_write32(struct ssb_device *dev, u16 offset, u32 value)
+{
+ struct ssb_bus *bus = dev->bus;
+
+ offset += dev->core_index * SSB_CORE_SIZE;
+ writel(value, bus->mmio + offset);
+}
+
+/* Ops for the plain SSB bus without a host-device (no PCI or PCMCIA). */
+static const struct ssb_bus_ops ssb_ssb_ops = {
+ .read16 = ssb_ssb_read16,
+ .read32 = ssb_ssb_read32,
+ .write16 = ssb_ssb_write16,
+ .write32 = ssb_ssb_write32,
+};
+
+static int ssb_fetch_invariants(struct ssb_bus *bus,
+ ssb_invariants_func_t get_invariants)
+{
+ struct ssb_init_invariants iv;
+ int err;
+
+ memset(&iv, 0, sizeof(iv));
+ err = get_invariants(bus, &iv);
+ if (err)
+ goto out;
+ memcpy(&bus->boardinfo, &iv.boardinfo, sizeof(iv.boardinfo));
+ memcpy(&bus->sprom, &iv.sprom, sizeof(iv.sprom));
+out:
+ return err;
+}
+
+static int ssb_bus_register(struct ssb_bus *bus,
+ ssb_invariants_func_t get_invariants,
+ unsigned long baseaddr)
+{
+ int err;
+
+ spin_lock_init(&bus->bar_lock);
+ INIT_LIST_HEAD(&bus->list);
+
+ /* Powerup the bus */
+ err = ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 1);
+ if (err)
+ goto out;
+ ssb_buses_lock();
+ bus->busnumber = next_busnumber;
+ /* Scan for devices (cores) */
+ err = ssb_bus_scan(bus, baseaddr);
+ if (err)
+ goto err_disable_xtal;
+
+ /* Init PCI-host device (if any) */
+ err = ssb_pci_init(bus);
+ if (err)
+ goto err_unmap;
+ /* Init PCMCIA-host device (if any) */
+ err = ssb_pcmcia_init(bus);
+ if (err)
+ goto err_pci_exit;
+
+ /* Initialize basic system devices (if available) */
+ ssb_chipcommon_init(&bus->chipco);
+ ssb_mipscore_init(&bus->mipscore);
+ err = ssb_fetch_invariants(bus, get_invariants);
+ if (err)
+ goto err_pcmcia_exit;
+
+ /* Queue it for attach.
+ * See the comment at the ssb_is_early_boot definition. */
+ list_add_tail(&bus->list, &attach_queue);
+ if (!ssb_is_early_boot) {
+ /* This is not early boot, so we must attach the bus now */
+ err = ssb_attach_queued_buses();
+ if (err)
+ goto err_dequeue;
+ }
+ next_busnumber++;
+ ssb_buses_unlock();
+
+out:
+ return err;
+
+err_dequeue:
+ list_del(&bus->list);
+err_pcmcia_exit:
+/* ssb_pcmcia_exit(bus); */
+err_pci_exit:
+ ssb_pci_exit(bus);
+err_unmap:
+ ssb_iounmap(bus);
+err_disable_xtal:
+ ssb_buses_unlock();
+ ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0);
+ return err;
+}
+
+#ifdef CONFIG_SSB_PCIHOST
+int ssb_bus_pcibus_register(struct ssb_bus *bus,
+ struct pci_dev *host_pci)
+{
+ int err;
+
+ bus->bustype = SSB_BUSTYPE_PCI;
+ bus->host_pci = host_pci;
+ bus->ops = &ssb_pci_ops;
+
+ err = ssb_bus_register(bus, ssb_pci_get_invariants, 0);
+ if (!err) {
+ ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found on "
+ "PCI device %s\n", host_pci->dev.bus_id);
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(ssb_bus_pcibus_register);
+#endif /* CONFIG_SSB_PCIHOST */
+
+#ifdef CONFIG_SSB_PCMCIAHOST
+int ssb_bus_pcmciabus_register(struct ssb_bus *bus,
+ struct pcmcia_device *pcmcia_dev,
+ unsigned long baseaddr)
+{
+ int err;
+
+ bus->bustype = SSB_BUSTYPE_PCMCIA;
+ bus->host_pcmcia = pcmcia_dev;
+ bus->ops = &ssb_pcmcia_ops;
+
+ err = ssb_bus_register(bus, ssb_pcmcia_get_invariants, baseaddr);
+ if (!err) {
+ ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found on "
+ "PCMCIA device %s\n", pcmcia_dev->devname);
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(ssb_bus_pcmciabus_register);
+#endif /* CONFIG_SSB_PCMCIAHOST */
+
+int ssb_bus_ssbbus_register(struct ssb_bus *bus,
+ unsigned long baseaddr,
+ ssb_invariants_func_t get_invariants)
+{
+ int err;
+
+ bus->bustype = SSB_BUSTYPE_SSB;
+ bus->ops = &ssb_ssb_ops;
+
+ err = ssb_bus_register(bus, get_invariants, baseaddr);
+ if (!err) {
+ ssb_printk(KERN_INFO PFX "Sonics Silicon Backplane found at "
+ "address 0x%08lX\n", baseaddr);
+ }
+
+ return err;
+}
+
+int __ssb_driver_register(struct ssb_driver *drv, struct module *owner)
+{
+ drv->drv.name = drv->name;
+ drv->drv.bus = &ssb_bustype;
+ drv->drv.owner = owner;
+
+ return driver_register(&drv->drv);
+}
+EXPORT_SYMBOL(__ssb_driver_register);
+
+void ssb_driver_unregister(struct ssb_driver *drv)
+{
+ driver_unregister(&drv->drv);
+}
+EXPORT_SYMBOL(ssb_driver_unregister);
+
+void ssb_set_devtypedata(struct ssb_device *dev, void *data)
+{
+ struct ssb_bus *bus = dev->bus;
+ struct ssb_device *ent;
+ int i;
+
+ for (i = 0; i < bus->nr_devices; i++) {
+ ent = &(bus->devices[i]);
+ if (ent->id.vendor != dev->id.vendor)
+ continue;
+ if (ent->id.coreid != dev->id.coreid)
+ continue;
+
+ ent->devtypedata = data;
+ }
+}
+EXPORT_SYMBOL(ssb_set_devtypedata);
+
+static u32 clkfactor_f6_resolve(u32 v)
+{
+ /* map the magic values */
+ switch (v) {
+ case SSB_CHIPCO_CLK_F6_2:
+ return 2;
+ case SSB_CHIPCO_CLK_F6_3:
+ return 3;
+ case SSB_CHIPCO_CLK_F6_4:
+ return 4;
+ case SSB_CHIPCO_CLK_F6_5:
+ return 5;
+ case SSB_CHIPCO_CLK_F6_6:
+ return 6;
+ case SSB_CHIPCO_CLK_F6_7:
+ return 7;
+ }
+ return 0;
+}
+
+/* Calculate the speed the backplane would run at a given set of clockcontrol values */
+u32 ssb_calc_clock_rate(u32 plltype, u32 n, u32 m)
+{
+ u32 n1, n2, clock, m1, m2, m3, mc;
+
+ n1 = (n & SSB_CHIPCO_CLK_N1);
+ n2 = ((n & SSB_CHIPCO_CLK_N2) >> SSB_CHIPCO_CLK_N2_SHIFT);
+
+ switch (plltype) {
+ case SSB_PLLTYPE_6: /* 100/200 or 120/240 only */
+ if (m & SSB_CHIPCO_CLK_T6_MMASK)
+ return SSB_CHIPCO_CLK_T6_M0;
+ return SSB_CHIPCO_CLK_T6_M1;
+ case SSB_PLLTYPE_1: /* 48Mhz base, 3 dividers */
+ case SSB_PLLTYPE_3: /* 25Mhz, 2 dividers */
+ case SSB_PLLTYPE_4: /* 48Mhz, 4 dividers */
+ case SSB_PLLTYPE_7: /* 25Mhz, 4 dividers */
+ n1 = clkfactor_f6_resolve(n1);
+ n2 += SSB_CHIPCO_CLK_F5_BIAS;
+ break;
+ case SSB_PLLTYPE_2: /* 48Mhz, 4 dividers */
+ n1 += SSB_CHIPCO_CLK_T2_BIAS;
+ n2 += SSB_CHIPCO_CLK_T2_BIAS;
+ SSB_WARN_ON(!((n1 >= 2) && (n1 <= 7)));
+ SSB_WARN_ON(!((n2 >= 5) && (n2 <= 23)));
+ break;
+ case SSB_PLLTYPE_5: /* 25Mhz, 4 dividers */
+ return 100000000;
+ default:
+ SSB_WARN_ON(1);
+ }
+
+ switch (plltype) {
+ case SSB_PLLTYPE_3: /* 25Mhz, 2 dividers */
+ case SSB_PLLTYPE_7: /* 25Mhz, 4 dividers */
+ clock = SSB_CHIPCO_CLK_BASE2 * n1 * n2;
+ break;
+ default:
+ clock = SSB_CHIPCO_CLK_BASE1 * n1 * n2;
+ }
+ if (!clock)
+ return 0;
+
+ m1 = (m & SSB_CHIPCO_CLK_M1);
+ m2 = ((m & SSB_CHIPCO_CLK_M2) >> SSB_CHIPCO_CLK_M2_SHIFT);
+ m3 = ((m & SSB_CHIPCO_CLK_M3) >> SSB_CHIPCO_CLK_M3_SHIFT);
+ mc = ((m & SSB_CHIPCO_CLK_MC) >> SSB_CHIPCO_CLK_MC_SHIFT);
+
+ switch (plltype) {
+ case SSB_PLLTYPE_1: /* 48Mhz base, 3 dividers */
+ case SSB_PLLTYPE_3: /* 25Mhz, 2 dividers */
+ case SSB_PLLTYPE_4: /* 48Mhz, 4 dividers */
+ case SSB_PLLTYPE_7: /* 25Mhz, 4 dividers */
+ m1 = clkfactor_f6_resolve(m1);
+ if ((plltype == SSB_PLLTYPE_1) ||
+ (plltype == SSB_PLLTYPE_3))
+ m2 += SSB_CHIPCO_CLK_F5_BIAS;
+ else
+ m2 = clkfactor_f6_resolve(m2);
+ m3 = clkfactor_f6_resolve(m3);
+
+ switch (mc) {
+ case SSB_CHIPCO_CLK_MC_BYPASS:
+ return clock;
+ case SSB_CHIPCO_CLK_MC_M1:
+ return (clock / m1);
+ case SSB_CHIPCO_CLK_MC_M1M2:
+ return (clock / (m1 * m2));
+ case SSB_CHIPCO_CLK_MC_M1M2M3:
+ return (clock / (m1 * m2 * m3));
+ case SSB_CHIPCO_CLK_MC_M1M3:
+ return (clock / (m1 * m3));
+ }
+ return 0;
+ case SSB_PLLTYPE_2:
+ m1 += SSB_CHIPCO_CLK_T2_BIAS;
+ m2 += SSB_CHIPCO_CLK_T2M2_BIAS;
+ m3 += SSB_CHIPCO_CLK_T2_BIAS;
+ SSB_WARN_ON(!((m1 >= 2) && (m1 <= 7)));
+ SSB_WARN_ON(!((m2 >= 3) && (m2 <= 10)));
+ SSB_WARN_ON(!((m3 >= 2) && (m3 <= 7)));
+
+ if (!(mc & SSB_CHIPCO_CLK_T2MC_M1BYP))
+ clock /= m1;
+ if (!(mc & SSB_CHIPCO_CLK_T2MC_M2BYP))
+ clock /= m2;
+ if (!(mc & SSB_CHIPCO_CLK_T2MC_M3BYP))
+ clock /= m3;
+ return clock;
+ default:
+ SSB_WARN_ON(1);
+ }
+ return 0;
+}
+
+/* Get the current speed the backplane is running at */
+u32 ssb_clockspeed(struct ssb_bus *bus)
+{
+ u32 rate;
+ u32 plltype;
+ u32 clkctl_n, clkctl_m;
+
+ //TODO if EXTIF: PLLTYPE == 1, read n from clockcontrol_n, m from clockcontrol_sb
+
+ if (bus->chipco.dev) {
+ ssb_chipco_get_clockcontrol(&bus->chipco, &plltype,
+ &clkctl_n, &clkctl_m);
+ } else
+ return 0;
+
+ if (bus->chip_id == 0x5365) {
+ rate = 100000000;
+ } else {
+ rate = ssb_calc_clock_rate(plltype, clkctl_n, clkctl_m);
+ if (plltype == SSB_PLLTYPE_3) /* 25Mhz, 2 dividers */
+ rate /= 2;
+ }
+
+ return rate;
+}
+EXPORT_SYMBOL(ssb_clockspeed);
+
+static u32 ssb_tmslow_reject_bitmask(struct ssb_device *dev)
+{
+ /* The REJECT bit changed position in TMSLOW between
+ * Backplane revisions. */
+ switch (ssb_read32(dev, SSB_IDLOW) & SSB_IDLOW_SSBREV) {
+ case SSB_IDLOW_SSBREV_22:
+ return SSB_TMSLOW_REJECT_22;
+ case SSB_IDLOW_SSBREV_23:
+ return SSB_TMSLOW_REJECT_23;
+ default:
+ WARN_ON(1);
+ }
+ return (SSB_TMSLOW_REJECT_22 | SSB_TMSLOW_REJECT_23);
+}
+
+int ssb_device_is_enabled(struct ssb_device *dev)
+{
+ u32 val;
+ u32 reject;
+
+ reject = ssb_tmslow_reject_bitmask(dev);
+ val = ssb_read32(dev, SSB_TMSLOW);
+ val &= SSB_TMSLOW_CLOCK | SSB_TMSLOW_RESET | reject;
+
+ return (val == SSB_TMSLOW_CLOCK);
+}
+EXPORT_SYMBOL(ssb_device_is_enabled);
+
+static void ssb_flush_tmslow(struct ssb_device *dev)
+{
+ /* Make _really_ sure the device has finished the TMSLOW
+ * register write transaction, as we risk running into
+ * a machine check exception otherwise.
+ * Do this by reading the register back to commit the
+ * PCI write and delay an additional usec for the device
+ * to react to the change. */
+ ssb_read32(dev, SSB_TMSLOW);
+ udelay(1);
+}
+
+void ssb_device_enable(struct ssb_device *dev, u32 core_specific_flags)
+{
+ u32 val;
+
+ ssb_device_disable(dev, core_specific_flags);
+ ssb_write32(dev, SSB_TMSLOW,
+ SSB_TMSLOW_RESET | SSB_TMSLOW_CLOCK |
+ SSB_TMSLOW_FGC | core_specific_flags);
+ ssb_flush_tmslow(dev);
+
+ /* Clear SERR if set. This is a hw bug workaround. */
+ if (ssb_read32(dev, SSB_TMSHIGH) & SSB_TMSHIGH_SERR)
+ ssb_write32(dev, SSB_TMSHIGH, 0);
+
+ val = ssb_read32(dev, SSB_IMSTATE);
+ if (val & (SSB_IMSTATE_IBE | SSB_IMSTATE_TO)) {
+ val &= ~(SSB_IMSTATE_IBE | SSB_IMSTATE_TO);
+ ssb_write32(dev, SSB_IMSTATE, val);
+ }
+
+ ssb_write32(dev, SSB_TMSLOW,
+ SSB_TMSLOW_CLOCK | SSB_TMSLOW_FGC |
+ core_specific_flags);
+ ssb_flush_tmslow(dev);
+
+ ssb_write32(dev, SSB_TMSLOW, SSB_TMSLOW_CLOCK |
+ core_specific_flags);
+ ssb_flush_tmslow(dev);
+}
+EXPORT_SYMBOL(ssb_device_enable);
+
+/* Wait for a bit in a register to get set or unset.
+ * timeout is in units of ten-microseconds */
+static int ssb_wait_bit(struct ssb_device *dev, u16 reg, u32 bitmask,
+ int timeout, int set)
+{
+ int i;
+ u32 val;
+
+ for (i = 0; i < timeout; i++) {
+ val = ssb_read32(dev, reg);
+ if (set) {
+ if (val & bitmask)
+ return 0;
+ } else {
+ if (!(val & bitmask))
+ return 0;
+ }
+ udelay(10);
+ }
+ printk(KERN_ERR PFX "Timeout waiting for bitmask %08X on "
+ "register %04X to %s.\n",
+ bitmask, reg, (set ? "set" : "clear"));
+
+ return -ETIMEDOUT;
+}
+
+void ssb_device_disable(struct ssb_device *dev, u32 core_specific_flags)
+{
+ u32 reject;
+
+ if (ssb_read32(dev, SSB_TMSLOW) & SSB_TMSLOW_RESET)
+ return;
+
+ reject = ssb_tmslow_reject_bitmask(dev);
+ ssb_write32(dev, SSB_TMSLOW, reject | SSB_TMSLOW_CLOCK);
+ ssb_wait_bit(dev, SSB_TMSLOW, reject, 1000, 1);
+ ssb_wait_bit(dev, SSB_TMSHIGH, SSB_TMSHIGH_BUSY, 1000, 0);
+ ssb_write32(dev, SSB_TMSLOW,
+ SSB_TMSLOW_FGC | SSB_TMSLOW_CLOCK |
+ reject | SSB_TMSLOW_RESET |
+ core_specific_flags);
+ ssb_flush_tmslow(dev);
+
+ ssb_write32(dev, SSB_TMSLOW,
+ reject | SSB_TMSLOW_RESET |
+ core_specific_flags);
+ ssb_flush_tmslow(dev);
+}
+EXPORT_SYMBOL(ssb_device_disable);
+
+u32 ssb_dma_translation(struct ssb_device *dev)
+{
+ switch (dev->bus->bustype) {
+ case SSB_BUSTYPE_SSB:
+ return 0;
+ case SSB_BUSTYPE_PCI:
+ case SSB_BUSTYPE_PCMCIA:
+ return SSB_PCI_DMA;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(ssb_dma_translation);
+
+int ssb_dma_set_mask(struct ssb_device *ssb_dev, u64 mask)
+{
+ struct device *dev = ssb_dev->dev;
+
+#ifdef CONFIG_SSB_PCIHOST
+ if (ssb_dev->bus->bustype == SSB_BUSTYPE_PCI &&
+ !dma_supported(dev, mask))
+ return -EIO;
+#endif
+ dev->coherent_dma_mask = mask;
+ dev->dma_mask = &dev->coherent_dma_mask;
+
+ return 0;
+}
+EXPORT_SYMBOL(ssb_dma_set_mask);
+
+int ssb_bus_may_powerdown(struct ssb_bus *bus)
+{
+ struct ssb_chipcommon *cc;
+ int err;
+
+ /* On buses where more than one core may be working
+ * at a time, we must not powerdown stuff if there are
+ * still cores that may want to run. */
+ if (bus->bustype == SSB_BUSTYPE_SSB)
+ return 0;
+
+ cc = &bus->chipco;
+ ssb_chipco_set_clockmode(cc, SSB_CLKMODE_SLOW);
+ err = ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0);
+ if (err)
+ goto error;
+
+ return 0;
+error:
+ ssb_printk(KERN_ERR PFX "Bus powerdown failed\n");
+ return err;
+}
+EXPORT_SYMBOL(ssb_bus_may_powerdown);
+
+int ssb_bus_powerup(struct ssb_bus *bus, bool dynamic_pctl)
+{
+ struct ssb_chipcommon *cc;
+ int err;
+ enum ssb_clkmode mode;
+
+ err = ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 1);
+ if (err)
+ goto error;
+ cc = &bus->chipco;
+ mode = dynamic_pctl ? SSB_CLKMODE_DYNAMIC : SSB_CLKMODE_FAST;
+ ssb_chipco_set_clockmode(cc, mode);
+
+ return 0;
+error:
+ ssb_printk(KERN_ERR PFX "Bus powerup failed\n");
+ return err;
+}
+EXPORT_SYMBOL(ssb_bus_powerup);
+
+u32 ssb_admatch_base(u32 adm)
+{
+ u32 base = 0;
+
+ switch (adm & SSB_ADM_TYPE) {
+ case SSB_ADM_TYPE0:
+ base = (adm & SSB_ADM_BASE0);
+ break;
+ case SSB_ADM_TYPE1:
+ SSB_WARN_ON(adm & SSB_ADM_NEG); /* unsupported */
+ base = (adm & SSB_ADM_BASE1);
+ break;
+ case SSB_ADM_TYPE2:
+ SSB_WARN_ON(adm & SSB_ADM_NEG); /* unsupported */
+ base = (adm & SSB_ADM_BASE2);
+ break;
+ default:
+ SSB_WARN_ON(1);
+ }
+
+ return base;
+}
+EXPORT_SYMBOL(ssb_admatch_base);
+
+u32 ssb_admatch_size(u32 adm)
+{
+ u32 size = 0;
+
+ switch (adm & SSB_ADM_TYPE) {
+ case SSB_ADM_TYPE0:
+ size = ((adm & SSB_ADM_SZ0) >> SSB_ADM_SZ0_SHIFT);
+ break;
+ case SSB_ADM_TYPE1:
+ SSB_WARN_ON(adm & SSB_ADM_NEG); /* unsupported */
+ size = ((adm & SSB_ADM_SZ1) >> SSB_ADM_SZ1_SHIFT);
+ break;
+ case SSB_ADM_TYPE2:
+ SSB_WARN_ON(adm & SSB_ADM_NEG); /* unsupported */
+ size = ((adm & SSB_ADM_SZ2) >> SSB_