summaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev/fsl_msi.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/powerpc/sysdev/fsl_msi.c')
-rw-r--r--arch/powerpc/sysdev/fsl_msi.c137
1 files changed, 132 insertions, 5 deletions
diff --git a/arch/powerpc/sysdev/fsl_msi.c b/arch/powerpc/sysdev/fsl_msi.c
index ab02db3..b346247 100644
--- a/arch/powerpc/sysdev/fsl_msi.c
+++ b/arch/powerpc/sysdev/fsl_msi.c
@@ -18,6 +18,7 @@
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/of_platform.h>
+#include <linux/iommu.h>
#include <sysdev/fsl_soc.h>
#include <asm/prom.h>
#include <asm/hw_irq.h>
@@ -96,10 +97,53 @@ static int fsl_msi_init_allocator(struct fsl_msi *msi_data)
return 0;
}
+static int fsl_msi_get_region_count(void)
+{
+ int count = 0;
+ struct fsl_msi *msi_data;
+
+ list_for_each_entry(msi_data, &msi_head, list)
+ count++;
+
+ return count;
+}
+
+static int fsl_msi_get_region(int region_num, struct msi_region *region)
+{
+ struct fsl_msi *msi_data;
+
+#define CCSR_BASE 0xffe000000
+
+ list_for_each_entry(msi_data, &msi_head, list) {
+ if (msi_data->bank_index == region_num) {
+ region->region_num = msi_data->bank_index;
+ /*
+ * FIXME Get absolute MSIIR address
+ * (remove define CCSR_BASE).
+ */
+ region->addr = CCSR_BASE + msi_data->msiir_offset;
+ region->size = 0x1000;
+ return 0;
+ }
+ }
+
+ return -ENODEV;
+}
+
static int fsl_msi_check_device(struct pci_dev *pdev, int nvec, int type)
{
- if (type == PCI_CAP_ID_MSIX)
- pr_debug("fslmsi: MSI-X untested, trying anyway.\n");
+ struct fsl_msi *msi;
+
+ if (type == PCI_CAP_ID_MSI) {
+ /*
+ * MPIC version 2.0 has erratum PIC1. For now MSI
+ * could not work. So check to prevent MSI from
+ * being used on the board with this erratum.
+ */
+ list_for_each_entry(msi, &msi_head, list)
+ if (msi->feature & MSI_HW_ERRATA_ENDIAN)
+ return -EINVAL;
+ }
return 0;
}
@@ -122,7 +166,43 @@ static void fsl_teardown_msi_irqs(struct pci_dev *pdev)
return;
}
-static void fsl_compose_msi_msg(struct pci_dev *pdev, int hwirq,
+static int fsl_iommu_get_iova(struct pci_dev *pdev, uint64_t *address)
+{
+ struct iommu_domain *domain;
+ struct iommu_domain_geometry geometry;
+ u32 wins = 0;
+ uint64_t iova, size, msi_phys;
+ int ret, i;
+
+ domain = iommu_get_dev_domain(&pdev->dev);
+ if (!domain)
+ return -EINVAL;
+
+ ret = iommu_domain_get_attr(domain, DOMAIN_ATTR_WINDOWS, &wins);
+ if (ret)
+ return ret;
+
+ ret = iommu_domain_get_attr(domain, DOMAIN_ATTR_GEOMETRY, &geometry);
+ if (ret)
+ return ret;
+
+ iova = geometry.aperture_start;
+ size = geometry.aperture_end - geometry.aperture_start + 1;
+ do_div(size, wins);
+ msi_phys = CCSR_BASE + (*address & 0x0007ffff);
+ for (i = 0; i < wins; i++) {
+ phys_addr_t phys;
+ phys = iommu_iova_to_phys(domain, iova);
+ if (phys == msi_phys) {
+ *address = (iova + (*address & 0x00000fff));
+ return 0;
+ }
+ iova += size;
+ }
+ return -EINVAL;
+}
+
+static int fsl_compose_msi_msg(struct pci_dev *pdev, int hwirq,
struct msi_msg *msg,
struct fsl_msi *fsl_msi_data)
{
@@ -131,6 +211,7 @@ static void fsl_compose_msi_msg(struct pci_dev *pdev, int hwirq,
u64 address; /* Physical address of the MSIIR */
int len;
const __be64 *reg;
+ int ret = 0;
/* If the msi-address-64 property exists, then use it */
reg = of_get_property(hose->dn, "msi-address-64", &len);
@@ -139,13 +220,35 @@ static void fsl_compose_msi_msg(struct pci_dev *pdev, int hwirq,
else
address = fsl_pci_immrbar_base(hose) + msi_data->msiir_offset;
+ /*
+ * If the device is attached with iommu domain then set MSI address
+ * to the iova configured in PAMU.
+ */
+ if (iommu_get_dev_domain(&pdev->dev)) {
+ ret = fsl_iommu_get_iova(pdev, &address);
+ if (ret)
+ return ret;
+ }
+
msg->address_lo = lower_32_bits(address);
msg->address_hi = upper_32_bits(address);
- msg->data = hwirq;
+ /*
+ * MPIC version 2.0 has erratum PIC1. It causes
+ * that neither MSI nor MSI-X can work fine.
+ * This is a workaround to allow MSI-X to function
+ * properly. It only works for MSI-X, we prevent
+ * MSI on buggy chips in fsl_msi_check_device().
+ */
+ if (msi_data->feature & MSI_HW_ERRATA_ENDIAN)
+ msg->data = __swab32(hwirq);
+ else
+ msg->data = hwirq;
pr_debug("%s: allocated srs: %d, ibs: %d\n",
__func__, hwirq / IRQS_PER_MSI_REG, hwirq % IRQS_PER_MSI_REG);
+
+ return ret;
}
static int fsl_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
@@ -215,7 +318,13 @@ static int fsl_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
/* chip_data is msi_data via host->hostdata in host->map() */
irq_set_msi_desc(virq, entry);
- fsl_compose_msi_msg(pdev, hwirq, &msg, msi_data);
+ if (fsl_compose_msi_msg(pdev, hwirq, &msg, msi_data)) {
+ dev_err(&pdev->dev, "fail setting MSI");
+ msi_bitmap_free_hwirqs(&msi_data->bitmap, hwirq, 1);
+ rc = -ENODEV;
+ goto out_free;
+ }
+
write_msi_msg(virq, &msg);
}
return 0;
@@ -363,6 +472,15 @@ static int fsl_msi_setup_hwirq(struct fsl_msi *msi, struct platform_device *dev,
return 0;
}
+/* MPIC version 2.0 has erratum PIC1 */
+static int mpic_has_erratum_pic1(void)
+{
+ if (fsl_mpic_primary_get_version() == 0x0200)
+ return 1;
+
+ return 0;
+}
+
static const struct of_device_id fsl_of_msi_ids[];
static int fsl_of_msi_probe(struct platform_device *dev)
{
@@ -376,6 +494,7 @@ static int fsl_of_msi_probe(struct platform_device *dev)
int len;
u32 offset;
static const u32 all_avail[] = { 0, NR_MSI_IRQS };
+ static int bank_index;
match = of_match_device(fsl_of_msi_ids, &dev->dev);
if (!match)
@@ -425,6 +544,11 @@ static int fsl_of_msi_probe(struct platform_device *dev)
msi->feature = features->fsl_pic_ip;
+ if ((features->fsl_pic_ip & FSL_PIC_IP_MASK) == FSL_PIC_IP_MPIC) {
+ if (mpic_has_erratum_pic1())
+ msi->feature |= MSI_HW_ERRATA_ENDIAN;
+ }
+
/*
* Remember the phandle, so that we can match with any PCI nodes
* that have an "fsl,msi" property.
@@ -470,6 +594,7 @@ static int fsl_of_msi_probe(struct platform_device *dev)
}
}
+ msi->bank_index = bank_index++;
list_add_tail(&msi->list, &msi_head);
/* The multiple setting ppc_md.setup_msi_irqs will not harm things */
@@ -477,6 +602,8 @@ static int fsl_of_msi_probe(struct platform_device *dev)
ppc_md.setup_msi_irqs = fsl_setup_msi_irqs;
ppc_md.teardown_msi_irqs = fsl_teardown_msi_irqs;
ppc_md.msi_check_device = fsl_msi_check_device;
+ ppc_md.msi_get_region_count = fsl_msi_get_region_count;
+ ppc_md.msi_get_region = fsl_msi_get_region;
} else if (ppc_md.setup_msi_irqs != fsl_setup_msi_irqs) {
dev_err(&dev->dev, "Different MSI driver already installed!\n");
err = -ENODEV;