[PATCH 2] Merge the Sonics Silicon Backplane subsystem

!MAILaRCHIVE_VOTE_RePLACE
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]
To: Andrew Morton <akpm@...>
Cc: linux-kernel <linux-kernel@...>, <bcm43xx-dev@...>, <netdev@...>, <linux-wireless@...>, Gary Zambrano <zambrano@...>, John Linville <linville@...>
Date: Friday, July 27, 2007 - 4:55 pm

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_
Previous message: [thread] [date] [author]
Next message: [thread] [date] [author]

Messages in current thread:
[PATCH 2] Merge the Sonics Silicon Backplane subsystem, Michael Buesch, (Fri Jul 27, 4:55 pm)