|
@@ -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);
|
|
|
+
|
|
|
+}
|