--- /dev/null
+/*
+ * CoreNet Coherency Fabric error reporting
+ *
+ * Copyright 2014 Freescale Semiconductor Inc.
+ *
+ * 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/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+enum ccf_version {
+       CCF1,
+       CCF2,
+};
+
+struct ccf_info {
+       enum ccf_version version;
+       int err_reg_offs;
+};
+
+static const struct ccf_info ccf1_info = {
+       .version = CCF1,
+       .err_reg_offs = 0xa00,
+};
+
+static const struct ccf_info ccf2_info = {
+       .version = CCF2,
+       .err_reg_offs = 0xe40,
+};
+
+static const struct of_device_id ccf_matches[] = {
+       {
+               .compatible = "fsl,corenet1-cf",
+               .data = &ccf1_info,
+       },
+       {
+               .compatible = "fsl,corenet2-cf",
+               .data = &ccf2_info,
+       },
+       {}
+};
+
+struct ccf_err_regs {
+       u32 errdet;             /* 0x00 Error Detect Register */
+       /* 0x04 Error Enable (ccf1)/Disable (ccf2) Register */
+       u32 errdis;
+       /* 0x08 Error Interrupt Enable Register (ccf2 only) */
+       u32 errinten;
+       u32 cecar;              /* 0x0c Error Capture Attribute Register */
+       u32 cecaddrh;           /* 0x10 Error Capture Address High */
+       u32 cecaddrl;           /* 0x14 Error Capture Address Low */
+       u32 cecar2;             /* 0x18 Error Capture Attribute Register 2 */
+};
+
+/* LAE/CV also valid for errdis and errinten */
+#define ERRDET_LAE             (1 << 0)  /* Local Access Error */
+#define ERRDET_CV              (1 << 1)  /* Coherency Violation */
+#define ERRDET_CTYPE_SHIFT     26        /* Capture Type (ccf2 only) */
+#define ERRDET_CTYPE_MASK      (0x1f << ERRDET_CTYPE_SHIFT)
+#define ERRDET_CAP             (1 << 31) /* Capture Valid (ccf2 only) */
+
+#define CECAR_VAL              (1 << 0)  /* Valid (ccf1 only) */
+#define CECAR_UVT              (1 << 15) /* Unavailable target ID (ccf1) */
+#define CECAR_SRCID_SHIFT_CCF1 24
+#define CECAR_SRCID_MASK_CCF1  (0xff << CECAR_SRCID_SHIFT_CCF1)
+#define CECAR_SRCID_SHIFT_CCF2 18
+#define CECAR_SRCID_MASK_CCF2  (0xff << CECAR_SRCID_SHIFT_CCF2)
+
+#define CECADDRH_ADDRH         0xff
+
+struct ccf_private {
+       const struct ccf_info *info;
+       struct device *dev;
+       void __iomem *regs;
+       struct ccf_err_regs __iomem *err_regs;
+};
+
+static irqreturn_t ccf_irq(int irq, void *dev_id)
+{
+       struct ccf_private *ccf = dev_id;
+       static DEFINE_RATELIMIT_STATE(ratelimit, DEFAULT_RATELIMIT_INTERVAL,
+                                     DEFAULT_RATELIMIT_BURST);
+       u32 errdet, cecar, cecar2;
+       u64 addr;
+       u32 src_id;
+       bool uvt = false;
+       bool cap_valid = false;
+
+       errdet = ioread32be(&ccf->err_regs->errdet);
+       cecar = ioread32be(&ccf->err_regs->cecar);
+       cecar2 = ioread32be(&ccf->err_regs->cecar2);
+       addr = ioread32be(&ccf->err_regs->cecaddrl);
+       addr |= ((u64)(ioread32be(&ccf->err_regs->cecaddrh) &
+                      CECADDRH_ADDRH)) << 32;
+
+       if (!__ratelimit(&ratelimit))
+               goto out;
+
+       switch (ccf->info->version) {
+       case CCF1:
+               if (cecar & CECAR_VAL) {
+                       if (cecar & CECAR_UVT)
+                               uvt = true;
+
+                       src_id = (cecar & CECAR_SRCID_MASK_CCF1) >>
+                                CECAR_SRCID_SHIFT_CCF1;
+                       cap_valid = true;
+               }
+
+               break;
+       case CCF2:
+               if (errdet & ERRDET_CAP) {
+                       src_id = (cecar & CECAR_SRCID_MASK_CCF2) >>
+                                CECAR_SRCID_SHIFT_CCF2;
+                       cap_valid = true;
+               }
+
+               break;
+       }
+
+       dev_crit(ccf->dev, "errdet 0x%08x cecar 0x%08x cecar2 0x%08x\n",
+                errdet, cecar, cecar2);
+
+       if (errdet & ERRDET_LAE) {
+               if (uvt)
+                       dev_crit(ccf->dev, "LAW Unavailable Target ID\n");
+               else
+                       dev_crit(ccf->dev, "Local Access Window Error\n");
+       }
+
+       if (errdet & ERRDET_CV)
+               dev_crit(ccf->dev, "Coherency Violation\n");
+
+       if (cap_valid) {
+               dev_crit(ccf->dev, "address 0x%09llx, src id 0x%x\n",
+                        addr, src_id);
+       }
+
+out:
+       iowrite32be(errdet, &ccf->err_regs->errdet);
+       return errdet ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int ccf_probe(struct platform_device *pdev)
+{
+       struct ccf_private *ccf;
+       struct resource *r;
+       const struct of_device_id *match;
+       int ret, irq;
+
+       match = of_match_device(ccf_matches, &pdev->dev);
+       if (WARN_ON(!match))
+               return -ENODEV;
+
+       ccf = devm_kzalloc(&pdev->dev, sizeof(*ccf), GFP_KERNEL);
+       if (!ccf)
+               return -ENOMEM;
+
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!r) {
+               dev_err(&pdev->dev, "%s: no mem resource\n", __func__);
+               return -ENXIO;
+       }
+
+       ccf->regs = devm_ioremap_resource(&pdev->dev, r);
+       if (IS_ERR(ccf->regs)) {
+               dev_err(&pdev->dev, "%s: can't map mem resource\n", __func__);
+               return PTR_ERR(ccf->regs);
+       }
+
+       ccf->dev = &pdev->dev;
+       ccf->info = match->data;
+       ccf->err_regs = ccf->regs + ccf->info->err_reg_offs;
+
+       dev_set_drvdata(&pdev->dev, ccf);
+
+       irq = platform_get_irq(pdev, 0);
+       if (!irq) {
+               dev_err(&pdev->dev, "%s: no irq\n", __func__);
+               return -ENXIO;
+       }
+
+       ret = devm_request_irq(&pdev->dev, irq, ccf_irq, 0, pdev->name, ccf);
+       if (ret) {
+               dev_err(&pdev->dev, "%s: can't request irq\n", __func__);
+               return ret;
+       }
+
+       switch (ccf->info->version) {
+       case CCF1:
+               /* On CCF1 this register enables rather than disables. */
+               iowrite32be(ERRDET_LAE | ERRDET_CV, &ccf->err_regs->errdis);
+               break;
+
+       case CCF2:
+               iowrite32be(0, &ccf->err_regs->errdis);
+               iowrite32be(ERRDET_LAE | ERRDET_CV, &ccf->err_regs->errinten);
+               break;
+       }
+
+       return 0;
+}
+
+static int ccf_remove(struct platform_device *pdev)
+{
+       struct ccf_private *ccf = dev_get_drvdata(&pdev->dev);
+
+       switch (ccf->info->version) {
+       case CCF1:
+               iowrite32be(0, &ccf->err_regs->errdis);
+               break;
+
+       case CCF2:
+               /*
+                * We clear errdis on ccf1 because that's the only way to
+                * disable interrupts, but on ccf2 there's no need to disable
+                * detection.
+                */
+               iowrite32be(0, &ccf->err_regs->errinten);
+               break;
+       }
+
+       return 0;
+}
+
+static struct platform_driver ccf_driver = {
+       .driver = {
+               .name = KBUILD_MODNAME,
+               .owner = THIS_MODULE,
+               .of_match_table = ccf_matches,
+       },
+       .probe = ccf_probe,
+       .remove = ccf_remove,
+};
+
+module_platform_driver(ccf_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Freescale Semiconductor");
+MODULE_DESCRIPTION("Freescale CoreNet Coherency Fabric error reporting");