diff options
Diffstat (limited to 'drivers/memory/tegra20-mc.c')
| -rw-r--r-- | drivers/memory/tegra20-mc.c | 255 | 
1 files changed, 255 insertions, 0 deletions
diff --git a/drivers/memory/tegra20-mc.c b/drivers/memory/tegra20-mc.c new file mode 100644 index 00000000000..7cd82b874ab --- /dev/null +++ b/drivers/memory/tegra20-mc.c @@ -0,0 +1,255 @@ +/* + * Tegra20 Memory Controller + * + * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ratelimit.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +#define DRV_NAME "tegra20-mc" + +#define MC_INTSTATUS			0x0 +#define MC_INTMASK			0x4 + +#define MC_INT_ERR_SHIFT		6 +#define MC_INT_ERR_MASK			(0x1f << MC_INT_ERR_SHIFT) +#define MC_INT_DECERR_EMEM		BIT(MC_INT_ERR_SHIFT) +#define MC_INT_INVALID_GART_PAGE	BIT(MC_INT_ERR_SHIFT + 1) +#define MC_INT_SECURITY_VIOLATION	BIT(MC_INT_ERR_SHIFT + 2) +#define MC_INT_ARBITRATION_EMEM		BIT(MC_INT_ERR_SHIFT + 3) + +#define MC_GART_ERROR_REQ		0x30 +#define MC_DECERR_EMEM_OTHERS_STATUS	0x58 +#define MC_SECURITY_VIOLATION_STATUS	0x74 + +#define SECURITY_VIOLATION_TYPE		BIT(30)	/* 0=TRUSTZONE, 1=CARVEOUT */ + +#define MC_CLIENT_ID_MASK		0x3f + +#define NUM_MC_REG_BANKS		2 + +struct tegra20_mc { +	void __iomem *regs[NUM_MC_REG_BANKS]; +	struct device *dev; +}; + +static inline u32 mc_readl(struct tegra20_mc *mc, u32 offs) +{ +	u32 val = 0; + +	if (offs < 0x24) +		val = readl(mc->regs[0] + offs); +	else if (offs < 0x400) +		val = readl(mc->regs[1] + offs - 0x3c); + +	return val; +} + +static inline void mc_writel(struct tegra20_mc *mc, u32 val, u32 offs) +{ +	if (offs < 0x24) +		writel(val, mc->regs[0] + offs); +	else if (offs < 0x400) +		writel(val, mc->regs[1] + offs - 0x3c); +} + +static const char * const tegra20_mc_client[] = { +	"cbr_display0a", +	"cbr_display0ab", +	"cbr_display0b", +	"cbr_display0bb", +	"cbr_display0c", +	"cbr_display0cb", +	"cbr_display1b", +	"cbr_display1bb", +	"cbr_eppup", +	"cbr_g2pr", +	"cbr_g2sr", +	"cbr_mpeunifbr", +	"cbr_viruv", +	"csr_avpcarm7r", +	"csr_displayhc", +	"csr_displayhcb", +	"csr_fdcdrd", +	"csr_g2dr", +	"csr_host1xdmar", +	"csr_host1xr", +	"csr_idxsrd", +	"csr_mpcorer", +	"csr_mpe_ipred", +	"csr_mpeamemrd", +	"csr_mpecsrd", +	"csr_ppcsahbdmar", +	"csr_ppcsahbslvr", +	"csr_texsrd", +	"csr_vdebsevr", +	"csr_vdember", +	"csr_vdemcer", +	"csr_vdetper", +	"cbw_eppu", +	"cbw_eppv", +	"cbw_eppy", +	"cbw_mpeunifbw", +	"cbw_viwsb", +	"cbw_viwu", +	"cbw_viwv", +	"cbw_viwy", +	"ccw_g2dw", +	"csw_avpcarm7w", +	"csw_fdcdwr", +	"csw_host1xw", +	"csw_ispw", +	"csw_mpcorew", +	"csw_mpecswr", +	"csw_ppcsahbdmaw", +	"csw_ppcsahbslvw", +	"csw_vdebsevw", +	"csw_vdembew", +	"csw_vdetpmw", +}; + +static void tegra20_mc_decode(struct tegra20_mc *mc, int n) +{ +	u32 addr, req; +	const char *client = "Unknown"; +	int idx, cid; +	const struct reg_info { +		u32 offset; +		u32 write_bit;	/* 0=READ, 1=WRITE */ +		int cid_shift; +		char *message; +	} reg[] = { +		{ +			.offset = MC_DECERR_EMEM_OTHERS_STATUS, +			.write_bit = 31, +			.message = "MC_DECERR", +		}, +		{ +			.offset	= MC_GART_ERROR_REQ, +			.cid_shift = 1, +			.message = "MC_GART_ERR", + +		}, +		{ +			.offset = MC_SECURITY_VIOLATION_STATUS, +			.write_bit = 31, +			.message = "MC_SECURITY_ERR", +		}, +	}; + +	idx = n - MC_INT_ERR_SHIFT; +	if ((idx < 0) || (idx >= ARRAY_SIZE(reg))) { +		dev_err_ratelimited(mc->dev, "Unknown interrupt status %08lx\n", +				    BIT(n)); +		return; +	} + +	req = mc_readl(mc, reg[idx].offset); +	cid = (req >> reg[idx].cid_shift) & MC_CLIENT_ID_MASK; +	if (cid < ARRAY_SIZE(tegra20_mc_client)) +		client = tegra20_mc_client[cid]; + +	addr = mc_readl(mc, reg[idx].offset + sizeof(u32)); + +	dev_err_ratelimited(mc->dev, "%s (0x%08x): 0x%08x %s (%s %s)\n", +			   reg[idx].message, req, addr, client, +			   (req & BIT(reg[idx].write_bit)) ? "write" : "read", +			   (reg[idx].offset == MC_SECURITY_VIOLATION_STATUS) ? +			   ((req & SECURITY_VIOLATION_TYPE) ? +			    "carveout" : "trustzone") : ""); +} + +static const struct of_device_id tegra20_mc_of_match[] = { +	{ .compatible = "nvidia,tegra20-mc", }, +	{}, +}; + +static irqreturn_t tegra20_mc_isr(int irq, void *data) +{ +	u32 stat, mask, bit; +	struct tegra20_mc *mc = data; + +	stat = mc_readl(mc, MC_INTSTATUS); +	mask = mc_readl(mc, MC_INTMASK); +	mask &= stat; +	if (!mask) +		return IRQ_NONE; +	while ((bit = ffs(mask)) != 0) { +		tegra20_mc_decode(mc, bit - 1); +		mask &= ~BIT(bit - 1); +	} + +	mc_writel(mc, stat, MC_INTSTATUS); +	return IRQ_HANDLED; +} + +static int tegra20_mc_probe(struct platform_device *pdev) +{ +	struct resource *irq; +	struct tegra20_mc *mc; +	int i, err; +	u32 intmask; + +	mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); +	if (!mc) +		return -ENOMEM; +	mc->dev = &pdev->dev; + +	for (i = 0; i < ARRAY_SIZE(mc->regs); i++) { +		struct resource *res; + +		res = platform_get_resource(pdev, IORESOURCE_MEM, i); +		mc->regs[i] = devm_ioremap_resource(&pdev->dev, res); +		if (IS_ERR(mc->regs[i])) +			return PTR_ERR(mc->regs[i]); +	} + +	irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (!irq) +		return -ENODEV; +	err = devm_request_irq(&pdev->dev, irq->start, tegra20_mc_isr, +			       IRQF_SHARED, dev_name(&pdev->dev), mc); +	if (err) +		return -ENODEV; + +	platform_set_drvdata(pdev, mc); + +	intmask = MC_INT_INVALID_GART_PAGE | +		MC_INT_DECERR_EMEM | MC_INT_SECURITY_VIOLATION; +	mc_writel(mc, intmask, MC_INTMASK); +	return 0; +} + +static struct platform_driver tegra20_mc_driver = { +	.probe = tegra20_mc_probe, +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = tegra20_mc_of_match, +	}, +}; +module_platform_driver(tegra20_mc_driver); + +MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 MC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME);  | 
