diff options
Diffstat (limited to 'drivers/memory/fsl-corenet-cf.c')
| -rw-r--r-- | drivers/memory/fsl-corenet-cf.c | 251 | 
1 files changed, 251 insertions, 0 deletions
diff --git a/drivers/memory/fsl-corenet-cf.c b/drivers/memory/fsl-corenet-cf.c new file mode 100644 index 000000000000..c9443fc136db --- /dev/null +++ b/drivers/memory/fsl-corenet-cf.c @@ -0,0 +1,251 @@ +/* + * 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");  |