powerpc/powernv: Add support for p5ioc2 PCI-X and PCIe
This adds support for PCI-X and PCIe on the p5ioc2 IO hub using OPAL. This includes allocating & setting up TCE tables and config space access routines. This also supports fallbacks via RTAS when OPAL is absent, using legacy TCE format pre-allocated via the device-tree (BML style) Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
This commit is contained in:
286
arch/powerpc/platforms/powernv/pci.c
Normal file
286
arch/powerpc/platforms/powernv/pci.c
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Support PCI/PCIe on PowerNV platforms
|
||||
*
|
||||
* Currently supports only P5IOC2
|
||||
*
|
||||
* Copyright 2011 Benjamin Herrenschmidt, IBM Corp.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/bootmem.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <asm/sections.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/prom.h>
|
||||
#include <asm/pci-bridge.h>
|
||||
#include <asm/machdep.h>
|
||||
#include <asm/ppc-pci.h>
|
||||
#include <asm/opal.h>
|
||||
#include <asm/iommu.h>
|
||||
#include <asm/tce.h>
|
||||
#include <asm/abs_addr.h>
|
||||
|
||||
#include "powernv.h"
|
||||
#include "pci.h"
|
||||
|
||||
|
||||
#define cfg_dbg(fmt...) do { } while(0)
|
||||
//#define cfg_dbg(fmt...) printk(fmt)
|
||||
|
||||
|
||||
static void pnv_pci_config_check_eeh(struct pnv_phb *phb, struct pci_bus *bus,
|
||||
u32 bdfn)
|
||||
{
|
||||
s64 rc;
|
||||
u8 fstate;
|
||||
u16 pcierr;
|
||||
u32 pe_no;
|
||||
|
||||
/* Get PE# if we support IODA */
|
||||
pe_no = phb->bdfn_to_pe ? phb->bdfn_to_pe(phb, bus, bdfn & 0xff) : 0;
|
||||
|
||||
/* Read freeze status */
|
||||
rc = opal_pci_eeh_freeze_status(phb->opal_id, pe_no, &fstate, &pcierr,
|
||||
NULL);
|
||||
if (rc) {
|
||||
pr_warning("PCI %d: Failed to read EEH status for PE#%d,"
|
||||
" err %lld\n", phb->hose->global_number, pe_no, rc);
|
||||
return;
|
||||
}
|
||||
cfg_dbg(" -> EEH check, bdfn=%04x PE%d fstate=%x\n",
|
||||
bdfn, pe_no, fstate);
|
||||
if (fstate != 0) {
|
||||
rc = opal_pci_eeh_freeze_clear(phb->opal_id, pe_no,
|
||||
OPAL_EEH_ACTION_CLEAR_FREEZE_ALL);
|
||||
if (rc) {
|
||||
pr_warning("PCI %d: Failed to clear EEH freeze state"
|
||||
" for PE#%d, err %lld\n",
|
||||
phb->hose->global_number, pe_no, rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int pnv_pci_read_config(struct pci_bus *bus,
|
||||
unsigned int devfn,
|
||||
int where, int size, u32 *val)
|
||||
{
|
||||
struct pci_controller *hose = pci_bus_to_host(bus);
|
||||
struct pnv_phb *phb = hose->private_data;
|
||||
u32 bdfn = (((uint64_t)bus->number) << 8) | devfn;
|
||||
s64 rc;
|
||||
|
||||
if (hose == NULL)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
switch (size) {
|
||||
case 1: {
|
||||
u8 v8;
|
||||
rc = opal_pci_config_read_byte(phb->opal_id, bdfn, where, &v8);
|
||||
*val = (rc == OPAL_SUCCESS) ? v8 : 0xff;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
u16 v16;
|
||||
rc = opal_pci_config_read_half_word(phb->opal_id, bdfn, where,
|
||||
&v16);
|
||||
*val = (rc == OPAL_SUCCESS) ? v16 : 0xffff;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
u32 v32;
|
||||
rc = opal_pci_config_read_word(phb->opal_id, bdfn, where, &v32);
|
||||
*val = (rc == OPAL_SUCCESS) ? v32 : 0xffffffff;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return PCIBIOS_FUNC_NOT_SUPPORTED;
|
||||
}
|
||||
cfg_dbg("pnv_pci_read_config bus: %x devfn: %x +%x/%x -> %08x\n",
|
||||
bus->number, devfn, where, size, *val);
|
||||
|
||||
/* Check if the PHB got frozen due to an error (no response) */
|
||||
pnv_pci_config_check_eeh(phb, bus, bdfn);
|
||||
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
static int pnv_pci_write_config(struct pci_bus *bus,
|
||||
unsigned int devfn,
|
||||
int where, int size, u32 val)
|
||||
{
|
||||
struct pci_controller *hose = pci_bus_to_host(bus);
|
||||
struct pnv_phb *phb = hose->private_data;
|
||||
u32 bdfn = (((uint64_t)bus->number) << 8) | devfn;
|
||||
|
||||
if (hose == NULL)
|
||||
return PCIBIOS_DEVICE_NOT_FOUND;
|
||||
|
||||
cfg_dbg("pnv_pci_write_config bus: %x devfn: %x +%x/%x -> %08x\n",
|
||||
bus->number, devfn, where, size, val);
|
||||
switch (size) {
|
||||
case 1:
|
||||
opal_pci_config_write_byte(phb->opal_id, bdfn, where, val);
|
||||
break;
|
||||
case 2:
|
||||
opal_pci_config_write_half_word(phb->opal_id, bdfn, where, val);
|
||||
break;
|
||||
case 4:
|
||||
opal_pci_config_write_word(phb->opal_id, bdfn, where, val);
|
||||
break;
|
||||
default:
|
||||
return PCIBIOS_FUNC_NOT_SUPPORTED;
|
||||
}
|
||||
/* Check if the PHB got frozen due to an error (no response) */
|
||||
pnv_pci_config_check_eeh(phb, bus, bdfn);
|
||||
|
||||
return PCIBIOS_SUCCESSFUL;
|
||||
}
|
||||
|
||||
struct pci_ops pnv_pci_ops = {
|
||||
.read = pnv_pci_read_config,
|
||||
.write = pnv_pci_write_config,
|
||||
};
|
||||
|
||||
static int pnv_tce_build(struct iommu_table *tbl, long index, long npages,
|
||||
unsigned long uaddr, enum dma_data_direction direction,
|
||||
struct dma_attrs *attrs)
|
||||
{
|
||||
u64 proto_tce;
|
||||
u64 *tcep;
|
||||
u64 rpn;
|
||||
|
||||
proto_tce = TCE_PCI_READ; // Read allowed
|
||||
|
||||
if (direction != DMA_TO_DEVICE)
|
||||
proto_tce |= TCE_PCI_WRITE;
|
||||
|
||||
tcep = ((u64 *)tbl->it_base) + index;
|
||||
|
||||
while (npages--) {
|
||||
/* can't move this out since we might cross LMB boundary */
|
||||
rpn = (virt_to_abs(uaddr)) >> TCE_SHIFT;
|
||||
*tcep = proto_tce | (rpn & TCE_RPN_MASK) << TCE_RPN_SHIFT;
|
||||
|
||||
uaddr += TCE_PAGE_SIZE;
|
||||
tcep++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pnv_tce_free(struct iommu_table *tbl, long index, long npages)
|
||||
{
|
||||
u64 *tcep = ((u64 *)tbl->it_base) + index;
|
||||
|
||||
while (npages--)
|
||||
*(tcep++) = 0;
|
||||
}
|
||||
|
||||
void pnv_pci_setup_iommu_table(struct iommu_table *tbl,
|
||||
void *tce_mem, u64 tce_size,
|
||||
u64 dma_offset)
|
||||
{
|
||||
tbl->it_blocksize = 16;
|
||||
tbl->it_base = (unsigned long)tce_mem;
|
||||
tbl->it_offset = dma_offset >> IOMMU_PAGE_SHIFT;
|
||||
tbl->it_index = 0;
|
||||
tbl->it_size = tce_size >> 3;
|
||||
tbl->it_busno = 0;
|
||||
tbl->it_type = TCE_PCI;
|
||||
}
|
||||
|
||||
static struct iommu_table * __devinit
|
||||
pnv_pci_setup_bml_iommu(struct pci_controller *hose)
|
||||
{
|
||||
struct iommu_table *tbl;
|
||||
const __be64 *basep;
|
||||
const __be32 *sizep;
|
||||
|
||||
basep = of_get_property(hose->dn, "linux,tce-base", NULL);
|
||||
sizep = of_get_property(hose->dn, "linux,tce-size", NULL);
|
||||
if (basep == NULL || sizep == NULL) {
|
||||
pr_err("PCI: %s has missing tce entries !\n", hose->dn->full_name);
|
||||
return NULL;
|
||||
}
|
||||
tbl = kzalloc_node(sizeof(struct iommu_table), GFP_KERNEL, hose->node);
|
||||
if (WARN_ON(!tbl))
|
||||
return NULL;
|
||||
pnv_pci_setup_iommu_table(tbl, __va(be64_to_cpup(basep)),
|
||||
be32_to_cpup(sizep), 0);
|
||||
iommu_init_table(tbl, hose->node);
|
||||
return tbl;
|
||||
}
|
||||
|
||||
static void __devinit pnv_pci_dma_fallback_setup(struct pci_controller *hose,
|
||||
struct pci_dev *pdev)
|
||||
{
|
||||
struct device_node *np = pci_bus_to_OF_node(hose->bus);
|
||||
struct pci_dn *pdn;
|
||||
|
||||
if (np == NULL)
|
||||
return;
|
||||
pdn = PCI_DN(np);
|
||||
if (!pdn->iommu_table)
|
||||
pdn->iommu_table = pnv_pci_setup_bml_iommu(hose);
|
||||
if (!pdn->iommu_table)
|
||||
return;
|
||||
set_iommu_table_base(&pdev->dev, pdn->iommu_table);
|
||||
}
|
||||
|
||||
static void __devinit pnv_pci_dma_dev_setup(struct pci_dev *pdev)
|
||||
{
|
||||
struct pci_controller *hose = pci_bus_to_host(pdev->bus);
|
||||
struct pnv_phb *phb = hose->private_data;
|
||||
|
||||
/* If we have no phb structure, try to setup a fallback based on
|
||||
* the device-tree (RTAS PCI for example)
|
||||
*/
|
||||
if (phb && phb->dma_dev_setup)
|
||||
phb->dma_dev_setup(phb, pdev);
|
||||
else
|
||||
pnv_pci_dma_fallback_setup(hose, pdev);
|
||||
}
|
||||
|
||||
void __init pnv_pci_init(void)
|
||||
{
|
||||
struct device_node *np;
|
||||
|
||||
pci_set_flags(PCI_CAN_SKIP_ISA_ALIGN);
|
||||
|
||||
/* We do not want to just probe */
|
||||
pci_probe_only = 0;
|
||||
|
||||
/* OPAL absent, try POPAL first then RTAS detection of PHBs */
|
||||
if (!firmware_has_feature(FW_FEATURE_OPAL)) {
|
||||
#ifdef CONFIG_PPC_POWERNV_RTAS
|
||||
init_pci_config_tokens();
|
||||
find_and_init_phbs();
|
||||
#endif /* CONFIG_PPC_POWERNV_RTAS */
|
||||
} else {
|
||||
/* OPAL is here, do our normal stuff */
|
||||
|
||||
/* Look for p5ioc2 IO-Hubs */
|
||||
for_each_compatible_node(np, NULL, "ibm,p5ioc2")
|
||||
pnv_pci_init_p5ioc2_hub(np);
|
||||
}
|
||||
|
||||
/* Setup the linkage between OF nodes and PHBs */
|
||||
pci_devs_phb_init();
|
||||
|
||||
/* Configure IOMMU DMA hooks */
|
||||
ppc_md.pci_dma_dev_setup = pnv_pci_dma_dev_setup;
|
||||
ppc_md.tce_build = pnv_tce_build;
|
||||
ppc_md.tce_free = pnv_tce_free;
|
||||
set_pci_dma_ops(&dma_iommu_ops);
|
||||
|
||||
}
|
Reference in New Issue
Block a user