diff options
Diffstat (limited to 'drivers/ssb')
| -rw-r--r-- | drivers/ssb/Kconfig | 75 | ||||
| -rw-r--r-- | drivers/ssb/Makefile | 6 | ||||
| -rw-r--r-- | drivers/ssb/b43_pci_bridge.c | 11 | ||||
| -rw-r--r-- | drivers/ssb/driver_chipcommon.c | 303 | ||||
| -rw-r--r-- | drivers/ssb/driver_chipcommon_pmu.c | 716 | ||||
| -rw-r--r-- | drivers/ssb/driver_chipcommon_sflash.c | 164 | ||||
| -rw-r--r-- | drivers/ssb/driver_extif.c | 69 | ||||
| -rw-r--r-- | drivers/ssb/driver_gige.c | 297 | ||||
| -rw-r--r-- | drivers/ssb/driver_gpio.c | 484 | ||||
| -rw-r--r-- | drivers/ssb/driver_mipscore.c | 181 | ||||
| -rw-r--r-- | drivers/ssb/driver_pcicore.c | 429 | ||||
| -rw-r--r-- | drivers/ssb/embedded.c | 128 | ||||
| -rw-r--r-- | drivers/ssb/main.c | 710 | ||||
| -rw-r--r-- | drivers/ssb/pci.c | 802 | ||||
| -rw-r--r-- | drivers/ssb/pcihost_wrapper.c | 25 | ||||
| -rw-r--r-- | drivers/ssb/pcmcia.c | 699 | ||||
| -rw-r--r-- | drivers/ssb/scan.c | 75 | ||||
| -rw-r--r-- | drivers/ssb/sdio.c | 606 | ||||
| -rw-r--r-- | drivers/ssb/sprom.c | 200 | ||||
| -rw-r--r-- | drivers/ssb/ssb_private.h | 177 |
20 files changed, 5368 insertions, 789 deletions
diff --git a/drivers/ssb/Kconfig b/drivers/ssb/Kconfig index adea792fb67..75b3603906c 100644 --- a/drivers/ssb/Kconfig +++ b/drivers/ssb/Kconfig @@ -1,10 +1,11 @@ -menu "Sonics Silicon Backplane" - config SSB_POSSIBLE bool - depends on HAS_IOMEM + depends on HAS_IOMEM && HAS_DMA default y +menu "Sonics Silicon Backplane" + depends on SSB_POSSIBLE + config SSB tristate "Sonics Silicon Backplane support" depends on SSB_POSSIBLE @@ -20,6 +21,15 @@ config SSB If unsure, say N. +# Common SPROM support routines +config SSB_SPROM + bool + +# Support for Block-I/O. SELECT this from the driver that needs it. +config SSB_BLOCKIO + bool + depends on SSB + config SSB_PCIHOST_POSSIBLE bool depends on SSB && (PCI = y || PCI = SSB) @@ -28,6 +38,7 @@ config SSB_PCIHOST_POSSIBLE config SSB_PCIHOST bool "Support for SSB on PCI-bus host" depends on SSB_PCIHOST_POSSIBLE + select SSB_SPROM default y help Support for a Sonics Silicon Backplane on top @@ -42,21 +53,36 @@ config SSB_B43_PCI_BRIDGE config SSB_PCMCIAHOST_POSSIBLE bool - depends on SSB && (PCMCIA = y || PCMCIA = SSB) && EXPERIMENTAL + depends on SSB && (PCMCIA = y || PCMCIA = SSB) default y config SSB_PCMCIAHOST - bool "Support for SSB on PCMCIA-bus host (EXPERIMENTAL)" + bool "Support for SSB on PCMCIA-bus host" depends on SSB_PCMCIAHOST_POSSIBLE + select SSB_SPROM help Support for a Sonics Silicon Backplane on top of a PCMCIA device. If unsure, say N +config SSB_SDIOHOST_POSSIBLE + bool + depends on SSB && (MMC = y || MMC = SSB) + default y + +config SSB_SDIOHOST + bool "Support for SSB on SDIO-bus host" + depends on SSB_SDIOHOST_POSSIBLE + help + Support for a Sonics Silicon Backplane on top + of a SDIO device. + + If unsure, say N + config SSB_SILENT bool "No SSB kernel messages" - depends on SSB && EMBEDDED + depends on SSB && EXPERT help This option turns off all Sonics Silicon Backplane printks. Note that you won't be able to identify problems, once @@ -95,14 +121,14 @@ config SSB_DRIVER_PCICORE If unsure, say Y config SSB_PCICORE_HOSTMODE - bool "Hostmode support for SSB PCI core (EXPERIMENTAL)" - depends on SSB_DRIVER_PCICORE && SSB_DRIVER_MIPS && EXPERIMENTAL + 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 (EXPERIMENTAL)" - depends on SSB && MIPS && EXPERIMENTAL + bool "SSB Broadcom MIPS core driver" + depends on SSB && MIPS select SSB_SERIAL help Driver for the Sonics Silicon Backplane attached @@ -110,19 +136,42 @@ config SSB_DRIVER_MIPS If unsure, say N +config SSB_SFLASH + bool "SSB serial flash support" + depends on SSB_DRIVER_MIPS + default y + # Assumption: We are on embedded, if we compile the MIPS core. config SSB_EMBEDDED bool - depends on SSB_DRIVER_MIPS + depends on SSB_DRIVER_MIPS && SSB_PCICORE_HOSTMODE default y config SSB_DRIVER_EXTIF - bool "SSB Broadcom EXTIF core driver (EXPERIMENTAL)" - depends on SSB_DRIVER_MIPS && EXPERIMENTAL + 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 +config SSB_DRIVER_GIGE + bool "SSB Broadcom Gigabit Ethernet driver" + depends on SSB_PCIHOST_POSSIBLE && SSB_EMBEDDED && MIPS + help + Driver for the Sonics Silicon Backplane attached + Broadcom Gigabit Ethernet. + + If unsure, say N + +config SSB_DRIVER_GPIO + bool "SSB GPIO driver" + depends on SSB && GPIOLIB + select IRQ_DOMAIN if SSB_EMBEDDED + help + Driver to provide access to the GPIO pins on the bus. + + If unsure, say N + endmenu diff --git a/drivers/ssb/Makefile b/drivers/ssb/Makefile index de94c2eb7a3..b1ddc116d38 100644 --- a/drivers/ssb/Makefile +++ b/drivers/ssb/Makefile @@ -1,16 +1,22 @@ # core ssb-y += main.o scan.o ssb-$(CONFIG_SSB_EMBEDDED) += embedded.o +ssb-$(CONFIG_SSB_SPROM) += sprom.o # host support ssb-$(CONFIG_SSB_PCIHOST) += pci.o pcihost_wrapper.o ssb-$(CONFIG_SSB_PCMCIAHOST) += pcmcia.o +ssb-$(CONFIG_SSB_SDIOHOST) += sdio.o # built-in drivers ssb-y += driver_chipcommon.o +ssb-y += driver_chipcommon_pmu.o +ssb-$(CONFIG_SSB_SFLASH) += driver_chipcommon_sflash.o ssb-$(CONFIG_SSB_DRIVER_MIPS) += driver_mipscore.o ssb-$(CONFIG_SSB_DRIVER_EXTIF) += driver_extif.o ssb-$(CONFIG_SSB_DRIVER_PCICORE) += driver_pcicore.o +ssb-$(CONFIG_SSB_DRIVER_GIGE) += driver_gige.o +ssb-$(CONFIG_SSB_DRIVER_GPIO) += driver_gpio.o # b43 pci-ssb-bridge driver # Not strictly a part of SSB, but kept here for convenience diff --git a/drivers/ssb/b43_pci_bridge.c b/drivers/ssb/b43_pci_bridge.c index 2d27d6d6d08..19396dc4ee4 100644 --- a/drivers/ssb/b43_pci_bridge.c +++ b/drivers/ssb/b43_pci_bridge.c @@ -5,12 +5,13 @@ * because of its small size we include it in the SSB core * instead of creating a standalone module. * - * Copyright 2007 Michael Buesch <mb@bu3sch.de> + * Copyright 2007 Michael Buesch <m@bues.ch> * * Licensed under the GNU/GPL. See COPYING for details. */ #include <linux/pci.h> +#include <linux/module.h> #include <linux/ssb/ssb.h> #include "ssb_private.h" @@ -18,17 +19,25 @@ static const struct pci_device_id b43_pci_bridge_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4301) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4306) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4307) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4311) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4312) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4315) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4318) }, + { PCI_DEVICE(PCI_VENDOR_ID_BCM_GVC, 0x4318) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4319) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4320) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4321) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4322) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 43222) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4324) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4325) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4328) }, { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4329) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x432b) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x432c) }, + { PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4350) }, { 0, }, }; MODULE_DEVICE_TABLE(pci, b43_pci_bridge_tbl); diff --git a/drivers/ssb/driver_chipcommon.c b/drivers/ssb/driver_chipcommon.c index e586321a473..7cb7d2c8fd8 100644 --- a/drivers/ssb/driver_chipcommon.c +++ b/drivers/ssb/driver_chipcommon.c @@ -3,14 +3,17 @@ * Broadcom ChipCommon core driver * * Copyright 2005, Broadcom Corporation - * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de> + * Copyright 2006, 2007, Michael Buesch <m@bues.ch> + * Copyright 2012, Hauke Mehrtens <hauke@hauke-m.de> * * Licensed under the GNU/GPL. See COPYING for details. */ #include <linux/ssb/ssb.h> #include <linux/ssb/ssb_regs.h> +#include <linux/export.h> #include <linux/pci.h> +#include <linux/bcm47xx_wdt.h> #include "ssb_private.h" @@ -26,19 +29,6 @@ enum ssb_clksrc { }; -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); -} - static inline u32 chipco_write32_masked(struct ssb_chipcommon *cc, u16 offset, u32 mask, u32 value) { @@ -59,40 +49,66 @@ void ssb_chipco_set_clockmode(struct ssb_chipcommon *cc, if (!ccdev) return; bus = ccdev->bus; + + /* We support SLOW only on 6..9 */ + if (ccdev->id.revision >= 10 && mode == SSB_CLKMODE_SLOW) + mode = SSB_CLKMODE_DYNAMIC; + + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) + return; /* PMU controls clockmode, separated function needed */ + SSB_WARN_ON(ccdev->id.revision >= 20); + /* 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 */ + + /* ChipCommon cores rev10+ need testing */ if (ccdev->id.revision >= 10) return; + if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL)) return; switch (mode) { - case SSB_CLKMODE_SLOW: + case SSB_CLKMODE_SLOW: /* For revs 6..9 only */ 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); + if (ccdev->id.revision < 10) { + 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); + } else { + chipco_write32(cc, SSB_CHIPCO_SYSCLKCTL, + (chipco_read32(cc, SSB_CHIPCO_SYSCLKCTL) | + SSB_CHIPCO_SYSCLKCTL_FORCEHT)); + /* udelay(150); TODO: not available in early init */ + } 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); + if (ccdev->id.revision < 10) { + 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); + } else { + chipco_write32(cc, SSB_CHIPCO_SYSCLKCTL, + (chipco_read32(cc, SSB_CHIPCO_SYSCLKCTL) & + ~SSB_CHIPCO_SYSCLKCTL_FORCEHT)); + } break; default: SSB_WARN_ON(1); @@ -222,6 +238,24 @@ static void chipco_powercontrol_init(struct ssb_chipcommon *cc) } } +/* http://bcm-v4.sipsolutions.net/802.11/PmuFastPwrupDelay */ +static u16 pmu_fast_powerup_delay(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + + switch (bus->chip_id) { + case 0x4312: + case 0x4322: + case 0x4328: + return 7000; + case 0x4325: + /* TODO: */ + default: + return 15000; + } +} + +/* http://bcm-v4.sipsolutions.net/802.11/ClkctlFastPwrupDelay */ static void calc_fast_powerup_delay(struct ssb_chipcommon *cc) { struct ssb_bus *bus = cc->dev->bus; @@ -231,6 +265,12 @@ static void calc_fast_powerup_delay(struct ssb_chipcommon *cc) if (bus->bustype != SSB_BUSTYPE_PCI) return; + + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) { + cc->fast_pwrup_delay = pmu_fast_powerup_delay(cc); + return; + } + if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL)) return; @@ -242,16 +282,97 @@ static void calc_fast_powerup_delay(struct ssb_chipcommon *cc) cc->fast_pwrup_delay = tmp; } +static u32 ssb_chipco_alp_clock(struct ssb_chipcommon *cc) +{ + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) + return ssb_pmu_get_alp_clock(cc); + + return 20000000; +} + +static u32 ssb_chipco_watchdog_get_max_timer(struct ssb_chipcommon *cc) +{ + u32 nb; + + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) { + if (cc->dev->id.revision < 26) + nb = 16; + else + nb = (cc->dev->id.revision >= 37) ? 32 : 24; + } else { + nb = 28; + } + if (nb == 32) + return 0xffffffff; + else + return (1 << nb) - 1; +} + +u32 ssb_chipco_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks) +{ + struct ssb_chipcommon *cc = bcm47xx_wdt_get_drvdata(wdt); + + if (cc->dev->bus->bustype != SSB_BUSTYPE_SSB) + return 0; + + return ssb_chipco_watchdog_timer_set(cc, ticks); +} + +u32 ssb_chipco_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms) +{ + struct ssb_chipcommon *cc = bcm47xx_wdt_get_drvdata(wdt); + u32 ticks; + + if (cc->dev->bus->bustype != SSB_BUSTYPE_SSB) + return 0; + + ticks = ssb_chipco_watchdog_timer_set(cc, cc->ticks_per_ms * ms); + return ticks / cc->ticks_per_ms; +} + +static int ssb_chipco_watchdog_ticks_per_ms(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) { + /* based on 32KHz ILP clock */ + return 32; + } else { + if (cc->dev->id.revision < 18) + return ssb_clockspeed(bus) / 1000; + else + return ssb_chipco_alp_clock(cc) / 1000; + } +} + void ssb_chipcommon_init(struct ssb_chipcommon *cc) { if (!cc->dev) return; /* We don't have a ChipCommon */ + + spin_lock_init(&cc->gpio_lock); + + if (cc->dev->id.revision >= 11) + cc->status = chipco_read32(cc, SSB_CHIPCO_CHIPSTAT); + ssb_dbg("chipcommon status is 0x%x\n", cc->status); + + if (cc->dev->id.revision >= 20) { + chipco_write32(cc, SSB_CHIPCO_GPIOPULLUP, 0); + chipco_write32(cc, SSB_CHIPCO_GPIOPULLDOWN, 0); + } + + ssb_pmu_init(cc); chipco_powercontrol_init(cc); ssb_chipco_set_clockmode(cc, SSB_CLKMODE_FAST); calc_fast_powerup_delay(cc); + + if (cc->dev->bus->bustype == SSB_BUSTYPE_SSB) { + cc->ticks_per_ms = ssb_chipco_watchdog_ticks_per_ms(cc); + cc->max_timer_ms = ssb_chipco_watchdog_get_max_timer(cc) / cc->ticks_per_ms; + } } -void ssb_chipco_suspend(struct ssb_chipcommon *cc, pm_message_t state) +void ssb_chipco_suspend(struct ssb_chipcommon *cc) { if (!cc->dev) return; @@ -347,10 +468,37 @@ void ssb_chipco_timing_init(struct ssb_chipcommon *cc, } /* Set chip watchdog reset timer to fire in 'ticks' backplane cycles */ -void ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, u32 ticks) +u32 ssb_chipco_watchdog_timer_set(struct ssb_chipcommon *cc, u32 ticks) +{ + u32 maxt; + enum ssb_clkmode clkmode; + + maxt = ssb_chipco_watchdog_get_max_timer(cc); + if (cc->capabilities & SSB_CHIPCO_CAP_PMU) { + if (ticks == 1) + ticks = 2; + else if (ticks > maxt) + ticks = maxt; + chipco_write32(cc, SSB_CHIPCO_PMU_WATCHDOG, ticks); + } else { + clkmode = ticks ? SSB_CLKMODE_FAST : SSB_CLKMODE_DYNAMIC; + ssb_chipco_set_clockmode(cc, clkmode); + if (ticks > maxt) + ticks = maxt; + /* instant NMI */ + chipco_write32(cc, SSB_CHIPCO_WATCHDOG, ticks); + } + return ticks; +} + +void ssb_chipco_irq_mask(struct ssb_chipcommon *cc, u32 mask, u32 value) +{ + chipco_write32_masked(cc, SSB_CHIPCO_IRQMASK, mask, value); +} + +u32 ssb_chipco_irq_status(struct ssb_chipcommon *cc, u32 mask) { - /* instant NMI */ - chipco_write32(cc, SSB_CHIPCO_WATCHDOG, ticks); + return chipco_read32(cc, SSB_CHIPCO_IRQSTAT) & mask; } u32 ssb_chipco_gpio_in(struct ssb_chipcommon *cc, u32 mask) @@ -360,27 +508,93 @@ u32 ssb_chipco_gpio_in(struct ssb_chipcommon *cc, u32 mask) u32 ssb_chipco_gpio_out(struct ssb_chipcommon *cc, u32 mask, u32 value) { - return chipco_write32_masked(cc, SSB_CHIPCO_GPIOOUT, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOOUT, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; } u32 ssb_chipco_gpio_outen(struct ssb_chipcommon *cc, u32 mask, u32 value) { - return chipco_write32_masked(cc, SSB_CHIPCO_GPIOOUTEN, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOOUTEN, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; } u32 ssb_chipco_gpio_control(struct ssb_chipcommon *cc, u32 mask, u32 value) { - return chipco_write32_masked(cc, SSB_CHIPCO_GPIOCTL, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOCTL, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; } +EXPORT_SYMBOL(ssb_chipco_gpio_control); u32 ssb_chipco_gpio_intmask(struct ssb_chipcommon *cc, u32 mask, u32 value) { - return chipco_write32_masked(cc, SSB_CHIPCO_GPIOIRQ, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOIRQ, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; } u32 ssb_chipco_gpio_polarity(struct ssb_chipcommon *cc, u32 mask, u32 value) { - return chipco_write32_masked(cc, SSB_CHIPCO_GPIOPOL, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOPOL, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; +} + +u32 ssb_chipco_gpio_pullup(struct ssb_chipcommon *cc, u32 mask, u32 value) +{ + unsigned long flags; + u32 res = 0; + + if (cc->dev->id.revision < 20) + return 0xffffffff; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOPULLUP, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; +} + +u32 ssb_chipco_gpio_pulldown(struct ssb_chipcommon *cc, u32 mask, u32 value) +{ + unsigned long flags; + u32 res = 0; + + if (cc->dev->id.revision < 20) + return 0xffffffff; + + spin_lock_irqsave(&cc->gpio_lock, flags); + res = chipco_write32_masked(cc, SSB_CHIPCO_GPIOPULLDOWN, mask, value); + spin_unlock_irqrestore(&cc->gpio_lock, flags); + + return res; } #ifdef CONFIG_SSB_SERIAL @@ -414,12 +628,7 @@ int ssb_chipco_serial_init(struct ssb_chipcommon *cc, chipco_read32(cc, SSB_CHIPCO_CORECTL) | SSB_CHIPCO_CORECTL_UARTCLK0); } else if ((ccrev >= 11) && (ccrev != 15)) { - /* Fixed ALP clock */ - baud_base = 20000000; - if (cc->capabilities & SSB_CHIPCO_CAP_PMU) { - /* FIXME: baud_base is different for devices with a PMU */ - SSB_WARN_ON(1); - } + baud_base = ssb_chipco_alp_clock(cc); div = 1; if (ccrev >= 21) { /* Turn off UART clock before switching clocksource. */ diff --git a/drivers/ssb/driver_chipcommon_pmu.c b/drivers/ssb/driver_chipcommon_pmu.c new file mode 100644 index 00000000000..1173a091b40 --- /dev/null +++ b/drivers/ssb/driver_chipcommon_pmu.c @@ -0,0 +1,716 @@ +/* + * Sonics Silicon Backplane + * Broadcom ChipCommon Power Management Unit driver + * + * Copyright 2009, Michael Buesch <m@bues.ch> + * Copyright 2007, Broadcom Corporation + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include <linux/ssb/ssb.h> +#include <linux/ssb/ssb_regs.h> +#include <linux/ssb/ssb_driver_chipcommon.h> +#include <linux/delay.h> +#include <linux/export.h> +#ifdef CONFIG_BCM47XX +#include <bcm47xx_nvram.h> +#endif + +#include "ssb_private.h" + +static u32 ssb_chipco_pll_read(struct ssb_chipcommon *cc, u32 offset) +{ + chipco_write32(cc, SSB_CHIPCO_PLLCTL_ADDR, offset); + return chipco_read32(cc, SSB_CHIPCO_PLLCTL_DATA); +} + +static void ssb_chipco_pll_write(struct ssb_chipcommon *cc, + u32 offset, u32 value) +{ + chipco_write32(cc, SSB_CHIPCO_PLLCTL_ADDR, offset); + chipco_write32(cc, SSB_CHIPCO_PLLCTL_DATA, value); +} + +static void ssb_chipco_regctl_maskset(struct ssb_chipcommon *cc, + u32 offset, u32 mask, u32 set) +{ + u32 value; + + chipco_read32(cc, SSB_CHIPCO_REGCTL_ADDR); + chipco_write32(cc, SSB_CHIPCO_REGCTL_ADDR, offset); + chipco_read32(cc, SSB_CHIPCO_REGCTL_ADDR); + value = chipco_read32(cc, SSB_CHIPCO_REGCTL_DATA); + value &= mask; + value |= set; + chipco_write32(cc, SSB_CHIPCO_REGCTL_DATA, value); + chipco_read32(cc, SSB_CHIPCO_REGCTL_DATA); +} + +struct pmu0_plltab_entry { + u16 freq; /* Crystal frequency in kHz.*/ + u8 xf; /* Crystal frequency value for PMU control */ + u8 wb_int; + u32 wb_frac; +}; + +static const struct pmu0_plltab_entry pmu0_plltab[] = { + { .freq = 12000, .xf = 1, .wb_int = 73, .wb_frac = 349525, }, + { .freq = 13000, .xf = 2, .wb_int = 67, .wb_frac = 725937, }, + { .freq = 14400, .xf = 3, .wb_int = 61, .wb_frac = 116508, }, + { .freq = 15360, .xf = 4, .wb_int = 57, .wb_frac = 305834, }, + { .freq = 16200, .xf = 5, .wb_int = 54, .wb_frac = 336579, }, + { .freq = 16800, .xf = 6, .wb_int = 52, .wb_frac = 399457, }, + { .freq = 19200, .xf = 7, .wb_int = 45, .wb_frac = 873813, }, + { .freq = 19800, .xf = 8, .wb_int = 44, .wb_frac = 466033, }, + { .freq = 20000, .xf = 9, .wb_int = 44, .wb_frac = 0, }, + { .freq = 25000, .xf = 10, .wb_int = 70, .wb_frac = 419430, }, + { .freq = 26000, .xf = 11, .wb_int = 67, .wb_frac = 725937, }, + { .freq = 30000, .xf = 12, .wb_int = 58, .wb_frac = 699050, }, + { .freq = 38400, .xf = 13, .wb_int = 45, .wb_frac = 873813, }, + { .freq = 40000, .xf = 14, .wb_int = 45, .wb_frac = 0, }, +}; +#define SSB_PMU0_DEFAULT_XTALFREQ 20000 + +static const struct pmu0_plltab_entry * pmu0_plltab_find_entry(u32 crystalfreq) +{ + const struct pmu0_plltab_entry *e; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pmu0_plltab); i++) { + e = &pmu0_plltab[i]; + if (e->freq == crystalfreq) + return e; + } + + return NULL; +} + +/* Tune the PLL to the crystal speed. crystalfreq is in kHz. */ +static void ssb_pmu0_pllinit_r0(struct ssb_chipcommon *cc, + u32 crystalfreq) +{ + struct ssb_bus *bus = cc->dev->bus; + const struct pmu0_plltab_entry *e = NULL; + u32 pmuctl, tmp, pllctl; + unsigned int i; + + if (crystalfreq) + e = pmu0_plltab_find_entry(crystalfreq); + if (!e) + e = pmu0_plltab_find_entry(SSB_PMU0_DEFAULT_XTALFREQ); + BUG_ON(!e); + crystalfreq = e->freq; + cc->pmu.crystalfreq = e->freq; + + /* Check if the PLL already is programmed to this frequency. */ + pmuctl = chipco_read32(cc, SSB_CHIPCO_PMU_CTL); + if (((pmuctl & SSB_CHIPCO_PMU_CTL_XTALFREQ) >> SSB_CHIPCO_PMU_CTL_XTALFREQ_SHIFT) == e->xf) { + /* We're already there... */ + return; + } + + ssb_info("Programming PLL to %u.%03u MHz\n", + crystalfreq / 1000, crystalfreq % 1000); + + /* First turn the PLL off. */ + switch (bus->chip_id) { + case 0x4328: + chipco_mask32(cc, SSB_CHIPCO_PMU_MINRES_MSK, + ~(1 << SSB_PMURES_4328_BB_PLL_PU)); + chipco_mask32(cc, SSB_CHIPCO_PMU_MAXRES_MSK, + ~(1 << SSB_PMURES_4328_BB_PLL_PU)); + break; + case 0x5354: + chipco_mask32(cc, SSB_CHIPCO_PMU_MINRES_MSK, + ~(1 << SSB_PMURES_5354_BB_PLL_PU)); + chipco_mask32(cc, SSB_CHIPCO_PMU_MAXRES_MSK, + ~(1 << SSB_PMURES_5354_BB_PLL_PU)); + break; + default: + SSB_WARN_ON(1); + } + for (i = 1500; i; i--) { + tmp = chipco_read32(cc, SSB_CHIPCO_CLKCTLST); + if (!(tmp & SSB_CHIPCO_CLKCTLST_HAVEHT)) + break; + udelay(10); + } + tmp = chipco_read32(cc, SSB_CHIPCO_CLKCTLST); + if (tmp & SSB_CHIPCO_CLKCTLST_HAVEHT) + ssb_emerg("Failed to turn the PLL off!\n"); + + /* Set PDIV in PLL control 0. */ + pllctl = ssb_chipco_pll_read(cc, SSB_PMU0_PLLCTL0); + if (crystalfreq >= SSB_PMU0_PLLCTL0_PDIV_FREQ) + pllctl |= SSB_PMU0_PLLCTL0_PDIV_MSK; + else + pllctl &= ~SSB_PMU0_PLLCTL0_PDIV_MSK; + ssb_chipco_pll_write(cc, SSB_PMU0_PLLCTL0, pllctl); + + /* Set WILD in PLL control 1. */ + pllctl = ssb_chipco_pll_read(cc, SSB_PMU0_PLLCTL1); + pllctl &= ~SSB_PMU0_PLLCTL1_STOPMOD; + pllctl &= ~(SSB_PMU0_PLLCTL1_WILD_IMSK | SSB_PMU0_PLLCTL1_WILD_FMSK); + pllctl |= ((u32)e->wb_int << SSB_PMU0_PLLCTL1_WILD_IMSK_SHIFT) & SSB_PMU0_PLLCTL1_WILD_IMSK; + pllctl |= ((u32)e->wb_frac << SSB_PMU0_PLLCTL1_WILD_FMSK_SHIFT) & SSB_PMU0_PLLCTL1_WILD_FMSK; + if (e->wb_frac == 0) + pllctl |= SSB_PMU0_PLLCTL1_STOPMOD; + ssb_chipco_pll_write(cc, SSB_PMU0_PLLCTL1, pllctl); + + /* Set WILD in PLL control 2. */ + pllctl = ssb_chipco_pll_read(cc, SSB_PMU0_PLLCTL2); + pllctl &= ~SSB_PMU0_PLLCTL2_WILD_IMSKHI; + pllctl |= (((u32)e->wb_int >> 4) << SSB_PMU0_PLLCTL2_WILD_IMSKHI_SHIFT) & SSB_PMU0_PLLCTL2_WILD_IMSKHI; + ssb_chipco_pll_write(cc, SSB_PMU0_PLLCTL2, pllctl); + + /* Set the crystalfrequency and the divisor. */ + pmuctl = chipco_read32(cc, SSB_CHIPCO_PMU_CTL); + pmuctl &= ~SSB_CHIPCO_PMU_CTL_ILP_DIV; + pmuctl |= (((crystalfreq + 127) / 128 - 1) << SSB_CHIPCO_PMU_CTL_ILP_DIV_SHIFT) + & SSB_CHIPCO_PMU_CTL_ILP_DIV; + pmuctl &= ~SSB_CHIPCO_PMU_CTL_XTALFREQ; + pmuctl |= ((u32)e->xf << SSB_CHIPCO_PMU_CTL_XTALFREQ_SHIFT) & SSB_CHIPCO_PMU_CTL_XTALFREQ; + chipco_write32(cc, SSB_CHIPCO_PMU_CTL, pmuctl); +} + +struct pmu1_plltab_entry { + u16 freq; /* Crystal frequency in kHz.*/ + u8 xf; /* Crystal frequency value for PMU control */ + u8 ndiv_int; + u32 ndiv_frac; + u8 p1div; + u8 p2div; +}; + +static const struct pmu1_plltab_entry pmu1_plltab[] = { + { .freq = 12000, .xf = 1, .p1div = 3, .p2div = 22, .ndiv_int = 0x9, .ndiv_frac = 0xFFFFEF, }, + { .freq = 13000, .xf = 2, .p1div = 1, .p2div = 6, .ndiv_int = 0xb, .ndiv_frac = 0x483483, }, + { .freq = 14400, .xf = 3, .p1div = 1, .p2div = 10, .ndiv_int = 0xa, .ndiv_frac = 0x1C71C7, }, + { .freq = 15360, .xf = 4, .p1div = 1, .p2div = 5, .ndiv_int = 0xb, .ndiv_frac = 0x755555, }, + { .freq = 16200, .xf = 5, .p1div = 1, .p2div = 10, .ndiv_int = 0x5, .ndiv_frac = 0x6E9E06, }, + { .freq = 16800, .xf = 6, .p1div = 1, .p2div = 10, .ndiv_int = 0x5, .ndiv_frac = 0x3CF3CF, }, + { .freq = 19200, .xf = 7, .p1div = 1, .p2div = 9, .ndiv_int = 0x5, .ndiv_frac = 0x17B425, }, + { .freq = 19800, .xf = 8, .p1div = 1, .p2div = 11, .ndiv_int = 0x4, .ndiv_frac = 0xA57EB, }, + { .freq = 20000, .xf = 9, .p1div = 1, .p2div = 11, .ndiv_int = 0x4, .ndiv_frac = 0, }, + { .freq = 24000, .xf = 10, .p1div = 3, .p2div = 11, .ndiv_int = 0xa, .ndiv_frac = 0, }, + { .freq = 25000, .xf = 11, .p1div = 5, .p2div = 16, .ndiv_int = 0xb, .ndiv_frac = 0, }, + { .freq = 26000, .xf = 12, .p1div = 1, .p2div = 2, .ndiv_int = 0x10, .ndiv_frac = 0xEC4EC4, }, + { .freq = 30000, .xf = 13, .p1div = 3, .p2div = 8, .ndiv_int = 0xb, .ndiv_frac = 0, }, + { .freq = 38400, .xf = 14, .p1div = 1, .p2div = 5, .ndiv_int = 0x4, .ndiv_frac = 0x955555, }, + { .freq = 40000, .xf = 15, .p1div = 1, .p2div = 2, .ndiv_int = 0xb, .ndiv_frac = 0, }, +}; + +#define SSB_PMU1_DEFAULT_XTALFREQ 15360 + +static const struct pmu1_plltab_entry * pmu1_plltab_find_entry(u32 crystalfreq) +{ + const struct pmu1_plltab_entry *e; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(pmu1_plltab); i++) { + e = &pmu1_plltab[i]; + if (e->freq == crystalfreq) + return e; + } + + return NULL; +} + +/* Tune the PLL to the crystal speed. crystalfreq is in kHz. */ +static void ssb_pmu1_pllinit_r0(struct ssb_chipcommon *cc, + u32 crystalfreq) +{ + struct ssb_bus *bus = cc->dev->bus; + const struct pmu1_plltab_entry *e = NULL; + u32 buffer_strength = 0; + u32 tmp, pllctl, pmuctl; + unsigned int i; + + if (bus->chip_id == 0x4312) { + /* We do not touch the BCM4312 PLL and assume + * the default crystal settings work out-of-the-box. */ + cc->pmu.crystalfreq = 20000; + return; + } + + if (crystalfreq) + e = pmu1_plltab_find_entry(crystalfreq); + if (!e) + e = pmu1_plltab_find_entry(SSB_PMU1_DEFAULT_XTALFREQ); + BUG_ON(!e); + crystalfreq = e->freq; + cc->pmu.crystalfreq = e->freq; + + /* Check if the PLL already is programmed to this frequency. */ + pmuctl = chipco_read32(cc, SSB_CHIPCO_PMU_CTL); + if (((pmuctl & SSB_CHIPCO_PMU_CTL_XTALFREQ) >> SSB_CHIPCO_PMU_CTL_XTALFREQ_SHIFT) == e->xf) { + /* We're already there... */ + return; + } + + ssb_info("Programming PLL to %u.%03u MHz\n", + crystalfreq / 1000, crystalfreq % 1000); + + /* First turn the PLL off. */ + switch (bus->chip_id) { + case 0x4325: + chipco_mask32(cc, SSB_CHIPCO_PMU_MINRES_MSK, + ~((1 << SSB_PMURES_4325_BBPLL_PWRSW_PU) | + (1 << SSB_PMURES_4325_HT_AVAIL))); + chipco_mask32(cc, SSB_CHIPCO_PMU_MAXRES_MSK, + ~((1 << SSB_PMURES_4325_BBPLL_PWRSW_PU) | + (1 << SSB_PMURES_4325_HT_AVAIL))); + /* Adjust the BBPLL to 2 on all channels later. */ + buffer_strength = 0x222222; + break; + default: + SSB_WARN_ON(1); + } + for (i = 1500; i; i--) { + tmp = chipco_read32(cc, SSB_CHIPCO_CLKCTLST); + if (!(tmp & SSB_CHIPCO_CLKCTLST_HAVEHT)) + break; + udelay(10); + } + tmp = chipco_read32(cc, SSB_CHIPCO_CLKCTLST); + if (tmp & SSB_CHIPCO_CLKCTLST_HAVEHT) + ssb_emerg("Failed to turn the PLL off!\n"); + + /* Set p1div and p2div. */ + pllctl = ssb_chipco_pll_read(cc, SSB_PMU1_PLLCTL0); + pllctl &= ~(SSB_PMU1_PLLCTL0_P1DIV | SSB_PMU1_PLLCTL0_P2DIV); + pllctl |= ((u32)e->p1div << SSB_PMU1_PLLCTL0_P1DIV_SHIFT) & SSB_PMU1_PLLCTL0_P1DIV; + pllctl |= ((u32)e->p2div << SSB_PMU1_PLLCTL0_P2DIV_SHIFT) & SSB_PMU1_PLLCTL0_P2DIV; + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL0, pllctl); + + /* Set ndiv int and ndiv mode */ + pllctl = ssb_chipco_pll_read(cc, SSB_PMU1_PLLCTL2); + pllctl &= ~(SSB_PMU1_PLLCTL2_NDIVINT | SSB_PMU1_PLLCTL2_NDIVMODE); + pllctl |= ((u32)e->ndiv_int << SSB_PMU1_PLLCTL2_NDIVINT_SHIFT) & SSB_PMU1_PLLCTL2_NDIVINT; + pllctl |= (1 << SSB_PMU1_PLLCTL2_NDIVMODE_SHIFT) & SSB_PMU1_PLLCTL2_NDIVMODE; + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL2, pllctl); + + /* Set ndiv frac */ + pllctl = ssb_chipco_pll_read(cc, SSB_PMU1_PLLCTL3); + pllctl &= ~SSB_PMU1_PLLCTL3_NDIVFRAC; + pllctl |= ((u32)e->ndiv_frac << SSB_PMU1_PLLCTL3_NDIVFRAC_SHIFT) & SSB_PMU1_PLLCTL3_NDIVFRAC; + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL3, pllctl); + + /* Change the drive strength, if required. */ + if (buffer_strength) { + pllctl = ssb_chipco_pll_read(cc, SSB_PMU1_PLLCTL5); + pllctl &= ~SSB_PMU1_PLLCTL5_CLKDRV; + pllctl |= (buffer_strength << SSB_PMU1_PLLCTL5_CLKDRV_SHIFT) & SSB_PMU1_PLLCTL5_CLKDRV; + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL5, pllctl); + } + + /* Tune the crystalfreq and the divisor. */ + pmuctl = chipco_read32(cc, SSB_CHIPCO_PMU_CTL); + pmuctl &= ~(SSB_CHIPCO_PMU_CTL_ILP_DIV | SSB_CHIPCO_PMU_CTL_XTALFREQ); + pmuctl |= ((((u32)e->freq + 127) / 128 - 1) << SSB_CHIPCO_PMU_CTL_ILP_DIV_SHIFT) + & SSB_CHIPCO_PMU_CTL_ILP_DIV; + pmuctl |= ((u32)e->xf << SSB_CHIPCO_PMU_CTL_XTALFREQ_SHIFT) & SSB_CHIPCO_PMU_CTL_XTALFREQ; + chipco_write32(cc, SSB_CHIPCO_PMU_CTL, pmuctl); +} + +static void ssb_pmu_pll_init(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + u32 crystalfreq = 0; /* in kHz. 0 = keep default freq. */ + + if (bus->bustype == SSB_BUSTYPE_SSB) { +#ifdef CONFIG_BCM47XX + char buf[20]; + if (bcm47xx_nvram_getenv("xtalfreq", buf, sizeof(buf)) >= 0) + crystalfreq = simple_strtoul(buf, NULL, 0); +#endif + } + + switch (bus->chip_id) { + case 0x4312: + case 0x4325: + ssb_pmu1_pllinit_r0(cc, crystalfreq); + break; + case 0x4328: + ssb_pmu0_pllinit_r0(cc, crystalfreq); + break; + case 0x5354: + if (crystalfreq == 0) + crystalfreq = 25000; + ssb_pmu0_pllinit_r0(cc, crystalfreq); + break; + case 0x4322: + if (cc->pmu.rev == 2) { + chipco_write32(cc, SSB_CHIPCO_PLLCTL_ADDR, 0x0000000A); + chipco_write32(cc, SSB_CHIPCO_PLLCTL_DATA, 0x380005C0); + } + break; + case 43222: + break; + default: + ssb_err("ERROR: PLL init unknown for device %04X\n", + bus->chip_id); + } +} + +struct pmu_res_updown_tab_entry { + u8 resource; /* The resource number */ + u16 updown; /* The updown value */ +}; + +enum pmu_res_depend_tab_task { + PMU_RES_DEP_SET = 1, + PMU_RES_DEP_ADD, + PMU_RES_DEP_REMOVE, +}; + +struct pmu_res_depend_tab_entry { + u8 resource; /* The resource number */ + u8 task; /* SET | ADD | REMOVE */ + u32 depend; /* The depend mask */ +}; + +static const struct pmu_res_updown_tab_entry pmu_res_updown_tab_4328a0[] = { + { .resource = SSB_PMURES_4328_EXT_SWITCHER_PWM, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_BB_SWITCHER_PWM, .updown = 0x1F01, }, + { .resource = SSB_PMURES_4328_BB_SWITCHER_BURST, .updown = 0x010F, }, + { .resource = SSB_PMURES_4328_BB_EXT_SWITCHER_BURST, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_ILP_REQUEST, .updown = 0x0202, }, + { .resource = SSB_PMURES_4328_RADIO_SWITCHER_PWM, .updown = 0x0F01, }, + { .resource = SSB_PMURES_4328_RADIO_SWITCHER_BURST, .updown = 0x0F01, }, + { .resource = SSB_PMURES_4328_ROM_SWITCH, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_PA_REF_LDO, .updown = 0x0F01, }, + { .resource = SSB_PMURES_4328_RADIO_LDO, .updown = 0x0F01, }, + { .resource = SSB_PMURES_4328_AFE_LDO, .updown = 0x0F01, }, + { .resource = SSB_PMURES_4328_PLL_LDO, .updown = 0x0F01, }, + { .resource = SSB_PMURES_4328_BG_FILTBYP, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_TX_FILTBYP, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_RX_FILTBYP, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_XTAL_PU, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_XTAL_EN, .updown = 0xA001, }, + { .resource = SSB_PMURES_4328_BB_PLL_FILTBYP, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_RF_PLL_FILTBYP, .updown = 0x0101, }, + { .resource = SSB_PMURES_4328_BB_PLL_PU, .updown = 0x0701, }, +}; + +static const struct pmu_res_depend_tab_entry pmu_res_depend_tab_4328a0[] = { + { + /* Adjust ILP Request to avoid forcing EXT/BB into burst mode. */ + .resource = SSB_PMURES_4328_ILP_REQUEST, + .task = PMU_RES_DEP_SET, + .depend = ((1 << SSB_PMURES_4328_EXT_SWITCHER_PWM) | + (1 << SSB_PMURES_4328_BB_SWITCHER_PWM)), + }, +}; + +static const struct pmu_res_updown_tab_entry pmu_res_updown_tab_4325a0[] = { + { .resource = SSB_PMURES_4325_XTAL_PU, .updown = 0x1501, }, +}; + +static const struct pmu_res_depend_tab_entry pmu_res_depend_tab_4325a0[] = { + { + /* Adjust HT-Available dependencies. */ + .resource = SSB_PMURES_4325_HT_AVAIL, + .task = PMU_RES_DEP_ADD, + .depend = ((1 << SSB_PMURES_4325_RX_PWRSW_PU) | + (1 << SSB_PMURES_4325_TX_PWRSW_PU) | + (1 << SSB_PMURES_4325_LOGEN_PWRSW_PU) | + (1 << SSB_PMURES_4325_AFE_PWRSW_PU)), + }, +}; + +static void ssb_pmu_resources_init(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + u32 min_msk = 0, max_msk = 0; + unsigned int i; + const struct pmu_res_updown_tab_entry *updown_tab = NULL; + unsigned int updown_tab_size = 0; + const struct pmu_res_depend_tab_entry *depend_tab = NULL; + unsigned int depend_tab_size = 0; + + switch (bus->chip_id) { + case 0x4312: + min_msk = 0xCBB; + break; + case 0x4322: + case 43222: + /* We keep the default settings: + * min_msk = 0xCBB + * max_msk = 0x7FFFF + */ + break; + case 0x4325: + /* Power OTP down later. */ + min_msk = (1 << SSB_PMURES_4325_CBUCK_BURST) | + (1 << SSB_PMURES_4325_LNLDO2_PU); + if (chipco_read32(cc, SSB_CHIPCO_CHIPSTAT) & + SSB_CHIPCO_CHST_4325_PMUTOP_2B) + min_msk |= (1 << SSB_PMURES_4325_CLDO_CBUCK_BURST); + /* The PLL may turn on, if it decides so. */ + max_msk = 0xFFFFF; + updown_tab = pmu_res_updown_tab_4325a0; + updown_tab_size = ARRAY_SIZE(pmu_res_updown_tab_4325a0); + depend_tab = pmu_res_depend_tab_4325a0; + depend_tab_size = ARRAY_SIZE(pmu_res_depend_tab_4325a0); + break; + case 0x4328: + min_msk = (1 << SSB_PMURES_4328_EXT_SWITCHER_PWM) | + (1 << SSB_PMURES_4328_BB_SWITCHER_PWM) | + (1 << SSB_PMURES_4328_XTAL_EN); + /* The PLL may turn on, if it decides so. */ + max_msk = 0xFFFFF; + updown_tab = pmu_res_updown_tab_4328a0; + updown_tab_size = ARRAY_SIZE(pmu_res_updown_tab_4328a0); + depend_tab = pmu_res_depend_tab_4328a0; + depend_tab_size = ARRAY_SIZE(pmu_res_depend_tab_4328a0); + break; + case 0x5354: + /* The PLL may turn on, if it decides so. */ + max_msk = 0xFFFFF; + break; + default: + ssb_err("ERROR: PMU resource config unknown for device %04X\n", + bus->chip_id); + } + + if (updown_tab) { + for (i = 0; i < updown_tab_size; i++) { + chipco_write32(cc, SSB_CHIPCO_PMU_RES_TABSEL, + updown_tab[i].resource); + chipco_write32(cc, SSB_CHIPCO_PMU_RES_UPDNTM, + updown_tab[i].updown); + } + } + if (depend_tab) { + for (i = 0; i < depend_tab_size; i++) { + chipco_write32(cc, SSB_CHIPCO_PMU_RES_TABSEL, + depend_tab[i].resource); + switch (depend_tab[i].task) { + case PMU_RES_DEP_SET: + chipco_write32(cc, SSB_CHIPCO_PMU_RES_DEPMSK, + depend_tab[i].depend); + break; + case PMU_RES_DEP_ADD: + chipco_set32(cc, SSB_CHIPCO_PMU_RES_DEPMSK, + depend_tab[i].depend); + break; + case PMU_RES_DEP_REMOVE: + chipco_mask32(cc, SSB_CHIPCO_PMU_RES_DEPMSK, + ~(depend_tab[i].depend)); + break; + default: + SSB_WARN_ON(1); + } + } + } + + /* Set the resource masks. */ + if (min_msk) + chipco_write32(cc, SSB_CHIPCO_PMU_MINRES_MSK, min_msk); + if (max_msk) + chipco_write32(cc, SSB_CHIPCO_PMU_MAXRES_MSK, max_msk); +} + +/* http://bcm-v4.sipsolutions.net/802.11/SSB/PmuInit */ +void ssb_pmu_init(struct ssb_chipcommon *cc) +{ + u32 pmucap; + + if (!(cc->capabilities & SSB_CHIPCO_CAP_PMU)) + return; + + pmucap = chipco_read32(cc, SSB_CHIPCO_PMU_CAP); + cc->pmu.rev = (pmucap & SSB_CHIPCO_PMU_CAP_REVISION); + + ssb_dbg("Found rev %u PMU (capabilities 0x%08X)\n", + cc->pmu.rev, pmucap); + + if (cc->pmu.rev == 1) + chipco_mask32(cc, SSB_CHIPCO_PMU_CTL, + ~SSB_CHIPCO_PMU_CTL_NOILPONW); + else + chipco_set32(cc, SSB_CHIPCO_PMU_CTL, + SSB_CHIPCO_PMU_CTL_NOILPONW); + ssb_pmu_pll_init(cc); + ssb_pmu_resources_init(cc); +} + +void ssb_pmu_set_ldo_voltage(struct ssb_chipcommon *cc, + enum ssb_pmu_ldo_volt_id id, u32 voltage) +{ + struct ssb_bus *bus = cc->dev->bus; + u32 addr, shift, mask; + + switch (bus->chip_id) { + case 0x4328: + case 0x5354: + switch (id) { + case LDO_VOLT1: + addr = 2; + shift = 25; + mask = 0xF; + break; + case LDO_VOLT2: + addr = 3; + shift = 1; + mask = 0xF; + break; + case LDO_VOLT3: + addr = 3; + shift = 9; + mask = 0xF; + break; + case LDO_PAREF: + addr = 3; + shift = 17; + mask = 0x3F; + break; + default: + SSB_WARN_ON(1); + return; + } + break; + case 0x4312: + if (SSB_WARN_ON(id != LDO_PAREF)) + return; + addr = 0; + shift = 21; + mask = 0x3F; + break; + default: + return; + } + + ssb_chipco_regctl_maskset(cc, addr, ~(mask << shift), + (voltage & mask) << shift); +} + +void ssb_pmu_set_ldo_paref(struct ssb_chipcommon *cc, bool on) +{ + struct ssb_bus *bus = cc->dev->bus; + int ldo; + + switch (bus->chip_id) { + case 0x4312: + ldo = SSB_PMURES_4312_PA_REF_LDO; + break; + case 0x4328: + ldo = SSB_PMURES_4328_PA_REF_LDO; + break; + case 0x5354: + ldo = SSB_PMURES_5354_PA_REF_LDO; + break; + default: + return; + } + + if (on) + chipco_set32(cc, SSB_CHIPCO_PMU_MINRES_MSK, 1 << ldo); + else + chipco_mask32(cc, SSB_CHIPCO_PMU_MINRES_MSK, ~(1 << ldo)); + chipco_read32(cc, SSB_CHIPCO_PMU_MINRES_MSK); //SPEC FIXME found via mmiotrace - dummy read? +} + +EXPORT_SYMBOL(ssb_pmu_set_ldo_voltage); +EXPORT_SYMBOL(ssb_pmu_set_ldo_paref); + +static u32 ssb_pmu_get_alp_clock_clk0(struct ssb_chipcommon *cc) +{ + u32 crystalfreq; + const struct pmu0_plltab_entry *e = NULL; + + crystalfreq = chipco_read32(cc, SSB_CHIPCO_PMU_CTL) & + SSB_CHIPCO_PMU_CTL_XTALFREQ >> SSB_CHIPCO_PMU_CTL_XTALFREQ_SHIFT; + e = pmu0_plltab_find_entry(crystalfreq); + BUG_ON(!e); + return e->freq * 1000; +} + +u32 ssb_pmu_get_alp_clock(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + + switch (bus->chip_id) { + case 0x5354: + ssb_pmu_get_alp_clock_clk0(cc); + default: + ssb_err("ERROR: PMU alp clock unknown for device %04X\n", + bus->chip_id); + return 0; + } +} + +u32 ssb_pmu_get_cpu_clock(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + + switch (bus->chip_id) { + case 0x5354: + /* 5354 chip uses a non programmable PLL of frequency 240MHz */ + return 240000000; + default: + ssb_err("ERROR: PMU cpu clock unknown for device %04X\n", + bus->chip_id); + return 0; + } +} + +u32 ssb_pmu_get_controlclock(struct ssb_chipcommon *cc) +{ + struct ssb_bus *bus = cc->dev->bus; + + switch (bus->chip_id) { + case 0x5354: + return 120000000; + default: + ssb_err("ERROR: PMU controlclock unknown for device %04X\n", + bus->chip_id); + return 0; + } +} + +void ssb_pmu_spuravoid_pllupdate(struct ssb_chipcommon *cc, int spuravoid) +{ + u32 pmu_ctl = 0; + + switch (cc->dev->bus->chip_id) { + case 0x4322: + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL0, 0x11100070); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL1, 0x1014140a); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL5, 0x88888854); + if (spuravoid == 1) + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL2, 0x05201828); + else + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL2, 0x05001828); + pmu_ctl = SSB_CHIPCO_PMU_CTL_PLL_UPD; + break; + case 43222: + if (spuravoid == 1) { + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL0, 0x11500008); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL1, 0x0C000C06); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL2, 0x0F600a08); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL3, 0x00000000); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL4, 0x2001E920); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL5, 0x88888815); + } else { + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL0, 0x11100008); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL1, 0x0c000c06); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL2, 0x03000a08); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL3, 0x00000000); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL4, 0x200005c0); + ssb_chipco_pll_write(cc, SSB_PMU1_PLLCTL5, 0x88888855); + } + pmu_ctl = SSB_CHIPCO_PMU_CTL_PLL_UPD; + break; + default: + ssb_printk(KERN_ERR PFX + "Unknown spuravoidance settings for chip 0x%04X, not changing PLL\n", + cc->dev->bus->chip_id); + return; + } + + chipco_set32(cc, SSB_CHIPCO_PMU_CTL, pmu_ctl); +} +EXPORT_SYMBOL_GPL(ssb_pmu_spuravoid_pllupdate); diff --git a/drivers/ssb/driver_chipcommon_sflash.c b/drivers/ssb/driver_chipcommon_sflash.c new file mode 100644 index 00000000000..937fc31971a --- /dev/null +++ b/drivers/ssb/driver_chipcommon_sflash.c @@ -0,0 +1,164 @@ +/* + * Sonics Silicon Backplane + * ChipCommon serial flash interface + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include <linux/ssb/ssb.h> + +#include "ssb_private.h" + +static struct resource ssb_sflash_resource = { + .name = "ssb_sflash", + .start = SSB_FLASH2, + .end = 0, + .flags = IORESOURCE_MEM | IORESOURCE_READONLY, +}; + +struct platform_device ssb_sflash_dev = { + .name = "ssb_sflash", + .resource = &ssb_sflash_resource, + .num_resources = 1, +}; + +struct ssb_sflash_tbl_e { + char *name; + u32 id; + u32 blocksize; + u16 numblocks; +}; + +static const struct ssb_sflash_tbl_e ssb_sflash_st_tbl[] = { + { "M25P20", 0x11, 0x10000, 4, }, + { "M25P40", 0x12, 0x10000, 8, }, + + { "M25P16", 0x14, 0x10000, 32, }, + { "M25P32", 0x15, 0x10000, 64, }, + { "M25P64", 0x16, 0x10000, 128, }, + { "M25FL128", 0x17, 0x10000, 256, }, + { NULL }, +}; + +static const struct ssb_sflash_tbl_e ssb_sflash_sst_tbl[] = { + { "SST25WF512", 1, 0x1000, 16, }, + { "SST25VF512", 0x48, 0x1000, 16, }, + { "SST25WF010", 2, 0x1000, 32, }, + { "SST25VF010", 0x49, 0x1000, 32, }, + { "SST25WF020", 3, 0x1000, 64, }, + { "SST25VF020", 0x43, 0x1000, 64, }, + { "SST25WF040", 4, 0x1000, 128, }, + { "SST25VF040", 0x44, 0x1000, 128, }, + { "SST25VF040B", 0x8d, 0x1000, 128, }, + { "SST25WF080", 5, 0x1000, 256, }, + { "SST25VF080B", 0x8e, 0x1000, 256, }, + { "SST25VF016", 0x41, 0x1000, 512, }, + { "SST25VF032", 0x4a, 0x1000, 1024, }, + { "SST25VF064", 0x4b, 0x1000, 2048, }, + { NULL }, +}; + +static const struct ssb_sflash_tbl_e ssb_sflash_at_tbl[] = { + { "AT45DB011", 0xc, 256, 512, }, + { "AT45DB021", 0x14, 256, 1024, }, + { "AT45DB041", 0x1c, 256, 2048, }, + { "AT45DB081", 0x24, 256, 4096, }, + { "AT45DB161", 0x2c, 512, 4096, }, + { "AT45DB321", 0x34, 512, 8192, }, + { "AT45DB642", 0x3c, 1024, 8192, }, + { NULL }, +}; + +static void ssb_sflash_cmd(struct ssb_chipcommon *cc, u32 opcode) +{ + int i; + chipco_write32(cc, SSB_CHIPCO_FLASHCTL, + SSB_CHIPCO_FLASHCTL_START | opcode); + for (i = 0; i < 1000; i++) { + if (!(chipco_read32(cc, SSB_CHIPCO_FLASHCTL) & + SSB_CHIPCO_FLASHCTL_BUSY)) + return; + cpu_relax(); + } + pr_err("SFLASH control command failed (timeout)!\n"); +} + +/* Initialize serial flash access */ +int ssb_sflash_init(struct ssb_chipcommon *cc) +{ + struct ssb_sflash *sflash = &cc->dev->bus->mipscore.sflash; + const struct ssb_sflash_tbl_e *e; + u32 id, id2; + + switch (cc->capabilities & SSB_CHIPCO_CAP_FLASHT) { + case SSB_CHIPCO_FLASHT_STSER: + ssb_sflash_cmd(cc, SSB_CHIPCO_FLASHCTL_ST_DP); + + chipco_write32(cc, SSB_CHIPCO_FLASHADDR, 0); + ssb_sflash_cmd(cc, SSB_CHIPCO_FLASHCTL_ST_RES); + id = chipco_read32(cc, SSB_CHIPCO_FLASHDATA); + + chipco_write32(cc, SSB_CHIPCO_FLASHADDR, 1); + ssb_sflash_cmd(cc, SSB_CHIPCO_FLASHCTL_ST_RES); + id2 = chipco_read32(cc, SSB_CHIPCO_FLASHDATA); + + switch (id) { + case 0xbf: + for (e = ssb_sflash_sst_tbl; e->name; e++) { + if (e->id == id2) + break; + } + break; + case 0x13: + return -ENOTSUPP; + default: + for (e = ssb_sflash_st_tbl; e->name; e++) { + if (e->id == id) + break; + } + break; + } + if (!e->name) { + pr_err("Unsupported ST serial flash (id: 0x%X, id2: 0x%X)\n", + id, id2); + return -ENOTSUPP; + } + + break; + case SSB_CHIPCO_FLASHT_ATSER: + ssb_sflash_cmd(cc, SSB_CHIPCO_FLASHCTL_AT_STATUS); + id = chipco_read32(cc, SSB_CHIPCO_FLASHDATA) & 0x3c; + + for (e = ssb_sflash_at_tbl; e->name; e++) { + if (e->id == id) + break; + } + if (!e->name) { + pr_err("Unsupported Atmel serial flash (id: 0x%X)\n", + id); + return -ENOTSUPP; + } + + break; + default: + pr_err("Unsupported flash type\n"); + return -ENOTSUPP; + } + + sflash->window = SSB_FLASH2; + sflash->blocksize = e->blocksize; + sflash->numblocks = e->numblocks; + sflash->size = sflash->blocksize * sflash->numblocks; + sflash->present = true; + + pr_info("Found %s serial flash (size: %dKiB, blocksize: 0x%X, blocks: %d)\n", + e->name, sflash->size / 1024, e->blocksize, e->numblocks); + + /* Prepare platform device, but don't register it yet. It's too early, + * malloc (required by device_private_init) is not available yet. */ + ssb_sflash_dev.resource[0].end = ssb_sflash_dev.resource[0].start + + sflash->size; + ssb_sflash_dev.dev.platform_data = sflash; + + return 0; +} diff --git a/drivers/ssb/driver_extif.c b/drivers/ssb/driver_extif.c index c3e1d3e6d61..59385fdab5b 100644 --- a/drivers/ssb/driver_extif.c +++ b/drivers/ssb/driver_extif.c @@ -3,7 +3,7 @@ * Broadcom EXTIF core driver * * Copyright 2005, Broadcom Corporation - * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de> + * Copyright 2006, 2007, Michael Buesch <m@bues.ch> * Copyright 2006, 2007, Felix Fietkau <nbd@openwrt.org> * Copyright 2007, Aurelien Jarno <aurelien@aurel32.net> * @@ -112,10 +112,37 @@ void ssb_extif_get_clockcontrol(struct ssb_extif *extif, *m = extif_read32(extif, SSB_EXTIF_CLOCK_SB); } -void ssb_extif_watchdog_timer_set(struct ssb_extif *extif, - u32 ticks) +u32 ssb_extif_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks) { + struct ssb_extif *extif = bcm47xx_wdt_get_drvdata(wdt); + + return ssb_extif_watchdog_timer_set(extif, ticks); +} + +u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms) +{ + struct ssb_extif *extif = bcm47xx_wdt_get_drvdata(wdt); + u32 ticks = (SSB_EXTIF_WATCHDOG_CLK / 1000) * ms; + + ticks = ssb_extif_watchdog_timer_set(extif, ticks); + + return (ticks * 1000) / SSB_EXTIF_WATCHDOG_CLK; +} + +u32 ssb_extif_watchdog_timer_set(struct ssb_extif *extif, u32 ticks) +{ + if (ticks > SSB_EXTIF_WATCHDOG_MAX_TIMER) + ticks = SSB_EXTIF_WATCHDOG_MAX_TIMER; extif_write32(extif, SSB_EXTIF_WATCHDOG, ticks); + + return ticks; +} + +void ssb_extif_init(struct ssb_extif *extif) +{ + if (!extif->dev) + return; /* We don't have a Extif core */ + spin_lock_init(&extif->gpio_lock); } u32 ssb_extif_gpio_in(struct ssb_extif *extif, u32 mask) @@ -125,22 +152,50 @@ u32 ssb_extif_gpio_in(struct ssb_extif *extif, u32 mask) u32 ssb_extif_gpio_out(struct ssb_extif *extif, u32 mask, u32 value) { - return extif_write32_masked(extif, SSB_EXTIF_GPIO_OUT(0), + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&extif->gpio_lock, flags); + res = extif_write32_masked(extif, SSB_EXTIF_GPIO_OUT(0), mask, value); + spin_unlock_irqrestore(&extif->gpio_lock, flags); + + return res; } u32 ssb_extif_gpio_outen(struct ssb_extif *extif, u32 mask, u32 value) { - return extif_write32_masked(extif, SSB_EXTIF_GPIO_OUTEN(0), + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&extif->gpio_lock, flags); + res = extif_write32_masked(extif, SSB_EXTIF_GPIO_OUTEN(0), mask, value); + spin_unlock_irqrestore(&extif->gpio_lock, flags); + + return res; } u32 ssb_extif_gpio_polarity(struct ssb_extif *extif, u32 mask, u32 value) { - return extif_write32_masked(extif, SSB_EXTIF_GPIO_INTPOL, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&extif->gpio_lock, flags); + res = extif_write32_masked(extif, SSB_EXTIF_GPIO_INTPOL, mask, value); + spin_unlock_irqrestore(&extif->gpio_lock, flags); + + return res; } u32 ssb_extif_gpio_intmask(struct ssb_extif *extif, u32 mask, u32 value) { - return extif_write32_masked(extif, SSB_EXTIF_GPIO_INTMASK, mask, value); + unsigned long flags; + u32 res = 0; + + spin_lock_irqsave(&extif->gpio_lock, flags); + res = extif_write32_masked(extif, SSB_EXTIF_GPIO_INTMASK, mask, value); + spin_unlock_irqrestore(&extif->gpio_lock, flags); + + return res; } diff --git a/drivers/ssb/driver_gige.c b/drivers/ssb/driver_gige.c new file mode 100644 index 00000000000..21f71a1581f --- /dev/null +++ b/drivers/ssb/driver_gige.c @@ -0,0 +1,297 @@ +/* + * Sonics Silicon Backplane + * Broadcom Gigabit Ethernet core driver + * + * Copyright 2008, Broadcom Corporation + * Copyright 2008, Michael Buesch <m@bues.ch> + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include <linux/ssb/ssb.h> +#include <linux/ssb/ssb_driver_gige.h> +#include <linux/export.h> +#include <linux/pci.h> +#include <linux/pci_regs.h> +#include <linux/slab.h> + + +/* +MODULE_DESCRIPTION("SSB Broadcom Gigabit Ethernet driver"); +MODULE_AUTHOR("Michael Buesch"); +MODULE_LICENSE("GPL"); +*/ + +static const struct ssb_device_id ssb_gige_tbl[] = { + SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_ETHERNET_GBIT, SSB_ANY_REV), + SSB_DEVTABLE_END +}; +/* MODULE_DEVICE_TABLE(ssb, ssb_gige_tbl); */ + + +static inline u8 gige_read8(struct ssb_gige *dev, u16 offset) +{ + return ssb_read8(dev->dev, offset); +} + +static inline u16 gige_read16(struct ssb_gige *dev, u16 offset) +{ + return ssb_read16(dev->dev, offset); +} + +static inline u32 gige_read32(struct ssb_gige *dev, u16 offset) +{ + return ssb_read32(dev->dev, offset); +} + +static inline void gige_write8(struct ssb_gige *dev, + u16 offset, u8 value) +{ + ssb_write8(dev->dev, offset, value); +} + +static inline void gige_write16(struct ssb_gige *dev, + u16 offset, u16 value) +{ + ssb_write16(dev->dev, offset, value); +} + +static inline void gige_write32(struct ssb_gige *dev, + u16 offset, u32 value) +{ + ssb_write32(dev->dev, offset, value); +} + +static inline +u8 gige_pcicfg_read8(struct ssb_gige *dev, unsigned int offset) +{ + BUG_ON(offset >= 256); + return gige_read8(dev, SSB_GIGE_PCICFG + offset); +} + +static inline +u16 gige_pcicfg_read16(struct ssb_gige *dev, unsigned int offset) +{ + BUG_ON(offset >= 256); + return gige_read16(dev, SSB_GIGE_PCICFG + offset); +} + +static inline +u32 gige_pcicfg_read32(struct ssb_gige *dev, unsigned int offset) +{ + BUG_ON(offset >= 256); + return gige_read32(dev, SSB_GIGE_PCICFG + offset); +} + +static inline +void gige_pcicfg_write8(struct ssb_gige *dev, + unsigned int offset, u8 value) +{ + BUG_ON(offset >= 256); + gige_write8(dev, SSB_GIGE_PCICFG + offset, value); +} + +static inline +void gige_pcicfg_write16(struct ssb_gige *dev, + unsigned int offset, u16 value) +{ + BUG_ON(offset >= 256); + gige_write16(dev, SSB_GIGE_PCICFG + offset, value); +} + +static inline +void gige_pcicfg_write32(struct ssb_gige *dev, + unsigned int offset, u32 value) +{ + BUG_ON(offset >= 256); + gige_write32(dev, SSB_GIGE_PCICFG + offset, value); +} + +static int ssb_gige_pci_read_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 *val) +{ + struct ssb_gige *dev = container_of(bus->ops, struct ssb_gige, pci_ops); + unsigned long flags; + + if ((PCI_SLOT(devfn) > 0) || (PCI_FUNC(devfn) > 0)) + return PCIBIOS_DEVICE_NOT_FOUND; + if (reg >= 256) + return PCIBIOS_DEVICE_NOT_FOUND; + + spin_lock_irqsave(&dev->lock, flags); + switch (size) { + case 1: + *val = gige_pcicfg_read8(dev, reg); + break; + case 2: + *val = gige_pcicfg_read16(dev, reg); + break; + case 4: + *val = gige_pcicfg_read32(dev, reg); + break; + default: + WARN_ON(1); + } + spin_unlock_irqrestore(&dev->lock, flags); + + return PCIBIOS_SUCCESSFUL; +} + +static int ssb_gige_pci_write_config(struct pci_bus *bus, unsigned int devfn, + int reg, int size, u32 val) +{ + struct ssb_gige *dev = container_of(bus->ops, struct ssb_gige, pci_ops); + unsigned long flags; + + if ((PCI_SLOT(devfn) > 0) || (PCI_FUNC(devfn) > 0)) + return PCIBIOS_DEVICE_NOT_FOUND; + if (reg >= 256) + return PCIBIOS_DEVICE_NOT_FOUND; + + spin_lock_irqsave(&dev->lock, flags); + switch (size) { + case 1: + gige_pcicfg_write8(dev, reg, val); + break; + case 2: + gige_pcicfg_write16(dev, reg, val); + break; + case 4: + gige_pcicfg_write32(dev, reg, val); + break; + default: + WARN_ON(1); + } + spin_unlock_irqrestore(&dev->lock, flags); + + return PCIBIOS_SUCCESSFUL; +} + +static int ssb_gige_probe(struct ssb_device *sdev, + const struct ssb_device_id *id) +{ + struct ssb_gige *dev; + u32 base, tmslow, tmshigh; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->dev = sdev; + + spin_lock_init(&dev->lock); + dev->pci_controller.pci_ops = &dev->pci_ops; + dev->pci_controller.io_resource = &dev->io_resource; + dev->pci_controller.mem_resource = &dev->mem_resource; + dev->pci_controller.io_map_base = 0x800; + dev->pci_ops.read = ssb_gige_pci_read_config; + dev->pci_ops.write = ssb_gige_pci_write_config; + + dev->io_resource.name = SSB_GIGE_IO_RES_NAME; + dev->io_resource.start = 0x800; + dev->io_resource.end = 0x8FF; + dev->io_resource.flags = IORESOURCE_IO | IORESOURCE_PCI_FIXED; + + if (!ssb_device_is_enabled(sdev)) + ssb_device_enable(sdev, 0); + + /* Setup BAR0. This is a 64k MMIO region. */ + base = ssb_admatch_base(ssb_read32(sdev, SSB_ADMATCH1)); + gige_pcicfg_write32(dev, PCI_BASE_ADDRESS_0, base); + gige_pcicfg_write32(dev, PCI_BASE_ADDRESS_1, 0); + + dev->mem_resource.name = SSB_GIGE_MEM_RES_NAME; + dev->mem_resource.start = base; + dev->mem_resource.end = base + 0x10000 - 1; + dev->mem_resource.flags = IORESOURCE_MEM | IORESOURCE_PCI_FIXED; + + /* Enable the memory region. */ + gige_pcicfg_write16(dev, PCI_COMMAND, + gige_pcicfg_read16(dev, PCI_COMMAND) + | PCI_COMMAND_MEMORY); + + /* Write flushing is controlled by the Flush Status Control register. + * We want to flush every register write with a timeout and we want + * to disable the IRQ mask while flushing to avoid concurrency. + * Note that automatic write flushing does _not_ work from + * an IRQ handler. The driver must flush manually by reading a register. + */ + gige_write32(dev, SSB_GIGE_SHIM_FLUSHSTAT, 0x00000068); + + /* Check if we have an RGMII or GMII PHY-bus. + * On RGMII do not bypass the DLLs */ + tmslow = ssb_read32(sdev, SSB_TMSLOW); + tmshigh = ssb_read32(sdev, SSB_TMSHIGH); + if (tmshigh & SSB_GIGE_TMSHIGH_RGMII) { + tmslow &= ~SSB_GIGE_TMSLOW_TXBYPASS; + tmslow &= ~SSB_GIGE_TMSLOW_RXBYPASS; + dev->has_rgmii = 1; + } else { + tmslow |= SSB_GIGE_TMSLOW_TXBYPASS; + tmslow |= SSB_GIGE_TMSLOW_RXBYPASS; + dev->has_rgmii = 0; + } + tmslow |= SSB_GIGE_TMSLOW_DLLEN; + ssb_write32(sdev, SSB_TMSLOW, tmslow); + + ssb_set_drvdata(sdev, dev); + register_pci_controller(&dev->pci_controller); + + return 0; +} + +bool pdev_is_ssb_gige_core(struct pci_dev *pdev) +{ + if (!pdev->resource[0].name) + return 0; + return (strcmp(pdev->resource[0].name, SSB_GIGE_MEM_RES_NAME) == 0); +} +EXPORT_SYMBOL(pdev_is_ssb_gige_core); + +int ssb_gige_pcibios_plat_dev_init(struct ssb_device *sdev, + struct pci_dev *pdev) +{ + struct ssb_gige *dev = ssb_get_drvdata(sdev); + struct resource *res; + + if (pdev->bus->ops != &dev->pci_ops) { + /* The PCI device is not on this SSB GigE bridge device. */ + return -ENODEV; + } + + /* Fixup the PCI resources. */ + res = &(pdev->resource[0]); + res->flags = IORESOURCE_MEM | IORESOURCE_PCI_FIXED; + res->name = dev->mem_resource.name; + res->start = dev->mem_resource.start; + res->end = dev->mem_resource.end; + + /* Fixup interrupt lines. */ + pdev->irq = ssb_mips_irq(sdev) + 2; + pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, pdev->irq); + + return 0; +} + +int ssb_gige_map_irq(struct ssb_device *sdev, + const struct pci_dev *pdev) +{ + struct ssb_gige *dev = ssb_get_drvdata(sdev); + + if (pdev->bus->ops != &dev->pci_ops) { + /* The PCI device is not on this SSB GigE bridge device. */ + return -ENODEV; + } + + return ssb_mips_irq(sdev) + 2; +} + +static struct ssb_driver ssb_gige_driver = { + .name = "BCM-GigE", + .id_table = ssb_gige_tbl, + .probe = ssb_gige_probe, +}; + +int ssb_gige_init(void) +{ + return ssb_driver_register(&ssb_gige_driver); +} diff --git a/drivers/ssb/driver_gpio.c b/drivers/ssb/driver_gpio.c new file mode 100644 index 00000000000..ba350d2035c --- /dev/null +++ b/drivers/ssb/driver_gpio.c @@ -0,0 +1,484 @@ +/* + * Sonics Silicon Backplane + * GPIO driver + * + * Copyright 2011, Broadcom Corporation + * Copyright 2012, Hauke Mehrtens <hauke@hauke-m.de> + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/export.h> +#include <linux/ssb/ssb.h> + +#include "ssb_private.h" + + +/************************************************** + * Shared + **************************************************/ + +static struct ssb_bus *ssb_gpio_get_bus(struct gpio_chip *chip) +{ + return container_of(chip, struct ssb_bus, gpio); +} + +#if IS_ENABLED(CONFIG_SSB_EMBEDDED) +static int ssb_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + if (bus->bustype == SSB_BUSTYPE_SSB) + return irq_find_mapping(bus->irq_domain, gpio); + else + return -EINVAL; +} +#endif + +/************************************************** + * ChipCommon + **************************************************/ + +static int ssb_gpio_chipco_get_value(struct gpio_chip *chip, unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + return !!ssb_chipco_gpio_in(&bus->chipco, 1 << gpio); +} + +static void ssb_gpio_chipco_set_value(struct gpio_chip *chip, unsigned gpio, + int value) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_chipco_gpio_out(&bus->chipco, 1 << gpio, value ? 1 << gpio : 0); +} + +static int ssb_gpio_chipco_direction_input(struct gpio_chip *chip, + unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_chipco_gpio_outen(&bus->chipco, 1 << gpio, 0); + return 0; +} + +static int ssb_gpio_chipco_direction_output(struct gpio_chip *chip, + unsigned gpio, int value) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_chipco_gpio_outen(&bus->chipco, 1 << gpio, 1 << gpio); + ssb_chipco_gpio_out(&bus->chipco, 1 << gpio, value ? 1 << gpio : 0); + return 0; +} + +static int ssb_gpio_chipco_request(struct gpio_chip *chip, unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_chipco_gpio_control(&bus->chipco, 1 << gpio, 0); + /* clear pulldown */ + ssb_chipco_gpio_pulldown(&bus->chipco, 1 << gpio, 0); + /* Set pullup */ + ssb_chipco_gpio_pullup(&bus->chipco, 1 << gpio, 1 << gpio); + + return 0; +} + +static void ssb_gpio_chipco_free(struct gpio_chip *chip, unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + /* clear pullup */ + ssb_chipco_gpio_pullup(&bus->chipco, 1 << gpio, 0); +} + +#if IS_ENABLED(CONFIG_SSB_EMBEDDED) +static void ssb_gpio_irq_chipco_mask(struct irq_data *d) +{ + struct ssb_bus *bus = irq_data_get_irq_chip_data(d); + int gpio = irqd_to_hwirq(d); + + ssb_chipco_gpio_intmask(&bus->chipco, BIT(gpio), 0); +} + +static void ssb_gpio_irq_chipco_unmask(struct irq_data *d) +{ + struct ssb_bus *bus = irq_data_get_irq_chip_data(d); + int gpio = irqd_to_hwirq(d); + u32 val = ssb_chipco_gpio_in(&bus->chipco, BIT(gpio)); + + ssb_chipco_gpio_polarity(&bus->chipco, BIT(gpio), val); + ssb_chipco_gpio_intmask(&bus->chipco, BIT(gpio), BIT(gpio)); +} + +static struct irq_chip ssb_gpio_irq_chipco_chip = { + .name = "SSB-GPIO-CC", + .irq_mask = ssb_gpio_irq_chipco_mask, + .irq_unmask = ssb_gpio_irq_chipco_unmask, +}; + +static irqreturn_t ssb_gpio_irq_chipco_handler(int irq, void *dev_id) +{ + struct ssb_bus *bus = dev_id; + struct ssb_chipcommon *chipco = &bus->chipco; + u32 val = chipco_read32(chipco, SSB_CHIPCO_GPIOIN); + u32 mask = chipco_read32(chipco, SSB_CHIPCO_GPIOIRQ); + u32 pol = chipco_read32(chipco, SSB_CHIPCO_GPIOPOL); + unsigned long irqs = (val ^ pol) & mask; + int gpio; + + if (!irqs) + return IRQ_NONE; + + for_each_set_bit(gpio, &irqs, bus->gpio.ngpio) + generic_handle_irq(ssb_gpio_to_irq(&bus->gpio, gpio)); + ssb_chipco_gpio_polarity(chipco, irqs, val & irqs); + + return IRQ_HANDLED; +} + +static int ssb_gpio_irq_chipco_domain_init(struct ssb_bus *bus) +{ + struct ssb_chipcommon *chipco = &bus->chipco; + struct gpio_chip *chip = &bus->gpio; + int gpio, hwirq, err; + + if (bus->bustype != SSB_BUSTYPE_SSB) + return 0; + + bus->irq_domain = irq_domain_add_linear(NULL, chip->ngpio, + &irq_domain_simple_ops, chipco); + if (!bus->irq_domain) { + err = -ENODEV; + goto err_irq_domain; + } + for (gpio = 0; gpio < chip->ngpio; gpio++) { + int irq = irq_create_mapping(bus->irq_domain, gpio); + + irq_set_chip_data(irq, bus); + irq_set_chip_and_handler(irq, &ssb_gpio_irq_chipco_chip, + handle_simple_irq); + } + + hwirq = ssb_mips_irq(bus->chipco.dev) + 2; + err = request_irq(hwirq, ssb_gpio_irq_chipco_handler, IRQF_SHARED, + "gpio", bus); + if (err) + goto err_req_irq; + + ssb_chipco_gpio_intmask(&bus->chipco, ~0, 0); + chipco_set32(chipco, SSB_CHIPCO_IRQMASK, SSB_CHIPCO_IRQ_GPIO); + + return 0; + +err_req_irq: + for (gpio = 0; gpio < chip->ngpio; gpio++) { + int irq = irq_find_mapping(bus->irq_domain, gpio); + + irq_dispose_mapping(irq); + } + irq_domain_remove(bus->irq_domain); +err_irq_domain: + return err; +} + +static void ssb_gpio_irq_chipco_domain_exit(struct ssb_bus *bus) +{ + struct ssb_chipcommon *chipco = &bus->chipco; + struct gpio_chip *chip = &bus->gpio; + int gpio; + + if (bus->bustype != SSB_BUSTYPE_SSB) + return; + + chipco_mask32(chipco, SSB_CHIPCO_IRQMASK, ~SSB_CHIPCO_IRQ_GPIO); + free_irq(ssb_mips_irq(bus->chipco.dev) + 2, chipco); + for (gpio = 0; gpio < chip->ngpio; gpio++) { + int irq = irq_find_mapping(bus->irq_domain, gpio); + + irq_dispose_mapping(irq); + } + irq_domain_remove(bus->irq_domain); +} +#else +static int ssb_gpio_irq_chipco_domain_init(struct ssb_bus *bus) +{ + return 0; +} + +static void ssb_gpio_irq_chipco_domain_exit(struct ssb_bus *bus) +{ +} +#endif + +static int ssb_gpio_chipco_init(struct ssb_bus *bus) +{ + struct gpio_chip *chip = &bus->gpio; + int err; + + chip->label = "ssb_chipco_gpio"; + chip->owner = THIS_MODULE; + chip->request = ssb_gpio_chipco_request; + chip->free = ssb_gpio_chipco_free; + chip->get = ssb_gpio_chipco_get_value; + chip->set = ssb_gpio_chipco_set_value; + chip->direction_input = ssb_gpio_chipco_direction_input; + chip->direction_output = ssb_gpio_chipco_direction_output; +#if IS_ENABLED(CONFIG_SSB_EMBEDDED) + chip->to_irq = ssb_gpio_to_irq; +#endif + chip->ngpio = 16; + /* There is just one SoC in one device and its GPIO addresses should be + * deterministic to address them more easily. The other buses could get + * a random base number. */ + if (bus->bustype == SSB_BUSTYPE_SSB) + chip->base = 0; + else + chip->base = -1; + + err = ssb_gpio_irq_chipco_domain_init(bus); + if (err) + return err; + + err = gpiochip_add(chip); + if (err) { + ssb_gpio_irq_chipco_domain_exit(bus); + return err; + } + + return 0; +} + +/************************************************** + * EXTIF + **************************************************/ + +#ifdef CONFIG_SSB_DRIVER_EXTIF + +static int ssb_gpio_extif_get_value(struct gpio_chip *chip, unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + return !!ssb_extif_gpio_in(&bus->extif, 1 << gpio); +} + +static void ssb_gpio_extif_set_value(struct gpio_chip *chip, unsigned gpio, + int value) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_extif_gpio_out(&bus->extif, 1 << gpio, value ? 1 << gpio : 0); +} + +static int ssb_gpio_extif_direction_input(struct gpio_chip *chip, + unsigned gpio) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_extif_gpio_outen(&bus->extif, 1 << gpio, 0); + return 0; +} + +static int ssb_gpio_extif_direction_output(struct gpio_chip *chip, + unsigned gpio, int value) +{ + struct ssb_bus *bus = ssb_gpio_get_bus(chip); + + ssb_extif_gpio_outen(&bus->extif, 1 << gpio, 1 << gpio); + ssb_extif_gpio_out(&bus->extif, 1 << gpio, value ? 1 << gpio : 0); + return 0; +} + +#if IS_ENABLED(CONFIG_SSB_EMBEDDED) +static void ssb_gpio_irq_extif_mask(struct irq_data *d) +{ + struct ssb_bus *bus = irq_data_get_irq_chip_data(d); + int gpio = irqd_to_hwirq(d); + + ssb_extif_gpio_intmask(&bus->extif, BIT(gpio), 0); +} + +static void ssb_gpio_irq_extif_unmask(struct irq_data *d) +{ + struct ssb_bus *bus = irq_data_get_irq_chip_data(d); + int gpio = irqd_to_hwirq(d); + u32 val = ssb_extif_gpio_in(&bus->extif, BIT(gpio)); + + ssb_extif_gpio_polarity(&bus->extif, BIT(gpio), val); + ssb_extif_gpio_intmask(&bus->extif, BIT(gpio), BIT(gpio)); +} + +static struct irq_chip ssb_gpio_irq_extif_chip = { + .name = "SSB-GPIO-EXTIF", + .irq_mask = ssb_gpio_irq_extif_mask, + .irq_unmask = ssb_gpio_irq_extif_unmask, +}; + +static irqreturn_t ssb_gpio_irq_extif_handler(int irq, void *dev_id) +{ + struct ssb_bus *bus = dev_id; + struct ssb_extif *extif = &bus->extif; + u32 val = ssb_read32(extif->dev, SSB_EXTIF_GPIO_IN); + u32 mask = ssb_read32(extif->dev, SSB_EXTIF_GPIO_INTMASK); + u32 pol = ssb_read32(extif->dev, SSB_EXTIF_GPIO_INTPOL); + unsigned long irqs = (val ^ pol) & mask; + int gpio; + + if (!irqs) + return IRQ_NONE; + + for_each_set_bit(gpio, &irqs, bus->gpio.ngpio) + generic_handle_irq(ssb_gpio_to_irq(&bus->gpio, gpio)); + ssb_extif_gpio_polarity(extif, irqs, val & irqs); + + return IRQ_HANDLED; +} + +static int ssb_gpio_irq_extif_domain_init(struct ssb_bus *bus) +{ + struct ssb_extif *extif = &bus->extif; + struct gpio_chip *chip = &bus->gpio; + int gpio, hwirq, err; + + if (bus->bustype != SSB_BUSTYPE_SSB) + return 0; + + bus->irq_domain = irq_domain_add_linear(NULL, chip->ngpio, + &irq_domain_simple_ops, extif); + if (!bus->irq_domain) { + err = -ENODEV; + goto err_irq_domain; + } + for (gpio = 0; gpio < chip->ngpio; gpio++) { + int irq = irq_create_mapping(bus->irq_domain, gpio); + + irq_set_chip_data(irq, bus); + irq_set_chip_and_handler(irq, &ssb_gpio_irq_extif_chip, + handle_simple_irq); + } + + hwirq = ssb_mips_irq(bus->extif.dev) + 2; + err = request_irq(hwirq, ssb_gpio_irq_extif_handler, IRQF_SHARED, + "gpio", bus); + if (err) + goto err_req_irq; + + ssb_extif_gpio_intmask(&bus->extif, ~0, 0); + + return 0; + +err_req_irq: + for (gpio = 0; gpio < chip->ngpio; gpio++) { + int irq = irq_find_mapping(bus->irq_domain, gpio); + + irq_dispose_mapping(irq); + } + irq_domain_remove(bus->irq_domain); +err_irq_domain: + return err; +} + +static void ssb_gpio_irq_extif_domain_exit(struct ssb_bus *bus) +{ + struct ssb_extif *extif = &bus->extif; + struct gpio_chip *chip = &bus->gpio; + int gpio; + + if (bus->bustype != SSB_BUSTYPE_SSB) + return; + + free_irq(ssb_mips_irq(bus->extif.dev) + 2, extif); + for (gpio = 0; gpio < chip->ngpio; gpio++) { + int irq = irq_find_mapping(bus->irq_domain, gpio); + + irq_dispose_mapping(irq); + } + irq_domain_remove(bus->irq_domain); +} +#else +static int ssb_gpio_irq_extif_domain_init(struct ssb_bus *bus) +{ + return 0; +} + +static void ssb_gpio_irq_extif_domain_exit(struct ssb_bus *bus) +{ +} +#endif + +static int ssb_gpio_extif_init(struct ssb_bus *bus) +{ + struct gpio_chip *chip = &bus->gpio; + int err; + + chip->label = "ssb_extif_gpio"; + chip->owner = THIS_MODULE; + chip->get = ssb_gpio_extif_get_value; + chip->set = ssb_gpio_extif_set_value; + chip->direction_input = ssb_gpio_extif_direction_input; + chip->direction_output = ssb_gpio_extif_direction_output; +#if IS_ENABLED(CONFIG_SSB_EMBEDDED) + chip->to_irq = ssb_gpio_to_irq; +#endif + chip->ngpio = 5; + /* There is just one SoC in one device and its GPIO addresses should be + * deterministic to address them more easily. The other buses could get + * a random base number. */ + if (bus->bustype == SSB_BUSTYPE_SSB) + chip->base = 0; + else + chip->base = -1; + + err = ssb_gpio_irq_extif_domain_init(bus); + if (err) + return err; + + err = gpiochip_add(chip); + if (err) { + ssb_gpio_irq_extif_domain_exit(bus); + return err; + } + + return 0; +} + +#else +static int ssb_gpio_extif_init(struct ssb_bus *bus) +{ + return -ENOTSUPP; +} +#endif + +/************************************************** + * Init + **************************************************/ + +int ssb_gpio_init(struct ssb_bus *bus) +{ + if (ssb_chipco_available(&bus->chipco)) + return ssb_gpio_chipco_init(bus); + else if (ssb_extif_available(&bus->extif)) + return ssb_gpio_extif_init(bus); + else + SSB_WARN_ON(1); + + return -1; +} + +int ssb_gpio_unregister(struct ssb_bus *bus) +{ + if (ssb_chipco_available(&bus->chipco) || + ssb_extif_available(&bus->extif)) { + return gpiochip_remove(&bus->gpio); + } else { + SSB_WARN_ON(1); + } + + return -1; +} diff --git a/drivers/ssb/driver_mipscore.c b/drivers/ssb/driver_mipscore.c index 3d3dd32bf3a..09077067b0c 100644 --- a/drivers/ssb/driver_mipscore.c +++ b/drivers/ssb/driver_mipscore.c @@ -3,13 +3,14 @@ * Broadcom MIPS core driver * * Copyright 2005, Broadcom Corporation - * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de> + * Copyright 2006, 2007, Michael Buesch <m@bues.ch> * * Licensed under the GNU/GPL. See COPYING for details. */ #include <linux/ssb/ssb.h> +#include <linux/mtd/physmap.h> #include <linux/serial.h> #include <linux/serial_core.h> #include <linux/serial_reg.h> @@ -17,6 +18,25 @@ #include "ssb_private.h" +static const char * const part_probes[] = { "bcm47xxpart", NULL }; + +static struct physmap_flash_data ssb_pflash_data = { + .part_probe_types = part_probes, +}; + +static struct resource ssb_pflash_resource = { + .name = "ssb_pflash", + .flags = IORESOURCE_MEM, +}; + +struct platform_device ssb_pflash_dev = { + .name = "physmap-flash", + .dev = { + .platform_data = &ssb_pflash_data, + }, + .resource = &ssb_pflash_resource, + .num_resources = 1, +}; static inline u32 mips_read32(struct ssb_mipscore *mcore, u16 offset) @@ -49,29 +69,54 @@ static const u32 ipsflag_irq_shift[] = { static inline u32 ssb_irqflag(struct ssb_device *dev) { - return ssb_read32(dev, SSB_TPSFLAG) & SSB_TPSFLAG_BPFLAG; + u32 tpsflag = ssb_read32(dev, SSB_TPSFLAG); + if (tpsflag) + return ssb_read32(dev, SSB_TPSFLAG) & SSB_TPSFLAG_BPFLAG; + else + /* not irq supported */ + return 0x3f; +} + +static struct ssb_device *find_device(struct ssb_device *rdev, int irqflag) +{ + struct ssb_bus *bus = rdev->bus; + int i; + for (i = 0; i < bus->nr_devices; i++) { + struct ssb_device *dev; + dev = &(bus->devices[i]); + if (ssb_irqflag(dev) == irqflag) + return dev; + } + return NULL; } /* Get the MIPS IRQ assignment for a specified device. * If unassigned, 0 is returned. + * If disabled, 5 is returned. + * If not supported, 6 is returned. */ unsigned int ssb_mips_irq(struct ssb_device *dev) { struct ssb_bus *bus = dev->bus; + struct ssb_device *mdev = bus->mipscore.dev; u32 irqflag; u32 ipsflag; u32 tmp; unsigned int irq; irqflag = ssb_irqflag(dev); + if (irqflag == 0x3f) + return 6; 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; + if (irq == 5) { + if ((1 << irqflag) & ssb_read32(mdev, SSB_INTVEC)) + irq = 0; + } return irq; } @@ -97,33 +142,66 @@ static void set_irq(struct ssb_device *dev, unsigned int irq) struct ssb_device *mdev = bus->mipscore.dev; u32 irqflag = ssb_irqflag(dev); + BUG_ON(oldirq == 6); + 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 + else if (oldirq != 5) clear_irq(bus, oldirq); /* assign the new one */ - if (irq == 0) - ssb_write32(mdev, SSB_INTVEC, ((1 << irqflag) & ssb_read32(mdev, SSB_INTVEC))); + if (irq == 0) { + ssb_write32(mdev, SSB_INTVEC, ((1 << irqflag) | ssb_read32(mdev, SSB_INTVEC))); + } else { + u32 ipsflag = ssb_read32(mdev, SSB_IPSFLAG); + if ((ipsflag & ipsflag_irq_mask[irq]) != ipsflag_irq_mask[irq]) { + u32 oldipsflag = (ipsflag & ipsflag_irq_mask[irq]) >> ipsflag_irq_shift[irq]; + struct ssb_device *olddev = find_device(dev, oldipsflag); + if (olddev) + set_irq(olddev, 0); + } + irqflag <<= ipsflag_irq_shift[irq]; + irqflag |= (ipsflag & ~ipsflag_irq_mask[irq]); + ssb_write32(mdev, SSB_IPSFLAG, irqflag); + } + ssb_dbg("set_irq: core 0x%04x, irq %d => %d\n", + dev->id.coreid, oldirq+2, irq+2); +} - irqflag <<= ipsflag_irq_shift[irq]; - irqflag |= (ssb_read32(mdev, SSB_IPSFLAG) & ~ipsflag_irq_mask[irq]); - ssb_write32(mdev, SSB_IPSFLAG, irqflag); +static void print_irq(struct ssb_device *dev, unsigned int irq) +{ + static const char *irq_name[] = {"2(S)", "3", "4", "5", "6", "D", "I"}; + ssb_dbg("core 0x%04x, irq : %s%s %s%s %s%s %s%s %s%s %s%s %s%s\n", + dev->id.coreid, + irq_name[0], irq == 0 ? "*" : " ", + irq_name[1], irq == 1 ? "*" : " ", + irq_name[2], irq == 2 ? "*" : " ", + irq_name[3], irq == 3 ? "*" : " ", + irq_name[4], irq == 4 ? "*" : " ", + irq_name[5], irq == 5 ? "*" : " ", + irq_name[6], irq == 6 ? "*" : " "); +} + +static void dump_irq(struct ssb_bus *bus) +{ + int i; + for (i = 0; i < bus->nr_devices; i++) { + struct ssb_device *dev; + dev = &(bus->devices[i]); + print_irq(dev, ssb_mips_irq(dev)); + } } static void ssb_mips_serial_init(struct ssb_mipscore *mcore) { struct ssb_bus *bus = mcore->dev->bus; - if (bus->extif.dev) + if (ssb_extif_available(&bus->extif)) mcore->nr_serial_ports = ssb_extif_serial_init(&bus->extif, mcore->serial_ports); - else if (bus->chipco.dev) + else if (ssb_chipco_available(&bus->chipco)) mcore->nr_serial_ports = ssb_chipco_serial_init(&bus->chipco, mcore->serial_ports); else mcore->nr_serial_ports = 0; @@ -132,17 +210,42 @@ static void ssb_mips_serial_init(struct ssb_mipscore *mcore) static void ssb_mips_flash_detect(struct ssb_mipscore *mcore) { struct ssb_bus *bus = mcore->dev->bus; + struct ssb_pflash *pflash = &mcore->pflash; + + /* When there is no chipcommon on the bus there is 4MB flash */ + if (!ssb_chipco_available(&bus->chipco)) { + pflash->present = true; + pflash->buswidth = 2; + pflash->window = SSB_FLASH1; + pflash->window_size = SSB_FLASH1_SZ; + goto ssb_pflash; + } - mcore->flash_buswidth = 2; - if (bus->chipco.dev) { - mcore->flash_window = 0x1c000000; - mcore->flash_window_size = 0x02000000; + /* There is ChipCommon, so use it to read info about flash */ + switch (bus->chipco.capabilities & SSB_CHIPCO_CAP_FLASHT) { + case SSB_CHIPCO_FLASHT_STSER: + case SSB_CHIPCO_FLASHT_ATSER: + pr_debug("Found serial flash\n"); + ssb_sflash_init(&bus->chipco); + break; + case SSB_CHIPCO_FLASHT_PARA: + pr_debug("Found parallel flash\n"); + pflash->present = true; + pflash->window = SSB_FLASH2; + pflash->window_size = SSB_FLASH2_SZ; if ((ssb_read32(bus->chipco.dev, SSB_CHIPCO_FLASH_CFG) & SSB_CHIPCO_CFG_DS16) == 0) - mcore->flash_buswidth = 1; - } else { - mcore->flash_window = 0x1fc00000; - mcore->flash_window_size = 0x00400000; + pflash->buswidth = 1; + else + pflash->buswidth = 2; + break; + } + +ssb_pflash: + if (pflash->present) { + ssb_pflash_data.width = pflash->buswidth; + ssb_pflash_resource.start = pflash->window; + ssb_pflash_resource.end = pflash->window + pflash->window_size; } } @@ -151,9 +254,12 @@ u32 ssb_cpu_clock(struct ssb_mipscore *mcore) struct ssb_bus *bus = mcore->dev->bus; u32 pll_type, n, m, rate = 0; - if (bus->extif.dev) { + if (bus->chipco.capabilities & SSB_CHIPCO_CAP_PMU) + return ssb_pmu_get_cpu_clock(&bus->chipco); + + if (ssb_extif_available(&bus->extif)) { ssb_extif_get_clockcontrol(&bus->extif, &pll_type, &n, &m); - } else if (bus->chipco.dev) { + } else if (ssb_chipco_available(&bus->chipco)) { ssb_chipco_get_clockcpu(&bus->chipco, &pll_type, &n, &m); } else return 0; @@ -181,7 +287,7 @@ void ssb_mipscore_init(struct ssb_mipscore *mcore) if (!mcore->dev) return; /* We don't have a MIPS core */ - ssb_dprintk(KERN_INFO PFX "Initializing MIPS core...\n"); + ssb_dbg("Initializing MIPS core...\n"); bus = mcore->dev->bus; hz = ssb_clockspeed(bus); @@ -189,26 +295,33 @@ void ssb_mipscore_init(struct ssb_mipscore *mcore) hz = 100000000; ns = 1000000000 / hz; - if (bus->extif.dev) + if (ssb_extif_available(&bus->extif)) ssb_extif_timing_init(&bus->extif, ns); - else if (bus->chipco.dev) + else if (ssb_chipco_available(&bus->chipco)) 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++) { + int mips_irq; dev = &(bus->devices[i]); - dev->irq = ssb_mips_irq(dev) + 2; + mips_irq = ssb_mips_irq(dev); + if (mips_irq > 4) + dev->irq = 0; + else + dev->irq = mips_irq + 2; + if (dev->irq > 5) + continue; 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 */ + break; case SSB_DEV_PCI: case SSB_DEV_ETHERNET: + case SSB_DEV_ETHERNET_GBIT: case SSB_DEV_80211: case SSB_DEV_USB20_HOST: /* These devices get their own IRQ line if available, the rest goes on IRQ0 */ @@ -216,8 +329,14 @@ void ssb_mipscore_init(struct ssb_mipscore *mcore) set_irq(dev, irq++); break; } + /* fallthrough */ + case SSB_DEV_EXTIF: + set_irq(dev, 0); + break; } } + ssb_dbg("after irq reconfiguration\n"); + dump_irq(bus); ssb_mips_serial_init(mcore); ssb_mips_flash_detect(mcore); diff --git a/drivers/ssb/driver_pcicore.c b/drivers/ssb/driver_pcicore.c index 74b9a8aea52..d75b72ba267 100644 --- a/drivers/ssb/driver_pcicore.c +++ b/drivers/ssb/driver_pcicore.c @@ -3,18 +3,24 @@ * Broadcom PCI-core driver * * Copyright 2005, Broadcom Corporation - * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de> + * Copyright 2006, 2007, Michael Buesch <m@bues.ch> * * Licensed under the GNU/GPL. See COPYING for details. */ #include <linux/ssb/ssb.h> #include <linux/pci.h> +#include <linux/export.h> #include <linux/delay.h> #include <linux/ssb/ssb_embedded.h> #include "ssb_private.h" +static u32 ssb_pcie_read(struct ssb_pcicore *pc, u32 address); +static void ssb_pcie_write(struct ssb_pcicore *pc, u32 address, u32 data); +static u16 ssb_pcie_mdio_read(struct ssb_pcicore *pc, u8 device, u8 address); +static void ssb_pcie_mdio_write(struct ssb_pcicore *pc, u8 device, + u8 address, u16 data); static inline u32 pcicore_read32(struct ssb_pcicore *pc, u16 offset) @@ -60,77 +66,6 @@ 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; - res->flags |= IORESOURCE_PCI_FIXED; - 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) -{ - u8 lat; - - if (dev->bus->number != 0 || PCI_SLOT(dev->devfn) != 0) - return; - - ssb_printk(KERN_INFO "PCI: Fixing up bridge %s\n", pci_name(dev)); - - /* Enable PCI bridge bus mastering and memory space */ - pci_set_master(dev); - if (pcibios_enable_device(dev, ~0) < 0) { - ssb_printk(KERN_ERR "PCI: SSB bridge enable failed\n"); - return; - } - - /* Enable PCI bridge BAR1 prefetch and burst */ - pci_write_config_dword(dev, SSB_BAR1_CONTROL, 3); - - /* Make sure our latency is high enough to handle the devices behind us */ - lat = 168; - ssb_printk(KERN_INFO "PCI: Fixing latency timer of device %s to %u\n", - pci_name(dev), lat); - pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat); -} -DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, ssb_fixup_pcibridge); - -int __init pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) -{ - return ssb_mips_irq(extpci_core->dev) + 2; -} static u32 get_cfgspace_addr(struct ssb_pcicore *pc, unsigned int bus, unsigned int dev, @@ -140,7 +75,7 @@ static u32 get_cfgspace_addr(struct ssb_pcicore *pc, u32 tmp; /* We do only have one cardbus device behind the bridge. */ - if (pc->cardbusmode && (dev >= 1)) + if (pc->cardbusmode && (dev > 1)) goto out; if (bus == 0) { @@ -317,9 +252,68 @@ 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, }; +/* This function is called when doing a pci_enable_device(). + * We must first check if the device is a device on the PCI-core bridge. */ +int ssb_pcicore_plat_dev_init(struct pci_dev *d) +{ + if (d->bus->ops != &ssb_pcicore_pciops) { + /* This is not a device on the PCI-core bridge. */ + return -ENODEV; + } + + ssb_info("PCI: Fixing up device %s\n", pci_name(d)); + + /* 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; +} + +/* Early PCI fixup for a device on the PCI-core bridge. */ +static void ssb_pcicore_fixup_pcibridge(struct pci_dev *dev) +{ + u8 lat; + + if (dev->bus->ops != &ssb_pcicore_pciops) { + /* This is not a device on the PCI-core bridge. */ + return; + } + if (dev->bus->number != 0 || PCI_SLOT(dev->devfn) != 0) + return; + + ssb_info("PCI: Fixing up bridge %s\n", pci_name(dev)); + + /* Enable PCI bridge bus mastering and memory space */ + pci_set_master(dev); + if (pcibios_enable_device(dev, ~0) < 0) { + ssb_err("PCI: SSB bridge enable failed\n"); + return; + } + + /* Enable PCI bridge BAR1 prefetch and burst */ + pci_write_config_dword(dev, SSB_BAR1_CONTROL, 3); + + /* Make sure our latency is high enough to handle the devices behind us */ + lat = 168; + ssb_info("PCI: Fixing latency timer of device %s to %u\n", + pci_name(dev), lat); + pci_write_config_byte(dev, PCI_LATENCY_TIMER, lat); +} +DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, ssb_pcicore_fixup_pcibridge); + +/* PCI device IRQ mapping. */ +int ssb_pcicore_pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + if (dev->bus->ops != &ssb_pcicore_pciops) { + /* This is not a device on the PCI-core bridge. */ + return -ENODEV; + } + return ssb_mips_irq(extpci_core->dev) + 2; +} + static void ssb_pcicore_init_hostmode(struct ssb_pcicore *pc) { u32 val; @@ -328,7 +322,7 @@ static void ssb_pcicore_init_hostmode(struct ssb_pcicore *pc) return; extpci_core = pc; - ssb_dprintk(KERN_INFO PFX "PCIcore in host mode found\n"); + ssb_dbg("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; @@ -343,7 +337,7 @@ static void ssb_pcicore_init_hostmode(struct ssb_pcicore *pc) udelay(1); /* Assertion time demanded by the PCI standard */ if (pc->dev->bus->has_cardbus_slot) { - ssb_dprintk(KERN_INFO PFX "CardBus slot detected\n"); + ssb_dbg("CardBus slot detected\n"); pc->cardbusmode = 1; /* GPIO 1 resets the bridge */ ssb_gpio_out(pc->dev->bus, 1, 1); @@ -414,6 +408,107 @@ static int pcicore_is_in_hostmode(struct ssb_pcicore *pc) } #endif /* CONFIG_SSB_PCICORE_HOSTMODE */ +/************************************************** + * Workarounds. + **************************************************/ + +static void ssb_pcicore_fix_sprom_core_index(struct ssb_pcicore *pc) +{ + u16 tmp = pcicore_read16(pc, SSB_PCICORE_SPROM(0)); + if (((tmp & 0xF000) >> 12) != pc->dev->core_index) { + tmp &= ~0xF000; + tmp |= (pc->dev->core_index << 12); + pcicore_write16(pc, SSB_PCICORE_SPROM(0), tmp); + } +} + +static u8 ssb_pcicore_polarity_workaround(struct ssb_pcicore *pc) +{ + return (ssb_pcie_read(pc, 0x204) & 0x10) ? 0xC0 : 0x80; +} + +static void ssb_pcicore_serdes_workaround(struct ssb_pcicore *pc) +{ + const u8 serdes_pll_device = 0x1D; + const u8 serdes_rx_device = 0x1F; + u16 tmp; + + ssb_pcie_mdio_write(pc, serdes_rx_device, 1 /* Control */, + ssb_pcicore_polarity_workaround(pc)); + tmp = ssb_pcie_mdio_read(pc, serdes_pll_device, 1 /* Control */); + if (tmp & 0x4000) + ssb_pcie_mdio_write(pc, serdes_pll_device, 1, tmp & ~0x4000); +} + +static void ssb_pcicore_pci_setup_workarounds(struct ssb_pcicore *pc) +{ + struct ssb_device *pdev = pc->dev; + struct ssb_bus *bus = pdev->bus; + u32 tmp; + + 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); + } +} + +static void ssb_pcicore_pcie_setup_workarounds(struct ssb_pcicore *pc) +{ + u32 tmp; + u8 rev = pc->dev->id.revision; + + if (rev == 0 || rev == 1) { + /* TLP Workaround register. */ + tmp = ssb_pcie_read(pc, 0x4); + tmp |= 0x8; + ssb_pcie_write(pc, 0x4, tmp); + } + if (rev == 1) { + /* DLLP Link Control register. */ + tmp = ssb_pcie_read(pc, 0x100); + tmp |= 0x40; + ssb_pcie_write(pc, 0x100, tmp); + } + + if (rev == 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 (rev == 3 || rev == 4 || rev == 5) { + /* TODO: DLLP Power Management Threshold */ + ssb_pcicore_serdes_workaround(pc); + /* TODO: ASPM */ + } else if (rev == 7) { + /* TODO: No PLL down */ + } + + if (rev >= 6) { + /* Miscellaneous Configuration Fixup */ + tmp = pcicore_read16(pc, SSB_PCICORE_SPROM(5)); + if (!(tmp & 0x8000)) + pcicore_write16(pc, SSB_PCICORE_SPROM(5), + tmp | 0x8000); + } +} /************************************************** * Generic and Clientmode operation code. @@ -421,18 +516,29 @@ static int pcicore_is_in_hostmode(struct ssb_pcicore *pc) static void ssb_pcicore_init_clientmode(struct ssb_pcicore *pc) { + struct ssb_device *pdev = pc->dev; + struct ssb_bus *bus = pdev->bus; + + if (bus->bustype == SSB_BUSTYPE_PCI) + ssb_pcicore_fix_sprom_core_index(pc); + /* Disable PCI interrupts. */ - ssb_write32(pc->dev, SSB_INTVEC, 0); + ssb_write32(pdev, SSB_INTVEC, 0); + + /* Additional PCIe always once-executed workarounds */ + if (pc->dev->id.coreid == SSB_DEV_PCIE) { + ssb_pcicore_serdes_workaround(pc); + /* TODO: ASPM */ + /* TODO: Clock Request Update */ + } } 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); @@ -457,11 +563,35 @@ static void ssb_pcie_write(struct ssb_pcicore *pc, u32 address, u32 data) pcicore_write32(pc, 0x134, data); } -static void ssb_pcie_mdio_write(struct ssb_pcicore *pc, u8 device, - u8 address, u16 data) +static void ssb_pcie_mdio_set_phy(struct ssb_pcicore *pc, u8 phy) +{ + const u16 mdio_control = 0x128; + const u16 mdio_data = 0x12C; + u32 v; + int i; + + v = (1 << 30); /* Start of Transaction */ + v |= (1 << 28); /* Write Transaction */ + v |= (1 << 17); /* Turnaround */ + v |= (0x1F << 18); + v |= (phy << 4); + pcicore_write32(pc, mdio_data, v); + + udelay(10); + for (i = 0; i < 200; i++) { + v = pcicore_read32(pc, mdio_control); + if (v & 0x100 /* Trans complete */) + break; + msleep(1); + } +} + +static u16 ssb_pcie_mdio_read(struct ssb_pcicore *pc, u8 device, u8 address) { const u16 mdio_control = 0x128; const u16 mdio_data = 0x12C; + int max_retries = 10; + u16 ret = 0; u32 v; int i; @@ -469,46 +599,68 @@ static void ssb_pcie_mdio_write(struct ssb_pcicore *pc, u8 device, v |= 0x2; /* MDIO Clock Divisor */ pcicore_write32(pc, mdio_control, v); + if (pc->dev->id.revision >= 10) { + max_retries = 200; + ssb_pcie_mdio_set_phy(pc, device); + } + v = (1 << 30); /* Start of Transaction */ - v |= (1 << 28); /* Write Transaction */ + v |= (1 << 29); /* Read Transaction */ v |= (1 << 17); /* Turnaround */ - v |= (u32)device << 22; + if (pc->dev->id.revision < 10) + 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++) { + for (i = 0; i < max_retries; i++) { v = pcicore_read32(pc, mdio_control); - if (v & 0x100 /* Trans complete */) + if (v & 0x100 /* Trans complete */) { + udelay(10); + ret = pcicore_read32(pc, mdio_data); break; + } msleep(1); } pcicore_write32(pc, mdio_control, 0); + return ret; } -static void ssb_broadcast_value(struct ssb_device *dev, - u32 address, u32 data) +static void ssb_pcie_mdio_write(struct ssb_pcicore *pc, u8 device, + u8 address, u16 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 */ -} + const u16 mdio_control = 0x128; + const u16 mdio_data = 0x12C; + int max_retries = 10; + u32 v; + int i; -static void ssb_commit_settings(struct ssb_bus *bus) -{ - struct ssb_device *dev; + v = 0x80; /* Enable Preamble Sequence */ + v |= 0x2; /* MDIO Clock Divisor */ + pcicore_write32(pc, mdio_control, v); - 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); + if (pc->dev->id.revision >= 10) { + max_retries = 200; + ssb_pcie_mdio_set_phy(pc, device); + } + + v = (1 << 30); /* Start of Transaction */ + v |= (1 << 28); /* Write Transaction */ + v |= (1 << 17); /* Turnaround */ + if (pc->dev->id.revision < 10) + 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 < max_retries; i++) { + v = pcicore_read32(pc, mdio_control); + if (v & 0x100 /* Trans complete */) + break; + msleep(1); + } + pcicore_write32(pc, mdio_control, 0); } int ssb_pcicore_dev_irqvecs_enable(struct ssb_pcicore *pc, @@ -519,20 +671,27 @@ int ssb_pcicore_dev_irqvecs_enable(struct ssb_pcicore *pc, int err = 0; u32 tmp; - might_sleep(); + if (dev->bus->bustype != SSB_BUSTYPE_PCI) { + /* This SSB device is not on a PCI host-bus. So the IRQs are + * not routed through the PCI core. + * So we must not enable routing through the PCI core. */ + goto out; + } if (!pdev) goto out; bus = pdev->bus; + might_sleep_if(pdev->id.coreid != SSB_DEV_PCI); + /* Enable interrupts for this device. */ - if (bus->host_pci && - ((pdev->id.revision >= 6) || (pdev->id.coreid == SSB_DEV_PCIE))) { + if ((pdev->id.revision >= 6) || (pdev->id.coreid == SSB_DEV_PCIE)) { u32 coremask; /* Calculate the "coremask" for the device. */ coremask = (1 << dev->core_index); + SSB_WARN_ON(bus->bustype != SSB_BUSTYPE_PCI); err = pci_read_config_dword(bus->host_pci, SSB_PCI_IRQMASK, &tmp); if (err) goto out; @@ -544,15 +703,9 @@ int ssb_pcicore_dev_irqvecs_enable(struct ssb_pcicore *pc, 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; - } + tmp = ssb_read32(dev, SSB_TPSFLAG); + tmp &= SSB_TPSFLAG_BPFLAG; + intvec |= (1 << tmp); ssb_write32(pdev, SSB_INTVEC, intvec); } @@ -560,48 +713,10 @@ int ssb_pcicore_dev_irqvecs_enable(struct ssb_pcicore *pc, 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); - } + ssb_pcicore_pci_setup_workarounds(pc); } 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); - } + ssb_pcicore_pcie_setup_workarounds(pc); } pc->setup_done = 1; out: diff --git a/drivers/ssb/embedded.c b/drivers/ssb/embedded.c index d3ade821555..55e10111503 100644 --- a/drivers/ssb/embedded.c +++ b/drivers/ssb/embedded.c @@ -3,13 +3,19 @@ * Embedded systems support code * * Copyright 2005-2008, Broadcom Corporation - * Copyright 2006-2008, Michael Buesch <mb@bu3sch.de> + * Copyright 2006-2008, Michael Buesch <m@bues.ch> + * Copyright 2012, Hauke Mehrtens <hauke@hauke-m.de> * * Licensed under the GNU/GPL. See COPYING for details. */ +#include <linux/export.h> +#include <linux/platform_device.h> #include <linux/ssb/ssb.h> #include <linux/ssb/ssb_embedded.h> +#include <linux/ssb/ssb_driver_pci.h> +#include <linux/ssb/ssb_driver_gige.h> +#include <linux/pci.h> #include "ssb_private.h" @@ -26,6 +32,39 @@ int ssb_watchdog_timer_set(struct ssb_bus *bus, u32 ticks) } return -ENODEV; } +EXPORT_SYMBOL(ssb_watchdog_timer_set); + +int ssb_watchdog_register(struct ssb_bus *bus) +{ + struct bcm47xx_wdt wdt = {}; + struct platform_device *pdev; + + if (ssb_chipco_available(&bus->chipco)) { + wdt.driver_data = &bus->chipco; + wdt.timer_set = ssb_chipco_watchdog_timer_set_wdt; + wdt.timer_set_ms = ssb_chipco_watchdog_timer_set_ms; + wdt.max_timer_ms = bus->chipco.max_timer_ms; + } else if (ssb_extif_available(&bus->extif)) { + wdt.driver_data = &bus->extif; + wdt.timer_set = ssb_extif_watchdog_timer_set_wdt; + wdt.timer_set_ms = ssb_extif_watchdog_timer_set_ms; + wdt.max_timer_ms = SSB_EXTIF_WATCHDOG_MAX_TIMER_MS; + } else { + return -ENODEV; + } + + pdev = platform_device_register_data(NULL, "bcm47xx-wdt", + bus->busnumber, &wdt, + sizeof(wdt)); + if (IS_ERR(pdev)) { + ssb_dbg("can not register watchdog device, err: %li\n", + PTR_ERR(pdev)); + return PTR_ERR(pdev); + } + + bus->watchdog = pdev; + return 0; +} u32 ssb_gpio_in(struct ssb_bus *bus, u32 mask) { @@ -130,3 +169,90 @@ u32 ssb_gpio_polarity(struct ssb_bus *bus, u32 mask, u32 value) return res; } EXPORT_SYMBOL(ssb_gpio_polarity); + +#ifdef CONFIG_SSB_DRIVER_GIGE +static int gige_pci_init_callback(struct ssb_bus *bus, unsigned long data) +{ + struct pci_dev *pdev = (struct pci_dev *)data; + struct ssb_device *dev; + unsigned int i; + int res; + + for (i = 0; i < bus->nr_devices; i++) { + dev = &(bus->devices[i]); + if (dev->id.coreid != SSB_DEV_ETHERNET_GBIT) + continue; + if (!dev->dev || + !dev->dev->driver || + !device_is_registered(dev->dev)) + continue; + res = ssb_gige_pcibios_plat_dev_init(dev, pdev); + if (res >= 0) + return res; + } + + return -ENODEV; +} +#endif /* CONFIG_SSB_DRIVER_GIGE */ + +int ssb_pcibios_plat_dev_init(struct pci_dev *dev) +{ + int err; + + err = ssb_pcicore_plat_dev_init(dev); + if (!err) + return 0; +#ifdef CONFIG_SSB_DRIVER_GIGE + err = ssb_for_each_bus_call((unsigned long)dev, gige_pci_init_callback); + if (err >= 0) + return err; +#endif + /* This is not a PCI device on any SSB device. */ + + return -ENODEV; +} + +#ifdef CONFIG_SSB_DRIVER_GIGE +static int gige_map_irq_callback(struct ssb_bus *bus, unsigned long data) +{ + const struct pci_dev *pdev = (const struct pci_dev *)data; + struct ssb_device *dev; + unsigned int i; + int res; + + for (i = 0; i < bus->nr_devices; i++) { + dev = &(bus->devices[i]); + if (dev->id.coreid != SSB_DEV_ETHERNET_GBIT) + continue; + if (!dev->dev || + !dev->dev->driver || + !device_is_registered(dev->dev)) + continue; + res = ssb_gige_map_irq(dev, pdev); + if (res >= 0) + return res; + } + + return -ENODEV; +} +#endif /* CONFIG_SSB_DRIVER_GIGE */ + +int ssb_pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin) +{ + int res; + + /* Check if this PCI device is a device on a SSB bus or device + * and return the IRQ number for it. */ + + res = ssb_pcicore_pcibios_map_irq(dev, slot, pin); + if (res >= 0) + return res; +#ifdef CONFIG_SSB_DRIVER_GIGE + res = ssb_for_each_bus_call((unsigned long)dev, gige_map_irq_callback); + if (res >= 0) + return res; +#endif + /* This is not a PCI device on any SSB device. */ + + return -ENODEV; +} diff --git a/drivers/ssb/main.c b/drivers/ssb/main.c index bedb2b4ee9d..2fead382084 100644 --- a/drivers/ssb/main.c +++ b/drivers/ssb/main.c @@ -3,7 +3,7 @@ * Subsystem core * * Copyright 2005, Broadcom Corporation - * Copyright 2006, 2007, Michael Buesch <mb@bu3sch.de> + * Copyright 2006, 2007, Michael Buesch <m@bues.ch> * * Licensed under the GNU/GPL. See COPYING for details. */ @@ -12,13 +12,16 @@ #include <linux/delay.h> #include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> #include <linux/ssb/ssb.h> #include <linux/ssb/ssb_regs.h> +#include <linux/ssb/ssb_driver_gige.h> #include <linux/dma-mapping.h> #include <linux/pci.h> +#include <linux/mmc/sdio_func.h> +#include <linux/slab.h> -#include <pcmcia/cs_types.h> -#include <pcmcia/cs.h> #include <pcmcia/cistpl.h> #include <pcmcia/ds.h> @@ -68,6 +71,63 @@ found: } #endif /* CONFIG_SSB_PCIHOST */ +#ifdef CONFIG_SSB_PCMCIAHOST +struct ssb_bus *ssb_pcmcia_dev_to_bus(struct pcmcia_device *pdev) +{ + struct ssb_bus *bus; + + ssb_buses_lock(); + list_for_each_entry(bus, &buses, list) { + if (bus->bustype == SSB_BUSTYPE_PCMCIA && + bus->host_pcmcia == pdev) + goto found; + } + bus = NULL; +found: + ssb_buses_unlock(); + + return bus; +} +#endif /* CONFIG_SSB_PCMCIAHOST */ + +#ifdef CONFIG_SSB_SDIOHOST +struct ssb_bus *ssb_sdio_func_to_bus(struct sdio_func *func) +{ + struct ssb_bus *bus; + + ssb_buses_lock(); + list_for_each_entry(bus, &buses, list) { + if (bus->bustype == SSB_BUSTYPE_SDIO && + bus->host_sdio == func) + goto found; + } + bus = NULL; +found: + ssb_buses_unlock(); + + return bus; +} +#endif /* CONFIG_SSB_SDIOHOST */ + +int ssb_for_each_bus_call(unsigned long data, + int (*func)(struct ssb_bus *bus, unsigned long data)) +{ + struct ssb_bus *bus; + int res; + + ssb_buses_lock(); + list_for_each_entry(bus, &buses, list) { + res = func(bus, data); + if (res >= 0) { + ssb_buses_unlock(); + return res; + } + } + ssb_buses_unlock(); + + return -ENODEV; +} + static struct ssb_device *ssb_device_get(struct ssb_device *dev) { if (dev) @@ -81,35 +141,12 @@ static void ssb_device_put(struct ssb_device *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) @@ -121,27 +158,10 @@ 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 -#ifdef CONFIG_SSB_DEBUG - bus->powered_up = 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) { @@ -151,105 +171,120 @@ static int ssb_device_suspend(struct device *dev, pm_message_t state) if (err) goto out; } +out: + return err; +} + +int ssb_bus_resume(struct ssb_bus *bus) +{ + int err; + + /* Reset HW state information in memory, so that HW is + * completely reinitialized. */ + bus->mapped_device = NULL; +#ifdef CONFIG_SSB_DRIVER_PCICORE + bus->pcicore.setup_done = 0; +#endif - 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); + err = ssb_bus_powerup(bus, 0); + if (err) + return err; + err = ssb_pcmcia_hardware_setup(bus); + if (err) { + ssb_bus_may_powerdown(bus); + return err; } + ssb_chipco_resume(&bus->chipco); + ssb_bus_may_powerdown(bus); -out: - return err; + return 0; } +EXPORT_SYMBOL(ssb_bus_resume); -#ifdef CONFIG_SSB_PCIHOST -int ssb_devices_freeze(struct ssb_bus *bus) +int ssb_bus_suspend(struct ssb_bus *bus) { - struct ssb_device *dev; - struct ssb_driver *drv; - int err = 0; - int i; - pm_message_t state = PMSG_FREEZE; + ssb_chipco_suspend(&bus->chipco); + ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0); + + return 0; +} +EXPORT_SYMBOL(ssb_bus_suspend); + +#ifdef CONFIG_SSB_SPROM +/** ssb_devices_freeze - Freeze all devices on the bus. + * + * After freezing no device driver will be handling a device + * on this bus anymore. ssb_devices_thaw() must be called after + * a successful freeze to reactivate the devices. + * + * @bus: The bus. + * @ctx: Context structure. Pass this to ssb_devices_thaw(). + */ +int ssb_devices_freeze(struct ssb_bus *bus, struct ssb_freeze_context *ctx) +{ + struct ssb_device *sdev; + struct ssb_driver *sdrv; + unsigned int i; + + memset(ctx, 0, sizeof(*ctx)); + ctx->bus = bus; + SSB_WARN_ON(bus->nr_devices > ARRAY_SIZE(ctx->device_frozen)); - /* 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) + sdev = ssb_device_get(&bus->devices[i]); + + if (!sdev->dev || !sdev->dev->driver || + !device_is_registered(sdev->dev)) { + ssb_device_put(sdev); 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) + sdrv = drv_to_ssb_drv(sdev->dev->driver); + if (SSB_WARN_ON(!sdrv->remove)) 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; - } + sdrv->remove(sdev); + ctx->device_frozen[i] = 1; } 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) +/** ssb_devices_thaw - Unfreeze all devices on the bus. + * + * This will re-attach the device drivers and re-init the devices. + * + * @ctx: The context structure from ssb_devices_freeze() + */ +int ssb_devices_thaw(struct ssb_freeze_context *ctx) { - struct ssb_device *dev; - struct ssb_driver *drv; - int err; - int i; + struct ssb_bus *bus = ctx->bus; + struct ssb_device *sdev; + struct ssb_driver *sdrv; + unsigned int i; + int err, result = 0; for (i = 0; i < bus->nr_devices; i++) { - dev = &(bus->devices[i]); - if (!dev->dev || - !dev->dev->driver || - !device_is_registered(dev->dev)) + if (!ctx->device_frozen[i]) continue; - drv = drv_to_ssb_drv(dev->dev->driver); - if (!drv) + sdev = &bus->devices[i]; + + if (SSB_WARN_ON(!sdev->dev || !sdev->dev->driver)) continue; - if (SSB_WARN_ON(!drv->resume)) + sdrv = drv_to_ssb_drv(sdev->dev->driver); + if (SSB_WARN_ON(!sdrv || !sdrv->probe)) continue; - err = drv->resume(dev); + + err = sdrv->probe(sdev, &sdev->id); if (err) { - ssb_printk(KERN_ERR PFX "Failed to thaw device %s\n", - dev->dev->bus_id); + ssb_err("Failed to thaw device %s\n", + dev_name(sdev->dev)); + result = err; } + ssb_device_put(sdev); } - return 0; + return result; } -#endif /* CONFIG_SSB_PCIHOST */ +#endif /* CONFIG_SSB_SPROM */ static void ssb_device_shutdown(struct device *dev) { @@ -334,6 +369,38 @@ static int ssb_device_uevent(struct device *dev, struct kobj_uevent_env *env) ssb_dev->id.revision); } +#define ssb_config_attr(attrib, field, format_string) \ +static ssize_t \ +attrib##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + return sprintf(buf, format_string, dev_to_ssb_dev(dev)->field); \ +} \ +static DEVICE_ATTR_RO(attrib); + +ssb_config_attr(core_num, core_index, "%u\n") +ssb_config_attr(coreid, id.coreid, "0x%04x\n") +ssb_config_attr(vendor, id.vendor, "0x%04x\n") +ssb_config_attr(revision, id.revision, "%u\n") +ssb_config_attr(irq, irq, "%u\n") +static ssize_t +name_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%s\n", + ssb_core_name(dev_to_ssb_dev(dev)->id.coreid)); +} +static DEVICE_ATTR_RO(name); + +static struct attribute *ssb_device_attrs[] = { + &dev_attr_name.attr, + &dev_attr_core_num.attr, + &dev_attr_coreid.attr, + &dev_attr_vendor.attr, + &dev_attr_revision.attr, + &dev_attr_irq.attr, + NULL, +}; +ATTRIBUTE_GROUPS(ssb_device); + static struct bus_type ssb_bustype = { .name = "ssb", .match = ssb_bus_match, @@ -343,6 +410,7 @@ static struct bus_type ssb_bustype = { .suspend = ssb_device_suspend, .resume = ssb_device_resume, .uevent = ssb_device_uevent, + .dev_groups = ssb_device_groups, }; static void ssb_buses_lock(void) @@ -369,16 +437,29 @@ static void ssb_devices_unregister(struct ssb_bus *bus) if (sdev->dev) device_unregister(sdev->dev); } + +#ifdef CONFIG_SSB_EMBEDDED + if (bus->bustype == SSB_BUSTYPE_SSB) + platform_device_unregister(bus->watchdog); +#endif } void ssb_bus_unregister(struct ssb_bus *bus) { + int err; + + err = ssb_gpio_unregister(bus); + if (err == -EBUSY) + ssb_dbg("Some GPIOs are still in use\n"); + else if (err) + ssb_dbg("Can not unregister GPIO driver: %i\n", err); + ssb_buses_lock(); ssb_devices_unregister(bus); list_del(&bus->list); ssb_buses_unlock(); - /* ssb_pcmcia_exit(bus); */ + ssb_pcmcia_exit(bus); ssb_pci_exit(bus); ssb_iounmap(bus); } @@ -418,8 +499,7 @@ static int ssb_devices_register(struct ssb_bus *bus) devwrap = kzalloc(sizeof(*devwrap), GFP_KERNEL); if (!devwrap) { - ssb_printk(KERN_ERR PFX - "Could not allocate device\n"); + ssb_err("Could not allocate device\n"); err = -ENOMEM; goto error; } @@ -428,32 +508,37 @@ static int ssb_devices_register(struct ssb_bus *bus) dev->release = ssb_release_dev; dev->bus = &ssb_bustype; - snprintf(dev->bus_id, sizeof(dev->bus_id), - "ssb%u:%d", bus->busnumber, dev_idx); + dev_set_name(dev, "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; + sdev->dma_dev = dev->parent; #endif break; case SSB_BUSTYPE_PCMCIA: #ifdef CONFIG_SSB_PCMCIAHOST - sdev->irq = bus->host_pcmcia->irq.AssignedIRQ; + sdev->irq = bus->host_pcmcia->irq; dev->parent = &bus->host_pcmcia->dev; #endif break; + case SSB_BUSTYPE_SDIO: +#ifdef CONFIG_SSB_SDIOHOST + dev->parent = &bus->host_sdio->dev; +#endif + break; case SSB_BUSTYPE_SSB: + dev->dma_mask = &dev->coherent_dma_mask; + sdev->dma_dev = dev; break; } sdev->dev = dev; err = device_register(dev); if (err) { - ssb_printk(KERN_ERR PFX - "Could not register %s\n", - dev->bus_id); + ssb_err("Could not register %s\n", dev_name(dev)); /* Set dev to NULL to not unregister * dev on error unwinding. */ sdev->dev = NULL; @@ -463,6 +548,22 @@ static int ssb_devices_register(struct ssb_bus *bus) dev_idx++; } +#ifdef CONFIG_SSB_DRIVER_MIPS + if (bus->mipscore.pflash.present) { + err = platform_device_register(&ssb_pflash_dev); + if (err) + pr_err("Error registering parallel flash\n"); + } +#endif + +#ifdef CONFIG_SSB_SFLASH + if (bus->mipscore.sflash.present) { + err = platform_device_register(&ssb_sflash_dev); + if (err) + pr_err("Error registering serial flash\n"); + } +#endif + return 0; error: /* Unwind the already registered devices. */ @@ -490,6 +591,15 @@ static int ssb_attach_queued_buses(void) if (err) goto error; ssb_pcicore_init(&bus->pcicore); + if (bus->bustype == SSB_BUSTYPE_SSB) + ssb_watchdog_register(bus); + + err = ssb_gpio_init(bus); + if (err == -ENOTSUPP) + ssb_dbg("GPIO driver not activated\n"); + else if (err) + ssb_dbg("Error registering GPIO driver: %i\n", err); + ssb_bus_may_powerdown(bus); err = ssb_devices_register(bus); @@ -505,6 +615,14 @@ error: return err; } +static u8 ssb_ssb_read8(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + + offset += dev->core_index * SSB_CORE_SIZE; + return readb(bus->mmio + offset); +} + static u16 ssb_ssb_read16(struct ssb_device *dev, u16 offset) { struct ssb_bus *bus = dev->bus; @@ -521,6 +639,63 @@ static u32 ssb_ssb_read32(struct ssb_device *dev, u16 offset) return readl(bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_ssb_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr; + + offset += dev->core_index * SSB_CORE_SIZE; + addr = bus->mmio + offset; + + switch (reg_width) { + case sizeof(u8): { + u8 *buf = buffer; + + while (count) { + *buf = __raw_readb(addr); + buf++; + count--; + } + break; + } + case sizeof(u16): { + __le16 *buf = buffer; + + SSB_WARN_ON(count & 1); + while (count) { + *buf = (__force __le16)__raw_readw(addr); + buf++; + count -= 2; + } + break; + } + case sizeof(u32): { + __le32 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + *buf = (__force __le32)__raw_readl(addr); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +} +#endif /* CONFIG_SSB_BLOCKIO */ + +static void ssb_ssb_write8(struct ssb_device *dev, u16 offset, u8 value) +{ + struct ssb_bus *bus = dev->bus; + + offset += dev->core_index * SSB_CORE_SIZE; + writeb(value, bus->mmio + offset); +} + static void ssb_ssb_write16(struct ssb_device *dev, u16 offset, u16 value) { struct ssb_bus *bus = dev->bus; @@ -537,12 +712,67 @@ static void ssb_ssb_write32(struct ssb_device *dev, u16 offset, u32 value) writel(value, bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_ssb_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr; + + offset += dev->core_index * SSB_CORE_SIZE; + addr = bus->mmio + offset; + + switch (reg_width) { + case sizeof(u8): { + const u8 *buf = buffer; + + while (count) { + __raw_writeb(*buf, addr); + buf++; + count--; + } + break; + } + case sizeof(u16): { + const __le16 *buf = buffer; + + SSB_WARN_ON(count & 1); + while (count) { + __raw_writew((__force u16)(*buf), addr); + buf++; + count -= 2; + } + break; + } + case sizeof(u32): { + const __le32 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + __raw_writel((__force u32)(*buf), addr); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +} +#endif /* CONFIG_SSB_BLOCKIO */ + /* Ops for the plain SSB bus without a host-device (no PCI or PCMCIA). */ static const struct ssb_bus_ops ssb_ssb_ops = { + .read8 = ssb_ssb_read8, .read16 = ssb_ssb_read16, .read32 = ssb_ssb_read32, + .write8 = ssb_ssb_write8, .write16 = ssb_ssb_write16, .write32 = ssb_ssb_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_ssb_block_read, + .block_write = ssb_ssb_block_write, +#endif }; static int ssb_fetch_invariants(struct ssb_bus *bus, @@ -578,12 +808,18 @@ static int ssb_bus_register(struct ssb_bus *bus, err = ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 1); if (err) goto out; + + /* Init SDIO-host device (if any), before the scan */ + err = ssb_sdio_init(bus); + if (err) + goto err_disable_xtal; + ssb_buses_lock(); bus->busnumber = next_busnumber; /* Scan for devices (cores) */ err = ssb_bus_scan(bus, baseaddr); if (err) - goto err_disable_xtal; + goto err_sdio_exit; /* Init PCI-host device (if any) */ err = ssb_pci_init(bus); @@ -599,6 +835,7 @@ static int ssb_bus_register(struct ssb_bus *bus, if (err) goto err_pcmcia_exit; ssb_chipcommon_init(&bus->chipco); + ssb_extif_init(&bus->extif); ssb_mipscore_init(&bus->mipscore); err = ssb_fetch_invariants(bus, get_invariants); if (err) { @@ -625,11 +862,13 @@ out: err_dequeue: list_del(&bus->list); err_pcmcia_exit: -/* ssb_pcmcia_exit(bus); */ + ssb_pcmcia_exit(bus); err_pci_exit: ssb_pci_exit(bus); err_unmap: ssb_iounmap(bus); +err_sdio_exit: + ssb_sdio_exit(bus); err_disable_xtal: ssb_buses_unlock(); ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0); @@ -637,8 +876,7 @@ err_disable_xtal: } #ifdef CONFIG_SSB_PCIHOST -int ssb_bus_pcibus_register(struct ssb_bus *bus, - struct pci_dev *host_pci) +int ssb_bus_pcibus_register(struct ssb_bus *bus, struct pci_dev *host_pci) { int err; @@ -648,8 +886,11 @@ int ssb_bus_pcibus_register(struct ssb_bus *bus, 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); + ssb_info("Sonics Silicon Backplane found on PCI device %s\n", + dev_name(&host_pci->dev)); + } else { + ssb_err("Failed to register PCI version of SSB with error %d\n", + err); } return err; @@ -670,8 +911,8 @@ int ssb_bus_pcmciabus_register(struct ssb_bus *bus, 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); + ssb_info("Sonics Silicon Backplane found on PCMCIA device %s\n", + pcmcia_dev->devname); } return err; @@ -679,8 +920,29 @@ int ssb_bus_pcmciabus_register(struct ssb_bus *bus, EXPORT_SYMBOL(ssb_bus_pcmciabus_register); #endif /* CONFIG_SSB_PCMCIAHOST */ -int ssb_bus_ssbbus_register(struct ssb_bus *bus, - unsigned long baseaddr, +#ifdef CONFIG_SSB_SDIOHOST +int ssb_bus_sdiobus_register(struct ssb_bus *bus, struct sdio_func *func, + unsigned int quirks) +{ + int err; + + bus->bustype = SSB_BUSTYPE_SDIO; + bus->host_sdio = func; + bus->ops = &ssb_sdio_ops; + bus->quirks = quirks; + + err = ssb_bus_register(bus, ssb_sdio_get_invariants, ~0); + if (!err) { + ssb_info("Sonics Silicon Backplane found on SDIO device %s\n", + sdio_func_id(func)); + } + + return err; +} +EXPORT_SYMBOL(ssb_bus_sdiobus_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; @@ -690,8 +952,8 @@ int ssb_bus_ssbbus_register(struct ssb_bus *bus, 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); + ssb_info("Sonics Silicon Backplane found at address 0x%08lX\n", + baseaddr); } return err; @@ -762,8 +1024,8 @@ u32 ssb_calc_clock_rate(u32 plltype, u32 n, u32 m) 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; + return SSB_CHIPCO_CLK_T6_M1; + return SSB_CHIPCO_CLK_T6_M0; case SSB_PLLTYPE_1: /* 48Mhz base, 3 dividers */ case SSB_PLLTYPE_3: /* 25Mhz, 2 dividers */ case SSB_PLLTYPE_4: /* 48Mhz, 4 dividers */ @@ -853,6 +1115,9 @@ u32 ssb_clockspeed(struct ssb_bus *bus) u32 plltype; u32 clkctl_n, clkctl_m; + if (bus->chipco.capabilities & SSB_CHIPCO_CAP_PMU) + return ssb_pmu_get_controlclock(&bus->chipco); + if (ssb_extif_available(&bus->extif)) ssb_extif_get_clockcontrol(&bus->extif, &plltype, &clkctl_n, &clkctl_m); @@ -878,23 +1143,21 @@ static u32 ssb_tmslow_reject_bitmask(struct ssb_device *dev) { u32 rev = ssb_read32(dev, SSB_IDLOW) & SSB_IDLOW_SSBREV; - /* The REJECT bit changed position in TMSLOW between - * Backplane revisions. */ + /* The REJECT bit seems to be different for Backplane rev 2.3 */ switch (rev) { case SSB_IDLOW_SSBREV_22: - return SSB_TMSLOW_REJECT_22; + case SSB_IDLOW_SSBREV_24: + case SSB_IDLOW_SSBREV_26: + return SSB_TMSLOW_REJECT; case SSB_IDLOW_SSBREV_23: return SSB_TMSLOW_REJECT_23; - case SSB_IDLOW_SSBREV_24: /* TODO - find the proper REJECT bits */ - case SSB_IDLOW_SSBREV_25: /* same here */ - case SSB_IDLOW_SSBREV_26: /* same here */ + case SSB_IDLOW_SSBREV_25: /* TODO - find the proper REJECT bit */ case SSB_IDLOW_SSBREV_27: /* same here */ - return SSB_TMSLOW_REJECT_23; /* this is a guess */ + return SSB_TMSLOW_REJECT; /* this is a guess */ default: - printk(KERN_INFO "ssb: Backplane Revision 0x%.8X\n", rev); - WARN_ON(1); + WARN(1, KERN_INFO "ssb: Backplane Revision 0x%.8X\n", rev); } - return (SSB_TMSLOW_REJECT_22 | SSB_TMSLOW_REJECT_23); + return (SSB_TMSLOW_REJECT | SSB_TMSLOW_REJECT_23); } int ssb_device_is_enabled(struct ssb_device *dev) @@ -953,10 +1216,10 @@ void ssb_device_enable(struct ssb_device *dev, u32 core_specific_flags) } EXPORT_SYMBOL(ssb_device_enable); -/* Wait for a bit in a register to get set or unset. +/* Wait for bitmask in a register to get set or cleared. * timeout is in units of ten-microseconds */ -static int ssb_wait_bit(struct ssb_device *dev, u16 reg, u32 bitmask, - int timeout, int set) +static int ssb_wait_bits(struct ssb_device *dev, u16 reg, u32 bitmask, + int timeout, int set) { int i; u32 val; @@ -964,7 +1227,7 @@ static int ssb_wait_bit(struct ssb_device *dev, u16 reg, u32 bitmask, for (i = 0; i < timeout; i++) { val = ssb_read32(dev, reg); if (set) { - if (val & bitmask) + if ((val & bitmask) == bitmask) return 0; } else { if (!(val & bitmask)) @@ -981,20 +1244,38 @@ static int ssb_wait_bit(struct ssb_device *dev, u16 reg, u32 bitmask, void ssb_device_disable(struct ssb_device *dev, u32 core_specific_flags) { - u32 reject; + u32 reject, val; 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); + + if (ssb_read32(dev, SSB_TMSLOW) & SSB_TMSLOW_CLOCK) { + ssb_write32(dev, SSB_TMSLOW, reject | SSB_TMSLOW_CLOCK); + ssb_wait_bits(dev, SSB_TMSLOW, reject, 1000, 1); + ssb_wait_bits(dev, SSB_TMSHIGH, SSB_TMSHIGH_BUSY, 1000, 0); + + if (ssb_read32(dev, SSB_IDLOW) & SSB_IDLOW_INITIATOR) { + val = ssb_read32(dev, SSB_IMSTATE); + val |= SSB_IMSTATE_REJECT; + ssb_write32(dev, SSB_IMSTATE, val); + ssb_wait_bits(dev, SSB_IMSTATE, SSB_IMSTATE_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); + + if (ssb_read32(dev, SSB_IDLOW) & SSB_IDLOW_INITIATOR) { + val = ssb_read32(dev, SSB_IMSTATE); + val &= ~SSB_IMSTATE_REJECT; + ssb_write32(dev, SSB_IMSTATE, val); + } + } ssb_write32(dev, SSB_TMSLOW, reject | SSB_TMSLOW_RESET | @@ -1003,35 +1284,41 @@ void ssb_device_disable(struct ssb_device *dev, u32 core_specific_flags) } EXPORT_SYMBOL(ssb_device_disable); +/* Some chipsets need routing known for PCIe and 64-bit DMA */ +static bool ssb_dma_translation_special_bit(struct ssb_device *dev) +{ + u16 chip_id = dev->bus->chip_id; + + if (dev->id.coreid == SSB_DEV_80211) { + return (chip_id == 0x4322 || chip_id == 43221 || + chip_id == 43231 || chip_id == 43222); + } + + return 0; +} + 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; + if (pci_is_pcie(dev->bus->host_pci) && + ssb_read32(dev, SSB_TMSHIGH) & SSB_TMSHIGH_DMA64) { + return SSB_PCIE_DMA_H32; + } else { + if (ssb_dma_translation_special_bit(dev)) + return SSB_PCIE_DMA_H32; + else + return SSB_PCI_DMA; + } + default: + __ssb_dma_not_implemented(dev); } 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; @@ -1044,6 +1331,12 @@ int ssb_bus_may_powerdown(struct ssb_bus *bus) goto out; cc = &bus->chipco; + + if (!cc->dev) + goto out; + if (cc->dev->id.revision < 5) + goto out; + ssb_chipco_set_clockmode(cc, SSB_CLKMODE_SLOW); err = ssb_pci_xtal(bus, SSB_GPIO_XTAL | SSB_GPIO_PLL, 0); if (err) @@ -1054,34 +1347,65 @@ out: #endif return err; error: - ssb_printk(KERN_ERR PFX "Bus powerdown failed\n"); + ssb_err("Bus powerdown failed\n"); goto out; } 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); #ifdef CONFIG_SSB_DEBUG bus->powered_up = 1; #endif + + mode = dynamic_pctl ? SSB_CLKMODE_DYNAMIC : SSB_CLKMODE_FAST; + ssb_chipco_set_clockmode(&bus->chipco, mode); + return 0; error: - ssb_printk(KERN_ERR PFX "Bus powerup failed\n"); + ssb_err("Bus powerup failed\n"); return err; } EXPORT_SYMBOL(ssb_bus_powerup); +static void ssb_broadcast_value(struct ssb_device *dev, + u32 address, u32 data) +{ +#ifdef CONFIG_SSB_DRIVER_PCICORE + /* 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); +#endif + + ssb_write32(dev, SSB_CHIPCO_BCAST_ADDR, address); + ssb_read32(dev, SSB_CHIPCO_BCAST_ADDR); /* flush */ + ssb_write32(dev, SSB_CHIPCO_BCAST_DATA, data); + ssb_read32(dev, SSB_CHIPCO_BCAST_DATA); /* flush */ +} + +void ssb_commit_settings(struct ssb_bus *bus) +{ + struct ssb_device *dev; + +#ifdef CONFIG_SSB_DRIVER_PCICORE + dev = bus->chipco.dev ? bus->chipco.dev : bus->pcicore.dev; +#else + dev = bus->chipco.dev; +#endif + if (WARN_ON(!dev)) + return; + /* This forces an update of the cached registers. */ + ssb_broadcast_value(dev, 0xFD8, 0); +} +EXPORT_SYMBOL(ssb_commit_settings); + u32 ssb_admatch_base(u32 adm) { u32 base = 0; @@ -1147,17 +1471,24 @@ static int __init ssb_modinit(void) ssb_buses_lock(); err = ssb_attach_queued_buses(); ssb_buses_unlock(); - if (err) + if (err) { bus_unregister(&ssb_bustype); + goto out; + } err = b43_pci_ssb_bridge_init(); if (err) { - ssb_printk(KERN_ERR "Broadcom 43xx PCI-SSB-bridge " - "initialization failed"); + ssb_err("Broadcom 43xx PCI-SSB-bridge initialization failed\n"); /* don't fail SSB init because of this */ err = 0; } - + err = ssb_gige_init(); + if (err) { + ssb_err("SSB Broadcom Gigabit Ethernet driver initialization failed\n"); + /* don't fail SSB init because of this */ + err = 0; + } +out: return err; } /* ssb must be initialized after PCI but before the ssb drivers. @@ -1167,6 +1498,7 @@ fs_initcall(ssb_modinit); static void __exit ssb_modexit(void) { + ssb_gige_exit(); b43_pci_ssb_bridge_exit(); bus_unregister(&ssb_bustype); } diff --git a/drivers/ssb/pci.c b/drivers/ssb/pci.c index b434df75047..a8dc95ebf2d 100644 --- a/drivers/ssb/pci.c +++ b/drivers/ssb/pci.c @@ -1,7 +1,7 @@ /* * Sonics Silicon Backplane PCI-Hostbus related functions. * - * Copyright (C) 2005-2006 Michael Buesch <mb@bu3sch.de> + * Copyright (C) 2005-2006 Michael Buesch <m@bues.ch> * Copyright (C) 2005 Martin Langer <martin-langer@gmx.de> * Copyright (C) 2005 Stefano Brivio <st3@riseup.net> * Copyright (C) 2005 Danny van Dyk <kugelfang@gentoo.org> @@ -17,6 +17,7 @@ #include <linux/ssb/ssb.h> #include <linux/ssb/ssb_regs.h> +#include <linux/slab.h> #include <linux/pci.h> #include <linux/delay.h> @@ -55,7 +56,7 @@ int ssb_pci_switch_coreidx(struct ssb_bus *bus, u8 coreidx) } return 0; error: - ssb_printk(KERN_ERR PFX "Failed to switch to core %u\n", coreidx); + ssb_err("Failed to switch to core %u\n", coreidx); return -ENODEV; } @@ -66,10 +67,9 @@ int ssb_pci_switch_core(struct ssb_bus *bus, unsigned long flags; #if SSB_VERBOSE_PCICORESWITCH_DEBUG - ssb_printk(KERN_INFO PFX - "Switching to %s core, index %d\n", - ssb_core_name(dev->id.coreid), - dev->core_index); + ssb_info("Switching to %s core, index %d\n", + ssb_core_name(dev->id.coreid), + dev->core_index); #endif spin_lock_irqsave(&bus->bar_lock, flags); @@ -167,10 +167,28 @@ err_pci: } /* Get the word-offset for a SSB_SPROM_XXX define. */ -#define SPOFF(offset) (((offset) - SSB_SPROM_BASE) / sizeof(u16)) +#define SPOFF(offset) ((offset) / sizeof(u16)) /* Helper to extract some _offset, which is one of the SSB_SPROM_XXX defines. */ -#define SPEX(_outvar, _offset, _mask, _shift) \ +#define SPEX16(_outvar, _offset, _mask, _shift) \ out->_outvar = ((in[SPOFF(_offset)] & (_mask)) >> (_shift)) +#define SPEX32(_outvar, _offset, _mask, _shift) \ + out->_outvar = ((((u32)in[SPOFF((_offset)+2)] << 16 | \ + in[SPOFF(_offset)]) & (_mask)) >> (_shift)) +#define SPEX(_outvar, _offset, _mask, _shift) \ + SPEX16(_outvar, _offset, _mask, _shift) + +#define SPEX_ARRAY8(_field, _offset, _mask, _shift) \ + do { \ + SPEX(_field[0], _offset + 0, _mask, _shift); \ + SPEX(_field[1], _offset + 2, _mask, _shift); \ + SPEX(_field[2], _offset + 4, _mask, _shift); \ + SPEX(_field[3], _offset + 6, _mask, _shift); \ + SPEX(_field[4], _offset + 8, _mask, _shift); \ + SPEX(_field[5], _offset + 10, _mask, _shift); \ + SPEX(_field[6], _offset + 12, _mask, _shift); \ + SPEX(_field[7], _offset + 14, _mask, _shift); \ + } while (0) + static inline u8 ssb_crc8(u8 crc, u8 data) { @@ -212,6 +230,15 @@ static inline u8 ssb_crc8(u8 crc, u8 data) return t[crc ^ data]; } +static void sprom_get_mac(char *mac, const u16 *in) +{ + int i; + for (i = 0; i < 3; i++) { + *mac++ = in[i] >> 8; + *mac++ = in[i]; + } +} + static u8 ssb_sprom_crc(const u16 *sprom, u16 size) { int word; @@ -227,7 +254,7 @@ static u8 ssb_sprom_crc(const u16 *sprom, u16 size) return crc; } -static int sprom_check_crc(const u16 *sprom, u16 size) +static int sprom_check_crc(const u16 *sprom, size_t size) { u8 crc; u8 expected_crc; @@ -242,12 +269,14 @@ static int sprom_check_crc(const u16 *sprom, u16 size) return 0; } -static void sprom_do_read(struct ssb_bus *bus, u16 *sprom) +static int sprom_do_read(struct ssb_bus *bus, u16 *sprom) { int i; for (i = 0; i < bus->sprom_size; i++) - sprom[i] = ioread16(bus->mmio + SSB_SPROM_BASE + (i * 2)); + sprom[i] = ioread16(bus->mmio + bus->sprom_offset + (i * 2)); + + return 0; } static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom) @@ -257,7 +286,7 @@ static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom) u32 spromctl; u16 size = bus->sprom_size; - ssb_printk(KERN_NOTICE PFX "Writing SPROM. Do NOT turn off the power! Please stand by...\n"); + ssb_notice("Writing SPROM. Do NOT turn off the power! Please stand by...\n"); err = pci_read_config_dword(pdev, SSB_SPROMCTL, &spromctl); if (err) goto err_ctlreg; @@ -265,18 +294,18 @@ static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom) err = pci_write_config_dword(pdev, SSB_SPROMCTL, spromctl); if (err) goto err_ctlreg; - ssb_printk(KERN_NOTICE PFX "[ 0%%"); + ssb_notice("[ 0%%"); msleep(500); for (i = 0; i < size; i++) { if (i == size / 4) - ssb_printk("25%%"); + ssb_cont("25%%"); else if (i == size / 2) - ssb_printk("50%%"); + ssb_cont("50%%"); else if (i == (size * 3) / 4) - ssb_printk("75%%"); + ssb_cont("75%%"); else if (i % 2) - ssb_printk("."); - writew(sprom[i], bus->mmio + SSB_SPROM_BASE + (i * 2)); + ssb_cont("."); + writew(sprom[i], bus->mmio + bus->sprom_offset + (i * 2)); mmiowb(); msleep(20); } @@ -288,12 +317,12 @@ static int sprom_do_write(struct ssb_bus *bus, const u16 *sprom) if (err) goto err_ctlreg; msleep(500); - ssb_printk("100%% ]\n"); - ssb_printk(KERN_NOTICE PFX "SPROM written.\n"); + ssb_cont("100%% ]\n"); + ssb_notice("SPROM written\n"); return 0; err_ctlreg: - ssb_printk(KERN_ERR PFX "Could not access SPROM control register.\n"); + ssb_err("Could not access SPROM control register.\n"); return err; } @@ -318,33 +347,36 @@ static s8 r123_extract_antgain(u8 sprom_revision, const u16 *in, return (s8)gain; } +static void sprom_extract_r23(struct ssb_sprom *out, const u16 *in) +{ + SPEX(boardflags_hi, SSB_SPROM2_BFLHI, 0xFFFF, 0); + SPEX(opo, SSB_SPROM2_OPO, SSB_SPROM2_OPO_VALUE, 0); + SPEX(pa1lob0, SSB_SPROM2_PA1LOB0, 0xFFFF, 0); + SPEX(pa1lob1, SSB_SPROM2_PA1LOB1, 0xFFFF, 0); + SPEX(pa1lob2, SSB_SPROM2_PA1LOB2, 0xFFFF, 0); + SPEX(pa1hib0, SSB_SPROM2_PA1HIB0, 0xFFFF, 0); + SPEX(pa1hib1, SSB_SPROM2_PA1HIB1, 0xFFFF, 0); + SPEX(pa1hib2, SSB_SPROM2_PA1HIB2, 0xFFFF, 0); + SPEX(maxpwr_ah, SSB_SPROM2_MAXP_A, SSB_SPROM2_MAXP_A_HI, 0); + SPEX(maxpwr_al, SSB_SPROM2_MAXP_A, SSB_SPROM2_MAXP_A_LO, + SSB_SPROM2_MAXP_A_LO_SHIFT); +} + static void sprom_extract_r123(struct ssb_sprom *out, const u16 *in) { - int i; - u16 v; - s8 gain; u16 loc[3]; - if (out->revision == 3) { /* rev 3 moved MAC */ + if (out->revision == 3) /* rev 3 moved MAC */ loc[0] = SSB_SPROM3_IL0MAC; - loc[1] = SSB_SPROM3_ET0MAC; - loc[2] = SSB_SPROM3_ET1MAC; - } else { + else { loc[0] = SSB_SPROM1_IL0MAC; loc[1] = SSB_SPROM1_ET0MAC; loc[2] = SSB_SPROM1_ET1MAC; } - for (i = 0; i < 3; i++) { - v = in[SPOFF(loc[0]) + i]; - *(((__be16 *)out->il0mac) + i) = cpu_to_be16(v); - } - for (i = 0; i < 3; i++) { - v = in[SPOFF(loc[1]) + i]; - *(((__be16 *)out->et0mac) + i) = cpu_to_be16(v); - } - for (i = 0; i < 3; i++) { - v = in[SPOFF(loc[2]) + i]; - *(((__be16 *)out->et1mac) + i) = cpu_to_be16(v); + sprom_get_mac(out->il0mac, &in[SPOFF(loc[0])]); + if (out->revision < 3) { /* only rev 1-2 have et0, et1 */ + sprom_get_mac(out->et0mac, &in[SPOFF(loc[1])]); + sprom_get_mac(out->et1mac, &in[SPOFF(loc[2])]); } SPEX(et0phyaddr, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET0A, 0); SPEX(et1phyaddr, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET1A, @@ -352,8 +384,10 @@ static void sprom_extract_r123(struct ssb_sprom *out, const u16 *in) SPEX(et0mdcport, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET0M, 14); SPEX(et1mdcport, SSB_SPROM1_ETHPHY, SSB_SPROM1_ETHPHY_ET1M, 15); SPEX(board_rev, SSB_SPROM1_BINF, SSB_SPROM1_BINF_BREV, 0); - SPEX(country_code, SSB_SPROM1_BINF, SSB_SPROM1_BINF_CCODE, - SSB_SPROM1_BINF_CCODE_SHIFT); + SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0); + if (out->revision == 1) + SPEX(country_code, SSB_SPROM1_BINF, SSB_SPROM1_BINF_CCODE, + SSB_SPROM1_BINF_CCODE_SHIFT); SPEX(ant_available_a, SSB_SPROM1_BINF, SSB_SPROM1_BINF_ANTA, SSB_SPROM1_BINF_ANTA_SHIFT); SPEX(ant_available_bg, SSB_SPROM1_BINF, SSB_SPROM1_BINF_ANTBG, @@ -377,50 +411,92 @@ static void sprom_extract_r123(struct ssb_sprom *out, const u16 *in) SSB_SPROM1_ITSSI_A_SHIFT); SPEX(itssi_bg, SSB_SPROM1_ITSSI, SSB_SPROM1_ITSSI_BG, 0); SPEX(boardflags_lo, SSB_SPROM1_BFLLO, 0xFFFF, 0); - if (out->revision >= 2) - SPEX(boardflags_hi, SSB_SPROM2_BFLHI, 0xFFFF, 0); + + SPEX(alpha2[0], SSB_SPROM1_CCODE, 0xff00, 8); + SPEX(alpha2[1], SSB_SPROM1_CCODE, 0x00ff, 0); /* Extract the antenna gain values. */ - gain = r123_extract_antgain(out->revision, in, - SSB_SPROM1_AGAIN_BG, - SSB_SPROM1_AGAIN_BG_SHIFT); - out->antenna_gain.ghz24.a0 = gain; - out->antenna_gain.ghz24.a1 = gain; - out->antenna_gain.ghz24.a2 = gain; - out->antenna_gain.ghz24.a3 = gain; - gain = r123_extract_antgain(out->revision, in, - SSB_SPROM1_AGAIN_A, - SSB_SPROM1_AGAIN_A_SHIFT); - out->antenna_gain.ghz5.a0 = gain; - out->antenna_gain.ghz5.a1 = gain; - out->antenna_gain.ghz5.a2 = gain; - out->antenna_gain.ghz5.a3 = gain; -} - -static void sprom_extract_r4(struct ssb_sprom *out, const u16 *in) + out->antenna_gain.a0 = r123_extract_antgain(out->revision, in, + SSB_SPROM1_AGAIN_BG, + SSB_SPROM1_AGAIN_BG_SHIFT); + out->antenna_gain.a1 = r123_extract_antgain(out->revision, in, + SSB_SPROM1_AGAIN_A, + SSB_SPROM1_AGAIN_A_SHIFT); + if (out->revision >= 2) + sprom_extract_r23(out, in); +} + +/* Revs 4 5 and 8 have partially shared layout */ +static void sprom_extract_r458(struct ssb_sprom *out, const u16 *in) { - int i; - u16 v; + SPEX(txpid2g[0], SSB_SPROM4_TXPID2G01, + SSB_SPROM4_TXPID2G0, SSB_SPROM4_TXPID2G0_SHIFT); + SPEX(txpid2g[1], SSB_SPROM4_TXPID2G01, + SSB_SPROM4_TXPID2G1, SSB_SPROM4_TXPID2G1_SHIFT); + SPEX(txpid2g[2], SSB_SPROM4_TXPID2G23, + SSB_SPROM4_TXPID2G2, SSB_SPROM4_TXPID2G2_SHIFT); + SPEX(txpid2g[3], SSB_SPROM4_TXPID2G23, + SSB_SPROM4_TXPID2G3, SSB_SPROM4_TXPID2G3_SHIFT); + + SPEX(txpid5gl[0], SSB_SPROM4_TXPID5GL01, + SSB_SPROM4_TXPID5GL0, SSB_SPROM4_TXPID5GL0_SHIFT); + SPEX(txpid5gl[1], SSB_SPROM4_TXPID5GL01, + SSB_SPROM4_TXPID5GL1, SSB_SPROM4_TXPID5GL1_SHIFT); + SPEX(txpid5gl[2], SSB_SPROM4_TXPID5GL23, + SSB_SPROM4_TXPID5GL2, SSB_SPROM4_TXPID5GL2_SHIFT); + SPEX(txpid5gl[3], SSB_SPROM4_TXPID5GL23, + SSB_SPROM4_TXPID5GL3, SSB_SPROM4_TXPID5GL3_SHIFT); + + SPEX(txpid5g[0], SSB_SPROM4_TXPID5G01, + SSB_SPROM4_TXPID5G0, SSB_SPROM4_TXPID5G0_SHIFT); + SPEX(txpid5g[1], SSB_SPROM4_TXPID5G01, + SSB_SPROM4_TXPID5G1, SSB_SPROM4_TXPID5G1_SHIFT); + SPEX(txpid5g[2], SSB_SPROM4_TXPID5G23, + SSB_SPROM4_TXPID5G2, SSB_SPROM4_TXPID5G2_SHIFT); + SPEX(txpid5g[3], SSB_SPROM4_TXPID5G23, + SSB_SPROM4_TXPID5G3, SSB_SPROM4_TXPID5G3_SHIFT); + + SPEX(txpid5gh[0], SSB_SPROM4_TXPID5GH01, + SSB_SPROM4_TXPID5GH0, SSB_SPROM4_TXPID5GH0_SHIFT); + SPEX(txpid5gh[1], SSB_SPROM4_TXPID5GH01, + SSB_SPROM4_TXPID5GH1, SSB_SPROM4_TXPID5GH1_SHIFT); + SPEX(txpid5gh[2], SSB_SPROM4_TXPID5GH23, + SSB_SPROM4_TXPID5GH2, SSB_SPROM4_TXPID5GH2_SHIFT); + SPEX(txpid5gh[3], SSB_SPROM4_TXPID5GH23, + SSB_SPROM4_TXPID5GH3, SSB_SPROM4_TXPID5GH3_SHIFT); +} + +static void sprom_extract_r45(struct ssb_sprom *out, const u16 *in) +{ + u16 il0mac_offset; + + if (out->revision == 4) + il0mac_offset = SSB_SPROM4_IL0MAC; + else + il0mac_offset = SSB_SPROM5_IL0MAC; + + sprom_get_mac(out->il0mac, &in[SPOFF(il0mac_offset)]); - /* extract the equivalent of the r1 variables */ - for (i = 0; i < 3; i++) { - v = in[SPOFF(SSB_SPROM4_IL0MAC) + i]; - *(((__be16 *)out->il0mac) + i) = cpu_to_be16(v); - } - for (i = 0; i < 3; i++) { - v = in[SPOFF(SSB_SPROM4_ET0MAC) + i]; - *(((__be16 *)out->et0mac) + i) = cpu_to_be16(v); - } - for (i = 0; i < 3; i++) { - v = in[SPOFF(SSB_SPROM4_ET1MAC) + i]; - *(((__be16 *)out->et1mac) + i) = cpu_to_be16(v); - } SPEX(et0phyaddr, SSB_SPROM4_ETHPHY, SSB_SPROM4_ETHPHY_ET0A, 0); SPEX(et1phyaddr, SSB_SPROM4_ETHPHY, SSB_SPROM4_ETHPHY_ET1A, SSB_SPROM4_ETHPHY_ET1A_SHIFT); - SPEX(country_code, SSB_SPROM4_CCODE, 0xFFFF, 0); - SPEX(boardflags_lo, SSB_SPROM4_BFLLO, 0xFFFF, 0); - SPEX(boardflags_hi, SSB_SPROM4_BFLHI, 0xFFFF, 0); + SPEX(board_rev, SSB_SPROM4_BOARDREV, 0xFFFF, 0); + SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0); + if (out->revision == 4) { + SPEX(alpha2[0], SSB_SPROM4_CCODE, 0xff00, 8); + SPEX(alpha2[1], SSB_SPROM4_CCODE, 0x00ff, 0); + SPEX(boardflags_lo, SSB_SPROM4_BFLLO, 0xFFFF, 0); + SPEX(boardflags_hi, SSB_SPROM4_BFLHI, 0xFFFF, 0); + SPEX(boardflags2_lo, SSB_SPROM4_BFL2LO, 0xFFFF, 0); + SPEX(boardflags2_hi, SSB_SPROM4_BFL2HI, 0xFFFF, 0); + } else { + SPEX(alpha2[0], SSB_SPROM5_CCODE, 0xff00, 8); + SPEX(alpha2[1], SSB_SPROM5_CCODE, 0x00ff, 0); + SPEX(boardflags_lo, SSB_SPROM5_BFLLO, 0xFFFF, 0); + SPEX(boardflags_hi, SSB_SPROM5_BFLHI, 0xFFFF, 0); + SPEX(boardflags2_lo, SSB_SPROM5_BFL2LO, 0xFFFF, 0); + SPEX(boardflags2_hi, SSB_SPROM5_BFL2HI, 0xFFFF, 0); + } SPEX(ant_available_a, SSB_SPROM4_ANTAVAIL, SSB_SPROM4_ANTAVAIL_A, SSB_SPROM4_ANTAVAIL_A_SHIFT); SPEX(ant_available_bg, SSB_SPROM4_ANTAVAIL, SSB_SPROM4_ANTAVAIL_BG, @@ -431,109 +507,366 @@ static void sprom_extract_r4(struct ssb_sprom *out, const u16 *in) SPEX(maxpwr_a, SSB_SPROM4_MAXP_A, SSB_SPROM4_MAXP_A_MASK, 0); SPEX(itssi_a, SSB_SPROM4_MAXP_A, SSB_SPROM4_ITSSI_A, SSB_SPROM4_ITSSI_A_SHIFT); - SPEX(gpio0, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P0, 0); - SPEX(gpio1, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P1, - SSB_SPROM4_GPIOA_P1_SHIFT); - SPEX(gpio2, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P2, 0); - SPEX(gpio3, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P3, - SSB_SPROM4_GPIOB_P3_SHIFT); + if (out->revision == 4) { + SPEX(gpio0, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P0, 0); + SPEX(gpio1, SSB_SPROM4_GPIOA, SSB_SPROM4_GPIOA_P1, + SSB_SPROM4_GPIOA_P1_SHIFT); + SPEX(gpio2, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P2, 0); + SPEX(gpio3, SSB_SPROM4_GPIOB, SSB_SPROM4_GPIOB_P3, + SSB_SPROM4_GPIOB_P3_SHIFT); + } else { + SPEX(gpio0, SSB_SPROM5_GPIOA, SSB_SPROM5_GPIOA_P0, 0); + SPEX(gpio1, SSB_SPROM5_GPIOA, SSB_SPROM5_GPIOA_P1, + SSB_SPROM5_GPIOA_P1_SHIFT); + SPEX(gpio2, SSB_SPROM5_GPIOB, SSB_SPROM5_GPIOB_P2, 0); + SPEX(gpio3, SSB_SPROM5_GPIOB, SSB_SPROM5_GPIOB_P3, + SSB_SPROM5_GPIOB_P3_SHIFT); + } /* Extract the antenna gain values. */ - SPEX(antenna_gain.ghz24.a0, SSB_SPROM4_AGAIN01, + SPEX(antenna_gain.a0, SSB_SPROM4_AGAIN01, SSB_SPROM4_AGAIN0, SSB_SPROM4_AGAIN0_SHIFT); - SPEX(antenna_gain.ghz24.a1, SSB_SPROM4_AGAIN01, + SPEX(antenna_gain.a1, SSB_SPROM4_AGAIN01, SSB_SPROM4_AGAIN1, SSB_SPROM4_AGAIN1_SHIFT); - SPEX(antenna_gain.ghz24.a2, SSB_SPROM4_AGAIN23, + SPEX(antenna_gain.a2, SSB_SPROM4_AGAIN23, SSB_SPROM4_AGAIN2, SSB_SPROM4_AGAIN2_SHIFT); - SPEX(antenna_gain.ghz24.a3, SSB_SPROM4_AGAIN23, + SPEX(antenna_gain.a3, SSB_SPROM4_AGAIN23, SSB_SPROM4_AGAIN3, SSB_SPROM4_AGAIN3_SHIFT); - memcpy(&out->antenna_gain.ghz5, &out->antenna_gain.ghz24, - sizeof(out->antenna_gain.ghz5)); + + sprom_extract_r458(out, in); /* TODO - get remaining rev 4 stuff needed */ } +static void sprom_extract_r8(struct ssb_sprom *out, const u16 *in) +{ + int i; + u16 o; + u16 pwr_info_offset[] = { + SSB_SROM8_PWR_INFO_CORE0, SSB_SROM8_PWR_INFO_CORE1, + SSB_SROM8_PWR_INFO_CORE2, SSB_SROM8_PWR_INFO_CORE3 + }; + BUILD_BUG_ON(ARRAY_SIZE(pwr_info_offset) != + ARRAY_SIZE(out->core_pwr_info)); + + /* extract the MAC address */ + sprom_get_mac(out->il0mac, &in[SPOFF(SSB_SPROM8_IL0MAC)]); + + SPEX(board_rev, SSB_SPROM8_BOARDREV, 0xFFFF, 0); + SPEX(board_type, SSB_SPROM1_SPID, 0xFFFF, 0); + SPEX(alpha2[0], SSB_SPROM8_CCODE, 0xff00, 8); + SPEX(alpha2[1], SSB_SPROM8_CCODE, 0x00ff, 0); + SPEX(boardflags_lo, SSB_SPROM8_BFLLO, 0xFFFF, 0); + SPEX(boardflags_hi, SSB_SPROM8_BFLHI, 0xFFFF, 0); + SPEX(boardflags2_lo, SSB_SPROM8_BFL2LO, 0xFFFF, 0); + SPEX(boardflags2_hi, SSB_SPROM8_BFL2HI, 0xFFFF, 0); + SPEX(ant_available_a, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_A, + SSB_SPROM8_ANTAVAIL_A_SHIFT); + SPEX(ant_available_bg, SSB_SPROM8_ANTAVAIL, SSB_SPROM8_ANTAVAIL_BG, + SSB_SPROM8_ANTAVAIL_BG_SHIFT); + SPEX(maxpwr_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_MAXP_BG_MASK, 0); + SPEX(itssi_bg, SSB_SPROM8_MAXP_BG, SSB_SPROM8_ITSSI_BG, + SSB_SPROM8_ITSSI_BG_SHIFT); + SPEX(maxpwr_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_MAXP_A_MASK, 0); + SPEX(itssi_a, SSB_SPROM8_MAXP_A, SSB_SPROM8_ITSSI_A, + SSB_SPROM8_ITSSI_A_SHIFT); + SPEX(maxpwr_ah, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AH_MASK, 0); + SPEX(maxpwr_al, SSB_SPROM8_MAXP_AHL, SSB_SPROM8_MAXP_AL_MASK, + SSB_SPROM8_MAXP_AL_SHIFT); + SPEX(gpio0, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P0, 0); + SPEX(gpio1, SSB_SPROM8_GPIOA, SSB_SPROM8_GPIOA_P1, + SSB_SPROM8_GPIOA_P1_SHIFT); + SPEX(gpio2, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P2, 0); + SPEX(gpio3, SSB_SPROM8_GPIOB, SSB_SPROM8_GPIOB_P3, + SSB_SPROM8_GPIOB_P3_SHIFT); + SPEX(tri2g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI2G, 0); + SPEX(tri5g, SSB_SPROM8_TRI25G, SSB_SPROM8_TRI5G, + SSB_SPROM8_TRI5G_SHIFT); + SPEX(tri5gl, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GL, 0); + SPEX(tri5gh, SSB_SPROM8_TRI5GHL, SSB_SPROM8_TRI5GH, + SSB_SPROM8_TRI5GH_SHIFT); + SPEX(rxpo2g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO2G, 0); + SPEX(rxpo5g, SSB_SPROM8_RXPO, SSB_SPROM8_RXPO5G, + SSB_SPROM8_RXPO5G_SHIFT); + SPEX(rssismf2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMF2G, 0); + SPEX(rssismc2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISMC2G, + SSB_SPROM8_RSSISMC2G_SHIFT); + SPEX(rssisav2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_RSSISAV2G, + SSB_SPROM8_RSSISAV2G_SHIFT); + SPEX(bxa2g, SSB_SPROM8_RSSIPARM2G, SSB_SPROM8_BXA2G, + SSB_SPROM8_BXA2G_SHIFT); + SPEX(rssismf5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMF5G, 0); + SPEX(rssismc5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISMC5G, + SSB_SPROM8_RSSISMC5G_SHIFT); + SPEX(rssisav5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_RSSISAV5G, + SSB_SPROM8_RSSISAV5G_SHIFT); + SPEX(bxa5g, SSB_SPROM8_RSSIPARM5G, SSB_SPROM8_BXA5G, + SSB_SPROM8_BXA5G_SHIFT); + SPEX(pa0b0, SSB_SPROM8_PA0B0, 0xFFFF, 0); + SPEX(pa0b1, SSB_SPROM8_PA0B1, 0xFFFF, 0); + SPEX(pa0b2, SSB_SPROM8_PA0B2, 0xFFFF, 0); + SPEX(pa1b0, SSB_SPROM8_PA1B0, 0xFFFF, 0); + SPEX(pa1b1, SSB_SPROM8_PA1B1, 0xFFFF, 0); + SPEX(pa1b2, SSB_SPROM8_PA1B2, 0xFFFF, 0); + SPEX(pa1lob0, SSB_SPROM8_PA1LOB0, 0xFFFF, 0); + SPEX(pa1lob1, SSB_SPROM8_PA1LOB1, 0xFFFF, 0); + SPEX(pa1lob2, SSB_SPROM8_PA1LOB2, 0xFFFF, 0); + SPEX(pa1hib0, SSB_SPROM8_PA1HIB0, 0xFFFF, 0); + SPEX(pa1hib1, SSB_SPROM8_PA1HIB1, 0xFFFF, 0); + SPEX(pa1hib2, SSB_SPROM8_PA1HIB2, 0xFFFF, 0); + SPEX(cck2gpo, SSB_SPROM8_CCK2GPO, 0xFFFF, 0); + SPEX32(ofdm2gpo, SSB_SPROM8_OFDM2GPO, 0xFFFFFFFF, 0); + SPEX32(ofdm5glpo, SSB_SPROM8_OFDM5GLPO, 0xFFFFFFFF, 0); + SPEX32(ofdm5gpo, SSB_SPROM8_OFDM5GPO, 0xFFFFFFFF, 0); + SPEX32(ofdm5ghpo, SSB_SPROM8_OFDM5GHPO, 0xFFFFFFFF, 0); + + /* Extract the antenna gain values. */ + SPEX(antenna_gain.a0, SSB_SPROM8_AGAIN01, + SSB_SPROM8_AGAIN0, SSB_SPROM8_AGAIN0_SHIFT); + SPEX(antenna_gain.a1, SSB_SPROM8_AGAIN01, + SSB_SPROM8_AGAIN1, SSB_SPROM8_AGAIN1_SHIFT); + SPEX(antenna_gain.a2, SSB_SPROM8_AGAIN23, + SSB_SPROM8_AGAIN2, SSB_SPROM8_AGAIN2_SHIFT); + SPEX(antenna_gain.a3, SSB_SPROM8_AGAIN23, + SSB_SPROM8_AGAIN3, SSB_SPROM8_AGAIN3_SHIFT); + + /* Extract cores power info info */ + for (i = 0; i < ARRAY_SIZE(pwr_info_offset); i++) { + o = pwr_info_offset[i]; + SPEX(core_pwr_info[i].itssi_2g, o + SSB_SROM8_2G_MAXP_ITSSI, + SSB_SPROM8_2G_ITSSI, SSB_SPROM8_2G_ITSSI_SHIFT); + SPEX(core_pwr_info[i].maxpwr_2g, o + SSB_SROM8_2G_MAXP_ITSSI, + SSB_SPROM8_2G_MAXP, 0); + + SPEX(core_pwr_info[i].pa_2g[0], o + SSB_SROM8_2G_PA_0, ~0, 0); + SPEX(core_pwr_info[i].pa_2g[1], o + SSB_SROM8_2G_PA_1, ~0, 0); + SPEX(core_pwr_info[i].pa_2g[2], o + SSB_SROM8_2G_PA_2, ~0, 0); + + SPEX(core_pwr_info[i].itssi_5g, o + SSB_SROM8_5G_MAXP_ITSSI, + SSB_SPROM8_5G_ITSSI, SSB_SPROM8_5G_ITSSI_SHIFT); + SPEX(core_pwr_info[i].maxpwr_5g, o + SSB_SROM8_5G_MAXP_ITSSI, + SSB_SPROM8_5G_MAXP, 0); + SPEX(core_pwr_info[i].maxpwr_5gh, o + SSB_SPROM8_5GHL_MAXP, + SSB_SPROM8_5GH_MAXP, 0); + SPEX(core_pwr_info[i].maxpwr_5gl, o + SSB_SPROM8_5GHL_MAXP, + SSB_SPROM8_5GL_MAXP, SSB_SPROM8_5GL_MAXP_SHIFT); + + SPEX(core_pwr_info[i].pa_5gl[0], o + SSB_SROM8_5GL_PA_0, ~0, 0); + SPEX(core_pwr_info[i].pa_5gl[1], o + SSB_SROM8_5GL_PA_1, ~0, 0); + SPEX(core_pwr_info[i].pa_5gl[2], o + SSB_SROM8_5GL_PA_2, ~0, 0); + SPEX(core_pwr_info[i].pa_5g[0], o + SSB_SROM8_5G_PA_0, ~0, 0); + SPEX(core_pwr_info[i].pa_5g[1], o + SSB_SROM8_5G_PA_1, ~0, 0); + SPEX(core_pwr_info[i].pa_5g[2], o + SSB_SROM8_5G_PA_2, ~0, 0); + SPEX(core_pwr_info[i].pa_5gh[0], o + SSB_SROM8_5GH_PA_0, ~0, 0); + SPEX(core_pwr_info[i].pa_5gh[1], o + SSB_SROM8_5GH_PA_1, ~0, 0); + SPEX(core_pwr_info[i].pa_5gh[2], o + SSB_SROM8_5GH_PA_2, ~0, 0); + } + + /* Extract FEM info */ + SPEX(fem.ghz2.tssipos, SSB_SPROM8_FEM2G, + SSB_SROM8_FEM_TSSIPOS, SSB_SROM8_FEM_TSSIPOS_SHIFT); + SPEX(fem.ghz2.extpa_gain, SSB_SPROM8_FEM2G, + SSB_SROM8_FEM_EXTPA_GAIN, SSB_SROM8_FEM_EXTPA_GAIN_SHIFT); + SPEX(fem.ghz2.pdet_range, SSB_SPROM8_FEM2G, + SSB_SROM8_FEM_PDET_RANGE, SSB_SROM8_FEM_PDET_RANGE_SHIFT); + SPEX(fem.ghz2.tr_iso, SSB_SPROM8_FEM2G, + SSB_SROM8_FEM_TR_ISO, SSB_SROM8_FEM_TR_ISO_SHIFT); + SPEX(fem.ghz2.antswlut, SSB_SPROM8_FEM2G, + SSB_SROM8_FEM_ANTSWLUT, SSB_SROM8_FEM_ANTSWLUT_SHIFT); + + SPEX(fem.ghz5.tssipos, SSB_SPROM8_FEM5G, + SSB_SROM8_FEM_TSSIPOS, SSB_SROM8_FEM_TSSIPOS_SHIFT); + SPEX(fem.ghz5.extpa_gain, SSB_SPROM8_FEM5G, + SSB_SROM8_FEM_EXTPA_GAIN, SSB_SROM8_FEM_EXTPA_GAIN_SHIFT); + SPEX(fem.ghz5.pdet_range, SSB_SPROM8_FEM5G, + SSB_SROM8_FEM_PDET_RANGE, SSB_SROM8_FEM_PDET_RANGE_SHIFT); + SPEX(fem.ghz5.tr_iso, SSB_SPROM8_FEM5G, + SSB_SROM8_FEM_TR_ISO, SSB_SROM8_FEM_TR_ISO_SHIFT); + SPEX(fem.ghz5.antswlut, SSB_SPROM8_FEM5G, + SSB_SROM8_FEM_ANTSWLUT, SSB_SROM8_FEM_ANTSWLUT_SHIFT); + + SPEX(leddc_on_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_ON, + SSB_SPROM8_LEDDC_ON_SHIFT); + SPEX(leddc_off_time, SSB_SPROM8_LEDDC, SSB_SPROM8_LEDDC_OFF, + SSB_SPROM8_LEDDC_OFF_SHIFT); + + SPEX(txchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_TXCHAIN, + SSB_SPROM8_TXRXC_TXCHAIN_SHIFT); + SPEX(rxchain, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_RXCHAIN, + SSB_SPROM8_TXRXC_RXCHAIN_SHIFT); + SPEX(antswitch, SSB_SPROM8_TXRXC, SSB_SPROM8_TXRXC_SWITCH, + SSB_SPROM8_TXRXC_SWITCH_SHIFT); + + SPEX(opo, SSB_SPROM8_OFDM2GPO, 0x00ff, 0); + + SPEX_ARRAY8(mcs2gpo, SSB_SPROM8_2G_MCSPO, ~0, 0); + SPEX_ARRAY8(mcs5gpo, SSB_SPROM8_5G_MCSPO, ~0, 0); + SPEX_ARRAY8(mcs5glpo, SSB_SPROM8_5GL_MCSPO, ~0, 0); + SPEX_ARRAY8(mcs5ghpo, SSB_SPROM8_5GH_MCSPO, ~0, 0); + + SPEX(rawtempsense, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_RAWTEMP, + SSB_SPROM8_RAWTS_RAWTEMP_SHIFT); + SPEX(measpower, SSB_SPROM8_RAWTS, SSB_SPROM8_RAWTS_MEASPOWER, + SSB_SPROM8_RAWTS_MEASPOWER_SHIFT); + SPEX(tempsense_slope, SSB_SPROM8_OPT_CORRX, + SSB_SPROM8_OPT_CORRX_TEMP_SLOPE, + SSB_SPROM8_OPT_CORRX_TEMP_SLOPE_SHIFT); + SPEX(tempcorrx, SSB_SPROM8_OPT_CORRX, SSB_SPROM8_OPT_CORRX_TEMPCORRX, + SSB_SPROM8_OPT_CORRX_TEMPCORRX_SHIFT); + SPEX(tempsense_option, SSB_SPROM8_OPT_CORRX, + SSB_SPROM8_OPT_CORRX_TEMP_OPTION, + SSB_SPROM8_OPT_CORRX_TEMP_OPTION_SHIFT); + SPEX(freqoffset_corr, SSB_SPROM8_HWIQ_IQSWP, + SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR, + SSB_SPROM8_HWIQ_IQSWP_FREQ_CORR_SHIFT); + SPEX(iqcal_swp_dis, SSB_SPROM8_HWIQ_IQSWP, + SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP, + SSB_SPROM8_HWIQ_IQSWP_IQCAL_SWP_SHIFT); + SPEX(hw_iqcal_en, SSB_SPROM8_HWIQ_IQSWP, SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL, + SSB_SPROM8_HWIQ_IQSWP_HW_IQCAL_SHIFT); + + SPEX(bw40po, SSB_SPROM8_BW40PO, ~0, 0); + SPEX(cddpo, SSB_SPROM8_CDDPO, ~0, 0); + SPEX(stbcpo, SSB_SPROM8_STBCPO, ~0, 0); + SPEX(bwduppo, SSB_SPROM8_BWDUPPO, ~0, 0); + + SPEX(tempthresh, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_TRESH, + SSB_SPROM8_THERMAL_TRESH_SHIFT); + SPEX(tempoffset, SSB_SPROM8_THERMAL, SSB_SPROM8_THERMAL_OFFSET, + SSB_SPROM8_THERMAL_OFFSET_SHIFT); + SPEX(phycal_tempdelta, SSB_SPROM8_TEMPDELTA, + SSB_SPROM8_TEMPDELTA_PHYCAL, + SSB_SPROM8_TEMPDELTA_PHYCAL_SHIFT); + SPEX(temps_period, SSB_SPROM8_TEMPDELTA, SSB_SPROM8_TEMPDELTA_PERIOD, + SSB_SPROM8_TEMPDELTA_PERIOD_SHIFT); + SPEX(temps_hysteresis, SSB_SPROM8_TEMPDELTA, + SSB_SPROM8_TEMPDELTA_HYSTERESIS, + SSB_SPROM8_TEMPDELTA_HYSTERESIS_SHIFT); + sprom_extract_r458(out, in); + + /* TODO - get remaining rev 8 stuff needed */ +} + static int sprom_extract(struct ssb_bus *bus, struct ssb_sprom *out, const u16 *in, u16 size) { memset(out, 0, sizeof(*out)); out->revision = in[size - 1] & 0x00FF; - ssb_dprintk(KERN_DEBUG PFX "SPROM revision %d detected.\n", out->revision); + ssb_dbg("SPROM revision %d detected\n", out->revision); + memset(out->et0mac, 0xFF, 6); /* preset et0 and et1 mac */ + memset(out->et1mac, 0xFF, 6); + if ((bus->chip_id & 0xFF00) == 0x4400) { /* Workaround: The BCM44XX chip has a stupid revision * number stored in the SPROM. * Always extract r1. */ out->revision = 1; + ssb_dbg("SPROM treated as revision %d\n", out->revision); + } + + switch (out->revision) { + case 1: + case 2: + case 3: + sprom_extract_r123(out, in); + break; + case 4: + case 5: + sprom_extract_r45(out, in); + break; + case 8: + sprom_extract_r8(out, in); + break; + default: + ssb_warn("Unsupported SPROM revision %d detected. Will extract v1\n", + out->revision); + out->revision = 1; sprom_extract_r123(out, in); - } else if (bus->chip_id == 0x4321) { - /* the BCM4328 has a chipid == 0x4321 and a rev 4 SPROM */ - out->revision = 4; - sprom_extract_r4(out, in); - } else { - if (out->revision == 0) - goto unsupported; - if (out->revision >= 1 && out->revision <= 3) { - sprom_extract_r123(out, in); - } - if (out->revision == 4) - sprom_extract_r4(out, in); - if (out->revision >= 5) - goto unsupported; } - return 0; -unsupported: - ssb_printk(KERN_WARNING PFX "Unsupported SPROM revision %d " - "detected. Will extract v1\n", out->revision); - sprom_extract_r123(out, in); + if (out->boardflags_lo == 0xFFFF) + out->boardflags_lo = 0; /* per specs */ + if (out->boardflags_hi == 0xFFFF) + out->boardflags_hi = 0; /* per specs */ + return 0; } static int ssb_pci_sprom_get(struct ssb_bus *bus, struct ssb_sprom *sprom) { - int err = -ENOMEM; + int err; u16 *buf; + if (!ssb_is_sprom_available(bus)) { + ssb_err("No SPROM available!\n"); + return -ENODEV; + } + if (bus->chipco.dev) { /* can be unavailable! */ + /* + * get SPROM offset: SSB_SPROM_BASE1 except for + * chipcommon rev >= 31 or chip ID is 0x4312 and + * chipcommon status & 3 == 2 + */ + if (bus->chipco.dev->id.revision >= 31) + bus->sprom_offset = SSB_SPROM_BASE31; + else if (bus->chip_id == 0x4312 && + (bus->chipco.status & 0x03) == 2) + bus->sprom_offset = SSB_SPROM_BASE31; + else + bus->sprom_offset = SSB_SPROM_BASE1; + } else { + bus->sprom_offset = SSB_SPROM_BASE1; + } + ssb_dbg("SPROM offset is 0x%x\n", bus->sprom_offset); + buf = kcalloc(SSB_SPROMSIZE_WORDS_R123, sizeof(u16), GFP_KERNEL); if (!buf) - goto out; + return -ENOMEM; bus->sprom_size = SSB_SPROMSIZE_WORDS_R123; sprom_do_read(bus, buf); err = sprom_check_crc(buf, bus->sprom_size); if (err) { - /* check for rev 4 sprom - has special signature */ - if (buf[32] == 0x5372) { - kfree(buf); - buf = kcalloc(SSB_SPROMSIZE_WORDS_R4, sizeof(u16), - GFP_KERNEL); - if (!buf) - goto out; - bus->sprom_size = SSB_SPROMSIZE_WORDS_R4; - sprom_do_read(bus, buf); - err = sprom_check_crc(buf, bus->sprom_size); + /* try for a 440 byte SPROM - revision 4 and higher */ + kfree(buf); + buf = kcalloc(SSB_SPROMSIZE_WORDS_R4, sizeof(u16), + GFP_KERNEL); + if (!buf) + return -ENOMEM; + bus->sprom_size = SSB_SPROMSIZE_WORDS_R4; + sprom_do_read(bus, buf); + err = sprom_check_crc(buf, bus->sprom_size); + if (err) { + /* All CRC attempts failed. + * Maybe there is no SPROM on the device? + * Now we ask the arch code if there is some sprom + * available for this device in some other storage */ + err = ssb_fill_sprom_with_fallback(bus, sprom); + if (err) { + ssb_warn("WARNING: Using fallback SPROM failed (err %d)\n", + err); + } else { + ssb_dbg("Using SPROM revision %d provided by platform\n", + sprom->revision); + err = 0; + goto out_free; + } + ssb_warn("WARNING: Invalid SPROM CRC (corrupt SPROM)\n"); } - if (err) - ssb_printk(KERN_WARNING PFX "WARNING: Invalid" - " SPROM CRC (corrupt SPROM)\n"); } err = sprom_extract(bus, sprom, buf, bus->sprom_size); +out_free: kfree(buf); -out: return err; } static void ssb_pci_get_boardinfo(struct ssb_bus *bus, struct ssb_boardinfo *bi) { - pci_read_config_word(bus->host_pci, PCI_SUBSYSTEM_VENDOR_ID, - &bi->vendor); - pci_read_config_word(bus->host_pci, PCI_SUBSYSTEM_ID, - &bi->type); - pci_read_config_word(bus->host_pci, PCI_REVISION_ID, - &bi->rev); + bi->vendor = bus->host_pci->subsystem_vendor; + bi->type = bus->host_pci->subsystem_device; } int ssb_pci_get_invariants(struct ssb_bus *bus, @@ -572,6 +905,19 @@ static inline int ssb_pci_assert_buspower(struct ssb_bus *bus) } #endif /* DEBUG */ +static u8 ssb_pci_read8(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + + if (unlikely(ssb_pci_assert_buspower(bus))) + return 0xFF; + if (unlikely(bus->mapped_device != dev)) { + if (unlikely(ssb_pci_switch_core(bus, dev))) + return 0xFF; + } + return ioread8(bus->mmio + offset); +} + static u16 ssb_pci_read16(struct ssb_device *dev, u16 offset) { struct ssb_bus *bus = dev->bus; @@ -598,6 +944,54 @@ static u32 ssb_pci_read32(struct ssb_device *dev, u16 offset) return ioread32(bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pci_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr = bus->mmio + offset; + + if (unlikely(ssb_pci_assert_buspower(bus))) + goto error; + if (unlikely(bus->mapped_device != dev)) { + if (unlikely(ssb_pci_switch_core(bus, dev))) + goto error; + } + switch (reg_width) { + case sizeof(u8): + ioread8_rep(addr, buffer, count); + break; + case sizeof(u16): + SSB_WARN_ON(count & 1); + ioread16_rep(addr, buffer, count >> 1); + break; + case sizeof(u32): + SSB_WARN_ON(count & 3); + ioread32_rep(addr, buffer, count >> 2); + break; + default: + SSB_WARN_ON(1); + } + + return; +error: + memset(buffer, 0xFF, count); +} +#endif /* CONFIG_SSB_BLOCKIO */ + +static void ssb_pci_write8(struct ssb_device *dev, u16 offset, u8 value) +{ + struct ssb_bus *bus = dev->bus; + + if (unlikely(ssb_pci_assert_buspower(bus))) + return; + if (unlikely(bus->mapped_device != dev)) { + if (unlikely(ssb_pci_switch_core(bus, dev))) + return; + } + iowrite8(value, bus->mmio + offset); +} + static void ssb_pci_write16(struct ssb_device *dev, u16 offset, u16 value) { struct ssb_bus *bus = dev->bus; @@ -624,79 +1018,63 @@ static void ssb_pci_write32(struct ssb_device *dev, u16 offset, u32 value) iowrite32(value, bus->mmio + offset); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pci_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + void __iomem *addr = bus->mmio + offset; + + if (unlikely(ssb_pci_assert_buspower(bus))) + return; + if (unlikely(bus->mapped_device != dev)) { + if (unlikely(ssb_pci_switch_core(bus, dev))) + return; + } + switch (reg_width) { + case sizeof(u8): + iowrite8_rep(addr, buffer, count); + break; + case sizeof(u16): + SSB_WARN_ON(count & 1); + iowrite16_rep(addr, buffer, count >> 1); + break; + case sizeof(u32): + SSB_WARN_ON(count & 3); + iowrite32_rep(addr, buffer, count >> 2); + break; + default: + SSB_WARN_ON(1); + } +} +#endif /* CONFIG_SSB_BLOCKIO */ + /* Not "static", as it's used in main.c */ const struct ssb_bus_ops ssb_pci_ops = { + .read8 = ssb_pci_read8, .read16 = ssb_pci_read16, .read32 = ssb_pci_read32, + .write8 = ssb_pci_write8, .write16 = ssb_pci_write16, .write32 = ssb_pci_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_pci_block_read, + .block_write = ssb_pci_block_write, +#endif }; -static int sprom2hex(const u16 *sprom, char *buf, size_t buf_len, u16 size) -{ - int i, pos = 0; - - for (i = 0; i < size; i++) - pos += snprintf(buf + pos, buf_len - pos - 1, - "%04X", swab16(sprom[i]) & 0xFFFF); - pos += snprintf(buf + pos, buf_len - pos - 1, "\n"); - - return pos + 1; -} - -static int hex2sprom(u16 *sprom, const char *dump, size_t len, u16 size) -{ - char tmp[5] = { 0 }; - int cnt = 0; - unsigned long parsed; - - if (len < size * 2) - return -EINVAL; - - while (cnt < size) { - memcpy(tmp, dump, 4); - dump += 4; - parsed = simple_strtoul(tmp, NULL, 16); - sprom[cnt++] = swab16((u16)parsed); - } - - return 0; -} - static ssize_t ssb_pci_attr_sprom_show(struct device *pcidev, struct device_attribute *attr, char *buf) { struct pci_dev *pdev = container_of(pcidev, struct pci_dev, dev); struct ssb_bus *bus; - u16 *sprom; - int err = -ENODEV; - ssize_t count = 0; bus = ssb_pci_dev_to_bus(pdev); if (!bus) - goto out; - err = -ENOMEM; - sprom = kcalloc(bus->sprom_size, sizeof(u16), GFP_KERNEL); - if (!sprom) - goto out; - - /* Use interruptible locking, as the SPROM write might - * be holding the lock for several seconds. So allow userspace - * to cancel operation. */ - err = -ERESTARTSYS; - if (mutex_lock_interruptible(&bus->pci_sprom_mutex)) - goto out_kfree; - sprom_do_read(bus, sprom); - mutex_unlock(&bus->pci_sprom_mutex); + return -ENODEV; - count = sprom2hex(sprom, buf, PAGE_SIZE, bus->sprom_size); - err = 0; - -out_kfree: - kfree(sprom); -out: - return err ? err : count; + return ssb_attr_sprom_show(bus, buf, sprom_do_read); } static ssize_t ssb_pci_attr_sprom_store(struct device *pcidev, @@ -705,55 +1083,13 @@ static ssize_t ssb_pci_attr_sprom_store(struct device *pcidev, { struct pci_dev *pdev = container_of(pcidev, struct pci_dev, dev); struct ssb_bus *bus; - u16 *sprom; - int res = 0, err = -ENODEV; bus = ssb_pci_dev_to_bus(pdev); if (!bus) - goto out; - err = -ENOMEM; - sprom = kcalloc(bus->sprom_size, sizeof(u16), GFP_KERNEL); - if (!sprom) - goto out; - err = hex2sprom(sprom, buf, count, bus->sprom_size); - if (err) { - err = -EINVAL; - goto out_kfree; - } - err = sprom_check_crc(sprom, bus->sprom_size); - if (err) { - err = -EINVAL; - goto out_kfree; - } + return -ENODEV; - /* Use interruptible locking, as the SPROM write might - * be holding the lock for several seconds. So allow userspace - * to cancel operation. */ - err = -ERESTARTSYS; - if (mutex_lock_interruptible(&bus->pci_sprom_mutex)) - goto out_kfree; - err = ssb_devices_freeze(bus); - if (err == -EOPNOTSUPP) { - ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze devices. " - "No suspend support. Is CONFIG_PM enabled?\n"); - goto out_unlock; - } - if (err) { - ssb_printk(KERN_ERR PFX "SPROM write: Could not freeze all devices\n"); - goto out_unlock; - } - res = sprom_do_write(bus, sprom); - err = ssb_devices_thaw(bus); - if (err) - ssb_printk(KERN_ERR PFX "SPROM write: Could not thaw all devices\n"); -out_unlock: - mutex_unlock(&bus->pci_sprom_mutex); -out_kfree: - kfree(sprom); -out: - if (res) - return res; - return err ? err : count; + return ssb_attr_sprom_store(bus, buf, count, + sprom_check_crc, sprom_do_write); } static DEVICE_ATTR(ssb_sprom, 0600, @@ -780,7 +1116,7 @@ int ssb_pci_init(struct ssb_bus *bus) return 0; pdev = bus->host_pci; - mutex_init(&bus->pci_sprom_mutex); + mutex_init(&bus->sprom_mutex); err = device_create_file(&pdev->dev, &dev_attr_ssb_sprom); if (err) goto out; diff --git a/drivers/ssb/pcihost_wrapper.c b/drivers/ssb/pcihost_wrapper.c index 82a10abef64..69161bbc4d0 100644 --- a/drivers/ssb/pcihost_wrapper.c +++ b/drivers/ssb/pcihost_wrapper.c @@ -6,18 +6,26 @@ * Copyright (c) 2005 Stefano Brivio <st3@riseup.net> * Copyright (c) 2005 Danny van Dyk <kugelfang@gentoo.org> * Copyright (c) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch> - * Copyright (c) 2005-2007 Michael Buesch <mbuesch@freenet.de> + * Copyright (c) 2005-2007 Michael Buesch <m@bues.ch> * * Licensed under the GNU/GPL. See COPYING for details. */ #include <linux/pci.h> +#include <linux/export.h> +#include <linux/slab.h> #include <linux/ssb/ssb.h> #ifdef CONFIG_PM static int ssb_pcihost_suspend(struct pci_dev *dev, pm_message_t state) { + struct ssb_bus *ssb = pci_get_drvdata(dev); + int err; + + err = ssb_bus_suspend(ssb); + if (err) + return err; pci_save_state(dev); pci_disable_device(dev); pci_set_power_state(dev, pci_choose_state(dev, state)); @@ -27,13 +35,17 @@ static int ssb_pcihost_suspend(struct pci_dev *dev, pm_message_t state) static int ssb_pcihost_resume(struct pci_dev *dev) { + struct ssb_bus *ssb = pci_get_drvdata(dev); int err; - pci_set_power_state(dev, 0); + pci_set_power_state(dev, PCI_D0); err = pci_enable_device(dev); if (err) return err; pci_restore_state(dev); + err = ssb_bus_resume(ssb); + if (err) + return err; return 0; } @@ -48,6 +60,7 @@ static int ssb_pcihost_probe(struct pci_dev *dev, struct ssb_bus *ssb; int err = -ENOMEM; const char *name; + u32 val; ssb = kzalloc(sizeof(*ssb), GFP_KERNEL); if (!ssb) @@ -55,7 +68,7 @@ static int ssb_pcihost_probe(struct pci_dev *dev, err = pci_enable_device(dev); if (err) goto err_kfree_ssb; - name = dev->dev.bus_id; + name = dev_name(&dev->dev); if (dev->driver && dev->driver->name) name = dev->driver->name; err = pci_request_regions(dev, name); @@ -63,6 +76,12 @@ static int ssb_pcihost_probe(struct pci_dev *dev, goto err_pci_disable; pci_set_master(dev); + /* Disable the RETRY_TIMEOUT register (0x41) to keep + * PCI Tx retries from interfering with C3 CPU state */ + pci_read_config_dword(dev, 0x40, &val); + if ((val & 0x0000ff00) != 0) + pci_write_config_dword(dev, 0x40, val & 0xffff00ff); + err = ssb_bus_pcibus_register(ssb, dev); if (err) goto err_pci_release_regions; diff --git a/drivers/ssb/pcmcia.c b/drivers/ssb/pcmcia.c index 46816cda8b9..b413e018708 100644 --- a/drivers/ssb/pcmcia.c +++ b/drivers/ssb/pcmcia.c @@ -3,7 +3,7 @@ * PCMCIA-Hostbus related functions * * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> - * Copyright 2007 Michael Buesch <mb@bu3sch.de> + * Copyright 2007-2008 Michael Buesch <m@bues.ch> * * Licensed under the GNU/GPL. See COPYING for details. */ @@ -11,9 +11,8 @@ #include <linux/ssb/ssb.h> #include <linux/delay.h> #include <linux/io.h> +#include <linux/etherdevice.h> -#include <pcmcia/cs_types.h> -#include <pcmcia/cs.h> #include <pcmcia/cistpl.h> #include <pcmcia/ciscode.h> #include <pcmcia/ds.h> @@ -26,59 +25,117 @@ #define SSB_VERBOSE_PCMCIACORESWITCH_DEBUG 0 +/* PCMCIA configuration registers */ +#define SSB_PCMCIA_ADDRESS0 0x2E +#define SSB_PCMCIA_ADDRESS1 0x30 +#define SSB_PCMCIA_ADDRESS2 0x32 +#define SSB_PCMCIA_MEMSEG 0x34 +#define SSB_PCMCIA_SPROMCTL 0x36 +#define SSB_PCMCIA_SPROMCTL_IDLE 0 +#define SSB_PCMCIA_SPROMCTL_WRITE 1 +#define SSB_PCMCIA_SPROMCTL_READ 2 +#define SSB_PCMCIA_SPROMCTL_WRITEEN 4 +#define SSB_PCMCIA_SPROMCTL_WRITEDIS 7 +#define SSB_PCMCIA_SPROMCTL_DONE 8 +#define SSB_PCMCIA_SPROM_DATALO 0x38 +#define SSB_PCMCIA_SPROM_DATAHI 0x3A +#define SSB_PCMCIA_SPROM_ADDRLO 0x3C +#define SSB_PCMCIA_SPROM_ADDRHI 0x3E + +/* Hardware invariants CIS tuples */ +#define SSB_PCMCIA_CIS 0x80 +#define SSB_PCMCIA_CIS_ID 0x01 +#define SSB_PCMCIA_CIS_BOARDREV 0x02 +#define SSB_PCMCIA_CIS_PA 0x03 +#define SSB_PCMCIA_CIS_PA_PA0B0_LO 0 +#define SSB_PCMCIA_CIS_PA_PA0B0_HI 1 +#define SSB_PCMCIA_CIS_PA_PA0B1_LO 2 +#define SSB_PCMCIA_CIS_PA_PA0B1_HI 3 +#define SSB_PCMCIA_CIS_PA_PA0B2_LO 4 +#define SSB_PCMCIA_CIS_PA_PA0B2_HI 5 +#define SSB_PCMCIA_CIS_PA_ITSSI 6 +#define SSB_PCMCIA_CIS_PA_MAXPOW 7 +#define SSB_PCMCIA_CIS_OEMNAME 0x04 +#define SSB_PCMCIA_CIS_CCODE 0x05 +#define SSB_PCMCIA_CIS_ANTENNA 0x06 +#define SSB_PCMCIA_CIS_ANTGAIN 0x07 +#define SSB_PCMCIA_CIS_BFLAGS 0x08 +#define SSB_PCMCIA_CIS_LEDS 0x09 + +/* PCMCIA SPROM size. */ +#define SSB_PCMCIA_SPROM_SIZE 256 +#define SSB_PCMCIA_SPROM_SIZE_BYTES (SSB_PCMCIA_SPROM_SIZE * sizeof(u16)) + + +/* Write to a PCMCIA configuration register. */ +static int ssb_pcmcia_cfg_write(struct ssb_bus *bus, u8 offset, u8 value) +{ + int res; + + res = pcmcia_write_config_byte(bus->host_pcmcia, offset, value); + if (unlikely(res != 0)) + return -EBUSY; + + return 0; +} + +/* Read from a PCMCIA configuration register. */ +static int ssb_pcmcia_cfg_read(struct ssb_bus *bus, u8 offset, u8 *value) +{ + int res; + + res = pcmcia_read_config_byte(bus->host_pcmcia, offset, value); + if (unlikely(res != 0)) + return -EBUSY; + + return 0; +} + int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus, u8 coreidx) { - struct pcmcia_device *pdev = bus->host_pcmcia; int err; int attempts = 0; u32 cur_core; - conf_reg_t reg; u32 addr; u32 read_addr; + u8 val; addr = (coreidx * SSB_CORE_SIZE) + SSB_ENUM_BASE; while (1) { - reg.Action = CS_WRITE; - reg.Offset = 0x2E; - reg.Value = (addr & 0x0000F000) >> 12; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS0, + (addr & 0x0000F000) >> 12); + if (err) goto error; - reg.Offset = 0x30; - reg.Value = (addr & 0x00FF0000) >> 16; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS1, + (addr & 0x00FF0000) >> 16); + if (err) goto error; - reg.Offset = 0x32; - reg.Value = (addr & 0xFF000000) >> 24; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS2, + (addr & 0xFF000000) >> 24); + if (err) goto error; read_addr = 0; - reg.Action = CS_READ; - reg.Offset = 0x2E; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS0, &val); + if (err) goto error; - read_addr |= ((u32)(reg.Value & 0x0F)) << 12; - reg.Offset = 0x30; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + read_addr |= ((u32)(val & 0x0F)) << 12; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS1, &val); + if (err) goto error; - read_addr |= ((u32)reg.Value) << 16; - reg.Offset = 0x32; - err = pcmcia_access_configuration_register(pdev, ®); - if (err != CS_SUCCESS) + read_addr |= ((u32)val) << 16; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS2, &val); + if (err) goto error; - read_addr |= ((u32)reg.Value) << 24; + read_addr |= ((u32)val) << 24; cur_core = (read_addr - SSB_ENUM_BASE) / SSB_CORE_SIZE; if (cur_core == coreidx) break; + err = -ETIMEDOUT; if (attempts++ > SSB_BAR0_MAX_RETRIES) goto error; udelay(10); @@ -86,8 +143,8 @@ int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus, return 0; error: - ssb_printk(KERN_ERR PFX "Failed to switch to core %u\n", coreidx); - return -ENODEV; + ssb_err("Failed to switch to core %u\n", coreidx); + return err; } int ssb_pcmcia_switch_core(struct ssb_bus *bus, @@ -96,10 +153,9 @@ int ssb_pcmcia_switch_core(struct ssb_bus *bus, int err; #if SSB_VERBOSE_PCMCIACORESWITCH_DEBUG - ssb_printk(KERN_INFO PFX - "Switching to %s core, index %d\n", - ssb_core_name(dev->id.coreid), - dev->core_index); + ssb_info("Switching to %s core, index %d\n", + ssb_core_name(dev->id.coreid), + dev->core_index); #endif err = ssb_pcmcia_switch_coreidx(bus, dev->core_index); @@ -112,27 +168,21 @@ int ssb_pcmcia_switch_core(struct ssb_bus *bus, int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg) { int attempts = 0; - conf_reg_t reg; - int res; + int err; + u8 val; SSB_WARN_ON((seg != 0) && (seg != 1)); - reg.Offset = 0x34; - reg.Function = 0; while (1) { - reg.Action = CS_WRITE; - reg.Value = seg; - res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (unlikely(res != CS_SUCCESS)) + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_MEMSEG, seg); + if (err) goto error; - reg.Value = 0xFF; - reg.Action = CS_READ; - res = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (unlikely(res != CS_SUCCESS)) + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_MEMSEG, &val); + if (err) goto error; - - if (reg.Value == seg) + if (val == seg) break; + err = -ETIMEDOUT; if (unlikely(attempts++ > SSB_BAR0_MAX_RETRIES)) goto error; udelay(10); @@ -141,8 +191,8 @@ int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg) return 0; error: - ssb_printk(KERN_ERR PFX "Failed to switch pcmcia segment\n"); - return -ENODEV; + ssb_err("Failed to switch pcmcia segment\n"); + return err; } static int select_core_and_segment(struct ssb_device *dev, @@ -172,6 +222,22 @@ static int select_core_and_segment(struct ssb_device *dev, return 0; } +static u8 ssb_pcmcia_read8(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; + u8 value = 0xFF; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) + value = readb(bus->mmio + offset); + spin_unlock_irqrestore(&bus->bar_lock, flags); + + return value; +} + static u16 ssb_pcmcia_read16(struct ssb_device *dev, u16 offset) { struct ssb_bus *bus = dev->bus; @@ -206,6 +272,78 @@ static u32 ssb_pcmcia_read32(struct ssb_device *dev, u16 offset) return (lo | (hi << 16)); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pcmcia_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + void __iomem *addr = bus->mmio + offset; + int err; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (unlikely(err)) { + memset(buffer, 0xFF, count); + goto unlock; + } + switch (reg_width) { + case sizeof(u8): { + u8 *buf = buffer; + + while (count) { + *buf = __raw_readb(addr); + buf++; + count--; + } + break; + } + case sizeof(u16): { + __le16 *buf = buffer; + + SSB_WARN_ON(count & 1); + while (count) { + *buf = (__force __le16)__raw_readw(addr); + buf++; + count -= 2; + } + break; + } + case sizeof(u32): { + __le16 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + *buf = (__force __le16)__raw_readw(addr); + buf++; + *buf = (__force __le16)__raw_readw(addr + 2); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +unlock: + spin_unlock_irqrestore(&bus->bar_lock, flags); +} +#endif /* CONFIG_SSB_BLOCKIO */ + +static void ssb_pcmcia_write8(struct ssb_device *dev, u16 offset, u8 value) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + int err; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (likely(!err)) + writeb(value, bus->mmio + offset); + mmiowb(); + spin_unlock_irqrestore(&bus->bar_lock, flags); +} + static void ssb_pcmcia_write16(struct ssb_device *dev, u16 offset, u16 value) { struct ssb_bus *bus = dev->bus; @@ -236,26 +374,422 @@ static void ssb_pcmcia_write32(struct ssb_device *dev, u16 offset, u32 value) spin_unlock_irqrestore(&bus->bar_lock, flags); } +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_pcmcia_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + struct ssb_bus *bus = dev->bus; + unsigned long flags; + void __iomem *addr = bus->mmio + offset; + int err; + + spin_lock_irqsave(&bus->bar_lock, flags); + err = select_core_and_segment(dev, &offset); + if (unlikely(err)) + goto unlock; + switch (reg_width) { + case sizeof(u8): { + const u8 *buf = buffer; + + while (count) { + __raw_writeb(*buf, addr); + buf++; + count--; + } + break; + } + case sizeof(u16): { + const __le16 *buf = buffer; + + SSB_WARN_ON(count & 1); + while (count) { + __raw_writew((__force u16)(*buf), addr); + buf++; + count -= 2; + } + break; + } + case sizeof(u32): { + const __le16 *buf = buffer; + + SSB_WARN_ON(count & 3); + while (count) { + __raw_writew((__force u16)(*buf), addr); + buf++; + __raw_writew((__force u16)(*buf), addr + 2); + buf++; + count -= 4; + } + break; + } + default: + SSB_WARN_ON(1); + } +unlock: + mmiowb(); + spin_unlock_irqrestore(&bus->bar_lock, flags); +} +#endif /* CONFIG_SSB_BLOCKIO */ + /* Not "static", as it's used in main.c */ const struct ssb_bus_ops ssb_pcmcia_ops = { + .read8 = ssb_pcmcia_read8, .read16 = ssb_pcmcia_read16, .read32 = ssb_pcmcia_read32, + .write8 = ssb_pcmcia_write8, .write16 = ssb_pcmcia_write16, .write32 = ssb_pcmcia_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_pcmcia_block_read, + .block_write = ssb_pcmcia_block_write, +#endif }; -#include <linux/etherdevice.h> +static int ssb_pcmcia_sprom_command(struct ssb_bus *bus, u8 command) +{ + unsigned int i; + int err; + u8 value; + + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROMCTL, command); + if (err) + return err; + for (i = 0; i < 1000; i++) { + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_SPROMCTL, &value); + if (err) + return err; + if (value & SSB_PCMCIA_SPROMCTL_DONE) + return 0; + udelay(10); + } + + return -ETIMEDOUT; +} + +/* offset is the 16bit word offset */ +static int ssb_pcmcia_sprom_read(struct ssb_bus *bus, u16 offset, u16 *value) +{ + int err; + u8 lo, hi; + + offset *= 2; /* Make byte offset */ + + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRLO, + (offset & 0x00FF)); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRHI, + (offset & 0xFF00) >> 8); + if (err) + return err; + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_READ); + if (err) + return err; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_SPROM_DATALO, &lo); + if (err) + return err; + err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_SPROM_DATAHI, &hi); + if (err) + return err; + *value = (lo | (((u16)hi) << 8)); + + return 0; +} + +/* offset is the 16bit word offset */ +static int ssb_pcmcia_sprom_write(struct ssb_bus *bus, u16 offset, u16 value) +{ + int err; + + offset *= 2; /* Make byte offset */ + + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRLO, + (offset & 0x00FF)); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_ADDRHI, + (offset & 0xFF00) >> 8); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_DATALO, + (value & 0x00FF)); + if (err) + return err; + err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_SPROM_DATAHI, + (value & 0xFF00) >> 8); + if (err) + return err; + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITE); + if (err) + return err; + msleep(20); + + return 0; +} + +/* Read the SPROM image. bufsize is in 16bit words. */ +static int ssb_pcmcia_sprom_read_all(struct ssb_bus *bus, u16 *sprom) +{ + int err, i; + + for (i = 0; i < SSB_PCMCIA_SPROM_SIZE; i++) { + err = ssb_pcmcia_sprom_read(bus, i, &sprom[i]); + if (err) + return err; + } + + return 0; +} + +/* Write the SPROM image. size is in 16bit words. */ +static int ssb_pcmcia_sprom_write_all(struct ssb_bus *bus, const u16 *sprom) +{ + int i, err; + bool failed = 0; + size_t size = SSB_PCMCIA_SPROM_SIZE; + + ssb_notice("Writing SPROM. Do NOT turn off the power! Please stand by...\n"); + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEEN); + if (err) { + ssb_notice("Could not enable SPROM write access\n"); + return -EBUSY; + } + ssb_notice("[ 0%%"); + msleep(500); + for (i = 0; i < size; i++) { + if (i == size / 4) + ssb_cont("25%%"); + else if (i == size / 2) + ssb_cont("50%%"); + else if (i == (size * 3) / 4) + ssb_cont("75%%"); + else if (i % 2) + ssb_cont("."); + err = ssb_pcmcia_sprom_write(bus, i, sprom[i]); + if (err) { + ssb_notice("Failed to write to SPROM\n"); + failed = 1; + break; + } + } + err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEDIS); + if (err) { + ssb_notice("Could not disable SPROM write access\n"); + failed = 1; + } + msleep(500); + if (!failed) { + ssb_cont("100%% ]\n"); + ssb_notice("SPROM written\n"); + } + + return failed ? -EBUSY : 0; +} + +static int ssb_pcmcia_sprom_check_crc(const u16 *sprom, size_t size) +{ + //TODO + return 0; +} + +#define GOTO_ERROR_ON(condition, description) do { \ + if (unlikely(condition)) { \ + error_description = description; \ + goto error; \ + } \ + } while (0) + +static int ssb_pcmcia_get_mac(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv) +{ + struct ssb_sprom *sprom = priv; + + if (tuple->TupleData[0] != CISTPL_FUNCE_LAN_NODE_ID) + return -EINVAL; + if (tuple->TupleDataLen != ETH_ALEN + 2) + return -EINVAL; + if (tuple->TupleData[1] != ETH_ALEN) + return -EINVAL; + memcpy(sprom->il0mac, &tuple->TupleData[2], ETH_ALEN); + return 0; +}; + +static int ssb_pcmcia_do_get_invariants(struct pcmcia_device *p_dev, + tuple_t *tuple, + void *priv) +{ + struct ssb_init_invariants *iv = priv; + struct ssb_sprom *sprom = &iv->sprom; + struct ssb_boardinfo *bi = &iv->boardinfo; + const char *error_description; + + GOTO_ERROR_ON(tuple->TupleDataLen < 1, "VEN tpl < 1"); + switch (tuple->TupleData[0]) { + case SSB_PCMCIA_CIS_ID: + GOTO_ERROR_ON((tuple->TupleDataLen != 5) && + (tuple->TupleDataLen != 7), + "id tpl size"); + bi->vendor = tuple->TupleData[1] | + ((u16)tuple->TupleData[2] << 8); + break; + case SSB_PCMCIA_CIS_BOARDREV: + GOTO_ERROR_ON(tuple->TupleDataLen != 2, + "boardrev tpl size"); + sprom->board_rev = tuple->TupleData[1]; + break; + case SSB_PCMCIA_CIS_PA: + GOTO_ERROR_ON((tuple->TupleDataLen != 9) && + (tuple->TupleDataLen != 10), + "pa tpl size"); + sprom->pa0b0 = tuple->TupleData[1] | + ((u16)tuple->TupleData[2] << 8); + sprom->pa0b1 = tuple->TupleData[3] | + ((u16)tuple->TupleData[4] << 8); + sprom->pa0b2 = tuple->TupleData[5] | + ((u16)tuple->TupleData[6] << 8); + sprom->itssi_a = tuple->TupleData[7]; + sprom->itssi_bg = tuple->TupleData[7]; + sprom->maxpwr_a = tuple->TupleData[8]; + sprom->maxpwr_bg = tuple->TupleData[8]; + break; + case SSB_PCMCIA_CIS_OEMNAME: + /* We ignore this. */ + break; + case SSB_PCMCIA_CIS_CCODE: + GOTO_ERROR_ON(tuple->TupleDataLen != 2, + "ccode tpl size"); + sprom->country_code = tuple->TupleData[1]; + break; + case SSB_PCMCIA_CIS_ANTENNA: + GOTO_ERROR_ON(tuple->TupleDataLen != 2, + "ant tpl size"); + sprom->ant_available_a = tuple->TupleData[1]; + sprom->ant_available_bg = tuple->TupleData[1]; + break; + case SSB_PCMCIA_CIS_ANTGAIN: + GOTO_ERROR_ON(tuple->TupleDataLen != 2, + "antg tpl size"); + sprom->antenna_gain.a0 = tuple->TupleData[1]; + sprom->antenna_gain.a1 = tuple->TupleData[1]; + sprom->antenna_gain.a2 = tuple->TupleData[1]; + sprom->antenna_gain.a3 = tuple->TupleData[1]; + break; + case SSB_PCMCIA_CIS_BFLAGS: + GOTO_ERROR_ON((tuple->TupleDataLen != 3) && + (tuple->TupleDataLen != 5), + "bfl tpl size"); + sprom->boardflags_lo = tuple->TupleData[1] | + ((u16)tuple->TupleData[2] << 8); + break; + case SSB_PCMCIA_CIS_LEDS: + GOTO_ERROR_ON(tuple->TupleDataLen != 5, + "leds tpl size"); + sprom->gpio0 = tuple->TupleData[1]; + sprom->gpio1 = tuple->TupleData[2]; + sprom->gpio2 = tuple->TupleData[3]; + sprom->gpio3 = tuple->TupleData[4]; + break; + } + return -ENOSPC; /* continue with next entry */ + +error: + ssb_err( + "PCMCIA: Failed to fetch device invariants: %s\n", + error_description); + return -ENODEV; +} + + int ssb_pcmcia_get_invariants(struct ssb_bus *bus, struct ssb_init_invariants *iv) { - //TODO - random_ether_addr(iv->sprom.il0mac); + struct ssb_sprom *sprom = &iv->sprom; + int res; + + memset(sprom, 0xFF, sizeof(*sprom)); + sprom->revision = 1; + sprom->boardflags_lo = 0; + sprom->boardflags_hi = 0; + + /* First fetch the MAC address. */ + res = pcmcia_loop_tuple(bus->host_pcmcia, CISTPL_FUNCE, + ssb_pcmcia_get_mac, sprom); + if (res != 0) { + ssb_err( + "PCMCIA: Failed to fetch MAC address\n"); + return -ENODEV; + } + + /* Fetch the vendor specific tuples. */ + res = pcmcia_loop_tuple(bus->host_pcmcia, SSB_PCMCIA_CIS, + ssb_pcmcia_do_get_invariants, iv); + if ((res == 0) || (res == -ENOSPC)) + return 0; + + ssb_err( + "PCMCIA: Failed to fetch device invariants\n"); + return -ENODEV; +} + +static ssize_t ssb_pcmcia_attr_sprom_show(struct device *pcmciadev, + struct device_attribute *attr, + char *buf) +{ + struct pcmcia_device *pdev = + container_of(pcmciadev, struct pcmcia_device, dev); + struct ssb_bus *bus; + + bus = ssb_pcmcia_dev_to_bus(pdev); + if (!bus) + return -ENODEV; + + return ssb_attr_sprom_show(bus, buf, + ssb_pcmcia_sprom_read_all); +} + +static ssize_t ssb_pcmcia_attr_sprom_store(struct device *pcmciadev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct pcmcia_device *pdev = + container_of(pcmciadev, struct pcmcia_device, dev); + struct ssb_bus *bus; + + bus = ssb_pcmcia_dev_to_bus(pdev); + if (!bus) + return -ENODEV; + + return ssb_attr_sprom_store(bus, buf, count, + ssb_pcmcia_sprom_check_crc, + ssb_pcmcia_sprom_write_all); +} + +static DEVICE_ATTR(ssb_sprom, 0600, + ssb_pcmcia_attr_sprom_show, + ssb_pcmcia_attr_sprom_store); + +static int ssb_pcmcia_cor_setup(struct ssb_bus *bus, u8 cor) +{ + u8 val; + int err; + + err = ssb_pcmcia_cfg_read(bus, cor, &val); + if (err) + return err; + val &= ~COR_SOFT_RESET; + val |= COR_FUNC_ENA | COR_IREQ_ENA | COR_LEVEL_REQ; + err = ssb_pcmcia_cfg_write(bus, cor, val); + if (err) + return err; + msleep(40); + return 0; } -int ssb_pcmcia_init(struct ssb_bus *bus) +/* Initialize the PCMCIA hardware. This is called on Init and Resume. */ +int ssb_pcmcia_hardware_setup(struct ssb_bus *bus) { - conf_reg_t reg; int err; if (bus->bustype != SSB_BUSTYPE_PCMCIA) @@ -264,24 +798,45 @@ int ssb_pcmcia_init(struct ssb_bus *bus) /* Switch segment to a known state and sync * bus->mapped_pcmcia_seg with hardware state. */ ssb_pcmcia_switch_segment(bus, 0); + /* Init the COR register. */ + err = ssb_pcmcia_cor_setup(bus, CISREG_COR); + if (err) + return err; + /* Some cards also need this register to get poked. */ + err = ssb_pcmcia_cor_setup(bus, CISREG_COR + 0x80); + if (err) + return err; + + return 0; +} + +void ssb_pcmcia_exit(struct ssb_bus *bus) +{ + if (bus->bustype != SSB_BUSTYPE_PCMCIA) + return; + + device_remove_file(&bus->host_pcmcia->dev, &dev_attr_ssb_sprom); +} + +int ssb_pcmcia_init(struct ssb_bus *bus) +{ + int err; - /* Init IRQ routing */ - reg.Action = CS_READ; - reg.Function = 0; - if (bus->chip_id == 0x4306) - reg.Offset = 0x00; - else - reg.Offset = 0x80; - err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (err != CS_SUCCESS) + if (bus->bustype != SSB_BUSTYPE_PCMCIA) + return 0; + + err = ssb_pcmcia_hardware_setup(bus); + if (err) goto error; - reg.Action = CS_WRITE; - reg.Value |= 0x04 | 0x01; - err = pcmcia_access_configuration_register(bus->host_pcmcia, ®); - if (err != CS_SUCCESS) + + bus->sprom_size = SSB_PCMCIA_SPROM_SIZE; + mutex_init(&bus->sprom_mutex); + err = device_create_file(&bus->host_pcmcia->dev, &dev_attr_ssb_sprom); + if (err) goto error; return 0; error: - return -ENODEV; + ssb_err("Failed to initialize PCMCIA host device\n"); + return err; } diff --git a/drivers/ssb/scan.c b/drivers/ssb/scan.c index 63ee5cfbefb..b9429df583e 100644 --- a/drivers/ssb/scan.c +++ b/drivers/ssb/scan.c @@ -2,7 +2,7 @@ * Sonics Silicon Backplane * Bus scanning * - * Copyright (C) 2005-2007 Michael Buesch <mb@bu3sch.de> + * Copyright (C) 2005-2007 Michael Buesch <m@bues.ch> * Copyright (C) 2005 Martin Langer <martin-langer@gmx.de> * Copyright (C) 2005 Stefano Brivio <st3@riseup.net> * Copyright (C) 2005 Danny van Dyk <kugelfang@gentoo.org> @@ -17,8 +17,6 @@ #include <linux/pci.h> #include <linux/io.h> -#include <pcmcia/cs_types.h> -#include <pcmcia/cs.h> #include <pcmcia/cistpl.h> #include <pcmcia/ds.h> @@ -92,6 +90,8 @@ const char *ssb_core_name(u16 coreid) return "ARM 1176"; case SSB_DEV_ARM_7TDMI: return "ARM 7TDMI"; + case SSB_DEV_ARM_CM3: + return "ARM Cortex M3"; } return "UNKNOWN"; } @@ -125,8 +125,7 @@ static u16 pcidev_to_chipid(struct pci_dev *pci_dev) chipid_fallback = 0x4401; break; default: - ssb_printk(KERN_ERR PFX - "PCI-ID not in fallback list\n"); + ssb_err("PCI-ID not in fallback list\n"); } return chipid_fallback; @@ -152,8 +151,7 @@ static u8 chipid_to_nrcores(u16 chipid) case 0x4704: return 9; default: - ssb_printk(KERN_ERR PFX - "CHIPID not in nrcores fallback list\n"); + ssb_err("CHIPID not in nrcores fallback list\n"); } return 1; @@ -162,6 +160,8 @@ static u8 chipid_to_nrcores(u16 chipid) static u32 scan_read32(struct ssb_bus *bus, u8 current_coreidx, u16 offset) { + u32 lo, hi; + switch (bus->bustype) { case SSB_BUSTYPE_SSB: offset += current_coreidx * SSB_CORE_SIZE; @@ -174,7 +174,12 @@ static u32 scan_read32(struct ssb_bus *bus, u8 current_coreidx, offset -= 0x800; } else ssb_pcmcia_switch_segment(bus, 0); - break; + lo = readw(bus->mmio + offset); + hi = readw(bus->mmio + offset + 2); + return lo | (hi << 16); + case SSB_BUSTYPE_SDIO: + offset += current_coreidx * SSB_CORE_SIZE; + return ssb_sdio_scan_read32(bus, offset); } return readl(bus->mmio + offset); } @@ -188,6 +193,8 @@ static int scan_switchcore(struct ssb_bus *bus, u8 coreidx) return ssb_pci_switch_coreidx(bus, coreidx); case SSB_BUSTYPE_PCMCIA: return ssb_pcmcia_switch_coreidx(bus, coreidx); + case SSB_BUSTYPE_SDIO: + return ssb_sdio_scan_switch_coreidx(bus, coreidx); } return 0; } @@ -206,6 +213,8 @@ void ssb_iounmap(struct ssb_bus *bus) SSB_BUG_ON(1); /* Can't reach this code. */ #endif break; + case SSB_BUSTYPE_SDIO: + break; } bus->mmio = NULL; bus->mapped_device = NULL; @@ -230,6 +239,10 @@ static void __iomem *ssb_ioremap(struct ssb_bus *bus, SSB_BUG_ON(1); /* Can't reach this code. */ #endif break; + case SSB_BUSTYPE_SDIO: + /* Nothing to ioremap in the SDIO case, just fake it */ + mmio = (void __iomem *)baseaddr; + break; } return mmio; @@ -245,7 +258,10 @@ static int we_support_multiple_80211_cores(struct ssb_bus *bus) #ifdef CONFIG_SSB_PCIHOST if (bus->bustype == SSB_BUSTYPE_PCI) { if (bus->host_pci->vendor == PCI_VENDOR_ID_BROADCOM && - bus->host_pci->device == 0x4324) + ((bus->host_pci->device == 0x4313) || + (bus->host_pci->device == 0x431A) || + (bus->host_pci->device == 0x4321) || + (bus->host_pci->device == 0x4324))) return 1; } #endif /* CONFIG_SSB_PCIHOST */ @@ -294,8 +310,7 @@ int ssb_bus_scan(struct ssb_bus *bus, } else { if (bus->bustype == SSB_BUSTYPE_PCI) { bus->chip_id = pcidev_to_chipid(bus->host_pci); - pci_read_config_word(bus->host_pci, PCI_REVISION_ID, - &bus->chip_rev); + bus->chip_rev = bus->host_pci->revision; bus->chip_package = 0; } else { bus->chip_id = 0x4710; @@ -303,12 +318,13 @@ int ssb_bus_scan(struct ssb_bus *bus, bus->chip_package = 0; } } + ssb_info("Found chip with id 0x%04X, rev 0x%02X and package 0x%02X\n", + bus->chip_id, bus->chip_rev, bus->chip_package); if (!bus->nr_devices) bus->nr_devices = chipid_to_nrcores(bus->chip_id); if (bus->nr_devices > ARRAY_SIZE(bus->devices)) { - ssb_printk(KERN_ERR PFX - "More than %d ssb cores found (%d)\n", - SSB_MAX_NR_CORES, bus->nr_devices); + ssb_err("More than %d ssb cores found (%d)\n", + SSB_MAX_NR_CORES, bus->nr_devices); goto err_unmap; } if (bus->bustype == SSB_BUSTYPE_SSB) { @@ -339,7 +355,7 @@ int ssb_bus_scan(struct ssb_bus *bus, dev->bus = bus; dev->ops = bus->ops; - ssb_dprintk(KERN_INFO PFX + printk(KERN_DEBUG PFX "Core %d found: %s " "(cc 0x%03X, rev 0x%02X, vendor 0x%04X)\n", i, ssb_core_name(dev->id.coreid), @@ -350,8 +366,7 @@ int ssb_bus_scan(struct ssb_bus *bus, nr_80211_cores++; if (nr_80211_cores > 1) { if (!we_support_multiple_80211_cores(bus)) { - ssb_dprintk(KERN_INFO PFX "Ignoring additional " - "802.11 core\n"); + ssb_dbg("Ignoring additional 802.11 core\n"); continue; } } @@ -359,8 +374,7 @@ int ssb_bus_scan(struct ssb_bus *bus, case SSB_DEV_EXTIF: #ifdef CONFIG_SSB_DRIVER_EXTIF if (bus->extif.dev) { - ssb_printk(KERN_WARNING PFX - "WARNING: Multiple EXTIFs found\n"); + ssb_warn("WARNING: Multiple EXTIFs found\n"); break; } bus->extif.dev = dev; @@ -368,8 +382,7 @@ int ssb_bus_scan(struct ssb_bus *bus, break; case SSB_DEV_CHIPCOMMON: if (bus->chipco.dev) { - ssb_printk(KERN_WARNING PFX - "WARNING: Multiple ChipCommon found\n"); + ssb_warn("WARNING: Multiple ChipCommon found\n"); break; } bus->chipco.dev = dev; @@ -378,8 +391,7 @@ int ssb_bus_scan(struct ssb_bus *bus, case SSB_DEV_MIPS_3302: #ifdef CONFIG_SSB_DRIVER_MIPS if (bus->mipscore.dev) { - ssb_printk(KERN_WARNING PFX - "WARNING: Multiple MIPS cores found\n"); + ssb_warn("WARNING: Multiple MIPS cores found\n"); break; } bus->mipscore.dev = dev; @@ -392,21 +404,30 @@ int ssb_bus_scan(struct ssb_bus *bus, /* Ignore PCI cores on PCI-E cards. * Ignore PCI-E cores on PCI cards. */ if (dev->id.coreid == SSB_DEV_PCI) { - if (bus->host_pci->is_pcie) + if (pci_is_pcie(bus->host_pci)) continue; } else { - if (!bus->host_pci->is_pcie) + if (!pci_is_pcie(bus->host_pci)) continue; } } if (bus->pcicore.dev) { - ssb_printk(KERN_WARNING PFX - "WARNING: Multiple PCI(E) cores found\n"); + ssb_warn("WARNING: Multiple PCI(E) cores found\n"); break; } bus->pcicore.dev = dev; #endif /* CONFIG_SSB_DRIVER_PCICORE */ break; + case SSB_DEV_ETHERNET: + if (bus->bustype == SSB_BUSTYPE_PCI) { + if (bus->host_pci->vendor == PCI_VENDOR_ID_BROADCOM && + (bus->host_pci->device & 0xFF00) == 0x4300) { + /* This is a dangling ethernet core on a + * wireless device. Ignore it. */ + continue; + } + } + break; default: break; } diff --git a/drivers/ssb/sdio.c b/drivers/ssb/sdio.c new file mode 100644 index 00000000000..b2d36f7736c --- /dev/null +++ b/drivers/ssb/sdio.c @@ -0,0 +1,606 @@ +/* + * Sonics Silicon Backplane + * SDIO-Hostbus related functions + * + * Copyright 2009 Albert Herranz <albert_herranz@yahoo.es> + * + * Based on drivers/ssb/pcmcia.c + * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> + * Copyright 2007-2008 Michael Buesch <m@bues.ch> + * + * Licensed under the GNU/GPL. See COPYING for details. + * + */ + +#include <linux/ssb/ssb.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/etherdevice.h> +#include <linux/mmc/sdio_func.h> + +#include "ssb_private.h" + +/* Define the following to 1 to enable a printk on each coreswitch. */ +#define SSB_VERBOSE_SDIOCORESWITCH_DEBUG 0 + + +/* Hardware invariants CIS tuples */ +#define SSB_SDIO_CIS 0x80 +#define SSB_SDIO_CIS_SROMREV 0x00 +#define SSB_SDIO_CIS_ID 0x01 +#define SSB_SDIO_CIS_BOARDREV 0x02 +#define SSB_SDIO_CIS_PA 0x03 +#define SSB_SDIO_CIS_PA_PA0B0_LO 0 +#define SSB_SDIO_CIS_PA_PA0B0_HI 1 +#define SSB_SDIO_CIS_PA_PA0B1_LO 2 +#define SSB_SDIO_CIS_PA_PA0B1_HI 3 +#define SSB_SDIO_CIS_PA_PA0B2_LO 4 +#define SSB_SDIO_CIS_PA_PA0B2_HI 5 +#define SSB_SDIO_CIS_PA_ITSSI 6 +#define SSB_SDIO_CIS_PA_MAXPOW 7 +#define SSB_SDIO_CIS_OEMNAME 0x04 +#define SSB_SDIO_CIS_CCODE 0x05 +#define SSB_SDIO_CIS_ANTENNA 0x06 +#define SSB_SDIO_CIS_ANTGAIN 0x07 +#define SSB_SDIO_CIS_BFLAGS 0x08 +#define SSB_SDIO_CIS_LEDS 0x09 + +#define CISTPL_FUNCE_LAN_NODE_ID 0x04 /* same as in PCMCIA */ + + +/* + * Function 1 miscellaneous registers. + * + * Definitions match src/include/sbsdio.h from the + * Android Open Source Project + * http://android.git.kernel.org/?p=platform/system/wlan/broadcom.git + * + */ +#define SBSDIO_FUNC1_SBADDRLOW 0x1000a /* SB Address window Low (b15) */ +#define SBSDIO_FUNC1_SBADDRMID 0x1000b /* SB Address window Mid (b23-b16) */ +#define SBSDIO_FUNC1_SBADDRHIGH 0x1000c /* SB Address window High (b24-b31) */ + +/* valid bits in SBSDIO_FUNC1_SBADDRxxx regs */ +#define SBSDIO_SBADDRLOW_MASK 0x80 /* Valid address bits in SBADDRLOW */ +#define SBSDIO_SBADDRMID_MASK 0xff /* Valid address bits in SBADDRMID */ +#define SBSDIO_SBADDRHIGH_MASK 0xff /* Valid address bits in SBADDRHIGH */ + +#define SBSDIO_SB_OFT_ADDR_MASK 0x7FFF /* sb offset addr is <= 15 bits, 32k */ + +/* REVISIT: this flag doesn't seem to matter */ +#define SBSDIO_SB_ACCESS_2_4B_FLAG 0x8000 /* forces 32-bit SB access */ + + +/* + * Address map within the SDIO function address space (128K). + * + * Start End Description + * ------- ------- ------------------------------------------ + * 0x00000 0x0ffff selected backplane address window (64K) + * 0x10000 0x1ffff backplane control registers (max 64K) + * + * The current address window is configured by writing to registers + * SBADDRLOW, SBADDRMID and SBADDRHIGH. + * + * In order to access the contents of a 32-bit Silicon Backplane address + * the backplane address window must be first loaded with the highest + * 16 bits of the target address. Then, an access must be done to the + * SDIO function address space using the lower 15 bits of the address. + * Bit 15 of the address must be set when doing 32 bit accesses. + * + * 10987654321098765432109876543210 + * WWWWWWWWWWWWWWWWW SB Address Window + * OOOOOOOOOOOOOOOO Offset within SB Address Window + * a 32-bit access flag + */ + + +/* + * SSB I/O via SDIO. + * + * NOTE: SDIO address @addr is 17 bits long (SDIO address space is 128K). + */ + +static inline struct device *ssb_sdio_dev(struct ssb_bus *bus) +{ + return &bus->host_sdio->dev; +} + +/* host claimed */ +static int ssb_sdio_writeb(struct ssb_bus *bus, unsigned int addr, u8 val) +{ + int error = 0; + + sdio_writeb(bus->host_sdio, val, addr, &error); + if (unlikely(error)) { + dev_dbg(ssb_sdio_dev(bus), "%08X <- %02x, error %d\n", + addr, val, error); + } + + return error; +} + +#if 0 +static u8 ssb_sdio_readb(struct ssb_bus *bus, unsigned int addr) +{ + u8 val; + int error = 0; + + val = sdio_readb(bus->host_sdio, addr, &error); + if (unlikely(error)) { + dev_dbg(ssb_sdio_dev(bus), "%08X -> %02x, error %d\n", + addr, val, error); + } + + return val; +} +#endif + +/* host claimed */ +static int ssb_sdio_set_sbaddr_window(struct ssb_bus *bus, u32 address) +{ + int error; + + error = ssb_sdio_writeb(bus, SBSDIO_FUNC1_SBADDRLOW, + (address >> 8) & SBSDIO_SBADDRLOW_MASK); + if (error) + goto out; + error = ssb_sdio_writeb(bus, SBSDIO_FUNC1_SBADDRMID, + (address >> 16) & SBSDIO_SBADDRMID_MASK); + if (error) + goto out; + error = ssb_sdio_writeb(bus, SBSDIO_FUNC1_SBADDRHIGH, + (address >> 24) & SBSDIO_SBADDRHIGH_MASK); + if (error) + goto out; + bus->sdio_sbaddr = address; +out: + if (error) { + dev_dbg(ssb_sdio_dev(bus), "failed to set address window" + " to 0x%08x, error %d\n", address, error); + } + + return error; +} + +/* for enumeration use only */ +u32 ssb_sdio_scan_read32(struct ssb_bus *bus, u16 offset) +{ + u32 val; + int error; + + sdio_claim_host(bus->host_sdio); + val = sdio_readl(bus->host_sdio, offset, &error); + sdio_release_host(bus->host_sdio); + if (unlikely(error)) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X > %08x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } + + return val; +} + +/* for enumeration use only */ +int ssb_sdio_scan_switch_coreidx(struct ssb_bus *bus, u8 coreidx) +{ + u32 sbaddr; + int error; + + sbaddr = (coreidx * SSB_CORE_SIZE) + SSB_ENUM_BASE; + sdio_claim_host(bus->host_sdio); + error = ssb_sdio_set_sbaddr_window(bus, sbaddr); + sdio_release_host(bus->host_sdio); + if (error) { + dev_err(ssb_sdio_dev(bus), "failed to switch to core %u," + " error %d\n", coreidx, error); + goto out; + } +out: + return error; +} + +/* host must be already claimed */ +int ssb_sdio_switch_core(struct ssb_bus *bus, struct ssb_device *dev) +{ + u8 coreidx = dev->core_index; + u32 sbaddr; + int error = 0; + + sbaddr = (coreidx * SSB_CORE_SIZE) + SSB_ENUM_BASE; + if (unlikely(bus->sdio_sbaddr != sbaddr)) { +#if SSB_VERBOSE_SDIOCORESWITCH_DEBUG + dev_info(ssb_sdio_dev(bus), + "switching to %s core, index %d\n", + ssb_core_name(dev->id.coreid), coreidx); +#endif + error = ssb_sdio_set_sbaddr_window(bus, sbaddr); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "failed to switch to" + " core %u, error %d\n", coreidx, error); + goto out; + } + bus->mapped_device = dev; + } + +out: + return error; +} + +static u8 ssb_sdio_read8(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + u8 val = 0xff; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) + goto out; + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + val = sdio_readb(bus->host_sdio, offset, &error); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X > %02x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } +out: + sdio_release_host(bus->host_sdio); + + return val; +} + +static u16 ssb_sdio_read16(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + u16 val = 0xffff; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) + goto out; + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + val = sdio_readw(bus->host_sdio, offset, &error); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X > %04x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } +out: + sdio_release_host(bus->host_sdio); + + return val; +} + +static u32 ssb_sdio_read32(struct ssb_device *dev, u16 offset) +{ + struct ssb_bus *bus = dev->bus; + u32 val = 0xffffffff; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) + goto out; + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + offset |= SBSDIO_SB_ACCESS_2_4B_FLAG; /* 32 bit data access */ + val = sdio_readl(bus->host_sdio, offset, &error); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X > %08x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } +out: + sdio_release_host(bus->host_sdio); + + return val; +} + +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_sdio_block_read(struct ssb_device *dev, void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + size_t saved_count = count; + struct ssb_bus *bus = dev->bus; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) { + error = -EIO; + memset(buffer, 0xff, count); + goto err_out; + } + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + + switch (reg_width) { + case sizeof(u8): { + error = sdio_readsb(bus->host_sdio, buffer, offset, count); + break; + } + case sizeof(u16): { + SSB_WARN_ON(count & 1); + error = sdio_readsb(bus->host_sdio, buffer, offset, count); + break; + } + case sizeof(u32): { + SSB_WARN_ON(count & 3); + offset |= SBSDIO_SB_ACCESS_2_4B_FLAG; /* 32 bit data access */ + error = sdio_readsb(bus->host_sdio, buffer, offset, count); + break; + } + default: + SSB_WARN_ON(1); + } + if (!error) + goto out; + +err_out: + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X (width=%u, len=%zu), error %d\n", + bus->sdio_sbaddr >> 16, offset, reg_width, saved_count, error); +out: + sdio_release_host(bus->host_sdio); +} +#endif /* CONFIG_SSB_BLOCKIO */ + +static void ssb_sdio_write8(struct ssb_device *dev, u16 offset, u8 val) +{ + struct ssb_bus *bus = dev->bus; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) + goto out; + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + sdio_writeb(bus->host_sdio, val, offset, &error); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X < %02x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } +out: + sdio_release_host(bus->host_sdio); +} + +static void ssb_sdio_write16(struct ssb_device *dev, u16 offset, u16 val) +{ + struct ssb_bus *bus = dev->bus; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) + goto out; + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + sdio_writew(bus->host_sdio, val, offset, &error); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X < %04x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } +out: + sdio_release_host(bus->host_sdio); +} + +static void ssb_sdio_write32(struct ssb_device *dev, u16 offset, u32 val) +{ + struct ssb_bus *bus = dev->bus; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) + goto out; + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + offset |= SBSDIO_SB_ACCESS_2_4B_FLAG; /* 32 bit data access */ + sdio_writel(bus->host_sdio, val, offset, &error); + if (error) { + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X < %08x, error %d\n", + bus->sdio_sbaddr >> 16, offset, val, error); + } + if (bus->quirks & SSB_QUIRK_SDIO_READ_AFTER_WRITE32) + sdio_readl(bus->host_sdio, 0, &error); +out: + sdio_release_host(bus->host_sdio); +} + +#ifdef CONFIG_SSB_BLOCKIO +static void ssb_sdio_block_write(struct ssb_device *dev, const void *buffer, + size_t count, u16 offset, u8 reg_width) +{ + size_t saved_count = count; + struct ssb_bus *bus = dev->bus; + int error = 0; + + sdio_claim_host(bus->host_sdio); + if (unlikely(ssb_sdio_switch_core(bus, dev))) { + error = -EIO; + memset((void *)buffer, 0xff, count); + goto err_out; + } + offset |= bus->sdio_sbaddr & 0xffff; + offset &= SBSDIO_SB_OFT_ADDR_MASK; + + switch (reg_width) { + case sizeof(u8): + error = sdio_writesb(bus->host_sdio, offset, + (void *)buffer, count); + break; + case sizeof(u16): + SSB_WARN_ON(count & 1); + error = sdio_writesb(bus->host_sdio, offset, + (void *)buffer, count); + break; + case sizeof(u32): + SSB_WARN_ON(count & 3); + offset |= SBSDIO_SB_ACCESS_2_4B_FLAG; /* 32 bit data access */ + error = sdio_writesb(bus->host_sdio, offset, + (void *)buffer, count); + break; + default: + SSB_WARN_ON(1); + } + if (!error) + goto out; + +err_out: + dev_dbg(ssb_sdio_dev(bus), "%04X:%04X (width=%u, len=%zu), error %d\n", + bus->sdio_sbaddr >> 16, offset, reg_width, saved_count, error); +out: + sdio_release_host(bus->host_sdio); +} + +#endif /* CONFIG_SSB_BLOCKIO */ + +/* Not "static", as it's used in main.c */ +const struct ssb_bus_ops ssb_sdio_ops = { + .read8 = ssb_sdio_read8, + .read16 = ssb_sdio_read16, + .read32 = ssb_sdio_read32, + .write8 = ssb_sdio_write8, + .write16 = ssb_sdio_write16, + .write32 = ssb_sdio_write32, +#ifdef CONFIG_SSB_BLOCKIO + .block_read = ssb_sdio_block_read, + .block_write = ssb_sdio_block_write, +#endif +}; + +#define GOTO_ERROR_ON(condition, description) do { \ + if (unlikely(condition)) { \ + error_description = description; \ + goto error; \ + } \ + } while (0) + +int ssb_sdio_get_invariants(struct ssb_bus *bus, + struct ssb_init_invariants *iv) +{ + struct ssb_sprom *sprom = &iv->sprom; + struct ssb_boardinfo *bi = &iv->boardinfo; + const char *error_description = "none"; + struct sdio_func_tuple *tuple; + void *mac; + + memset(sprom, 0xFF, sizeof(*sprom)); + sprom->boardflags_lo = 0; + sprom->boardflags_hi = 0; + + tuple = bus->host_sdio->tuples; + while (tuple) { + switch (tuple->code) { + case 0x22: /* extended function */ + switch (tuple->data[0]) { + case CISTPL_FUNCE_LAN_NODE_ID: + GOTO_ERROR_ON((tuple->size != 7) && + (tuple->data[1] != 6), + "mac tpl size"); + /* fetch the MAC address. */ + mac = tuple->data + 2; + memcpy(sprom->il0mac, mac, ETH_ALEN); + memcpy(sprom->et1mac, mac, ETH_ALEN); + break; + default: + break; + } + break; + case 0x80: /* vendor specific tuple */ + switch (tuple->data[0]) { + case SSB_SDIO_CIS_SROMREV: + GOTO_ERROR_ON(tuple->size != 2, + "sromrev tpl size"); + sprom->revision = tuple->data[1]; + break; + case SSB_SDIO_CIS_ID: + GOTO_ERROR_ON((tuple->size != 5) && + (tuple->size != 7), + "id tpl size"); + bi->vendor = tuple->data[1] | + (tuple->data[2]<<8); + break; + case SSB_SDIO_CIS_BOARDREV: + GOTO_ERROR_ON(tuple->size != 2, + "boardrev tpl size"); + sprom->board_rev = tuple->data[1]; + break; + case SSB_SDIO_CIS_PA: + GOTO_ERROR_ON((tuple->size != 9) && + (tuple->size != 10), + "pa tpl size"); + sprom->pa0b0 = tuple->data[1] | + ((u16)tuple->data[2] << 8); + sprom->pa0b1 = tuple->data[3] | + ((u16)tuple->data[4] << 8); + sprom->pa0b2 = tuple->data[5] | + ((u16)tuple->data[6] << 8); + sprom->itssi_a = tuple->data[7]; + sprom->itssi_bg = tuple->data[7]; + sprom->maxpwr_a = tuple->data[8]; + sprom->maxpwr_bg = tuple->data[8]; + break; + case SSB_SDIO_CIS_OEMNAME: + /* Not present */ + break; + case SSB_SDIO_CIS_CCODE: + GOTO_ERROR_ON(tuple->size != 2, + "ccode tpl size"); + sprom->country_code = tuple->data[1]; + break; + case SSB_SDIO_CIS_ANTENNA: + GOTO_ERROR_ON(tuple->size != 2, + "ant tpl size"); + sprom->ant_available_a = tuple->data[1]; + sprom->ant_available_bg = tuple->data[1]; + break; + case SSB_SDIO_CIS_ANTGAIN: + GOTO_ERROR_ON(tuple->size != 2, + "antg tpl size"); + sprom->antenna_gain.a0 = tuple->data[1]; + sprom->antenna_gain.a1 = tuple->data[1]; + sprom->antenna_gain.a2 = tuple->data[1]; + sprom->antenna_gain.a3 = tuple->data[1]; + break; + case SSB_SDIO_CIS_BFLAGS: + GOTO_ERROR_ON((tuple->size != 3) && + (tuple->size != 5), + "bfl tpl size"); + sprom->boardflags_lo = tuple->data[1] | + ((u16)tuple->data[2] << 8); + break; + case SSB_SDIO_CIS_LEDS: + GOTO_ERROR_ON(tuple->size != 5, + "leds tpl size"); + sprom->gpio0 = tuple->data[1]; + sprom->gpio1 = tuple->data[2]; + sprom->gpio2 = tuple->data[3]; + sprom->gpio3 = tuple->data[4]; + break; + default: + break; + } + break; + default: + break; + } + tuple = tuple->next; + } + + return 0; +error: + dev_err(ssb_sdio_dev(bus), "failed to fetch device invariants: %s\n", + error_description); + return -ENODEV; +} + +void ssb_sdio_exit(struct ssb_bus *bus) +{ + if (bus->bustype != SSB_BUSTYPE_SDIO) + return; + /* Nothing to do here. */ +} + +int ssb_sdio_init(struct ssb_bus *bus) +{ + if (bus->bustype != SSB_BUSTYPE_SDIO) + return 0; + + bus->sdio_sbaddr = ~0; + + return 0; +} diff --git a/drivers/ssb/sprom.c b/drivers/ssb/sprom.c new file mode 100644 index 00000000000..e753fbe302a --- /dev/null +++ b/drivers/ssb/sprom.c @@ -0,0 +1,200 @@ +/* + * Sonics Silicon Backplane + * Common SPROM support routines + * + * Copyright (C) 2005-2008 Michael Buesch <m@bues.ch> + * Copyright (C) 2005 Martin Langer <martin-langer@gmx.de> + * Copyright (C) 2005 Stefano Brivio <st3@riseup.net> + * Copyright (C) 2005 Danny van Dyk <kugelfang@gentoo.org> + * Copyright (C) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch> + * + * Licensed under the GNU/GPL. See COPYING for details. + */ + +#include "ssb_private.h" + +#include <linux/ctype.h> +#include <linux/slab.h> + + +static int(*get_fallback_sprom)(struct ssb_bus *dev, struct ssb_sprom *out); + + +static int sprom2hex(const u16 *sprom, char *buf, size_t buf_len, + size_t sprom_size_words) +{ + int i, pos = 0; + + for (i = 0; i < sprom_size_words; i++) + pos += snprintf(buf + pos, buf_len - pos - 1, + "%04X", swab16(sprom[i]) & 0xFFFF); + pos += snprintf(buf + pos, buf_len - pos - 1, "\n"); + + return pos + 1; +} + +static int hex2sprom(u16 *sprom, const char *dump, size_t len, + size_t sprom_size_words) +{ + char c, tmp[5] = { 0 }; + int err, cnt = 0; + unsigned long parsed; + + /* Strip whitespace at the end. */ + while (len) { + c = dump[len - 1]; + if (!isspace(c) && c != '\0') + break; + len--; + } + /* Length must match exactly. */ + if (len != sprom_size_words * 4) + return -EINVAL; + + while (cnt < sprom_size_words) { + memcpy(tmp, dump, 4); + dump += 4; + err = kstrtoul(tmp, 16, &parsed); + if (err) + return err; + sprom[cnt++] = swab16((u16)parsed); + } + + return 0; +} + +/* Common sprom device-attribute show-handler */ +ssize_t ssb_attr_sprom_show(struct ssb_bus *bus, char *buf, + int (*sprom_read)(struct ssb_bus *bus, u16 *sprom)) +{ + u16 *sprom; + int err = -ENOMEM; + ssize_t count = 0; + size_t sprom_size_words = bus->sprom_size; + + sprom = kcalloc(sprom_size_words, sizeof(u16), GFP_KERNEL); + if (!sprom) + goto out; + + /* Use interruptible locking, as the SPROM write might + * be holding the lock for several seconds. So allow userspace + * to cancel operation. */ + err = -ERESTARTSYS; + if (mutex_lock_interruptible(&bus->sprom_mutex)) + goto out_kfree; + err = sprom_read(bus, sprom); + mutex_unlock(&bus->sprom_mutex); + + if (!err) + count = sprom2hex(sprom, buf, PAGE_SIZE, sprom_size_words); + +out_kfree: + kfree(sprom); +out: + return err ? err : count; +} + +/* Common sprom device-attribute store-handler */ +ssize_t ssb_attr_sprom_store(struct ssb_bus *bus, + const char *buf, size_t count, + int (*sprom_check_crc)(const u16 *sprom, size_t size), + int (*sprom_write)(struct ssb_bus *bus, const u16 *sprom)) +{ + u16 *sprom; + int res = 0, err = -ENOMEM; + size_t sprom_size_words = bus->sprom_size; + struct ssb_freeze_context freeze; + + sprom = kcalloc(bus->sprom_size, sizeof(u16), GFP_KERNEL); + if (!sprom) + goto out; + err = hex2sprom(sprom, buf, count, sprom_size_words); + if (err) { + err = -EINVAL; + goto out_kfree; + } + err = sprom_check_crc(sprom, sprom_size_words); + if (err) { + err = -EINVAL; + goto out_kfree; + } + + /* Use interruptible locking, as the SPROM write might + * be holding the lock for several seconds. So allow userspace + * to cancel operation. */ + err = -ERESTARTSYS; + if (mutex_lock_interruptible(&bus->sprom_mutex)) + goto out_kfree; + err = ssb_devices_freeze(bus, &freeze); + if (err) { + ssb_err("SPROM write: Could not freeze all devices\n"); + goto out_unlock; + } + res = sprom_write(bus, sprom); + err = ssb_devices_thaw(&freeze); + if (err) + ssb_err("SPROM write: Could not thaw all devices\n"); +out_unlock: + mutex_unlock(&bus->sprom_mutex); +out_kfree: + kfree(sprom); +out: + if (res) + return res; + return err ? err : count; +} + +/** + * ssb_arch_register_fallback_sprom - Registers a method providing a + * fallback SPROM if no SPROM is found. + * + * @sprom_callback: The callback function. + * + * With this function the architecture implementation may register a + * callback handler which fills the SPROM data structure. The fallback is + * only used for PCI based SSB devices, where no valid SPROM can be found + * in the shadow registers. + * + * This function is useful for weird architectures that have a half-assed + * SSB device hardwired to their PCI bus. + * + * Note that it does only work with PCI attached SSB devices. PCMCIA + * devices currently don't use this fallback. + * Architectures must provide the SPROM for native SSB devices anyway, so + * the fallback also isn't used for native devices. + * + * This function is available for architecture code, only. So it is not + * exported. + */ +int ssb_arch_register_fallback_sprom(int (*sprom_callback)(struct ssb_bus *bus, + struct ssb_sprom *out)) +{ + if (get_fallback_sprom) + return -EEXIST; + get_fallback_sprom = sprom_callback; + + return 0; +} + +int ssb_fill_sprom_with_fallback(struct ssb_bus *bus, struct ssb_sprom *out) +{ + if (!get_fallback_sprom) + return -ENOENT; + + return get_fallback_sprom(bus, out); +} + +/* http://bcm-v4.sipsolutions.net/802.11/IsSpromAvailable */ +bool ssb_is_sprom_available(struct ssb_bus *bus) +{ + /* status register only exists on chipcomon rev >= 11 and we need check + for >= 31 only */ + /* this routine differs from specs as we do not access SPROM directly + on PCMCIA */ + if (bus->bustype == SSB_BUSTYPE_PCI && + bus->chipco.dev && /* can be unavailable! */ + bus->chipco.dev->id.revision >= 31) + return bus->chipco.capabilities & SSB_CHIPCO_CAP_SPROM; + + return true; +} diff --git a/drivers/ssb/ssb_private.h b/drivers/ssb/ssb_private.h index 21eca2b5118..eb507a50a56 100644 --- a/drivers/ssb/ssb_private.h +++ b/drivers/ssb/ssb_private.h @@ -3,21 +3,33 @@ #include <linux/ssb/ssb.h> #include <linux/types.h> +#include <linux/bcm47xx_wdt.h> #define PFX "ssb: " #ifdef CONFIG_SSB_SILENT -# define ssb_printk(fmt, x...) do { /* nothing */ } while (0) +# define ssb_printk(fmt, ...) \ + do { if (0) printk(fmt, ##__VA_ARGS__); } while (0) #else -# define ssb_printk printk +# define ssb_printk(fmt, ...) \ + printk(fmt, ##__VA_ARGS__) #endif /* CONFIG_SSB_SILENT */ +#define ssb_emerg(fmt, ...) ssb_printk(KERN_EMERG PFX fmt, ##__VA_ARGS__) +#define ssb_err(fmt, ...) ssb_printk(KERN_ERR PFX fmt, ##__VA_ARGS__) +#define ssb_warn(fmt, ...) ssb_printk(KERN_WARNING PFX fmt, ##__VA_ARGS__) +#define ssb_notice(fmt, ...) ssb_printk(KERN_NOTICE PFX fmt, ##__VA_ARGS__) +#define ssb_info(fmt, ...) ssb_printk(KERN_INFO PFX fmt, ##__VA_ARGS__) +#define ssb_cont(fmt, ...) ssb_printk(KERN_CONT fmt, ##__VA_ARGS__) + /* dprintk: Debugging printk; vanishes for non-debug compilation */ #ifdef CONFIG_SSB_DEBUG -# define ssb_dprintk(fmt, x...) ssb_printk(fmt , ##x) +# define ssb_dbg(fmt, ...) \ + ssb_printk(KERN_DEBUG PFX fmt, ##__VA_ARGS__) #else -# define ssb_dprintk(fmt, x...) do { /* nothing */ } while (0) +# define ssb_dbg(fmt, ...) \ + do { if (0) printk(KERN_DEBUG PFX fmt, ##__VA_ARGS__); } while (0) #endif #ifdef CONFIG_SSB_DEBUG @@ -81,6 +93,8 @@ extern int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg); extern int ssb_pcmcia_get_invariants(struct ssb_bus *bus, struct ssb_init_invariants *iv); +extern int ssb_pcmcia_hardware_setup(struct ssb_bus *bus); +extern void ssb_pcmcia_exit(struct ssb_bus *bus); extern int ssb_pcmcia_init(struct ssb_bus *bus); extern const struct ssb_bus_ops ssb_pcmcia_ops; #else /* CONFIG_SSB_PCMCIAHOST */ @@ -99,12 +113,59 @@ static inline int ssb_pcmcia_switch_segment(struct ssb_bus *bus, { return 0; } +static inline int ssb_pcmcia_hardware_setup(struct ssb_bus *bus) +{ + return 0; +} +static inline void ssb_pcmcia_exit(struct ssb_bus *bus) +{ +} static inline int ssb_pcmcia_init(struct ssb_bus *bus) { return 0; } #endif /* CONFIG_SSB_PCMCIAHOST */ +/* sdio.c */ +#ifdef CONFIG_SSB_SDIOHOST +extern int ssb_sdio_get_invariants(struct ssb_bus *bus, + struct ssb_init_invariants *iv); + +extern u32 ssb_sdio_scan_read32(struct ssb_bus *bus, u16 offset); +extern int ssb_sdio_switch_core(struct ssb_bus *bus, struct ssb_device *dev); +extern int ssb_sdio_scan_switch_coreidx(struct ssb_bus *bus, u8 coreidx); +extern int ssb_sdio_hardware_setup(struct ssb_bus *bus); +extern void ssb_sdio_exit(struct ssb_bus *bus); +extern int ssb_sdio_init(struct ssb_bus *bus); + +extern const struct ssb_bus_ops ssb_sdio_ops; +#else /* CONFIG_SSB_SDIOHOST */ +static inline u32 ssb_sdio_scan_read32(struct ssb_bus *bus, u16 offset) +{ + return 0; +} +static inline int ssb_sdio_switch_core(struct ssb_bus *bus, + struct ssb_device *dev) +{ + return 0; +} +static inline int ssb_sdio_scan_switch_coreidx(struct ssb_bus *bus, u8 coreidx) +{ + return 0; +} +static inline int ssb_sdio_hardware_setup(struct ssb_bus *bus) +{ + return 0; +} +static inline void ssb_sdio_exit(struct ssb_bus *bus) +{ +} +static inline int ssb_sdio_init(struct ssb_bus *bus) +{ + return 0; +} +#endif /* CONFIG_SSB_SDIOHOST */ + /* scan.c */ extern const char *ssb_core_name(u16 coreid); @@ -113,17 +174,42 @@ extern int ssb_bus_scan(struct ssb_bus *bus, extern void ssb_iounmap(struct ssb_bus *ssb); +/* sprom.c */ +extern +ssize_t ssb_attr_sprom_show(struct ssb_bus *bus, char *buf, + int (*sprom_read)(struct ssb_bus *bus, u16 *sprom)); +extern +ssize_t ssb_attr_sprom_store(struct ssb_bus *bus, + const char *buf, size_t count, + int (*sprom_check_crc)(const u16 *sprom, size_t size), + int (*sprom_write)(struct ssb_bus *bus, const u16 *sprom)); +extern int ssb_fill_sprom_with_fallback(struct ssb_bus *bus, + struct ssb_sprom *out); + + /* core.c */ extern u32 ssb_calc_clock_rate(u32 plltype, u32 n, u32 m); -extern int ssb_devices_freeze(struct ssb_bus *bus); -extern int ssb_devices_thaw(struct ssb_bus *bus); extern struct ssb_bus *ssb_pci_dev_to_bus(struct pci_dev *pdev); +int ssb_for_each_bus_call(unsigned long data, + int (*func)(struct ssb_bus *bus, unsigned long data)); +extern struct ssb_bus *ssb_pcmcia_dev_to_bus(struct pcmcia_device *pdev); + +struct ssb_freeze_context { + /* Pointer to the bus */ + struct ssb_bus *bus; + /* Boolean list to indicate whether a device is frozen on this bus. */ + bool device_frozen[SSB_MAX_NR_CORES]; +}; +extern int ssb_devices_freeze(struct ssb_bus *bus, struct ssb_freeze_context *ctx); +extern int ssb_devices_thaw(struct ssb_freeze_context *ctx); + + /* b43_pci_bridge.c */ #ifdef CONFIG_SSB_B43_PCI_BRIDGE extern int __init b43_pci_ssb_bridge_init(void); extern void __exit b43_pci_ssb_bridge_exit(void); -#else /* CONFIG_SSB_B43_PCI_BRIDGR */ +#else /* CONFIG_SSB_B43_PCI_BRIDGE */ static inline int b43_pci_ssb_bridge_init(void) { return 0; @@ -131,6 +217,81 @@ static inline int b43_pci_ssb_bridge_init(void) static inline void b43_pci_ssb_bridge_exit(void) { } -#endif /* CONFIG_SSB_PCIHOST */ +#endif /* CONFIG_SSB_B43_PCI_BRIDGE */ + +/* driver_chipcommon_pmu.c */ +extern u32 ssb_pmu_get_cpu_clock(struct ssb_chipcommon *cc); +extern u32 ssb_pmu_get_controlclock(struct ssb_chipcommon *cc); +extern u32 ssb_pmu_get_alp_clock(struct ssb_chipcommon *cc); + +extern u32 ssb_chipco_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, + u32 ticks); +extern u32 ssb_chipco_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms); + +/* driver_chipcommon_sflash.c */ +#ifdef CONFIG_SSB_SFLASH +int ssb_sflash_init(struct ssb_chipcommon *cc); +#else +static inline int ssb_sflash_init(struct ssb_chipcommon *cc) +{ + pr_err("Serial flash not supported\n"); + return 0; +} +#endif /* CONFIG_SSB_SFLASH */ + +#ifdef CONFIG_SSB_DRIVER_MIPS +extern struct platform_device ssb_pflash_dev; +#endif + +#ifdef CONFIG_SSB_SFLASH +extern struct platform_device ssb_sflash_dev; +#endif + +#ifdef CONFIG_SSB_DRIVER_EXTIF +extern u32 ssb_extif_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, u32 ticks); +extern u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, u32 ms); +#else +static inline u32 ssb_extif_watchdog_timer_set_wdt(struct bcm47xx_wdt *wdt, + u32 ticks) +{ + return 0; +} +static inline u32 ssb_extif_watchdog_timer_set_ms(struct bcm47xx_wdt *wdt, + u32 ms) +{ + return 0; +} +#endif + +#ifdef CONFIG_SSB_EMBEDDED +extern int ssb_watchdog_register(struct ssb_bus *bus); +#else /* CONFIG_SSB_EMBEDDED */ +static inline int ssb_watchdog_register(struct ssb_bus *bus) +{ + return 0; +} +#endif /* CONFIG_SSB_EMBEDDED */ + +#ifdef CONFIG_SSB_DRIVER_EXTIF +extern void ssb_extif_init(struct ssb_extif *extif); +#else +static inline void ssb_extif_init(struct ssb_extif *extif) +{ +} +#endif + +#ifdef CONFIG_SSB_DRIVER_GPIO +extern int ssb_gpio_init(struct ssb_bus *bus); +extern int ssb_gpio_unregister(struct ssb_bus *bus); +#else /* CONFIG_SSB_DRIVER_GPIO */ +static inline int ssb_gpio_init(struct ssb_bus *bus) +{ + return -ENOTSUPP; +} +static inline int ssb_gpio_unregister(struct ssb_bus *bus) +{ + return 0; +} +#endif /* CONFIG_SSB_DRIVER_GPIO */ #endif /* LINUX_SSB_PRIVATE_H_ */ |
