c839e0eff5
This adds to the previous patch the support for the 4xx PCI 2.x bridges. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Josh Boyer <jwboyer@linux.vnet.ibm.com>
518 lines
13 KiB
C
518 lines
13 KiB
C
/*
|
|
* PCI / PCI-X / PCI-Express support for 4xx parts
|
|
*
|
|
* Copyright 2007 Ben. Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/init.h>
|
|
#include <linux/of.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/pci-bridge.h>
|
|
#include <asm/machdep.h>
|
|
|
|
#include "ppc4xx_pci.h"
|
|
|
|
static int dma_offset_set;
|
|
|
|
/* Move that to a useable header */
|
|
extern unsigned long total_memory;
|
|
|
|
static void fixup_ppc4xx_pci_bridge(struct pci_dev *dev)
|
|
{
|
|
struct pci_controller *hose;
|
|
int i;
|
|
|
|
if (dev->devfn != 0 || dev->bus->self != NULL)
|
|
return;
|
|
|
|
hose = pci_bus_to_host(dev->bus);
|
|
if (hose == NULL)
|
|
return;
|
|
|
|
if (!of_device_is_compatible(hose->dn, "ibm,plb-pciex") &&
|
|
!of_device_is_compatible(hose->dn, "ibm,plb-pcix") &&
|
|
!of_device_is_compatible(hose->dn, "ibm,plb-pci"))
|
|
return;
|
|
|
|
/* Hide the PCI host BARs from the kernel as their content doesn't
|
|
* fit well in the resource management
|
|
*/
|
|
for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
|
|
dev->resource[i].start = dev->resource[i].end = 0;
|
|
dev->resource[i].flags = 0;
|
|
}
|
|
|
|
printk(KERN_INFO "PCI: Hiding 4xx host bridge resources %s\n",
|
|
pci_name(dev));
|
|
}
|
|
DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, fixup_ppc4xx_pci_bridge);
|
|
|
|
static int __init ppc4xx_parse_dma_ranges(struct pci_controller *hose,
|
|
void __iomem *reg,
|
|
struct resource *res)
|
|
{
|
|
u64 size;
|
|
const u32 *ranges;
|
|
int rlen;
|
|
int pna = of_n_addr_cells(hose->dn);
|
|
int np = pna + 5;
|
|
|
|
/* Default */
|
|
res->start = 0;
|
|
res->end = size = 0x80000000;
|
|
res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
|
|
|
|
/* Get dma-ranges property */
|
|
ranges = of_get_property(hose->dn, "dma-ranges", &rlen);
|
|
if (ranges == NULL)
|
|
goto out;
|
|
|
|
/* Walk it */
|
|
while ((rlen -= np * 4) >= 0) {
|
|
u32 pci_space = ranges[0];
|
|
u64 pci_addr = of_read_number(ranges + 1, 2);
|
|
u64 cpu_addr = of_translate_dma_address(hose->dn, ranges + 3);
|
|
size = of_read_number(ranges + pna + 3, 2);
|
|
ranges += np;
|
|
if (cpu_addr == OF_BAD_ADDR || size == 0)
|
|
continue;
|
|
|
|
/* We only care about memory */
|
|
if ((pci_space & 0x03000000) != 0x02000000)
|
|
continue;
|
|
|
|
/* We currently only support memory at 0, and pci_addr
|
|
* within 32 bits space
|
|
*/
|
|
if (cpu_addr != 0 || pci_addr > 0xffffffff) {
|
|
printk(KERN_WARNING "%s: Ignored unsupported dma range"
|
|
" 0x%016llx...0x%016llx -> 0x%016llx\n",
|
|
hose->dn->full_name,
|
|
pci_addr, pci_addr + size - 1, cpu_addr);
|
|
continue;
|
|
}
|
|
|
|
/* Check if not prefetchable */
|
|
if (!(pci_space & 0x40000000))
|
|
res->flags &= ~IORESOURCE_PREFETCH;
|
|
|
|
|
|
/* Use that */
|
|
res->start = pci_addr;
|
|
#ifndef CONFIG_RESOURCES_64BIT
|
|
/* Beware of 32 bits resources */
|
|
if ((pci_addr + size) > 0x100000000ull)
|
|
res->end = 0xffffffff;
|
|
else
|
|
#endif
|
|
res->end = res->start + size - 1;
|
|
break;
|
|
}
|
|
|
|
/* We only support one global DMA offset */
|
|
if (dma_offset_set && pci_dram_offset != res->start) {
|
|
printk(KERN_ERR "%s: dma-ranges(s) mismatch\n",
|
|
hose->dn->full_name);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Check that we can fit all of memory as we don't support
|
|
* DMA bounce buffers
|
|
*/
|
|
if (size < total_memory) {
|
|
printk(KERN_ERR "%s: dma-ranges too small "
|
|
"(size=%llx total_memory=%lx)\n",
|
|
hose->dn->full_name, size, total_memory);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Check we are a power of 2 size and that base is a multiple of size*/
|
|
if (!is_power_of_2(size) ||
|
|
(res->start & (size - 1)) != 0) {
|
|
printk(KERN_ERR "%s: dma-ranges unaligned\n",
|
|
hose->dn->full_name);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Check that we are fully contained within 32 bits space */
|
|
if (res->end > 0xffffffff) {
|
|
printk(KERN_ERR "%s: dma-ranges outside of 32 bits space\n",
|
|
hose->dn->full_name);
|
|
return -ENXIO;
|
|
}
|
|
out:
|
|
dma_offset_set = 1;
|
|
pci_dram_offset = res->start;
|
|
|
|
printk(KERN_INFO "4xx PCI DMA offset set to 0x%08lx\n",
|
|
pci_dram_offset);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* 4xx PCI 2.x part
|
|
*/
|
|
|
|
static void __init ppc4xx_configure_pci_PMMs(struct pci_controller *hose,
|
|
void __iomem *reg)
|
|
{
|
|
u32 la, ma, pcila, pciha;
|
|
int i, j;
|
|
|
|
/* Setup outbound memory windows */
|
|
for (i = j = 0; i < 3; i++) {
|
|
struct resource *res = &hose->mem_resources[i];
|
|
|
|
/* we only care about memory windows */
|
|
if (!(res->flags & IORESOURCE_MEM))
|
|
continue;
|
|
if (j > 2) {
|
|
printk(KERN_WARNING "%s: Too many ranges\n",
|
|
hose->dn->full_name);
|
|
break;
|
|
}
|
|
|
|
/* Calculate register values */
|
|
la = res->start;
|
|
#ifdef CONFIG_RESOURCES_64BIT
|
|
pciha = (res->start - hose->pci_mem_offset) >> 32;
|
|
pcila = (res->start - hose->pci_mem_offset) & 0xffffffffu;
|
|
#else
|
|
pciha = 0;
|
|
pcila = res->start - hose->pci_mem_offset;
|
|
#endif
|
|
|
|
ma = res->end + 1 - res->start;
|
|
if (!is_power_of_2(ma) || ma < 0x1000 || ma > 0xffffffffu) {
|
|
printk(KERN_WARNING "%s: Resource out of range\n",
|
|
hose->dn->full_name);
|
|
continue;
|
|
}
|
|
ma = (0xffffffffu << ilog2(ma)) | 0x1;
|
|
if (res->flags & IORESOURCE_PREFETCH)
|
|
ma |= 0x2;
|
|
|
|
/* Program register values */
|
|
writel(la, reg + PCIL0_PMM0LA + (0x10 * j));
|
|
writel(pcila, reg + PCIL0_PMM0PCILA + (0x10 * j));
|
|
writel(pciha, reg + PCIL0_PMM0PCIHA + (0x10 * j));
|
|
writel(ma, reg + PCIL0_PMM0MA + (0x10 * j));
|
|
j++;
|
|
}
|
|
}
|
|
|
|
static void __init ppc4xx_configure_pci_PTMs(struct pci_controller *hose,
|
|
void __iomem *reg,
|
|
const struct resource *res)
|
|
{
|
|
resource_size_t size = res->end - res->start + 1;
|
|
u32 sa;
|
|
|
|
/* Calculate window size */
|
|
sa = (0xffffffffu << ilog2(size)) | 1;
|
|
sa |= 0x1;
|
|
|
|
/* RAM is always at 0 local for now */
|
|
writel(0, reg + PCIL0_PTM1LA);
|
|
writel(sa, reg + PCIL0_PTM1MS);
|
|
|
|
/* Map on PCI side */
|
|
early_write_config_dword(hose, hose->first_busno, 0,
|
|
PCI_BASE_ADDRESS_1, res->start);
|
|
early_write_config_dword(hose, hose->first_busno, 0,
|
|
PCI_BASE_ADDRESS_2, 0x00000000);
|
|
early_write_config_word(hose, hose->first_busno, 0,
|
|
PCI_COMMAND, 0x0006);
|
|
}
|
|
|
|
static void __init ppc4xx_probe_pci_bridge(struct device_node *np)
|
|
{
|
|
/* NYI */
|
|
struct resource rsrc_cfg;
|
|
struct resource rsrc_reg;
|
|
struct resource dma_window;
|
|
struct pci_controller *hose = NULL;
|
|
void __iomem *reg = NULL;
|
|
const int *bus_range;
|
|
int primary = 0;
|
|
|
|
/* Fetch config space registers address */
|
|
if (of_address_to_resource(np, 0, &rsrc_cfg)) {
|
|
printk(KERN_ERR "%s:Can't get PCI config register base !",
|
|
np->full_name);
|
|
return;
|
|
}
|
|
/* Fetch host bridge internal registers address */
|
|
if (of_address_to_resource(np, 3, &rsrc_reg)) {
|
|
printk(KERN_ERR "%s: Can't get PCI internal register base !",
|
|
np->full_name);
|
|
return;
|
|
}
|
|
|
|
/* Check if primary bridge */
|
|
if (of_get_property(np, "primary", NULL))
|
|
primary = 1;
|
|
|
|
/* Get bus range if any */
|
|
bus_range = of_get_property(np, "bus-range", NULL);
|
|
|
|
/* Map registers */
|
|
reg = ioremap(rsrc_reg.start, rsrc_reg.end + 1 - rsrc_reg.start);
|
|
if (reg == NULL) {
|
|
printk(KERN_ERR "%s: Can't map registers !", np->full_name);
|
|
goto fail;
|
|
}
|
|
|
|
/* Allocate the host controller data structure */
|
|
hose = pcibios_alloc_controller(np);
|
|
if (!hose)
|
|
goto fail;
|
|
|
|
hose->first_busno = bus_range ? bus_range[0] : 0x0;
|
|
hose->last_busno = bus_range ? bus_range[1] : 0xff;
|
|
|
|
/* Setup config space */
|
|
setup_indirect_pci(hose, rsrc_cfg.start, rsrc_cfg.start + 0x4, 0);
|
|
|
|
/* Disable all windows */
|
|
writel(0, reg + PCIL0_PMM0MA);
|
|
writel(0, reg + PCIL0_PMM1MA);
|
|
writel(0, reg + PCIL0_PMM2MA);
|
|
writel(0, reg + PCIL0_PTM1MS);
|
|
writel(0, reg + PCIL0_PTM2MS);
|
|
|
|
/* Parse outbound mapping resources */
|
|
pci_process_bridge_OF_ranges(hose, np, primary);
|
|
|
|
/* Parse inbound mapping resources */
|
|
if (ppc4xx_parse_dma_ranges(hose, reg, &dma_window) != 0)
|
|
goto fail;
|
|
|
|
/* Configure outbound ranges POMs */
|
|
ppc4xx_configure_pci_PMMs(hose, reg);
|
|
|
|
/* Configure inbound ranges PIMs */
|
|
ppc4xx_configure_pci_PTMs(hose, reg, &dma_window);
|
|
|
|
/* We don't need the registers anymore */
|
|
iounmap(reg);
|
|
return;
|
|
|
|
fail:
|
|
if (hose)
|
|
pcibios_free_controller(hose);
|
|
if (reg)
|
|
iounmap(reg);
|
|
}
|
|
|
|
/*
|
|
* 4xx PCI-X part
|
|
*/
|
|
|
|
static void __init ppc4xx_configure_pcix_POMs(struct pci_controller *hose,
|
|
void __iomem *reg)
|
|
{
|
|
u32 lah, lal, pciah, pcial, sa;
|
|
int i, j;
|
|
|
|
/* Setup outbound memory windows */
|
|
for (i = j = 0; i < 3; i++) {
|
|
struct resource *res = &hose->mem_resources[i];
|
|
|
|
/* we only care about memory windows */
|
|
if (!(res->flags & IORESOURCE_MEM))
|
|
continue;
|
|
if (j > 1) {
|
|
printk(KERN_WARNING "%s: Too many ranges\n",
|
|
hose->dn->full_name);
|
|
break;
|
|
}
|
|
|
|
/* Calculate register values */
|
|
#ifdef CONFIG_RESOURCES_64BIT
|
|
lah = res->start >> 32;
|
|
lal = res->start & 0xffffffffu;
|
|
pciah = (res->start - hose->pci_mem_offset) >> 32;
|
|
pcial = (res->start - hose->pci_mem_offset) & 0xffffffffu;
|
|
#else
|
|
lah = pciah = 0;
|
|
lal = res->start;
|
|
pcial = res->start - hose->pci_mem_offset;
|
|
#endif
|
|
sa = res->end + 1 - res->start;
|
|
if (!is_power_of_2(sa) || sa < 0x100000 ||
|
|
sa > 0xffffffffu) {
|
|
printk(KERN_WARNING "%s: Resource out of range\n",
|
|
hose->dn->full_name);
|
|
continue;
|
|
}
|
|
sa = (0xffffffffu << ilog2(sa)) | 0x1;
|
|
|
|
/* Program register values */
|
|
if (j == 0) {
|
|
writel(lah, reg + PCIX0_POM0LAH);
|
|
writel(lal, reg + PCIX0_POM0LAL);
|
|
writel(pciah, reg + PCIX0_POM0PCIAH);
|
|
writel(pcial, reg + PCIX0_POM0PCIAL);
|
|
writel(sa, reg + PCIX0_POM0SA);
|
|
} else {
|
|
writel(lah, reg + PCIX0_POM1LAH);
|
|
writel(lal, reg + PCIX0_POM1LAL);
|
|
writel(pciah, reg + PCIX0_POM1PCIAH);
|
|
writel(pcial, reg + PCIX0_POM1PCIAL);
|
|
writel(sa, reg + PCIX0_POM1SA);
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
|
|
static void __init ppc4xx_configure_pcix_PIMs(struct pci_controller *hose,
|
|
void __iomem *reg,
|
|
const struct resource *res,
|
|
int big_pim,
|
|
int enable_msi_hole)
|
|
{
|
|
resource_size_t size = res->end - res->start + 1;
|
|
u32 sa;
|
|
|
|
/* RAM is always at 0 */
|
|
writel(0x00000000, reg + PCIX0_PIM0LAH);
|
|
writel(0x00000000, reg + PCIX0_PIM0LAL);
|
|
|
|
/* Calculate window size */
|
|
sa = (0xffffffffu << ilog2(size)) | 1;
|
|
sa |= 0x1;
|
|
if (res->flags & IORESOURCE_PREFETCH)
|
|
sa |= 0x2;
|
|
if (enable_msi_hole)
|
|
sa |= 0x4;
|
|
writel(sa, reg + PCIX0_PIM0SA);
|
|
if (big_pim)
|
|
writel(0xffffffff, reg + PCIX0_PIM0SAH);
|
|
|
|
/* Map on PCI side */
|
|
writel(0x00000000, reg + PCIX0_BAR0H);
|
|
writel(res->start, reg + PCIX0_BAR0L);
|
|
writew(0x0006, reg + PCIX0_COMMAND);
|
|
}
|
|
|
|
static void __init ppc4xx_probe_pcix_bridge(struct device_node *np)
|
|
{
|
|
struct resource rsrc_cfg;
|
|
struct resource rsrc_reg;
|
|
struct resource dma_window;
|
|
struct pci_controller *hose = NULL;
|
|
void __iomem *reg = NULL;
|
|
const int *bus_range;
|
|
int big_pim = 0, msi = 0, primary = 0;
|
|
|
|
/* Fetch config space registers address */
|
|
if (of_address_to_resource(np, 0, &rsrc_cfg)) {
|
|
printk(KERN_ERR "%s:Can't get PCI-X config register base !",
|
|
np->full_name);
|
|
return;
|
|
}
|
|
/* Fetch host bridge internal registers address */
|
|
if (of_address_to_resource(np, 3, &rsrc_reg)) {
|
|
printk(KERN_ERR "%s: Can't get PCI-X internal register base !",
|
|
np->full_name);
|
|
return;
|
|
}
|
|
|
|
/* Check if it supports large PIMs (440GX) */
|
|
if (of_get_property(np, "large-inbound-windows", NULL))
|
|
big_pim = 1;
|
|
|
|
/* Check if we should enable MSIs inbound hole */
|
|
if (of_get_property(np, "enable-msi-hole", NULL))
|
|
msi = 1;
|
|
|
|
/* Check if primary bridge */
|
|
if (of_get_property(np, "primary", NULL))
|
|
primary = 1;
|
|
|
|
/* Get bus range if any */
|
|
bus_range = of_get_property(np, "bus-range", NULL);
|
|
|
|
/* Map registers */
|
|
reg = ioremap(rsrc_reg.start, rsrc_reg.end + 1 - rsrc_reg.start);
|
|
if (reg == NULL) {
|
|
printk(KERN_ERR "%s: Can't map registers !", np->full_name);
|
|
goto fail;
|
|
}
|
|
|
|
/* Allocate the host controller data structure */
|
|
hose = pcibios_alloc_controller(np);
|
|
if (!hose)
|
|
goto fail;
|
|
|
|
hose->first_busno = bus_range ? bus_range[0] : 0x0;
|
|
hose->last_busno = bus_range ? bus_range[1] : 0xff;
|
|
|
|
/* Setup config space */
|
|
setup_indirect_pci(hose, rsrc_cfg.start, rsrc_cfg.start + 0x4, 0);
|
|
|
|
/* Disable all windows */
|
|
writel(0, reg + PCIX0_POM0SA);
|
|
writel(0, reg + PCIX0_POM1SA);
|
|
writel(0, reg + PCIX0_POM2SA);
|
|
writel(0, reg + PCIX0_PIM0SA);
|
|
writel(0, reg + PCIX0_PIM1SA);
|
|
writel(0, reg + PCIX0_PIM2SA);
|
|
if (big_pim) {
|
|
writel(0, reg + PCIX0_PIM0SAH);
|
|
writel(0, reg + PCIX0_PIM2SAH);
|
|
}
|
|
|
|
/* Parse outbound mapping resources */
|
|
pci_process_bridge_OF_ranges(hose, np, primary);
|
|
|
|
/* Parse inbound mapping resources */
|
|
if (ppc4xx_parse_dma_ranges(hose, reg, &dma_window) != 0)
|
|
goto fail;
|
|
|
|
/* Configure outbound ranges POMs */
|
|
ppc4xx_configure_pcix_POMs(hose, reg);
|
|
|
|
/* Configure inbound ranges PIMs */
|
|
ppc4xx_configure_pcix_PIMs(hose, reg, &dma_window, big_pim, msi);
|
|
|
|
/* We don't need the registers anymore */
|
|
iounmap(reg);
|
|
return;
|
|
|
|
fail:
|
|
if (hose)
|
|
pcibios_free_controller(hose);
|
|
if (reg)
|
|
iounmap(reg);
|
|
}
|
|
|
|
/*
|
|
* 4xx PCI-Express part
|
|
*/
|
|
static void __init ppc4xx_probe_pciex_bridge(struct device_node *np)
|
|
{
|
|
/* NYI */
|
|
}
|
|
|
|
static int __init ppc4xx_pci_find_bridges(void)
|
|
{
|
|
struct device_node *np;
|
|
|
|
for_each_compatible_node(np, NULL, "ibm,plb-pciex")
|
|
ppc4xx_probe_pciex_bridge(np);
|
|
for_each_compatible_node(np, NULL, "ibm,plb-pcix")
|
|
ppc4xx_probe_pcix_bridge(np);
|
|
for_each_compatible_node(np, NULL, "ibm,plb-pci")
|
|
ppc4xx_probe_pci_bridge(np);
|
|
|
|
return 0;
|
|
}
|
|
arch_initcall(ppc4xx_pci_find_bridges);
|
|
|