diff options
author | Hiroshi DOYU <hdoyu@nvidia.com> | 2012-05-10 03:42:32 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-05-10 17:46:09 -0400 |
commit | af4681097b23fe9c63a03d774de7c742fa3a920e (patch) | |
tree | 326ef53d1fa28d46410896f866f5e4ff9ddf792e /drivers/memory/tegra30-mc.c | |
parent | c542fb79fba4c63aa6e2a27f90373b0516614eca (diff) |
ARM: tegra30: Add Tegra Memory Controller(MC) driver
Tegra Memory Controller(MC) driver for Tegra30
Added to support MC General interrupts, mainly for IOMMU(SMMU).
Signed-off-by: Hiroshi DOYU <hdoyu@nvidia.com>
Acked-by: Stephen Warren <swarren@wwwdotorg.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/memory/tegra30-mc.c')
-rw-r--r-- | drivers/memory/tegra30-mc.c | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/drivers/memory/tegra30-mc.c b/drivers/memory/tegra30-mc.c new file mode 100644 index 000000000000..c9821258117a --- /dev/null +++ b/drivers/memory/tegra30-mc.c | |||
@@ -0,0 +1,391 @@ | |||
1 | /* | ||
2 | * Tegra30 Memory Controller | ||
3 | * | ||
4 | * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms and conditions of the GNU General Public License, | ||
8 | * version 2, as published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along with | ||
16 | * this program; if not, write to the Free Software Foundation, Inc., | ||
17 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/ratelimit.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/interrupt.h> | ||
25 | #include <linux/io.h> | ||
26 | |||
27 | #define DRV_NAME "tegra30-mc" | ||
28 | |||
29 | #define MC_INTSTATUS 0x0 | ||
30 | #define MC_INTMASK 0x4 | ||
31 | |||
32 | #define MC_INT_ERR_SHIFT 6 | ||
33 | #define MC_INT_ERR_MASK (0x1f << MC_INT_ERR_SHIFT) | ||
34 | #define MC_INT_DECERR_EMEM BIT(MC_INT_ERR_SHIFT) | ||
35 | #define MC_INT_SECURITY_VIOLATION BIT(MC_INT_ERR_SHIFT + 2) | ||
36 | #define MC_INT_ARBITRATION_EMEM BIT(MC_INT_ERR_SHIFT + 3) | ||
37 | #define MC_INT_INVALID_SMMU_PAGE BIT(MC_INT_ERR_SHIFT + 4) | ||
38 | |||
39 | #define MC_ERR_STATUS 0x8 | ||
40 | #define MC_ERR_ADR 0xc | ||
41 | |||
42 | #define MC_ERR_TYPE_SHIFT 28 | ||
43 | #define MC_ERR_TYPE_MASK (7 << MC_ERR_TYPE_SHIFT) | ||
44 | #define MC_ERR_TYPE_DECERR_EMEM 2 | ||
45 | #define MC_ERR_TYPE_SECURITY_TRUSTZONE 3 | ||
46 | #define MC_ERR_TYPE_SECURITY_CARVEOUT 4 | ||
47 | #define MC_ERR_TYPE_INVALID_SMMU_PAGE 6 | ||
48 | |||
49 | #define MC_ERR_INVALID_SMMU_PAGE_SHIFT 25 | ||
50 | #define MC_ERR_INVALID_SMMU_PAGE_MASK (7 << MC_ERR_INVALID_SMMU_PAGE_SHIFT) | ||
51 | #define MC_ERR_RW_SHIFT 16 | ||
52 | #define MC_ERR_RW BIT(MC_ERR_RW_SHIFT) | ||
53 | #define MC_ERR_SECURITY BIT(MC_ERR_RW_SHIFT + 1) | ||
54 | |||
55 | #define SECURITY_VIOLATION_TYPE BIT(30) /* 0=TRUSTZONE, 1=CARVEOUT */ | ||
56 | |||
57 | #define MC_EMEM_ARB_CFG 0x90 | ||
58 | #define MC_EMEM_ARB_OUTSTANDING_REQ 0x94 | ||
59 | #define MC_EMEM_ARB_TIMING_RCD 0x98 | ||
60 | #define MC_EMEM_ARB_TIMING_RP 0x9c | ||
61 | #define MC_EMEM_ARB_TIMING_RC 0xa0 | ||
62 | #define MC_EMEM_ARB_TIMING_RAS 0xa4 | ||
63 | #define MC_EMEM_ARB_TIMING_FAW 0xa8 | ||
64 | #define MC_EMEM_ARB_TIMING_RRD 0xac | ||
65 | #define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0 | ||
66 | #define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4 | ||
67 | #define MC_EMEM_ARB_TIMING_R2R 0xb8 | ||
68 | #define MC_EMEM_ARB_TIMING_W2W 0xbc | ||
69 | #define MC_EMEM_ARB_TIMING_R2W 0xc0 | ||
70 | #define MC_EMEM_ARB_TIMING_W2R 0xc4 | ||
71 | |||
72 | #define MC_EMEM_ARB_DA_TURNS 0xd0 | ||
73 | #define MC_EMEM_ARB_DA_COVERS 0xd4 | ||
74 | #define MC_EMEM_ARB_MISC0 0xd8 | ||
75 | #define MC_EMEM_ARB_MISC1 0xdc | ||
76 | |||
77 | #define MC_EMEM_ARB_RING3_THROTTLE 0xe4 | ||
78 | #define MC_EMEM_ARB_OVERRIDE 0xe8 | ||
79 | |||
80 | #define MC_TIMING_CONTROL 0xfc | ||
81 | |||
82 | #define MC_CLIENT_ID_MASK 0x7f | ||
83 | |||
84 | #define NUM_MC_REG_BANKS 4 | ||
85 | |||
86 | struct tegra30_mc { | ||
87 | void __iomem *regs[NUM_MC_REG_BANKS]; | ||
88 | struct device *dev; | ||
89 | u32 ctx[0]; | ||
90 | }; | ||
91 | |||
92 | static inline u32 mc_readl(struct tegra30_mc *mc, u32 offs) | ||
93 | { | ||
94 | if (offs < 0x10) | ||
95 | return readl(mc->regs[0] + offs); | ||
96 | BUG_ON(offs < 0x3c); | ||
97 | if (offs < 0x1f0) | ||
98 | return readl(mc->regs[1] + offs - 0x3c); | ||
99 | BUG_ON(offs < 0x200); | ||
100 | if (offs < 0x228) | ||
101 | return readl(mc->regs[2] + offs - 0x200); | ||
102 | BUG_ON(offs < 0x284); | ||
103 | if (offs < 0x400) | ||
104 | return readl(mc->regs[3] + offs - 0x284); | ||
105 | BUG(); | ||
106 | } | ||
107 | |||
108 | static inline void mc_writel(struct tegra30_mc *mc, u32 val, u32 offs) | ||
109 | { | ||
110 | if (offs < 0x10) { | ||
111 | writel(val, mc->regs[0] + offs); | ||
112 | return; | ||
113 | } | ||
114 | BUG_ON(offs < 0x3c); | ||
115 | if (offs < 0x1f0) { | ||
116 | writel(val, mc->regs[1] + offs - 0x3c); | ||
117 | return; | ||
118 | } | ||
119 | BUG_ON(offs < 0x200); | ||
120 | if (offs < 0x228) { | ||
121 | writel(val, mc->regs[2] + offs - 0x200); | ||
122 | return; | ||
123 | } | ||
124 | BUG_ON(offs < 0x284); | ||
125 | if (offs < 0x400) { | ||
126 | writel(val, mc->regs[3] + offs - 0x284); | ||
127 | return; | ||
128 | } | ||
129 | BUG(); | ||
130 | } | ||
131 | |||
132 | static const char * const tegra30_mc_client[] = { | ||
133 | "csr_ptcr", | ||
134 | "cbr_display0a", | ||
135 | "cbr_display0ab", | ||
136 | "cbr_display0b", | ||
137 | "cbr_display0bb", | ||
138 | "cbr_display0c", | ||
139 | "cbr_display0cb", | ||
140 | "cbr_display1b", | ||
141 | "cbr_display1bb", | ||
142 | "cbr_eppup", | ||
143 | "cbr_g2pr", | ||
144 | "cbr_g2sr", | ||
145 | "cbr_mpeunifbr", | ||
146 | "cbr_viruv", | ||
147 | "csr_afir", | ||
148 | "csr_avpcarm7r", | ||
149 | "csr_displayhc", | ||
150 | "csr_displayhcb", | ||
151 | "csr_fdcdrd", | ||
152 | "csr_fdcdrd2", | ||
153 | "csr_g2dr", | ||
154 | "csr_hdar", | ||
155 | "csr_host1xdmar", | ||
156 | "csr_host1xr", | ||
157 | "csr_idxsrd", | ||
158 | "csr_idxsrd2", | ||
159 | "csr_mpe_ipred", | ||
160 | "csr_mpeamemrd", | ||
161 | "csr_mpecsrd", | ||
162 | "csr_ppcsahbdmar", | ||
163 | "csr_ppcsahbslvr", | ||
164 | "csr_satar", | ||
165 | "csr_texsrd", | ||
166 | "csr_texsrd2", | ||
167 | "csr_vdebsevr", | ||
168 | "csr_vdember", | ||
169 | "csr_vdemcer", | ||
170 | "csr_vdetper", | ||
171 | "csr_mpcorelpr", | ||
172 | "csr_mpcorer", | ||
173 | "cbw_eppu", | ||
174 | "cbw_eppv", | ||
175 | "cbw_eppy", | ||
176 | "cbw_mpeunifbw", | ||
177 | "cbw_viwsb", | ||
178 | "cbw_viwu", | ||
179 | "cbw_viwv", | ||
180 | "cbw_viwy", | ||
181 | "ccw_g2dw", | ||
182 | "csw_afiw", | ||
183 | "csw_avpcarm7w", | ||
184 | "csw_fdcdwr", | ||
185 | "csw_fdcdwr2", | ||
186 | "csw_hdaw", | ||
187 | "csw_host1xw", | ||
188 | "csw_ispw", | ||
189 | "csw_mpcorelpw", | ||
190 | "csw_mpcorew", | ||
191 | "csw_mpecswr", | ||
192 | "csw_ppcsahbdmaw", | ||
193 | "csw_ppcsahbslvw", | ||
194 | "csw_sataw", | ||
195 | "csw_vdebsevw", | ||
196 | "csw_vdedbgw", | ||
197 | "csw_vdembew", | ||
198 | "csw_vdetpmw", | ||
199 | }; | ||
200 | |||
201 | static void tegra30_mc_decode(struct tegra30_mc *mc, int n) | ||
202 | { | ||
203 | u32 err, addr; | ||
204 | const char * const mc_int_err[] = { | ||
205 | "MC_DECERR", | ||
206 | "Unknown", | ||
207 | "MC_SECURITY_ERR", | ||
208 | "MC_ARBITRATION_EMEM", | ||
209 | "MC_SMMU_ERR", | ||
210 | }; | ||
211 | const char * const err_type[] = { | ||
212 | "Unknown", | ||
213 | "Unknown", | ||
214 | "DECERR_EMEM", | ||
215 | "SECURITY_TRUSTZONE", | ||
216 | "SECURITY_CARVEOUT", | ||
217 | "Unknown", | ||
218 | "INVALID_SMMU_PAGE", | ||
219 | "Unknown", | ||
220 | }; | ||
221 | char attr[6]; | ||
222 | int cid, perm, type, idx; | ||
223 | const char *client = "Unknown"; | ||
224 | |||
225 | idx = n - MC_INT_ERR_SHIFT; | ||
226 | if ((idx < 0) || (idx >= ARRAY_SIZE(mc_int_err)) || (idx == 1)) { | ||
227 | pr_err_ratelimited("Unknown interrupt status %08lx\n", BIT(n)); | ||
228 | return; | ||
229 | } | ||
230 | |||
231 | err = readl(mc + MC_ERR_STATUS); | ||
232 | |||
233 | type = (err & MC_ERR_TYPE_MASK) >> MC_ERR_TYPE_SHIFT; | ||
234 | perm = (err & MC_ERR_INVALID_SMMU_PAGE_MASK) >> | ||
235 | MC_ERR_INVALID_SMMU_PAGE_SHIFT; | ||
236 | if (type == MC_ERR_TYPE_INVALID_SMMU_PAGE) | ||
237 | sprintf(attr, "%c-%c-%c", | ||
238 | (perm & BIT(2)) ? 'R' : '-', | ||
239 | (perm & BIT(1)) ? 'W' : '-', | ||
240 | (perm & BIT(0)) ? 'S' : '-'); | ||
241 | else | ||
242 | attr[0] = '\0'; | ||
243 | |||
244 | cid = err & MC_CLIENT_ID_MASK; | ||
245 | if (cid < ARRAY_SIZE(tegra30_mc_client)) | ||
246 | client = tegra30_mc_client[cid]; | ||
247 | |||
248 | addr = readl(mc + MC_ERR_ADR); | ||
249 | |||
250 | pr_err_ratelimited("%s (0x%08x): 0x%08x %s (%s %s %s %s)\n", | ||
251 | mc_int_err[idx], err, addr, client, | ||
252 | (err & MC_ERR_SECURITY) ? "secure" : "non-secure", | ||
253 | (err & MC_ERR_RW) ? "write" : "read", | ||
254 | err_type[type], attr); | ||
255 | } | ||
256 | |||
257 | static const u32 tegra30_mc_ctx[] = { | ||
258 | MC_EMEM_ARB_CFG, | ||
259 | MC_EMEM_ARB_OUTSTANDING_REQ, | ||
260 | MC_EMEM_ARB_TIMING_RCD, | ||
261 | MC_EMEM_ARB_TIMING_RP, | ||
262 | MC_EMEM_ARB_TIMING_RC, | ||
263 | MC_EMEM_ARB_TIMING_RAS, | ||
264 | MC_EMEM_ARB_TIMING_FAW, | ||
265 | MC_EMEM_ARB_TIMING_RRD, | ||
266 | MC_EMEM_ARB_TIMING_RAP2PRE, | ||
267 | MC_EMEM_ARB_TIMING_WAP2PRE, | ||
268 | MC_EMEM_ARB_TIMING_R2R, | ||
269 | MC_EMEM_ARB_TIMING_W2W, | ||
270 | MC_EMEM_ARB_TIMING_R2W, | ||
271 | MC_EMEM_ARB_TIMING_W2R, | ||
272 | MC_EMEM_ARB_DA_TURNS, | ||
273 | MC_EMEM_ARB_DA_COVERS, | ||
274 | MC_EMEM_ARB_MISC0, | ||
275 | MC_EMEM_ARB_MISC1, | ||
276 | MC_EMEM_ARB_RING3_THROTTLE, | ||
277 | MC_EMEM_ARB_OVERRIDE, | ||
278 | MC_INTMASK, | ||
279 | }; | ||
280 | |||
281 | static int tegra30_mc_suspend(struct device *dev) | ||
282 | { | ||
283 | int i; | ||
284 | struct tegra30_mc *mc = dev_get_drvdata(dev); | ||
285 | |||
286 | for (i = 0; i < ARRAY_SIZE(tegra30_mc_ctx); i++) | ||
287 | mc->ctx[i] = mc_readl(mc, tegra30_mc_ctx[i]); | ||
288 | return 0; | ||
289 | } | ||
290 | |||
291 | static int tegra30_mc_resume(struct device *dev) | ||
292 | { | ||
293 | int i; | ||
294 | struct tegra30_mc *mc = dev_get_drvdata(dev); | ||
295 | |||
296 | for (i = 0; i < ARRAY_SIZE(tegra30_mc_ctx); i++) | ||
297 | mc_writel(mc, mc->ctx[i], tegra30_mc_ctx[i]); | ||
298 | |||
299 | mc_writel(mc, 1, MC_TIMING_CONTROL); | ||
300 | /* Read-back to ensure that write reached */ | ||
301 | mc_readl(mc, MC_TIMING_CONTROL); | ||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | static UNIVERSAL_DEV_PM_OPS(tegra30_mc_pm, | ||
306 | tegra30_mc_suspend, | ||
307 | tegra30_mc_resume, NULL); | ||
308 | |||
309 | static const struct of_device_id tegra30_mc_of_match[] __devinitconst = { | ||
310 | { .compatible = "nvidia,tegra30-mc", }, | ||
311 | {}, | ||
312 | }; | ||
313 | |||
314 | static irqreturn_t tegra30_mc_isr(int irq, void *data) | ||
315 | { | ||
316 | u32 stat, mask, bit; | ||
317 | struct tegra30_mc *mc = data; | ||
318 | |||
319 | stat = mc_readl(mc, MC_INTSTATUS); | ||
320 | mask = mc_readl(mc, MC_INTMASK); | ||
321 | mask &= stat; | ||
322 | if (!mask) | ||
323 | return IRQ_NONE; | ||
324 | while ((bit = ffs(mask)) != 0) | ||
325 | tegra30_mc_decode(mc, bit - 1); | ||
326 | mc_writel(mc, stat, MC_INTSTATUS); | ||
327 | return IRQ_HANDLED; | ||
328 | } | ||
329 | |||
330 | static int __devinit tegra30_mc_probe(struct platform_device *pdev) | ||
331 | { | ||
332 | struct resource *irq; | ||
333 | struct tegra30_mc *mc; | ||
334 | size_t bytes; | ||
335 | int err, i; | ||
336 | u32 intmask; | ||
337 | |||
338 | bytes = sizeof(*mc) + sizeof(u32) * ARRAY_SIZE(tegra30_mc_ctx); | ||
339 | mc = devm_kzalloc(&pdev->dev, bytes, GFP_KERNEL); | ||
340 | if (!mc) | ||
341 | return -ENOMEM; | ||
342 | mc->dev = &pdev->dev; | ||
343 | |||
344 | for (i = 0; i < ARRAY_SIZE(mc->regs); i++) { | ||
345 | struct resource *res; | ||
346 | |||
347 | res = platform_get_resource(pdev, IORESOURCE_MEM, i); | ||
348 | if (!res) | ||
349 | return -ENODEV; | ||
350 | mc->regs[i] = devm_request_and_ioremap(&pdev->dev, res); | ||
351 | if (!mc->regs[i]) | ||
352 | return -EBUSY; | ||
353 | } | ||
354 | |||
355 | irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); | ||
356 | if (!irq) | ||
357 | return -ENODEV; | ||
358 | err = devm_request_irq(&pdev->dev, irq->start, tegra30_mc_isr, | ||
359 | IRQF_SHARED, dev_name(&pdev->dev), mc); | ||
360 | if (err) | ||
361 | return -ENODEV; | ||
362 | |||
363 | platform_set_drvdata(pdev, mc); | ||
364 | |||
365 | intmask = MC_INT_INVALID_SMMU_PAGE | | ||
366 | MC_INT_DECERR_EMEM | MC_INT_SECURITY_VIOLATION; | ||
367 | mc_writel(mc, intmask, MC_INTMASK); | ||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static int __devexit tegra30_mc_remove(struct platform_device *pdev) | ||
372 | { | ||
373 | return 0; | ||
374 | } | ||
375 | |||
376 | static struct platform_driver tegra30_mc_driver = { | ||
377 | .probe = tegra30_mc_probe, | ||
378 | .remove = __devexit_p(tegra30_mc_remove), | ||
379 | .driver = { | ||
380 | .name = DRV_NAME, | ||
381 | .owner = THIS_MODULE, | ||
382 | .of_match_table = tegra30_mc_of_match, | ||
383 | .pm = &tegra30_mc_pm, | ||
384 | }, | ||
385 | }; | ||
386 | module_platform_driver(tegra30_mc_driver); | ||
387 | |||
388 | MODULE_AUTHOR("Hiroshi DOYU <hdoyu@nvidia.com>"); | ||
389 | MODULE_DESCRIPTION("Tegra30 MC driver"); | ||
390 | MODULE_LICENSE("GPL v2"); | ||
391 | MODULE_ALIAS("platform:" DRV_NAME); | ||