|  | /* | 
|  | * 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/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[] __devinitconst = { | 
|  | { .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); | 
|  | mc_writel(mc, stat, MC_INTSTATUS); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static int __devinit 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); | 
|  | if (!res) | 
|  | return -ENODEV; | 
|  | mc->regs[i] = devm_request_and_ioremap(&pdev->dev, res); | 
|  | if (!mc->regs[i]) | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | 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); |