Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
# SPDX-License-Identifier: GPL-2.0-only
config SSB_POSSIBLE
bool
depends on HAS_IOMEM && HAS_DMA
default y
menuconfig SSB
tristate "Sonics Silicon Backplane support"
depends on SSB_POSSIBLE
help
Support for the Sonics Silicon Backplane bus.
You only need to enable this option, if you are
configuring a kernel for an embedded system with
this bus.
It will be auto-selected if needed in other
environments.
The module will be called ssb.
If unsure, say N.
if SSB
# 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) && (PCI_DRIVERS_LEGACY || !MIPS)
default y
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
of a PCI device.
If unsure, say Y
config SSB_B43_PCI_BRIDGE
bool
depends on SSB_PCIHOST
default n
config SSB_PCMCIAHOST_POSSIBLE
bool
depends on SSB && (PCMCIA = y || PCMCIA = SSB)
default y
config SSB_PCMCIAHOST
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_HOST_SOC
bool "Support for SSB bus on SoC"
depends on SSB && BCM47XX_NVRAM
select SSB_SPROM
help
Host interface for a SSB directly mapped into memory. This is
for some Broadcom SoCs from the BCM47xx and BCM53xx lines.
If unsure, say N
config SSB_SERIAL
bool
depends on SSB
# ChipCommon and ExtIf serial support routines.
config SSB_DRIVER_PCICORE_POSSIBLE
bool
depends on SSB_PCIHOST
default y
config SSB_DRIVER_PCICORE
bool "SSB PCI core driver"
depends on SSB_DRIVER_PCICORE_POSSIBLE
help
Driver for the Sonics Silicon Backplane attached
Broadcom PCI core.
If unsure, say Y
config SSB_PCICORE_HOSTMODE
bool "Hostmode support for SSB PCI core"
depends on SSB_DRIVER_PCICORE && SSB_DRIVER_MIPS && SSB = y
help
PCIcore hostmode operation (external PCI bus).
config SSB_DRIVER_MIPS
bool "SSB Broadcom MIPS core driver"
depends on SSB && MIPS
select SSB_SERIAL
select SSB_SFLASH
help
Driver for the Sonics Silicon Backplane attached
Broadcom MIPS core.
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 PCI=n || SSB_PCICORE_HOSTMODE
default y
config SSB_DRIVER_EXTIF
bool "SSB Broadcom EXTIF core driver"
depends on SSB_DRIVER_MIPS
help
Driver for the Sonics Silicon Backplane attached
Broadcom EXTIF core.
If unsure, say N
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
endif # SSB
+27
View File
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
# 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 bridge_pcmcia_80211.o
ssb-$(CONFIG_SSB_SDIOHOST) += sdio.o
ssb-$(CONFIG_SSB_HOST_SOC) += host_soc.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
ssb-$(CONFIG_SSB_B43_PCI_BRIDGE) += b43_pci_bridge.o
obj-$(CONFIG_SSB) += ssb.o
+60
View File
@@ -0,0 +1,60 @@
/*
* Broadcom 43xx PCI-SSB bridge module
*
* This technically is a separate PCI driver module, but
* because of its small size we include it in the SSB core
* instead of creating a standalone module.
*
* Copyright 2007 Michael Buesch <m@bues.ch>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/ssb/ssb.h>
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) },
{ PCI_DEVICE(PCI_VENDOR_ID_BROADCOM, 0x4351) },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, b43_pci_bridge_tbl);
static struct pci_driver b43_pci_bridge_driver = {
.name = "b43-pci-bridge",
.id_table = b43_pci_bridge_tbl,
};
int __init b43_pci_ssb_bridge_init(void)
{
return ssb_pcihost_register(&b43_pci_bridge_driver);
}
void __exit b43_pci_ssb_bridge_exit(void)
{
ssb_pcihost_unregister(&b43_pci_bridge_driver);
}
@@ -0,0 +1,133 @@
/*
* Broadcom 43xx PCMCIA-SSB bridge module
*
* Copyright (c) 2007 Michael Buesch <m@bues.ch>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ciscode.h>
#include <pcmcia/ds.h>
#include <pcmcia/cisreg.h>
static const struct pcmcia_device_id ssb_host_pcmcia_tbl[] = {
PCMCIA_DEVICE_MANF_CARD(0x2D0, 0x448),
PCMCIA_DEVICE_MANF_CARD(0x2D0, 0x476),
PCMCIA_DEVICE_NULL,
};
MODULE_DEVICE_TABLE(pcmcia, ssb_host_pcmcia_tbl);
static int ssb_host_pcmcia_probe(struct pcmcia_device *dev)
{
struct ssb_bus *ssb;
int err = -ENOMEM;
int res = 0;
ssb = kzalloc_obj(*ssb);
if (!ssb)
goto out_error;
err = -ENODEV;
dev->config_flags |= CONF_ENABLE_IRQ;
dev->resource[2]->flags |= WIN_ENABLE | WIN_DATA_WIDTH_16 |
WIN_USE_WAIT;
dev->resource[2]->start = 0;
dev->resource[2]->end = SSB_CORE_SIZE;
res = pcmcia_request_window(dev, dev->resource[2], 250);
if (res != 0)
goto err_kfree_ssb;
res = pcmcia_map_mem_page(dev, dev->resource[2], 0);
if (res != 0)
goto err_disable;
if (!dev->irq)
goto err_disable;
res = pcmcia_enable_device(dev);
if (res != 0)
goto err_disable;
err = ssb_bus_pcmciabus_register(ssb, dev, dev->resource[2]->start);
if (err)
goto err_disable;
dev->priv = ssb;
return 0;
err_disable:
pcmcia_disable_device(dev);
err_kfree_ssb:
kfree(ssb);
out_error:
dev_err(&dev->dev, "Initialization failed (%d, %d)\n", res, err);
return err;
}
static void ssb_host_pcmcia_remove(struct pcmcia_device *dev)
{
struct ssb_bus *ssb = dev->priv;
ssb_bus_unregister(ssb);
pcmcia_disable_device(dev);
kfree(ssb);
dev->priv = NULL;
}
#ifdef CONFIG_PM
static int ssb_host_pcmcia_suspend(struct pcmcia_device *dev)
{
struct ssb_bus *ssb = dev->priv;
return ssb_bus_suspend(ssb);
}
static int ssb_host_pcmcia_resume(struct pcmcia_device *dev)
{
struct ssb_bus *ssb = dev->priv;
return ssb_bus_resume(ssb);
}
#else /* CONFIG_PM */
# define ssb_host_pcmcia_suspend NULL
# define ssb_host_pcmcia_resume NULL
#endif /* CONFIG_PM */
static struct pcmcia_driver ssb_host_pcmcia_driver = {
.owner = THIS_MODULE,
.name = "ssb-pcmcia",
.id_table = ssb_host_pcmcia_tbl,
.probe = ssb_host_pcmcia_probe,
.remove = ssb_host_pcmcia_remove,
.suspend = ssb_host_pcmcia_suspend,
.resume = ssb_host_pcmcia_resume,
};
static int pcmcia_init_failed;
/*
* These are not module init/exit functions!
* The module_pcmcia_driver() helper cannot be used here.
*/
int ssb_host_pcmcia_init(void)
{
pcmcia_init_failed = pcmcia_register_driver(&ssb_host_pcmcia_driver);
return pcmcia_init_failed;
}
void ssb_host_pcmcia_exit(void)
{
if (!pcmcia_init_failed)
pcmcia_unregister_driver(&ssb_host_pcmcia_driver);
}
+697
View File
@@ -0,0 +1,697 @@
/*
* Sonics Silicon Backplane
* Broadcom ChipCommon core driver
*
* Copyright 2005, Broadcom Corporation
* 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 "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/ssb/ssb_regs.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/bcm47xx_wdt.h>
/* Clock sources */
enum ssb_clksrc {
/* PCI clock */
SSB_CHIPCO_CLKSRC_PCI,
/* Crystal slow clock oscillator */
SSB_CHIPCO_CLKSRC_XTALOS,
/* Low power oscillator */
SSB_CHIPCO_CLKSRC_LOPWROS,
};
static inline u32 chipco_write32_masked(struct ssb_chipcommon *cc, u16 offset,
u32 mask, u32 value)
{
value &= mask;
value |= chipco_read32(cc, offset) & ~mask;
chipco_write32(cc, offset, value);
return value;
}
void ssb_chipco_set_clockmode(struct ssb_chipcommon *cc,
enum ssb_clkmode mode)
{
struct ssb_device *ccdev = cc->dev;
struct ssb_bus *bus;
u32 tmp;
if (!ccdev)
return;
bus = ccdev->bus;
/* 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 */
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+ need testing */
if (ccdev->id.revision >= 10)
return;
if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL))
return;
switch (mode) {
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:
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:
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:
WARN_ON(1);
}
}
/* Get the Slow Clock Source */
static enum ssb_clksrc chipco_pctl_get_slowclksrc(struct ssb_chipcommon *cc)
{
struct ssb_bus *bus = cc->dev->bus;
u32 tmp;
if (cc->dev->id.revision < 6) {
if (bus->bustype == SSB_BUSTYPE_SSB ||
bus->bustype == SSB_BUSTYPE_PCMCIA)
return SSB_CHIPCO_CLKSRC_XTALOS;
if (bus->bustype == SSB_BUSTYPE_PCI) {
pci_read_config_dword(bus->host_pci, SSB_GPIO_OUT, &tmp);
if (tmp & 0x10)
return SSB_CHIPCO_CLKSRC_PCI;
return SSB_CHIPCO_CLKSRC_XTALOS;
}
}
if (cc->dev->id.revision < 10) {
tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
tmp &= 0x7;
if (tmp == 0)
return SSB_CHIPCO_CLKSRC_LOPWROS;
if (tmp == 1)
return SSB_CHIPCO_CLKSRC_XTALOS;
if (tmp == 2)
return SSB_CHIPCO_CLKSRC_PCI;
}
return SSB_CHIPCO_CLKSRC_XTALOS;
}
/* Get maximum or minimum (depending on get_max flag) slowclock frequency. */
static int chipco_pctl_clockfreqlimit(struct ssb_chipcommon *cc, int get_max)
{
int limit;
enum ssb_clksrc clocksrc;
int divisor = 1;
u32 tmp;
clocksrc = chipco_pctl_get_slowclksrc(cc);
if (cc->dev->id.revision < 6) {
switch (clocksrc) {
case SSB_CHIPCO_CLKSRC_PCI:
divisor = 64;
break;
case SSB_CHIPCO_CLKSRC_XTALOS:
divisor = 32;
break;
default:
WARN_ON(1);
}
} else if (cc->dev->id.revision < 10) {
switch (clocksrc) {
case SSB_CHIPCO_CLKSRC_LOPWROS:
break;
case SSB_CHIPCO_CLKSRC_XTALOS:
case SSB_CHIPCO_CLKSRC_PCI:
tmp = chipco_read32(cc, SSB_CHIPCO_SLOWCLKCTL);
divisor = (tmp >> 16) + 1;
divisor *= 4;
break;
}
} else {
tmp = chipco_read32(cc, SSB_CHIPCO_SYSCLKCTL);
divisor = (tmp >> 16) + 1;
divisor *= 4;
}
switch (clocksrc) {
case SSB_CHIPCO_CLKSRC_LOPWROS:
if (get_max)
limit = 43000;
else
limit = 25000;
break;
case SSB_CHIPCO_CLKSRC_XTALOS:
if (get_max)
limit = 20200000;
else
limit = 19800000;
break;
case SSB_CHIPCO_CLKSRC_PCI:
if (get_max)
limit = 34000000;
else
limit = 25000000;
break;
}
limit /= divisor;
return limit;
}
static void chipco_powercontrol_init(struct ssb_chipcommon *cc)
{
struct ssb_bus *bus = cc->dev->bus;
if (bus->chip_id == 0x4321) {
if (bus->chip_rev == 0)
chipco_write32(cc, SSB_CHIPCO_CHIPCTL, 0x3A4);
else if (bus->chip_rev == 1)
chipco_write32(cc, SSB_CHIPCO_CHIPCTL, 0xA4);
}
if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL))
return;
if (cc->dev->id.revision >= 10) {
/* Set Idle Power clock rate to 1Mhz */
chipco_write32(cc, SSB_CHIPCO_SYSCLKCTL,
(chipco_read32(cc, SSB_CHIPCO_SYSCLKCTL) &
0x0000FFFF) | 0x00040000);
} else {
int maxfreq;
maxfreq = chipco_pctl_clockfreqlimit(cc, 1);
chipco_write32(cc, SSB_CHIPCO_PLLONDELAY,
(maxfreq * 150 + 999999) / 1000000);
chipco_write32(cc, SSB_CHIPCO_FREFSELDELAY,
(maxfreq * 15 + 999999) / 1000000);
}
}
/* https://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;
}
}
/* https://bcm-v4.sipsolutions.net/802.11/ClkctlFastPwrupDelay */
static void calc_fast_powerup_delay(struct ssb_chipcommon *cc)
{
struct ssb_bus *bus = cc->dev->bus;
int minfreq;
unsigned int tmp;
u32 pll_on_delay;
if (bus->bustype != SSB_BUSTYPE_PCI)
return;
if (cc->capabilities & SSB_CHIPCO_CAP_PMU) {
cc->fast_pwrup_delay = pmu_fast_powerup_delay(cc);
return;
}
if (!(cc->capabilities & SSB_CHIPCO_CAP_PCTL))
return;
minfreq = chipco_pctl_clockfreqlimit(cc, 0);
pll_on_delay = chipco_read32(cc, SSB_CHIPCO_PLLONDELAY);
tmp = (((pll_on_delay + 2) * 1000000) + (minfreq - 1)) / minfreq;
WARN_ON(tmp & ~0xFFFF);
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);
dev_dbg(cc->dev->dev, "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)
{
if (!cc->dev)
return;
ssb_chipco_set_clockmode(cc, SSB_CLKMODE_SLOW);
}
void ssb_chipco_resume(struct ssb_chipcommon *cc)
{
if (!cc->dev)
return;
chipco_powercontrol_init(cc);
ssb_chipco_set_clockmode(cc, SSB_CLKMODE_FAST);
}
/* Get the processor clock */
void ssb_chipco_get_clockcpu(struct ssb_chipcommon *cc,
u32 *plltype, u32 *n, u32 *m)
{
*n = chipco_read32(cc, SSB_CHIPCO_CLOCK_N);
*plltype = (cc->capabilities & SSB_CHIPCO_CAP_PLLT);
switch (*plltype) {
case SSB_PLLTYPE_2:
case SSB_PLLTYPE_4:
case SSB_PLLTYPE_6:
case SSB_PLLTYPE_7:
*m = chipco_read32(cc, SSB_CHIPCO_CLOCK_MIPS);
break;
case SSB_PLLTYPE_3:
/* 5350 uses m2 to control mips */
*m = chipco_read32(cc, SSB_CHIPCO_CLOCK_M2);
break;
default:
*m = chipco_read32(cc, SSB_CHIPCO_CLOCK_SB);
break;
}
}
/* Get the bus clock */
void ssb_chipco_get_clockcontrol(struct ssb_chipcommon *cc,
u32 *plltype, u32 *n, u32 *m)
{
*n = chipco_read32(cc, SSB_CHIPCO_CLOCK_N);
*plltype = (cc->capabilities & SSB_CHIPCO_CAP_PLLT);
switch (*plltype) {
case SSB_PLLTYPE_6: /* 100/200 or 120/240 only */
*m = chipco_read32(cc, SSB_CHIPCO_CLOCK_MIPS);
break;
case SSB_PLLTYPE_3: /* 25Mhz, 2 dividers */
if (cc->dev->bus->chip_id != 0x5365) {
*m = chipco_read32(cc, SSB_CHIPCO_CLOCK_M2);
break;
}
fallthrough;
default:
*m = chipco_read32(cc, SSB_CHIPCO_CLOCK_SB);
}
}
void ssb_chipco_timing_init(struct ssb_chipcommon *cc,
unsigned long ns)
{
struct ssb_device *dev = cc->dev;
struct ssb_bus *bus = dev->bus;
u32 tmp;
/* set register for external IO to control LED. */
chipco_write32(cc, SSB_CHIPCO_PROG_CFG, 0x11);
tmp = DIV_ROUND_UP(10, ns) << SSB_PROG_WCNT_3_SHIFT; /* Waitcount-3 = 10ns */
tmp |= DIV_ROUND_UP(40, ns) << SSB_PROG_WCNT_1_SHIFT; /* Waitcount-1 = 40ns */
tmp |= DIV_ROUND_UP(240, ns); /* Waitcount-0 = 240ns */
chipco_write32(cc, SSB_CHIPCO_PROG_WAITCNT, tmp); /* 0x01020a0c for a 100Mhz clock */
/* Set timing for the flash */
tmp = DIV_ROUND_UP(10, ns) << SSB_FLASH_WCNT_3_SHIFT; /* Waitcount-3 = 10nS */
tmp |= DIV_ROUND_UP(10, ns) << SSB_FLASH_WCNT_1_SHIFT; /* Waitcount-1 = 10nS */
tmp |= DIV_ROUND_UP(120, ns); /* Waitcount-0 = 120nS */
if ((bus->chip_id == 0x5365) ||
(dev->id.revision < 9))
chipco_write32(cc, SSB_CHIPCO_FLASH_WAITCNT, tmp);
if ((bus->chip_id == 0x5365) ||
(dev->id.revision < 9) ||
((bus->chip_id == 0x5350) && (bus->chip_rev == 0)))
chipco_write32(cc, SSB_CHIPCO_PCMCIA_MEMWAIT, tmp);
if (bus->chip_id == 0x5350) {
/* Enable EXTIF */
tmp = DIV_ROUND_UP(10, ns) << SSB_PROG_WCNT_3_SHIFT; /* Waitcount-3 = 10ns */
tmp |= DIV_ROUND_UP(20, ns) << SSB_PROG_WCNT_2_SHIFT; /* Waitcount-2 = 20ns */
tmp |= DIV_ROUND_UP(100, ns) << SSB_PROG_WCNT_1_SHIFT; /* Waitcount-1 = 100ns */
tmp |= DIV_ROUND_UP(120, ns); /* Waitcount-0 = 120ns */
chipco_write32(cc, SSB_CHIPCO_PROG_WAITCNT, tmp); /* 0x01020a0c for a 100Mhz clock */
}
}
/* Set chip watchdog reset timer to fire in 'ticks' backplane cycles */
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)
{
return chipco_read32(cc, SSB_CHIPCO_IRQSTAT) & mask;
}
u32 ssb_chipco_gpio_in(struct ssb_chipcommon *cc, u32 mask)
{
return chipco_read32(cc, SSB_CHIPCO_GPIOIN) & mask;
}
u32 ssb_chipco_gpio_out(struct ssb_chipcommon *cc, u32 mask, u32 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)
{
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)
{
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)
{
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)
{
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
int ssb_chipco_serial_init(struct ssb_chipcommon *cc,
struct ssb_serial_port *ports)
{
struct ssb_bus *bus = cc->dev->bus;
int nr_ports = 0;
u32 plltype;
unsigned int irq;
u32 baud_base, div;
u32 i, n;
unsigned int ccrev = cc->dev->id.revision;
plltype = (cc->capabilities & SSB_CHIPCO_CAP_PLLT);
irq = ssb_mips_irq(cc->dev);
if (plltype == SSB_PLLTYPE_1) {
/* PLL clock */
baud_base = ssb_calc_clock_rate(plltype,
chipco_read32(cc, SSB_CHIPCO_CLOCK_N),
chipco_read32(cc, SSB_CHIPCO_CLOCK_M2));
div = 1;
} else {
if (ccrev == 20) {
/* BCM5354 uses constant 25MHz clock */
baud_base = 25000000;
div = 48;
/* Set the override bit so we don't divide it */
chipco_write32(cc, SSB_CHIPCO_CORECTL,
chipco_read32(cc, SSB_CHIPCO_CORECTL)
| SSB_CHIPCO_CORECTL_UARTCLK0);
} else if ((ccrev >= 11) && (ccrev != 15)) {
baud_base = ssb_chipco_alp_clock(cc);
div = 1;
if (ccrev >= 21) {
/* Turn off UART clock before switching clocksource. */
chipco_write32(cc, SSB_CHIPCO_CORECTL,
chipco_read32(cc, SSB_CHIPCO_CORECTL)
& ~SSB_CHIPCO_CORECTL_UARTCLKEN);
}
/* Set the override bit so we don't divide it */
chipco_write32(cc, SSB_CHIPCO_CORECTL,
chipco_read32(cc, SSB_CHIPCO_CORECTL)
| SSB_CHIPCO_CORECTL_UARTCLK0);
if (ccrev >= 21) {
/* Re-enable the UART clock. */
chipco_write32(cc, SSB_CHIPCO_CORECTL,
chipco_read32(cc, SSB_CHIPCO_CORECTL)
| SSB_CHIPCO_CORECTL_UARTCLKEN);
}
} else if (ccrev >= 3) {
/* Internal backplane clock */
baud_base = ssb_clockspeed(bus);
div = chipco_read32(cc, SSB_CHIPCO_CLKDIV)
& SSB_CHIPCO_CLKDIV_UART;
} else {
/* Fixed internal backplane clock */
baud_base = 88000000;
div = 48;
}
/* Clock source depends on strapping if UartClkOverride is unset */
if ((ccrev > 0) &&
!(chipco_read32(cc, SSB_CHIPCO_CORECTL) & SSB_CHIPCO_CORECTL_UARTCLK0)) {
if ((cc->capabilities & SSB_CHIPCO_CAP_UARTCLK) ==
SSB_CHIPCO_CAP_UARTCLK_INT) {
/* Internal divided backplane clock */
baud_base /= div;
} else {
/* Assume external clock of 1.8432 MHz */
baud_base = 1843200;
}
}
}
/* Determine the registers of the UARTs */
n = (cc->capabilities & SSB_CHIPCO_CAP_NRUART);
for (i = 0; i < n; i++) {
void __iomem *cc_mmio;
void __iomem *uart_regs;
cc_mmio = cc->dev->bus->mmio + (cc->dev->core_index * SSB_CORE_SIZE);
uart_regs = cc_mmio + SSB_CHIPCO_UART0_DATA;
/* Offset changed at after rev 0 */
if (ccrev == 0)
uart_regs += (i * 8);
else
uart_regs += (i * 256);
nr_ports++;
ports[i].regs = uart_regs;
ports[i].irq = irq;
ports[i].baud_base = baud_base;
ports[i].reg_shift = 0;
}
return nr_ports;
}
#endif /* CONFIG_SSB_SERIAL */
@@ -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 "ssb_private.h"
#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 <linux/bcm47xx_nvram.h>
#endif
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;
}
dev_info(cc->dev->dev, "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:
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)
dev_emerg(cc->dev->dev, "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;
}
dev_info(cc->dev->dev, "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:
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)
dev_emerg(cc->dev->dev, "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:
dev_err(cc->dev->dev, "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:
dev_err(cc->dev->dev, "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:
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);
}
/* https://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);
dev_dbg(cc->dev->dev, "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:
WARN_ON(1);
return;
}
break;
case 0x4312:
if (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:
return ssb_pmu_get_alp_clock_clk0(cc);
default:
dev_err(cc->dev->dev, "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:
dev_err(cc->dev->dev, "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:
dev_err(cc->dev->dev, "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:
dev_err(cc->dev->dev,
"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);
@@ -0,0 +1,164 @@
/*
* Sonics Silicon Backplane
* ChipCommon serial flash interface
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/ssb/ssb.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();
}
dev_err(cc->dev->dev, "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;
}
+201
View File
@@ -0,0 +1,201 @@
/*
* Sonics Silicon Backplane
* Broadcom EXTIF core driver
*
* Copyright 2005, Broadcom Corporation
* Copyright 2006, 2007, Michael Buesch <m@bues.ch>
* Copyright 2006, 2007, Felix Fietkau <nbd@openwrt.org>
* Copyright 2007, Aurelien Jarno <aurelien@aurel32.net>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/serial_reg.h>
static inline u32 extif_read32(struct ssb_extif *extif, u16 offset)
{
return ssb_read32(extif->dev, offset);
}
static inline void extif_write32(struct ssb_extif *extif, u16 offset, u32 value)
{
ssb_write32(extif->dev, offset, value);
}
static inline u32 extif_write32_masked(struct ssb_extif *extif, u16 offset,
u32 mask, u32 value)
{
value &= mask;
value |= extif_read32(extif, offset) & ~mask;
extif_write32(extif, offset, value);
return value;
}
#ifdef CONFIG_SSB_SERIAL
static bool serial_exists(u8 *regs)
{
u8 save_mcr, msr = 0;
if (regs) {
save_mcr = regs[UART_MCR];
regs[UART_MCR] = (UART_MCR_LOOP | UART_MCR_OUT2 | UART_MCR_RTS);
msr = regs[UART_MSR] & (UART_MSR_DCD | UART_MSR_RI
| UART_MSR_CTS | UART_MSR_DSR);
regs[UART_MCR] = save_mcr;
}
return (msr == (UART_MSR_DCD | UART_MSR_CTS));
}
int ssb_extif_serial_init(struct ssb_extif *extif, struct ssb_serial_port *ports)
{
u32 i, nr_ports = 0;
/* Disable GPIO interrupt initially */
extif_write32(extif, SSB_EXTIF_GPIO_INTPOL, 0);
extif_write32(extif, SSB_EXTIF_GPIO_INTMASK, 0);
for (i = 0; i < 2; i++) {
void __iomem *uart_regs;
uart_regs = ioremap(SSB_EUART, 16);
if (uart_regs) {
uart_regs += (i * 8);
if (serial_exists(uart_regs) && ports) {
extif_write32(extif, SSB_EXTIF_GPIO_INTMASK, 2);
nr_ports++;
ports[i].regs = uart_regs;
ports[i].irq = 2;
ports[i].baud_base = 13500000;
ports[i].reg_shift = 0;
}
iounmap(uart_regs);
}
}
return nr_ports;
}
#endif /* CONFIG_SSB_SERIAL */
void ssb_extif_timing_init(struct ssb_extif *extif, unsigned long ns)
{
u32 tmp;
/* Initialize extif so we can get to the LEDs and external UART */
extif_write32(extif, SSB_EXTIF_PROG_CFG, SSB_EXTCFG_EN);
/* Set timing for the flash */
tmp = DIV_ROUND_UP(10, ns) << SSB_PROG_WCNT_3_SHIFT;
tmp |= DIV_ROUND_UP(40, ns) << SSB_PROG_WCNT_1_SHIFT;
tmp |= DIV_ROUND_UP(120, ns);
extif_write32(extif, SSB_EXTIF_PROG_WAITCNT, tmp);
/* Set programmable interface timing for external uart */
tmp = DIV_ROUND_UP(10, ns) << SSB_PROG_WCNT_3_SHIFT;
tmp |= DIV_ROUND_UP(20, ns) << SSB_PROG_WCNT_2_SHIFT;
tmp |= DIV_ROUND_UP(100, ns) << SSB_PROG_WCNT_1_SHIFT;
tmp |= DIV_ROUND_UP(120, ns);
extif_write32(extif, SSB_EXTIF_PROG_WAITCNT, tmp);
}
void ssb_extif_get_clockcontrol(struct ssb_extif *extif,
u32 *pll_type, u32 *n, u32 *m)
{
*pll_type = SSB_PLLTYPE_1;
*n = extif_read32(extif, SSB_EXTIF_CLOCK_N);
*m = extif_read32(extif, SSB_EXTIF_CLOCK_SB);
}
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)
{
return extif_read32(extif, SSB_EXTIF_GPIO_IN) & mask;
}
u32 ssb_extif_gpio_out(struct ssb_extif *extif, u32 mask, u32 value)
{
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)
{
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)
{
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)
{
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;
}
+297
View File
@@ -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),
{},
};
/* 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_obj(*dev);
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 false;
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);
}
+482
View File
@@ -0,0 +1,482 @@
/*
* 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 "ssb_private.h"
#include <linux/gpio/driver.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/export.h>
#include <linux/ssb/ssb.h>
/**************************************************
* Shared
**************************************************/
#if IS_ENABLED(CONFIG_SSB_EMBEDDED)
static int ssb_gpio_to_irq(struct gpio_chip *chip, unsigned int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(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 int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(chip);
return !!ssb_chipco_gpio_in(&bus->chipco, 1 << gpio);
}
static int ssb_gpio_chipco_set_value(struct gpio_chip *chip, unsigned int gpio,
int value)
{
struct ssb_bus *bus = gpiochip_get_data(chip);
ssb_chipco_gpio_out(&bus->chipco, 1 << gpio, value ? 1 << gpio : 0);
return 0;
}
static int ssb_gpio_chipco_direction_input(struct gpio_chip *chip,
unsigned int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(chip);
ssb_chipco_gpio_outen(&bus->chipco, 1 << gpio, 0);
return 0;
}
static int ssb_gpio_chipco_direction_output(struct gpio_chip *chip,
unsigned int gpio, int value)
{
struct ssb_bus *bus = gpiochip_get_data(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 int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(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 int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(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_domain_irq_safe(bus->irq_domain, 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_create_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_data(chip, bus);
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 int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(chip);
return !!ssb_extif_gpio_in(&bus->extif, 1 << gpio);
}
static int ssb_gpio_extif_set_value(struct gpio_chip *chip, unsigned int gpio,
int value)
{
struct ssb_bus *bus = gpiochip_get_data(chip);
ssb_extif_gpio_out(&bus->extif, 1 << gpio, value ? 1 << gpio : 0);
return 0;
}
static int ssb_gpio_extif_direction_input(struct gpio_chip *chip,
unsigned int gpio)
{
struct ssb_bus *bus = gpiochip_get_data(chip);
ssb_extif_gpio_outen(&bus->extif, 1 << gpio, 0);
return 0;
}
static int ssb_gpio_extif_direction_output(struct gpio_chip *chip,
unsigned int gpio, int value)
{
struct ssb_bus *bus = gpiochip_get_data(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_domain_irq_safe(bus->irq_domain, 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_create_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_data(chip, bus);
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);
return -1;
}
int ssb_gpio_unregister(struct ssb_bus *bus)
{
if (ssb_chipco_available(&bus->chipco) ||
ssb_extif_available(&bus->extif)) {
gpiochip_remove(&bus->gpio);
return 0;
}
return -1;
}
+356
View File
@@ -0,0 +1,356 @@
/*
* Sonics Silicon Backplane
* Broadcom MIPS core driver
*
* Copyright 2005, Broadcom Corporation
* Copyright 2006, 2007, Michael Buesch <m@bues.ch>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/mtd/physmap.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/serial_reg.h>
#include <linux/time.h>
#ifdef CONFIG_BCM47XX
#include <linux/bcm47xx_nvram.h>
#endif
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)
{
return ssb_read32(mcore->dev, offset);
}
static inline void mips_write32(struct ssb_mipscore *mcore,
u16 offset,
u32 value)
{
ssb_write32(mcore->dev, offset, value);
}
static const u32 ipsflag_irq_mask[] = {
0,
SSB_IPSFLAG_IRQ1,
SSB_IPSFLAG_IRQ2,
SSB_IPSFLAG_IRQ3,
SSB_IPSFLAG_IRQ4,
};
static const u32 ipsflag_irq_shift[] = {
0,
SSB_IPSFLAG_IRQ1_SHIFT,
SSB_IPSFLAG_IRQ2_SHIFT,
SSB_IPSFLAG_IRQ3_SHIFT,
SSB_IPSFLAG_IRQ4_SHIFT,
};
static inline u32 ssb_irqflag(struct ssb_device *dev)
{
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) {
if ((1 << irqflag) & ssb_read32(mdev, SSB_INTVEC))
irq = 0;
}
return irq;
}
static void clear_irq(struct ssb_bus *bus, unsigned int irq)
{
struct ssb_device *dev = bus->mipscore.dev;
/* Clear the IRQ in the MIPScore backplane registers */
if (irq == 0) {
ssb_write32(dev, SSB_INTVEC, 0);
} else {
ssb_write32(dev, SSB_IPSFLAG,
ssb_read32(dev, SSB_IPSFLAG) |
ipsflag_irq_mask[irq]);
}
}
static void set_irq(struct ssb_device *dev, unsigned int irq)
{
unsigned int oldirq = ssb_mips_irq(dev);
struct ssb_bus *bus = dev->bus;
struct ssb_device *mdev = bus->mipscore.dev;
u32 irqflag = ssb_irqflag(dev);
BUG_ON(oldirq == 6);
dev->irq = irq + 2;
/* clear the old irq */
if (oldirq == 0)
ssb_write32(mdev, SSB_INTVEC, (~(1 << irqflag) & ssb_read32(mdev, SSB_INTVEC)));
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)));
} 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);
}
dev_dbg(dev->dev, "set_irq: core 0x%04x, irq %d => %d\n",
dev->id.coreid, oldirq+2, irq+2);
}
static void print_irq(struct ssb_device *dev, unsigned int irq)
{
static const char *irq_name[] = {"2(S)", "3", "4", "5", "6", "D", "I"};
dev_dbg(dev->dev,
"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 (ssb_extif_available(&bus->extif))
mcore->nr_serial_ports = ssb_extif_serial_init(&bus->extif, mcore->serial_ports);
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;
}
static void ssb_mips_flash_detect(struct ssb_mipscore *mcore)
{
struct ssb_bus *bus = mcore->dev->bus;
struct ssb_sflash *sflash = &mcore->sflash;
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;
}
/* 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:
dev_dbg(mcore->dev->dev, "Found serial flash\n");
ssb_sflash_init(&bus->chipco);
break;
case SSB_CHIPCO_FLASHT_PARA:
dev_dbg(mcore->dev->dev, "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)
pflash->buswidth = 1;
else
pflash->buswidth = 2;
break;
}
ssb_pflash:
if (sflash->present) {
#ifdef CONFIG_BCM47XX
bcm47xx_nvram_init_from_mem(sflash->window, sflash->size);
#endif
} else if (pflash->present) {
#ifdef CONFIG_BCM47XX
bcm47xx_nvram_init_from_mem(pflash->window, pflash->window_size);
#endif
ssb_pflash_data.width = pflash->buswidth;
ssb_pflash_resource.start = pflash->window;
ssb_pflash_resource.end = pflash->window + pflash->window_size;
}
}
u32 ssb_cpu_clock(struct ssb_mipscore *mcore)
{
struct ssb_bus *bus = mcore->dev->bus;
u32 pll_type, n, m, rate = 0;
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 (ssb_chipco_available(&bus->chipco)) {
ssb_chipco_get_clockcpu(&bus->chipco, &pll_type, &n, &m);
} else
return 0;
if ((pll_type == SSB_PLLTYPE_5) || (bus->chip_id == 0x5365)) {
rate = 200000000;
} else {
rate = ssb_calc_clock_rate(pll_type, n, m);
}
if (pll_type == SSB_PLLTYPE_6) {
rate *= 2;
}
return rate;
}
void ssb_mipscore_init(struct ssb_mipscore *mcore)
{
struct ssb_bus *bus;
struct ssb_device *dev;
unsigned long hz, ns;
unsigned int irq, i;
if (!mcore->dev)
return; /* We don't have a MIPS core */
dev_dbg(mcore->dev->dev, "Initializing MIPS core...\n");
bus = mcore->dev->bus;
hz = ssb_clockspeed(bus);
if (!hz)
hz = 100000000;
ns = 1000000000 / hz;
if (ssb_extif_available(&bus->extif))
ssb_extif_timing_init(&bus->extif, ns);
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]);
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;
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 */
if (irq <= 4) {
set_irq(dev, irq++);
break;
}
fallthrough;
case SSB_DEV_EXTIF:
set_irq(dev, 0);
break;
}
}
dev_dbg(mcore->dev->dev, "after irq reconfiguration\n");
dump_irq(bus);
ssb_mips_serial_init(mcore);
ssb_mips_flash_detect(mcore);
}
+742
View File
@@ -0,0 +1,742 @@
/*
* Sonics Silicon Backplane
* Broadcom PCI-core driver
*
* Copyright 2005, Broadcom Corporation
* Copyright 2006, 2007, Michael Buesch <m@bues.ch>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/pci.h>
#include <linux/export.h>
#include <linux/delay.h>
#include <linux/ssb/ssb_embedded.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)
{
return ssb_read32(pc->dev, offset);
}
static inline
void pcicore_write32(struct ssb_pcicore *pc, u16 offset, u32 value)
{
ssb_write32(pc->dev, offset, value);
}
static inline
u16 pcicore_read16(struct ssb_pcicore *pc, u16 offset)
{
return ssb_read16(pc->dev, offset);
}
static inline
void pcicore_write16(struct ssb_pcicore *pc, u16 offset, u16 value)
{
ssb_write16(pc->dev, offset, value);
}
/**************************************************
* Code for hostmode operation.
**************************************************/
#ifdef CONFIG_SSB_PCICORE_HOSTMODE
#include <asm/paccess.h>
/* Probe a 32bit value on the bus and catch bus exceptions.
* Returns nonzero on a bus exception.
* This is MIPS specific
*/
#define mips_busprobe32(val, addr) get_dbe((val), ((u32 *)(addr)))
/* Assume one-hot slot wiring */
#define SSB_PCI_SLOT_MAX 16
/* Global lock is OK, as we won't have more than one extpci anyway. */
static DEFINE_SPINLOCK(cfgspace_lock);
/* Core to access the external PCI config space. Can only have one. */
static struct ssb_pcicore *extpci_core;
static u32 get_cfgspace_addr(struct ssb_pcicore *pc,
unsigned int bus, unsigned int dev,
unsigned int func, unsigned int off)
{
u32 addr = 0;
u32 tmp;
/* We do only have one cardbus device behind the bridge. */
if (pc->cardbusmode && (dev > 1))
goto out;
if (bus == 0) {
/* Type 0 transaction */
if (unlikely(dev >= SSB_PCI_SLOT_MAX))
goto out;
/* Slide the window */
tmp = SSB_PCICORE_SBTOPCI_CFG0;
tmp |= ((1 << (dev + 16)) & SSB_PCICORE_SBTOPCI1_MASK);
pcicore_write32(pc, SSB_PCICORE_SBTOPCI1, tmp);
/* Calculate the address */
addr = SSB_PCI_CFG;
addr |= ((1 << (dev + 16)) & ~SSB_PCICORE_SBTOPCI1_MASK);
addr |= (func << 8);
addr |= (off & ~3);
} else {
/* Type 1 transaction */
pcicore_write32(pc, SSB_PCICORE_SBTOPCI1,
SSB_PCICORE_SBTOPCI_CFG1);
/* Calculate the address */
addr = SSB_PCI_CFG;
addr |= (bus << 16);
addr |= (dev << 11);
addr |= (func << 8);
addr |= (off & ~3);
}
out:
return addr;
}
static int ssb_extpci_read_config(struct ssb_pcicore *pc,
unsigned int bus, unsigned int dev,
unsigned int func, unsigned int off,
void *buf, int len)
{
int err = -EINVAL;
u32 addr, val;
void __iomem *mmio;
WARN_ON(!pc->hostmode);
if (unlikely(len != 1 && len != 2 && len != 4))
goto out;
addr = get_cfgspace_addr(pc, bus, dev, func, off);
if (unlikely(!addr))
goto out;
err = -ENOMEM;
mmio = ioremap(addr, len);
if (!mmio)
goto out;
if (mips_busprobe32(val, mmio)) {
val = 0xffffffff;
goto unmap;
}
val = readl(mmio);
val >>= (8 * (off & 3));
switch (len) {
case 1:
*((u8 *)buf) = (u8)val;
break;
case 2:
*((u16 *)buf) = (u16)val;
break;
case 4:
*((u32 *)buf) = (u32)val;
break;
}
err = 0;
unmap:
iounmap(mmio);
out:
return err;
}
static int ssb_extpci_write_config(struct ssb_pcicore *pc,
unsigned int bus, unsigned int dev,
unsigned int func, unsigned int off,
const void *buf, int len)
{
int err = -EINVAL;
u32 addr, val = 0;
void __iomem *mmio;
WARN_ON(!pc->hostmode);
if (unlikely(len != 1 && len != 2 && len != 4))
goto out;
addr = get_cfgspace_addr(pc, bus, dev, func, off);
if (unlikely(!addr))
goto out;
err = -ENOMEM;
mmio = ioremap(addr, len);
if (!mmio)
goto out;
if (mips_busprobe32(val, mmio)) {
val = 0xffffffff;
goto unmap;
}
switch (len) {
case 1:
val = readl(mmio);
val &= ~(0xFF << (8 * (off & 3)));
val |= *((const u8 *)buf) << (8 * (off & 3));
break;
case 2:
val = readl(mmio);
val &= ~(0xFFFF << (8 * (off & 3)));
val |= *((const u16 *)buf) << (8 * (off & 3));
break;
case 4:
val = *((const u32 *)buf);
break;
}
writel(val, mmio);
err = 0;
unmap:
iounmap(mmio);
out:
return err;
}
static int ssb_pcicore_read_config(struct pci_bus *bus, unsigned int devfn,
int reg, int size, u32 *val)
{
unsigned long flags;
int err;
spin_lock_irqsave(&cfgspace_lock, flags);
err = ssb_extpci_read_config(extpci_core, bus->number, PCI_SLOT(devfn),
PCI_FUNC(devfn), reg, val, size);
spin_unlock_irqrestore(&cfgspace_lock, flags);
return err ? PCIBIOS_DEVICE_NOT_FOUND : PCIBIOS_SUCCESSFUL;
}
static int ssb_pcicore_write_config(struct pci_bus *bus, unsigned int devfn,
int reg, int size, u32 val)
{
unsigned long flags;
int err;
spin_lock_irqsave(&cfgspace_lock, flags);
err = ssb_extpci_write_config(extpci_core, bus->number, PCI_SLOT(devfn),
PCI_FUNC(devfn), reg, &val, size);
spin_unlock_irqrestore(&cfgspace_lock, flags);
return err ? PCIBIOS_DEVICE_NOT_FOUND : PCIBIOS_SUCCESSFUL;
}
static struct pci_ops ssb_pcicore_pciops = {
.read = ssb_pcicore_read_config,
.write = ssb_pcicore_write_config,
};
static struct resource ssb_pcicore_mem_resource = {
.name = "SSB PCIcore external memory",
.start = SSB_PCI_DMA,
.end = SSB_PCI_DMA + SSB_PCI_DMA_SZ - 1,
.flags = IORESOURCE_MEM | IORESOURCE_PCI_FIXED,
};
static struct resource ssb_pcicore_io_resource = {
.name = "SSB PCIcore external I/O",
.start = 0x100,
.end = 0x7FF,
.flags = IORESOURCE_IO | IORESOURCE_PCI_FIXED,
};
static struct pci_controller ssb_pcicore_controller = {
.pci_ops = &ssb_pcicore_pciops,
.io_resource = &ssb_pcicore_io_resource,
.mem_resource = &ssb_pcicore_mem_resource,
};
/* 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;
}
dev_info(&d->dev, "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;
dev_info(&dev->dev, "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) {
dev_err(&dev->dev, "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;
dev_info(&dev->dev,
"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;
if (WARN_ON(extpci_core))
return;
extpci_core = pc;
dev_dbg(pc->dev->dev, "PCIcore in host mode found\n");
/* Reset devices on the external PCI bus */
val = SSB_PCICORE_CTL_RST_OE;
val |= SSB_PCICORE_CTL_CLK_OE;
pcicore_write32(pc, SSB_PCICORE_CTL, val);
val |= SSB_PCICORE_CTL_CLK; /* Clock on */
pcicore_write32(pc, SSB_PCICORE_CTL, val);
udelay(150); /* Assertion time demanded by the PCI standard */
val |= SSB_PCICORE_CTL_RST; /* Deassert RST# */
pcicore_write32(pc, SSB_PCICORE_CTL, val);
val = SSB_PCICORE_ARBCTL_INTERN;
pcicore_write32(pc, SSB_PCICORE_ARBCTL, val);
udelay(1); /* Assertion time demanded by the PCI standard */
if (pc->dev->bus->has_cardbus_slot) {
dev_dbg(pc->dev->dev, "CardBus slot detected\n");
pc->cardbusmode = 1;
/* GPIO 1 resets the bridge */
ssb_gpio_out(pc->dev->bus, 1, 1);
ssb_gpio_outen(pc->dev->bus, 1, 1);
pcicore_write16(pc, SSB_PCICORE_SPROM(0),
pcicore_read16(pc, SSB_PCICORE_SPROM(0))
| 0x0400);
}
/* 64MB I/O window */
pcicore_write32(pc, SSB_PCICORE_SBTOPCI0,
SSB_PCICORE_SBTOPCI_IO);
/* 64MB config space */
pcicore_write32(pc, SSB_PCICORE_SBTOPCI1,
SSB_PCICORE_SBTOPCI_CFG0);
/* 1GB memory window */
pcicore_write32(pc, SSB_PCICORE_SBTOPCI2,
SSB_PCICORE_SBTOPCI_MEM | SSB_PCI_DMA);
/*
* Accessing PCI config without a proper delay after devices reset (not
* GPIO reset) was causing reboots on WRT300N v1.0 (BCM4704).
* Tested delay 850 us lowered reboot chance to 50-80%, 1000 us fixed it
* completely. Flushing all writes was also tested but with no luck.
* The same problem was reported for WRT350N v1 (BCM4705), so we just
* sleep here unconditionally.
*/
usleep_range(1000, 2000);
/* Enable PCI bridge BAR0 prefetch and burst */
val = PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
ssb_extpci_write_config(pc, 0, 0, 0, PCI_COMMAND, &val, 2);
/* Clear error conditions */
val = 0;
ssb_extpci_write_config(pc, 0, 0, 0, PCI_STATUS, &val, 2);
/* Enable PCI interrupts */
pcicore_write32(pc, SSB_PCICORE_IMASK,
SSB_PCICORE_IMASK_INTA);
/* Ok, ready to run, register it to the system.
* The following needs change, if we want to port hostmode
* to non-MIPS platform.
*/
ssb_pcicore_controller.io_map_base = (unsigned long)ioremap(SSB_PCI_MEM, 0x04000000);
set_io_port_base(ssb_pcicore_controller.io_map_base);
/* Give some time to the PCI controller to configure itself with the new
* values. Not waiting at this point causes crashes of the machine.
*/
mdelay(10);
register_pci_controller(&ssb_pcicore_controller);
}
static int pcicore_is_in_hostmode(struct ssb_pcicore *pc)
{
struct ssb_bus *bus = pc->dev->bus;
u16 chipid_top;
u32 tmp;
chipid_top = (bus->chip_id & 0xFF00);
if (chipid_top != 0x4700 &&
chipid_top != 0x5300)
return 0;
if (bus->sprom.boardflags_lo & SSB_PCICORE_BFL_NOPCI)
return 0;
/* The 200-pin BCM4712 package does not bond out PCI. Even when
* PCI is bonded out, some boards may leave the pins floating.
*/
if (bus->chip_id == 0x4712) {
if (bus->chip_package == SSB_CHIPPACK_BCM4712S)
return 0;
if (bus->chip_package == SSB_CHIPPACK_BCM4712M)
return 0;
}
if (bus->chip_id == 0x5350)
return 0;
return !mips_busprobe32(tmp, (bus->mmio + (pc->dev->core_index * SSB_CORE_SIZE)));
}
#endif /* CONFIG_SSB_PCICORE_HOSTMODE */
/**************************************************
* 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.
**************************************************/
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(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;
if (!dev)
return;
if (!ssb_device_is_enabled(dev))
ssb_device_enable(dev, 0);
#ifdef CONFIG_SSB_PCICORE_HOSTMODE
pc->hostmode = pcicore_is_in_hostmode(pc);
if (pc->hostmode)
ssb_pcicore_init_hostmode(pc);
#endif /* CONFIG_SSB_PCICORE_HOSTMODE */
if (!pc->hostmode)
ssb_pcicore_init_clientmode(pc);
}
static u32 ssb_pcie_read(struct ssb_pcicore *pc, u32 address)
{
pcicore_write32(pc, 0x130, address);
return pcicore_read32(pc, 0x134);
}
static void ssb_pcie_write(struct ssb_pcicore *pc, u32 address, u32 data)
{
pcicore_write32(pc, 0x130, address);
pcicore_write32(pc, 0x134, data);
}
static void ssb_pcie_mdio_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;
v = 0x80; /* Enable Preamble Sequence */
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 << 29); /* Read Transaction */
v |= (1 << 17); /* Turnaround */
if (pc->dev->id.revision < 10)
v |= (u32)device << 22;
v |= (u32)address << 18;
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 */) {
udelay(10);
ret = pcicore_read32(pc, mdio_data);
break;
}
msleep(1);
}
pcicore_write32(pc, mdio_control, 0);
return ret;
}
static void ssb_pcie_mdio_write(struct ssb_pcicore *pc, u8 device,
u8 address, u16 data)
{
const u16 mdio_control = 0x128;
const u16 mdio_data = 0x12C;
int max_retries = 10;
u32 v;
int i;
v = 0x80; /* Enable Preamble Sequence */
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 << 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,
struct ssb_device *dev)
{
struct ssb_device *pdev = pc->dev;
struct ssb_bus *bus;
int err = 0;
u32 tmp;
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 ((pdev->id.revision >= 6) || (pdev->id.coreid == SSB_DEV_PCIE)) {
u32 coremask;
/* Calculate the "coremask" for the device. */
coremask = (1 << dev->core_index);
WARN_ON(bus->bustype != SSB_BUSTYPE_PCI);
err = pci_read_config_dword(bus->host_pci, SSB_PCI_IRQMASK, &tmp);
if (err)
goto out;
tmp |= coremask << 8;
err = pci_write_config_dword(bus->host_pci, SSB_PCI_IRQMASK, tmp);
if (err)
goto out;
} else {
u32 intvec;
intvec = ssb_read32(pdev, SSB_INTVEC);
tmp = ssb_read32(dev, SSB_TPSFLAG);
tmp &= SSB_TPSFLAG_BPFLAG;
intvec |= (1 << tmp);
ssb_write32(pdev, SSB_INTVEC, intvec);
}
/* Setup PCIcore operation. */
if (pc->setup_done)
goto out;
if (pdev->id.coreid == SSB_DEV_PCI) {
ssb_pcicore_pci_setup_workarounds(pc);
} else {
WARN_ON(pdev->id.coreid != SSB_DEV_PCIE);
ssb_pcicore_pcie_setup_workarounds(pc);
}
pc->setup_done = 1;
out:
return err;
}
EXPORT_SYMBOL(ssb_pcicore_dev_irqvecs_enable);
+258
View File
@@ -0,0 +1,258 @@
/*
* Sonics Silicon Backplane
* Embedded systems support code
*
* Copyright 2005-2008, Broadcom Corporation
* 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 "ssb_private.h"
#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>
int ssb_watchdog_timer_set(struct ssb_bus *bus, u32 ticks)
{
if (ssb_chipco_available(&bus->chipco)) {
ssb_chipco_watchdog_timer_set(&bus->chipco, ticks);
return 0;
}
if (ssb_extif_available(&bus->extif)) {
ssb_extif_watchdog_timer_set(&bus->extif, ticks);
return 0;
}
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)) {
pr_debug("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)
{
unsigned long flags;
u32 res = 0;
spin_lock_irqsave(&bus->gpio_lock, flags);
if (ssb_chipco_available(&bus->chipco))
res = ssb_chipco_gpio_in(&bus->chipco, mask);
else if (ssb_extif_available(&bus->extif))
res = ssb_extif_gpio_in(&bus->extif, mask);
else
WARN_ON(1);
spin_unlock_irqrestore(&bus->gpio_lock, flags);
return res;
}
EXPORT_SYMBOL(ssb_gpio_in);
u32 ssb_gpio_out(struct ssb_bus *bus, u32 mask, u32 value)
{
unsigned long flags;
u32 res = 0;
spin_lock_irqsave(&bus->gpio_lock, flags);
if (ssb_chipco_available(&bus->chipco))
res = ssb_chipco_gpio_out(&bus->chipco, mask, value);
else if (ssb_extif_available(&bus->extif))
res = ssb_extif_gpio_out(&bus->extif, mask, value);
else
WARN_ON(1);
spin_unlock_irqrestore(&bus->gpio_lock, flags);
return res;
}
EXPORT_SYMBOL(ssb_gpio_out);
u32 ssb_gpio_outen(struct ssb_bus *bus, u32 mask, u32 value)
{
unsigned long flags;
u32 res = 0;
spin_lock_irqsave(&bus->gpio_lock, flags);
if (ssb_chipco_available(&bus->chipco))
res = ssb_chipco_gpio_outen(&bus->chipco, mask, value);
else if (ssb_extif_available(&bus->extif))
res = ssb_extif_gpio_outen(&bus->extif, mask, value);
else
WARN_ON(1);
spin_unlock_irqrestore(&bus->gpio_lock, flags);
return res;
}
EXPORT_SYMBOL(ssb_gpio_outen);
u32 ssb_gpio_control(struct ssb_bus *bus, u32 mask, u32 value)
{
unsigned long flags;
u32 res = 0;
spin_lock_irqsave(&bus->gpio_lock, flags);
if (ssb_chipco_available(&bus->chipco))
res = ssb_chipco_gpio_control(&bus->chipco, mask, value);
spin_unlock_irqrestore(&bus->gpio_lock, flags);
return res;
}
EXPORT_SYMBOL(ssb_gpio_control);
u32 ssb_gpio_intmask(struct ssb_bus *bus, u32 mask, u32 value)
{
unsigned long flags;
u32 res = 0;
spin_lock_irqsave(&bus->gpio_lock, flags);
if (ssb_chipco_available(&bus->chipco))
res = ssb_chipco_gpio_intmask(&bus->chipco, mask, value);
else if (ssb_extif_available(&bus->extif))
res = ssb_extif_gpio_intmask(&bus->extif, mask, value);
else
WARN_ON(1);
spin_unlock_irqrestore(&bus->gpio_lock, flags);
return res;
}
EXPORT_SYMBOL(ssb_gpio_intmask);
u32 ssb_gpio_polarity(struct ssb_bus *bus, u32 mask, u32 value)
{
unsigned long flags;
u32 res = 0;
spin_lock_irqsave(&bus->gpio_lock, flags);
if (ssb_chipco_available(&bus->chipco))
res = ssb_chipco_gpio_polarity(&bus->chipco, mask, value);
else if (ssb_extif_available(&bus->extif))
res = ssb_extif_gpio_polarity(&bus->extif, mask, value);
else
WARN_ON(1);
spin_unlock_irqrestore(&bus->gpio_lock, flags);
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;
}
+210
View File
@@ -0,0 +1,210 @@
/*
* Sonics Silicon Backplane SoC host related functions.
* Subsystem core
*
* Copyright 2005, Broadcom Corporation
* Copyright 2006, 2007, Michael Buesch <m@bues.ch>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/bcm47xx_nvram.h>
#include <linux/ssb/ssb.h>
static u8 ssb_host_soc_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_host_soc_read16(struct ssb_device *dev, u16 offset)
{
struct ssb_bus *bus = dev->bus;
offset += dev->core_index * SSB_CORE_SIZE;
return readw(bus->mmio + offset);
}
static u32 ssb_host_soc_read32(struct ssb_device *dev, u16 offset)
{
struct ssb_bus *bus = dev->bus;
offset += dev->core_index * SSB_CORE_SIZE;
return readl(bus->mmio + offset);
}
#ifdef CONFIG_SSB_BLOCKIO
static void ssb_host_soc_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;
WARN_ON(count & 1);
while (count) {
*buf = (__force __le16)__raw_readw(addr);
buf++;
count -= 2;
}
break;
}
case sizeof(u32): {
__le32 *buf = buffer;
WARN_ON(count & 3);
while (count) {
*buf = (__force __le32)__raw_readl(addr);
buf++;
count -= 4;
}
break;
}
default:
WARN_ON(1);
}
}
#endif /* CONFIG_SSB_BLOCKIO */
static void ssb_host_soc_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_host_soc_write16(struct ssb_device *dev, u16 offset, u16 value)
{
struct ssb_bus *bus = dev->bus;
offset += dev->core_index * SSB_CORE_SIZE;
writew(value, bus->mmio + offset);
}
static void ssb_host_soc_write32(struct ssb_device *dev, u16 offset, u32 value)
{
struct ssb_bus *bus = dev->bus;
offset += dev->core_index * SSB_CORE_SIZE;
writel(value, bus->mmio + offset);
}
#ifdef CONFIG_SSB_BLOCKIO
static void ssb_host_soc_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;
WARN_ON(count & 1);
while (count) {
__raw_writew((__force u16)(*buf), addr);
buf++;
count -= 2;
}
break;
}
case sizeof(u32): {
const __le32 *buf = buffer;
WARN_ON(count & 3);
while (count) {
__raw_writel((__force u32)(*buf), addr);
buf++;
count -= 4;
}
break;
}
default:
WARN_ON(1);
}
}
#endif /* CONFIG_SSB_BLOCKIO */
/* Ops for the plain SSB bus without a host-device (no PCI or PCMCIA). */
const struct ssb_bus_ops ssb_host_soc_ops = {
.read8 = ssb_host_soc_read8,
.read16 = ssb_host_soc_read16,
.read32 = ssb_host_soc_read32,
.write8 = ssb_host_soc_write8,
.write16 = ssb_host_soc_write16,
.write32 = ssb_host_soc_write32,
#ifdef CONFIG_SSB_BLOCKIO
.block_read = ssb_host_soc_block_read,
.block_write = ssb_host_soc_block_write,
#endif
};
int ssb_host_soc_get_invariants(struct ssb_bus *bus,
struct ssb_init_invariants *iv)
{
char buf[20];
int len, err;
/* Fill boardinfo structure */
memset(&iv->boardinfo, 0, sizeof(struct ssb_boardinfo));
len = bcm47xx_nvram_getenv("boardvendor", buf, sizeof(buf));
if (len > 0) {
err = kstrtou16(strim(buf), 0, &iv->boardinfo.vendor);
if (err)
pr_warn("Couldn't parse nvram board vendor entry with value \"%s\"\n",
buf);
}
if (!iv->boardinfo.vendor)
iv->boardinfo.vendor = SSB_BOARDVENDOR_BCM;
len = bcm47xx_nvram_getenv("boardtype", buf, sizeof(buf));
if (len > 0) {
err = kstrtou16(strim(buf), 0, &iv->boardinfo.type);
if (err)
pr_warn("Couldn't parse nvram board type entry with value \"%s\"\n",
buf);
}
memset(&iv->sprom, 0, sizeof(struct ssb_sprom));
ssb_fill_sprom_with_fallback(bus, &iv->sprom);
if (bcm47xx_nvram_getenv("cardbus", buf, sizeof(buf)) >= 0)
iv->has_cardbus_slot = !!simple_strtoul(buf, NULL, 10);
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+130
View File
@@ -0,0 +1,130 @@
/*
* Sonics Silicon Backplane
* PCI Hostdevice wrapper
*
* 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>
* Copyright (c) 2005-2007 Michael Buesch <m@bues.ch>
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include <linux/pm.h>
#include <linux/pci.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/ssb/ssb.h>
#ifdef CONFIG_PM_SLEEP
static int ssb_pcihost_suspend(struct device *d)
{
struct pci_dev *dev = to_pci_dev(d);
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);
/* if there is a wakeup enabled child device on ssb bus,
enable pci wakeup posibility. */
device_set_wakeup_enable(d, d->power.wakeup_path);
pci_prepare_to_sleep(dev);
return 0;
}
static int ssb_pcihost_resume(struct device *d)
{
struct pci_dev *dev = to_pci_dev(d);
struct ssb_bus *ssb = pci_get_drvdata(dev);
int err;
pci_back_from_sleep(dev);
err = pci_enable_device(dev);
if (err)
return err;
pci_restore_state(dev);
err = ssb_bus_resume(ssb);
if (err)
return err;
return 0;
}
static const struct dev_pm_ops ssb_pcihost_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ssb_pcihost_suspend, ssb_pcihost_resume)
};
#endif /* CONFIG_PM_SLEEP */
static int ssb_pcihost_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct ssb_bus *ssb;
int err = -ENOMEM;
u32 val;
ssb = kzalloc_obj(*ssb);
if (!ssb)
goto out;
err = pci_enable_device(dev);
if (err)
goto err_kfree_ssb;
err = pci_request_regions(dev, dev_driver_string(&dev->dev));
if (err)
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;
pci_set_drvdata(dev, ssb);
out:
return err;
err_pci_release_regions:
pci_release_regions(dev);
err_pci_disable:
pci_disable_device(dev);
err_kfree_ssb:
kfree(ssb);
return err;
}
static void ssb_pcihost_remove(struct pci_dev *dev)
{
struct ssb_bus *ssb = pci_get_drvdata(dev);
ssb_bus_unregister(ssb);
pci_release_regions(dev);
pci_disable_device(dev);
kfree(ssb);
pci_set_drvdata(dev, NULL);
}
int ssb_pcihost_register(struct pci_driver *driver)
{
driver->probe = ssb_pcihost_probe;
driver->remove = ssb_pcihost_remove;
#ifdef CONFIG_PM_SLEEP
driver->driver.pm = &ssb_pcihost_pm_ops;
#endif
return pci_register_driver(driver);
}
EXPORT_SYMBOL(ssb_pcihost_register);
+831
View File
@@ -0,0 +1,831 @@
/*
* Sonics Silicon Backplane
* PCMCIA-Hostbus related functions
*
* 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 "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/etherdevice.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ciscode.h>
#include <pcmcia/ds.h>
#include <pcmcia/cisreg.h>
/* Define the following to 1 to enable a printk on each coreswitch. */
#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)
{
int err;
int attempts = 0;
u32 cur_core;
u32 addr;
u32 read_addr;
u8 val;
addr = (coreidx * SSB_CORE_SIZE) + SSB_ENUM_BASE;
while (1) {
err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS0,
(addr & 0x0000F000) >> 12);
if (err)
goto error;
err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS1,
(addr & 0x00FF0000) >> 16);
if (err)
goto error;
err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_ADDRESS2,
(addr & 0xFF000000) >> 24);
if (err)
goto error;
read_addr = 0;
err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS0, &val);
if (err)
goto error;
read_addr |= ((u32)(val & 0x0F)) << 12;
err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS1, &val);
if (err)
goto error;
read_addr |= ((u32)val) << 16;
err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_ADDRESS2, &val);
if (err)
goto error;
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);
}
return 0;
error:
pr_err("Failed to switch to core %u\n", coreidx);
return err;
}
static int ssb_pcmcia_switch_core(struct ssb_bus *bus, struct ssb_device *dev)
{
int err;
#if SSB_VERBOSE_PCMCIACORESWITCH_DEBUG
pr_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);
if (!err)
bus->mapped_device = dev;
return err;
}
int ssb_pcmcia_switch_segment(struct ssb_bus *bus, u8 seg)
{
int attempts = 0;
int err;
u8 val;
WARN_ON((seg != 0) && (seg != 1));
while (1) {
err = ssb_pcmcia_cfg_write(bus, SSB_PCMCIA_MEMSEG, seg);
if (err)
goto error;
err = ssb_pcmcia_cfg_read(bus, SSB_PCMCIA_MEMSEG, &val);
if (err)
goto error;
if (val == seg)
break;
err = -ETIMEDOUT;
if (unlikely(attempts++ > SSB_BAR0_MAX_RETRIES))
goto error;
udelay(10);
}
bus->mapped_pcmcia_seg = seg;
return 0;
error:
pr_err("Failed to switch pcmcia segment\n");
return err;
}
static int select_core_and_segment(struct ssb_device *dev,
u16 *offset)
{
struct ssb_bus *bus = dev->bus;
int err;
u8 need_segment;
if (*offset >= 0x800) {
*offset -= 0x800;
need_segment = 1;
} else
need_segment = 0;
if (unlikely(dev != bus->mapped_device)) {
err = ssb_pcmcia_switch_core(bus, dev);
if (unlikely(err))
return err;
}
if (unlikely(need_segment != bus->mapped_pcmcia_seg)) {
err = ssb_pcmcia_switch_segment(bus, need_segment);
if (unlikely(err))
return err;
}
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;
unsigned long flags;
int err;
u16 value = 0xFFFF;
spin_lock_irqsave(&bus->bar_lock, flags);
err = select_core_and_segment(dev, &offset);
if (likely(!err))
value = readw(bus->mmio + offset);
spin_unlock_irqrestore(&bus->bar_lock, flags);
return value;
}
static u32 ssb_pcmcia_read32(struct ssb_device *dev, u16 offset)
{
struct ssb_bus *bus = dev->bus;
unsigned long flags;
int err;
u32 lo = 0xFFFFFFFF, hi = 0xFFFFFFFF;
spin_lock_irqsave(&bus->bar_lock, flags);
err = select_core_and_segment(dev, &offset);
if (likely(!err)) {
lo = readw(bus->mmio + offset);
hi = readw(bus->mmio + offset + 2);
}
spin_unlock_irqrestore(&bus->bar_lock, flags);
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;
WARN_ON(count & 1);
while (count) {
*buf = (__force __le16)__raw_readw(addr);
buf++;
count -= 2;
}
break;
}
case sizeof(u32): {
__le16 *buf = buffer;
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:
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);
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;
unsigned long flags;
int err;
spin_lock_irqsave(&bus->bar_lock, flags);
err = select_core_and_segment(dev, &offset);
if (likely(!err))
writew(value, bus->mmio + offset);
spin_unlock_irqrestore(&bus->bar_lock, flags);
}
static void ssb_pcmcia_write32(struct ssb_device *dev, u16 offset, u32 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)) {
writew((value & 0x0000FFFF), bus->mmio + offset);
writew(((value & 0xFFFF0000) >> 16), bus->mmio + offset + 2);
}
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;
WARN_ON(count & 1);
while (count) {
__raw_writew((__force u16)(*buf), addr);
buf++;
count -= 2;
}
break;
}
case sizeof(u32): {
const __le16 *buf = buffer;
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:
WARN_ON(1);
}
unlock:
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
};
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;
pr_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) {
pr_notice("Could not enable SPROM write access\n");
return -EBUSY;
}
pr_notice("[ 0%%");
msleep(500);
for (i = 0; i < size; i++) {
if (i == size / 4)
pr_cont("25%%");
else if (i == size / 2)
pr_cont("50%%");
else if (i == (size * 3) / 4)
pr_cont("75%%");
else if (i % 2)
pr_cont(".");
err = ssb_pcmcia_sprom_write(bus, i, sprom[i]);
if (err) {
pr_notice("Failed to write to SPROM\n");
failed = 1;
break;
}
}
err = ssb_pcmcia_sprom_command(bus, SSB_PCMCIA_SPROMCTL_WRITEDIS);
if (err) {
pr_notice("Could not disable SPROM write access\n");
failed = 1;
}
msleep(500);
if (!failed) {
pr_cont("100%% ]\n");
pr_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:
pr_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)
{
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) {
pr_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;
pr_err("PCMCIA: Failed to fetch device invariants\n");
return -ENODEV;
}
static ssize_t ssb_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_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_ADMIN_RW(ssb_sprom);
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;
}
/* Initialize the PCMCIA hardware. This is called on Init and Resume. */
int ssb_pcmcia_hardware_setup(struct ssb_bus *bus)
{
int err;
if (bus->bustype != SSB_BUSTYPE_PCMCIA)
return 0;
/* 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;
if (bus->bustype != SSB_BUSTYPE_PCMCIA)
return 0;
err = ssb_pcmcia_hardware_setup(bus);
if (err)
goto error;
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:
pr_err("Failed to initialize PCMCIA host device\n");
return err;
}
+446
View File
@@ -0,0 +1,446 @@
/*
* Sonics Silicon Backplane
* Bus scanning
*
* 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>
* Copyright (C) 2005 Andreas Jaggi <andreas.jaggi@waterwave.ch>
* Copyright (C) 2006 Broadcom Corporation.
*
* Licensed under the GNU/GPL. See COPYING for details.
*/
#include "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/ssb/ssb_regs.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>
const char *ssb_core_name(u16 coreid)
{
switch (coreid) {
case SSB_DEV_CHIPCOMMON:
return "ChipCommon";
case SSB_DEV_ILINE20:
return "ILine 20";
case SSB_DEV_SDRAM:
return "SDRAM";
case SSB_DEV_PCI:
return "PCI";
case SSB_DEV_MIPS:
return "MIPS";
case SSB_DEV_ETHERNET:
return "Fast Ethernet";
case SSB_DEV_V90:
return "V90";
case SSB_DEV_USB11_HOSTDEV:
return "USB 1.1 Hostdev";
case SSB_DEV_ADSL:
return "ADSL";
case SSB_DEV_ILINE100:
return "ILine 100";
case SSB_DEV_IPSEC:
return "IPSEC";
case SSB_DEV_PCMCIA:
return "PCMCIA";
case SSB_DEV_INTERNAL_MEM:
return "Internal Memory";
case SSB_DEV_MEMC_SDRAM:
return "MEMC SDRAM";
case SSB_DEV_EXTIF:
return "EXTIF";
case SSB_DEV_80211:
return "IEEE 802.11";
case SSB_DEV_MIPS_3302:
return "MIPS 3302";
case SSB_DEV_USB11_HOST:
return "USB 1.1 Host";
case SSB_DEV_USB11_DEV:
return "USB 1.1 Device";
case SSB_DEV_USB20_HOST:
return "USB 2.0 Host";
case SSB_DEV_USB20_DEV:
return "USB 2.0 Device";
case SSB_DEV_SDIO_HOST:
return "SDIO Host";
case SSB_DEV_ROBOSWITCH:
return "Roboswitch";
case SSB_DEV_PARA_ATA:
return "PATA";
case SSB_DEV_SATA_XORDMA:
return "SATA XOR-DMA";
case SSB_DEV_ETHERNET_GBIT:
return "GBit Ethernet";
case SSB_DEV_PCIE:
return "PCI-E";
case SSB_DEV_MIMO_PHY:
return "MIMO PHY";
case SSB_DEV_SRAM_CTRLR:
return "SRAM Controller";
case SSB_DEV_MINI_MACPHY:
return "Mini MACPHY";
case SSB_DEV_ARM_1176:
return "ARM 1176";
case SSB_DEV_ARM_7TDMI:
return "ARM 7TDMI";
case SSB_DEV_ARM_CM3:
return "ARM Cortex M3";
}
return "UNKNOWN";
}
static u16 pcidev_to_chipid(struct pci_dev *pci_dev)
{
u16 chipid_fallback = 0;
switch (pci_dev->device) {
case 0x4301:
chipid_fallback = 0x4301;
break;
case 0x4305 ... 0x4307:
chipid_fallback = 0x4307;
break;
case 0x4403:
chipid_fallback = 0x4402;
break;
case 0x4610 ... 0x4615:
chipid_fallback = 0x4610;
break;
case 0x4710 ... 0x4715:
chipid_fallback = 0x4710;
break;
case 0x4320 ... 0x4325:
chipid_fallback = 0x4309;
break;
case PCI_DEVICE_ID_BCM4401:
case PCI_DEVICE_ID_BCM4401B0:
case PCI_DEVICE_ID_BCM4401B1:
chipid_fallback = 0x4401;
break;
default:
dev_err(&pci_dev->dev, "PCI-ID not in fallback list\n");
}
return chipid_fallback;
}
static u8 chipid_to_nrcores(u16 chipid)
{
switch (chipid) {
case 0x5365:
return 7;
case 0x4306:
return 6;
case 0x4310:
return 8;
case 0x4307:
case 0x4301:
return 5;
case 0x4401:
case 0x4402:
return 3;
case 0x4710:
case 0x4610:
case 0x4704:
return 9;
default:
pr_err("CHIPID not in nrcores fallback list\n");
}
return 1;
}
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;
break;
case SSB_BUSTYPE_PCI:
break;
case SSB_BUSTYPE_PCMCIA:
if (offset >= 0x800) {
ssb_pcmcia_switch_segment(bus, 1);
offset -= 0x800;
} else
ssb_pcmcia_switch_segment(bus, 0);
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);
}
static int scan_switchcore(struct ssb_bus *bus, u8 coreidx)
{
switch (bus->bustype) {
case SSB_BUSTYPE_SSB:
break;
case SSB_BUSTYPE_PCI:
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;
}
void ssb_iounmap(struct ssb_bus *bus)
{
switch (bus->bustype) {
case SSB_BUSTYPE_SSB:
case SSB_BUSTYPE_PCMCIA:
iounmap(bus->mmio);
break;
case SSB_BUSTYPE_PCI:
#ifdef CONFIG_SSB_PCIHOST
pci_iounmap(bus->host_pci, bus->mmio);
#else
WARN_ON(1); /* Can't reach this code. */
#endif
break;
case SSB_BUSTYPE_SDIO:
break;
}
bus->mmio = NULL;
bus->mapped_device = NULL;
}
static void __iomem *ssb_ioremap(struct ssb_bus *bus,
unsigned long baseaddr)
{
void __iomem *mmio = NULL;
switch (bus->bustype) {
case SSB_BUSTYPE_SSB:
/* Only map the first core for now. */
fallthrough;
case SSB_BUSTYPE_PCMCIA:
mmio = ioremap(baseaddr, SSB_CORE_SIZE);
break;
case SSB_BUSTYPE_PCI:
#ifdef CONFIG_SSB_PCIHOST
mmio = pci_iomap(bus->host_pci, 0, ~0UL);
#else
WARN_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;
}
static int we_support_multiple_80211_cores(struct ssb_bus *bus)
{
/* More than one 802.11 core is only supported by special chips.
* There are chips with two 802.11 cores, but with dangling
* pins on the second core. Be careful and reject them here.
*/
#ifdef CONFIG_SSB_PCIHOST
if (bus->bustype == SSB_BUSTYPE_PCI) {
if (bus->host_pci->vendor == PCI_VENDOR_ID_BROADCOM &&
((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 */
return 0;
}
int ssb_bus_scan(struct ssb_bus *bus,
unsigned long baseaddr)
{
int err = -ENOMEM;
void __iomem *mmio;
u32 idhi, cc, rev, tmp;
int dev_i, i;
struct ssb_device *dev;
int nr_80211_cores = 0;
mmio = ssb_ioremap(bus, baseaddr);
if (!mmio)
goto out;
bus->mmio = mmio;
err = scan_switchcore(bus, 0); /* Switch to first core */
if (err)
goto err_unmap;
idhi = scan_read32(bus, 0, SSB_IDHIGH);
cc = (idhi & SSB_IDHIGH_CC) >> SSB_IDHIGH_CC_SHIFT;
rev = (idhi & SSB_IDHIGH_RCLO);
rev |= (idhi & SSB_IDHIGH_RCHI) >> SSB_IDHIGH_RCHI_SHIFT;
bus->nr_devices = 0;
if (cc == SSB_DEV_CHIPCOMMON) {
tmp = scan_read32(bus, 0, SSB_CHIPCO_CHIPID);
bus->chip_id = (tmp & SSB_CHIPCO_IDMASK);
bus->chip_rev = (tmp & SSB_CHIPCO_REVMASK) >>
SSB_CHIPCO_REVSHIFT;
bus->chip_package = (tmp & SSB_CHIPCO_PACKMASK) >>
SSB_CHIPCO_PACKSHIFT;
if (rev >= 4) {
bus->nr_devices = (tmp & SSB_CHIPCO_NRCORESMASK) >>
SSB_CHIPCO_NRCORESSHIFT;
}
tmp = scan_read32(bus, 0, SSB_CHIPCO_CAP);
bus->chipco.capabilities = tmp;
} else {
if (bus->bustype == SSB_BUSTYPE_PCI) {
bus->chip_id = pcidev_to_chipid(bus->host_pci);
bus->chip_rev = bus->host_pci->revision;
bus->chip_package = 0;
} else {
bus->chip_id = 0x4710;
bus->chip_rev = 0;
bus->chip_package = 0;
}
}
pr_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)) {
pr_err("More than %d ssb cores found (%d)\n",
SSB_MAX_NR_CORES, bus->nr_devices);
err = -EINVAL;
goto err_unmap;
}
if (bus->bustype == SSB_BUSTYPE_SSB) {
/* Now that we know the number of cores,
* remap the whole IO space for all cores.
*/
err = -ENOMEM;
iounmap(mmio);
mmio = ioremap(baseaddr, SSB_CORE_SIZE * bus->nr_devices);
if (!mmio)
goto out;
bus->mmio = mmio;
}
/* Fetch basic information about each core/device */
for (i = 0, dev_i = 0; i < bus->nr_devices; i++) {
err = scan_switchcore(bus, i);
if (err)
goto err_unmap;
dev = &(bus->devices[dev_i]);
idhi = scan_read32(bus, i, SSB_IDHIGH);
dev->id.coreid = (idhi & SSB_IDHIGH_CC) >> SSB_IDHIGH_CC_SHIFT;
dev->id.revision = (idhi & SSB_IDHIGH_RCLO);
dev->id.revision |= (idhi & SSB_IDHIGH_RCHI) >> SSB_IDHIGH_RCHI_SHIFT;
dev->id.vendor = (idhi & SSB_IDHIGH_VC) >> SSB_IDHIGH_VC_SHIFT;
dev->core_index = i;
dev->bus = bus;
dev->ops = bus->ops;
pr_debug("Core %d found: %s (cc 0x%03X, rev 0x%02X, vendor 0x%04X)\n",
i, ssb_core_name(dev->id.coreid),
dev->id.coreid, dev->id.revision, dev->id.vendor);
switch (dev->id.coreid) {
case SSB_DEV_80211:
nr_80211_cores++;
if (nr_80211_cores > 1) {
if (!we_support_multiple_80211_cores(bus)) {
pr_debug("Ignoring additional 802.11 core\n");
continue;
}
}
break;
case SSB_DEV_EXTIF:
#ifdef CONFIG_SSB_DRIVER_EXTIF
if (bus->extif.dev) {
pr_warn("WARNING: Multiple EXTIFs found\n");
break;
}
bus->extif.dev = dev;
#endif /* CONFIG_SSB_DRIVER_EXTIF */
break;
case SSB_DEV_CHIPCOMMON:
if (bus->chipco.dev) {
pr_warn("WARNING: Multiple ChipCommon found\n");
break;
}
bus->chipco.dev = dev;
break;
case SSB_DEV_MIPS:
case SSB_DEV_MIPS_3302:
#ifdef CONFIG_SSB_DRIVER_MIPS
if (bus->mipscore.dev) {
pr_warn("WARNING: Multiple MIPS cores found\n");
break;
}
bus->mipscore.dev = dev;
#endif /* CONFIG_SSB_DRIVER_MIPS */
break;
case SSB_DEV_PCI:
case SSB_DEV_PCIE:
#ifdef CONFIG_SSB_DRIVER_PCICORE
if (bus->bustype == SSB_BUSTYPE_PCI) {
/* Ignore PCI cores on PCI-E cards.
* Ignore PCI-E cores on PCI cards.
*/
if (dev->id.coreid == SSB_DEV_PCI) {
if (pci_is_pcie(bus->host_pci))
continue;
} else {
if (!pci_is_pcie(bus->host_pci))
continue;
}
}
if (bus->pcicore.dev) {
pr_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;
}
dev_i++;
}
bus->nr_devices = dev_i;
err = 0;
out:
return err;
err_unmap:
ssb_iounmap(bus);
goto out;
}
+605
View File
@@ -0,0 +1,605 @@
/*
* 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 "ssb_private.h"
#include <linux/ssb/ssb.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/etherdevice.h>
#include <linux/mmc/sdio_func.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 */
static 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): {
WARN_ON(count & 1);
error = sdio_readsb(bus->host_sdio, buffer, offset, count);
break;
}
case sizeof(u32): {
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:
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;
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):
WARN_ON(count & 1);
error = sdio_writesb(bus->host_sdio, offset,
(void *)buffer, count);
break;
case sizeof(u32):
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:
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;
}
+204
View File
@@ -0,0 +1,204 @@
/*
* 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 += scnprintf(buf + pos, buf_len - pos - 1,
"%04X", swab16(sprom[i]) & 0xFFFF);
pos += scnprintf(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) {
pr_err("SPROM write: Could not freeze all devices\n");
goto out_unlock;
}
res = sprom_write(bus, sprom);
err = ssb_devices_thaw(&freeze);
if (err)
pr_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);
}
/* https://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;
}
+266
View File
@@ -0,0 +1,266 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef LINUX_SSB_PRIVATE_H_
#define LINUX_SSB_PRIVATE_H_
#define PFX "ssb: "
#define pr_fmt(fmt) PFX fmt
#include <linux/ssb/ssb.h>
#include <linux/types.h>
#include <linux/bcm47xx_wdt.h>
/* pci.c */
#ifdef CONFIG_SSB_PCIHOST
extern int ssb_pci_switch_core(struct ssb_bus *bus,
struct ssb_device *dev);
extern int ssb_pci_switch_coreidx(struct ssb_bus *bus,
u8 coreidx);
extern int ssb_pci_xtal(struct ssb_bus *bus, u32 what,
int turn_on);
extern int ssb_pci_get_invariants(struct ssb_bus *bus,
struct ssb_init_invariants *iv);
extern void ssb_pci_exit(struct ssb_bus *bus);
extern int ssb_pci_init(struct ssb_bus *bus);
extern const struct ssb_bus_ops ssb_pci_ops;
#else /* CONFIG_SSB_PCIHOST */
static inline int ssb_pci_switch_core(struct ssb_bus *bus,
struct ssb_device *dev)
{
return 0;
}
static inline int ssb_pci_switch_coreidx(struct ssb_bus *bus,
u8 coreidx)
{
return 0;
}
static inline int ssb_pci_xtal(struct ssb_bus *bus, u32 what,
int turn_on)
{
return 0;
}
static inline void ssb_pci_exit(struct ssb_bus *bus)
{
}
static inline int ssb_pci_init(struct ssb_bus *bus)
{
return 0;
}
#endif /* CONFIG_SSB_PCIHOST */
/* pcmcia.c */
#ifdef CONFIG_SSB_PCMCIAHOST
extern int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus,
u8 coreidx);
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 int ssb_host_pcmcia_init(void);
extern void ssb_host_pcmcia_exit(void);
extern const struct ssb_bus_ops ssb_pcmcia_ops;
#else /* CONFIG_SSB_PCMCIAHOST */
static inline int ssb_pcmcia_switch_coreidx(struct ssb_bus *bus,
u8 coreidx)
{
return 0;
}
static inline int ssb_pcmcia_switch_segment(struct ssb_bus *bus,
u8 seg)
{
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;
}
static inline int ssb_host_pcmcia_init(void)
{
return 0;
}
static inline void ssb_host_pcmcia_exit(void)
{
}
#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_scan_switch_coreidx(struct ssb_bus *bus, u8 coreidx);
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_scan_switch_coreidx(struct ssb_bus *bus, u8 coreidx)
{
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 */
/**************************************************
* host_soc.c
**************************************************/
#ifdef CONFIG_SSB_HOST_SOC
extern const struct ssb_bus_ops ssb_host_soc_ops;
extern int ssb_host_soc_get_invariants(struct ssb_bus *bus,
struct ssb_init_invariants *iv);
#endif
/* scan.c */
extern const char *ssb_core_name(u16 coreid);
extern int ssb_bus_scan(struct ssb_bus *bus,
unsigned long baseaddr);
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 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_BRIDGE */
static inline int b43_pci_ssb_bridge_init(void)
{
return 0;
}
static inline void b43_pci_ssb_bridge_exit(void)
{
}
#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_ */