| /* | 
 |  * 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); |