blob: afe6e995ff677acaea03c64985ba9b0440fb0d27 [file] [log] [blame]
/*
* Copyright (C) 2009 - 2012 Broadcom Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#define pr_fmt(fmt) "PCI: " fmt
#include <linux/init.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/ioport.h>
#include <linux/compiler.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/printk.h>
#include <linux/syscore_ops.h>
#include <linux/brcmstb/brcmstb.h>
/* NOTE: all PHYSICAL addresses */
/* Use assigned bus numbers so "ops" can tell the controllers apart */
#define BRCM_BUSNO_PCIE 0x00
#define PCIE_OUTBOUND_WIN(win, start, len) do { \
BDEV_WR(BCHP_PCIE_MISC_CPU_2_PCIE_MEM_WIN##win##_LO, \
(start) + MMIO_ENDIAN); \
BDEV_WR(BCHP_PCIE_MISC_CPU_2_PCIE_MEM_WIN##win##_HI, 0); \
BDEV_WR(BCHP_PCIE_MISC_CPU_2_PCIE_MEM_WIN##win##_BASE_LIMIT, \
(((start) >> 20) << 4) | \
((((start) + (len) - 1) >> 20) << 20)); \
} while (0)
static int brcm_pci_read_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *data);
static int brcm_pci_write_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 data);
static inline int get_busno_pcie(void) { return BRCM_BUSNO_PCIE; }
static struct pci_ops brcmstb_pci_ops = {
.read = brcm_pci_read_config,
.write = brcm_pci_write_config,
};
/*
* Note: our PCIe core does not support IO BARs at all. MEM only.
* The Linux PCI code insists on having IO resources and io_map_base for
* all PCI controllers, so those values are totally bogus.
*/
#define IO_ADDR_PCIE 0x400
#define PCIE_IO_SIZE 0x400
#define BOGUS_IO_MAP_BASE 1
/* pci_controller resources */
static struct resource pcie_mem_resource = {
.name = "External PCIe MEM",
.start = PCIE_MEM_START,
.end = PCIE_MEM_START + PCIE_MEM_SIZE - 1,
.flags = IORESOURCE_MEM,
};
static struct resource pcie_io_resource = {
.name = "External PCIe IO (unavailable)",
.start = IO_ADDR_PCIE,
.end = IO_ADDR_PCIE + PCIE_IO_SIZE - 1,
.flags = IORESOURCE_MEM,
};
static struct pci_controller brcmstb_pcie_controller = {
.pci_ops = &brcmstb_pci_ops,
.io_resource = &pcie_io_resource,
.mem_resource = &pcie_mem_resource,
.get_busno = &get_busno_pcie,
.io_map_base = BOGUS_IO_MAP_BASE,
};
struct brcm_pci_bus {
struct pci_controller *controller;
char *name;
unsigned int hw_busnum;
unsigned int busnum_shift;
unsigned int slot_shift;
unsigned int func_shift;
int memory_hole;
unsigned long idx_reg;
unsigned long data_reg;
struct clk *clk;
};
static struct brcm_pci_bus brcm_buses[] = {
[BRCM_BUSNO_PCIE] = {
&brcmstb_pcie_controller, "PCIe", 1, 20, 15, 12, 0 },
};
/***********************************************************************
* PCIe Bridge setup
***********************************************************************/
#if defined(__BIG_ENDIAN)
#define DATA_ENDIAN 2 /* PCI->DDR inbound accesses */
#define MMIO_ENDIAN 2 /* MIPS->PCI outbound accesses */
#else
#define DATA_ENDIAN 0
#define MMIO_ENDIAN 0
#endif
static struct wktmr_time pcie_reset_started;
#define PCIE_LINK_UP() \
(((BDEV_RD(BCHP_PCIE_MISC_PCIE_STATUS) & 0x30) == 0x30) ? 1 : 0)
void brcm_early_pcie_setup(void)
{
/*
* Called from bchip_early_setup() in order to start PCIe link
* negotiation immediately at kernel boot time. The RC is supposed
* to give the endpoint device 100ms to settle down before
* attempting configuration accesses. So we let the link negotiation
* happen in the background instead of busy-waiting.
*/
struct wktmr_time tmp;
/* reset the bridge and the endpoint device */
BDEV_WR_F_RB(HIF_RGR1_SW_INIT_1, PCIE_BRIDGE_SW_INIT, 1);
BDEV_WR_F_RB(HIF_RGR1_SW_INIT_1, PCIE_SW_PERST, 1);
/* delay 100us */
wktmr_read(&tmp);
while (wktmr_elapsed(&tmp) < (100 * WKTMR_1US))
;
/* take the bridge out of reset */
BDEV_WR_F_RB(HIF_RGR1_SW_INIT_1, PCIE_BRIDGE_SW_INIT, 0);
/* enable SCB_MAX_BURST_SIZE | CSR_READ_UR_MODE | SCB_ACCESS_EN */
BDEV_WR(BCHP_PCIE_MISC_MISC_CTRL, 0x00103000);
/* set up MIPS->PCIE memory windows (4x 128MB) */
PCIE_OUTBOUND_WIN(0, PCIE_MEM_START + 0x00000000, 0x08000000);
PCIE_OUTBOUND_WIN(1, PCIE_MEM_START + 0x08000000, 0x08000000);
PCIE_OUTBOUND_WIN(2, PCIE_MEM_START + 0x10000000, 0x08000000);
PCIE_OUTBOUND_WIN(3, PCIE_MEM_START + 0x18000000, 0x08000000);
#if defined(CONFIG_BRCM_HAS_2GB_MEMC0)
/* set up 4GB PCIE->SCB memory window on BAR2 */
BDEV_WR(BCHP_PCIE_MISC_RC_BAR2_CONFIG_LO, 0x00000011);
BDEV_WR(BCHP_PCIE_MISC_RC_BAR2_CONFIG_HI, 0x00000000);
BDEV_WR_F(PCIE_MISC_MISC_CTRL, SCB0_SIZE, 0x10);
BDEV_WR_F(PCIE_MISC_MISC_CTRL, SCB1_SIZE, 0x0f);
#else
/* set up 1GB PCIE->SCB memory window on BAR2 */
BDEV_WR(BCHP_PCIE_MISC_RC_BAR2_CONFIG_LO, 0x0000000f);
BDEV_WR(BCHP_PCIE_MISC_RC_BAR2_CONFIG_HI, 0x00000000);
#endif
/* disable PCIE->GISB window */
BDEV_WR(BCHP_PCIE_MISC_RC_BAR1_CONFIG_LO, 0x00000000);
/* disable the other PCIE->SCB memory window */
BDEV_WR(BCHP_PCIE_MISC_RC_BAR3_CONFIG_LO, 0x00000000);
/* disable MSI (for now...) */
BDEV_WR(BCHP_PCIE_MISC_MSI_BAR_CONFIG_LO, 0);
/* set up L2 interrupt masks */
BDEV_WR_RB(BCHP_PCIE_INTR2_CPU_CLEAR, 0);
BDEV_WR_RB(BCHP_PCIE_INTR2_CPU_MASK_CLEAR, 0);
BDEV_WR_RB(BCHP_PCIE_INTR2_CPU_MASK_SET, 0xffffffff);
/* take the EP device out of reset */
BDEV_WR_F_RB(HIF_RGR1_SW_INIT_1, PCIE_SW_PERST, 0);
/* record the current time */
wktmr_read(&pcie_reset_started);
}
void brcm_setup_pcie_bridge(void)
{
/* give the RC/EP time to wake up, before trying to configure RC */
while (wktmr_elapsed(&pcie_reset_started) < (100 * WKTMR_1MS))
;
if (!PCIE_LINK_UP()) {
struct clk *clk;
brcm_pcie_enabled = 0;
clk = brcm_buses[BRCM_BUSNO_PCIE].clk;
if (clk) {
clk_disable(clk);
clk_put(clk);
}
pr_info("PCIe link down\n");
return;
}
pr_info("PCIe link up, %sGbps x%lu\n",
BDEV_RD_F(PCIE_RC_CFG_PCIE_LINK_STATUS_CONTROL,
NEG_LINK_SPEED) == 0x2 ? "5.0" : "2.5",
BDEV_RD_F(PCIE_RC_CFG_PCIE_LINK_STATUS_CONTROL,
NEG_LINK_WIDTH));
/* enable MEM_SPACE and BUS_MASTER for RC */
BDEV_WR(BCHP_PCIE_RC_CFG_TYPE1_STATUS_COMMAND, 0x6);
/* set base/limit for outbound transactions */
#if defined(CONFIG_BRCM_HAS_2GB_MEMC0)
BDEV_WR(BCHP_PCIE_RC_CFG_TYPE1_RC_MEM_BASE_LIMIT, 0xeff0d000);
#else
BDEV_WR(BCHP_PCIE_RC_CFG_TYPE1_RC_MEM_BASE_LIMIT, 0xbff0a000);
#endif
/* disable the prefetch range */
BDEV_WR(BCHP_PCIE_RC_CFG_TYPE1_RC_PREF_BASE_LIMIT, 0x0000fff0);
/* set pri/sec bus numbers */
BDEV_WR(BCHP_PCIE_RC_CFG_TYPE1_PRI_SEC_BUS_NO, 0x00010100);
/* enable configuration request retry (see pci_scan_device()) */
BDEV_WR_F(PCIE_RC_CFG_PCIE_ROOT_CAP_CONTROL, RC_CRS_EN, 1);
/* PCIE->SCB endian mode for BAR2 */
BDEV_WR_F_RB(PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1, ENDIAN_MODE_BAR2,
DATA_ENDIAN);
}
#if defined(CONFIG_PM)
/*
* syscore device to handle PCIe bus suspend and resume
*/
static inline void pcie_enable(int enable)
{
struct clk *clk;
if (!brcm_pcie_enabled)
return;
clk = brcm_buses[BRCM_BUSNO_PCIE].clk;
if (clk)
enable ? clk_enable(clk) : clk_disable(clk);
}
static int pcie_suspend(void)
{
pcie_enable(0);
return 0;
}
static void pcie_resume(void)
{
pcie_enable(1);
}
static struct syscore_ops pcie_pm_ops = {
.suspend = pcie_suspend,
.resume = pcie_resume,
};
#endif
/***********************************************************************
* PCI controller registration
***********************************************************************/
static int __init brcmstb_pci_init(void)
{
if (brcm_pcie_enabled) {
brcm_buses[BRCM_BUSNO_PCIE].clk = clk_get(NULL, "pcie");
brcm_setup_pcie_bridge();
brcm_buses[BRCM_BUSNO_PCIE].idx_reg =
BVIRTADDR(BCHP_PCIE_EXT_CFG_PCIE_EXT_CFG_INDEX);
brcm_buses[BRCM_BUSNO_PCIE].data_reg =
BVIRTADDR(BCHP_PCIE_EXT_CFG_PCIE_EXT_CFG_DATA);
register_pci_controller(&brcmstb_pcie_controller);
#if defined(CONFIG_PM)
register_syscore_ops(&pcie_pm_ops);
#endif
}
return 0;
}
arch_initcall(brcmstb_pci_init);
/***********************************************************************
* Read/write PCI configuration registers
***********************************************************************/
#define CFG_INDEX(bus, devfn, reg) \
(((PCI_SLOT(devfn) & 0x1f) << (brcm_buses[bus->number].slot_shift)) | \
((PCI_FUNC(devfn) & 0x07) << (brcm_buses[bus->number].func_shift)) | \
(brcm_buses[bus->number].hw_busnum << \
brcm_buses[bus->number].busnum_shift) | \
(reg))
static int devfn_ok(struct pci_bus *bus, unsigned int devfn)
{
/* PCIe: check for link down or invalid slot number */
if (bus->number == BRCM_BUSNO_PCIE &&
(!PCIE_LINK_UP() || PCI_SLOT(devfn) != 0))
return 0;
return 1; /* OK */
}
static int brcm_pci_write_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 data)
{
u32 val = 0, mask, shift;
if (!devfn_ok(bus, devfn))
return PCIBIOS_FUNC_NOT_SUPPORTED;
BUG_ON(((where & 3) + size) > 4);
if (size < 4) {
/* partial word - read, modify, write */
DEV_WR_RB(brcm_buses[bus->number].idx_reg,
CFG_INDEX(bus, devfn, where & ~3));
val = DEV_RD(brcm_buses[bus->number].data_reg);
}
shift = (where & 3) << 3;
mask = (0xffffffff >> ((4 - size) << 3)) << shift;
DEV_WR_RB(brcm_buses[bus->number].idx_reg,
CFG_INDEX(bus, devfn, where & ~3));
val = (val & ~mask) | ((data << shift) & mask);
DEV_WR_RB(brcm_buses[bus->number].data_reg, val);
return PCIBIOS_SUCCESSFUL;
}
static int brcm_pci_read_config(struct pci_bus *bus, unsigned int devfn,
int where, int size, u32 *data)
{
u32 val, mask, shift;
if (!devfn_ok(bus, devfn))
return PCIBIOS_FUNC_NOT_SUPPORTED;
BUG_ON(((where & 3) + size) > 4);
DEV_WR_RB(brcm_buses[bus->number].idx_reg,
CFG_INDEX(bus, devfn, where & ~3));
val = DEV_RD(brcm_buses[bus->number].data_reg);
shift = (where & 3) << 3;
mask = (0xffffffff >> ((4 - size) << 3)) << shift;
*data = (val & mask) >> shift;
return PCIBIOS_SUCCESSFUL;
}
/***********************************************************************
* PCI slot to IRQ mappings (aka "fixup")
***********************************************************************/
int __devinit pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
if (dev->bus->number == BRCM_BUSNO_PCIE) {
const int pcie_irq[] = {
BRCM_IRQ_PCIE_INTA, BRCM_IRQ_PCIE_INTB,
BRCM_IRQ_PCIE_INTC, BRCM_IRQ_PCIE_INTD,
};
if ((pin - 1) > 3)
return 0;
return pcie_irq[pin - 1];
}
return 0;
}
/* Do platform specific device initialization at pci_enable_device() time */
int pcibios_plat_dev_init(struct pci_dev *dev)
{
return 0;
}
/***********************************************************************
* Per-device initialization
***********************************************************************/
static void __devinit brcm_pcibios_fixup(struct pci_dev *dev)
{
int slot = PCI_SLOT(dev->devfn);
pr_info("found device %04x:%04x on %s bus, slot %d (irq %d)\n",
dev->vendor, dev->device, brcm_buses[dev->bus->number].name,
slot, pcibios_map_irq(dev, slot, 1));
/* zero out the BARs and let Linux assign an address */
pci_write_config_dword(dev, PCI_COMMAND, 0);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, 0);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, 0);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_2, 0);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_3, 0);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_4, 0);
pci_write_config_dword(dev, PCI_BASE_ADDRESS_5, 0);
pci_write_config_dword(dev, PCI_INTERRUPT_LINE, 0);
}
DECLARE_PCI_FIXUP_EARLY(PCI_ANY_ID, PCI_ANY_ID, brcm_pcibios_fixup);
/***********************************************************************
* DMA address remapping
***********************************************************************/
/*
* PCIe inbound BARs collapse the "holes" in the chip's SCB address space.
* Therefore the PCI addresses need to be adjusted as they will not match
* the SCB addresses (MIPS physical addresses).
*
* The address maps can be found in <asm/mach-brcmstb/spaces.h> .
*/
static int dev_collapses_memory_hole(struct device *dev)
{
#if defined(CONFIG_BRCM_UPPER_MEMORY) || defined(CONFIG_HIGHMEM)
struct pci_dev *pdev;
if (unlikely(dev == NULL) ||
likely(dev->bus != &pci_bus_type))
return 0;
pdev = to_pci_dev(dev);
if (unlikely(pdev == NULL) ||
brcm_buses[pdev->bus->number].memory_hole)
return 0;
return 1;
#else
return 0;
#endif
}
static dma_addr_t brcm_phys_to_pci(struct device *dev, unsigned long phys)
{
if (!dev_collapses_memory_hole(dev))
return phys;
if (phys >= MEMC1_START)
return phys - MEMC1_PCI_OFFSET;
if (phys >= (BRCM_PCI_HOLE_START + BRCM_PCI_HOLE_SIZE))
return phys - BRCM_PCI_HOLE_SIZE;
return phys;
}
dma_addr_t plat_map_dma_mem(struct device *dev, void *addr, size_t size)
{
return brcm_phys_to_pci(dev, virt_to_phys(addr));
}
dma_addr_t plat_map_dma_mem_page(struct device *dev, struct page *page)
{
return brcm_phys_to_pci(dev, page_to_phys(page));
}
unsigned long plat_dma_addr_to_phys(struct device *dev, dma_addr_t dma_addr)
{
if (!dev_collapses_memory_hole(dev))
return dma_addr;
if (dma_addr >= (MEMC1_START - MEMC1_PCI_OFFSET))
return dma_addr + MEMC1_PCI_OFFSET;
if (dma_addr >= BRCM_PCI_HOLE_START)
return dma_addr + BRCM_PCI_HOLE_SIZE;
return dma_addr;
}