blob: 4de6bfd50fecd275d8e0801178c6b4b75514e919 [file] [log] [blame]
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -08001/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070011 */
12
13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14#include <linux/kernel.h>
15#include <linux/module.h>
16#include <linux/platform_device.h>
17#include <linux/errno.h>
18#include <linux/io.h>
19#include <linux/interrupt.h>
20#include <linux/list.h>
21#include <linux/spinlock.h>
22#include <linux/slab.h>
23#include <linux/iommu.h>
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -080024#include <linux/clk.h>
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070025
26#include <asm/cacheflush.h>
27#include <asm/sizes.h>
28
29#include <mach/iommu_hw-8xxx.h>
30#include <mach/iommu.h>
31
Stepan Moskovchenko100832c2010-11-15 18:20:08 -080032#define MRC(reg, processor, op1, crn, crm, op2) \
33__asm__ __volatile__ ( \
34" mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 "\n" \
35: "=r" (reg))
36
37#define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0)
38#define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1)
39
40static int msm_iommu_tex_class[4];
41
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070042DEFINE_SPINLOCK(msm_iommu_lock);
43
44struct msm_priv {
45 unsigned long *pgtable;
46 struct list_head list_attached;
47};
48
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -080049static int __enable_clocks(struct msm_iommu_drvdata *drvdata)
50{
51 int ret;
52
53 ret = clk_enable(drvdata->pclk);
54 if (ret)
55 goto fail;
56
57 if (drvdata->clk) {
58 ret = clk_enable(drvdata->clk);
59 if (ret)
60 clk_disable(drvdata->pclk);
61 }
62fail:
63 return ret;
64}
65
66static void __disable_clocks(struct msm_iommu_drvdata *drvdata)
67{
68 if (drvdata->clk)
69 clk_disable(drvdata->clk);
70 clk_disable(drvdata->pclk);
71}
72
Stepan Moskovchenko33069732010-11-12 19:30:00 -080073static int __flush_iotlb(struct iommu_domain *domain)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070074{
75 struct msm_priv *priv = domain->priv;
76 struct msm_iommu_drvdata *iommu_drvdata;
77 struct msm_iommu_ctx_drvdata *ctx_drvdata;
Stepan Moskovchenko33069732010-11-12 19:30:00 -080078 int ret = 0;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070079#ifndef CONFIG_IOMMU_PGTABLES_L2
80 unsigned long *fl_table = priv->pgtable;
81 int i;
82
Stepan Moskovchenkof6f41eb2010-11-12 19:29:54 -080083 if (!list_empty(&priv->list_attached)) {
84 dmac_flush_range(fl_table, fl_table + SZ_16K);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070085
Stepan Moskovchenkof6f41eb2010-11-12 19:29:54 -080086 for (i = 0; i < NUM_FL_PTE; i++)
87 if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) {
88 void *sl_table = __va(fl_table[i] &
89 FL_BASE_MASK);
90 dmac_flush_range(sl_table, sl_table + SZ_4K);
91 }
92 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070093#endif
94
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070095 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070096 list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) {
97 if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent)
98 BUG();
99
100 iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent);
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800101 BUG_ON(!iommu_drvdata);
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800102
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800103 ret = __enable_clocks(iommu_drvdata);
104 if (ret)
105 goto fail;
106
107 SET_CTX_TLBIALL(iommu_drvdata->base, ctx_drvdata->num, 0);
108 __disable_clocks(iommu_drvdata);
109 }
110fail:
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800111 return ret;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700112}
113
114static void __reset_context(void __iomem *base, int ctx)
115{
116 SET_BPRCOSH(base, ctx, 0);
117 SET_BPRCISH(base, ctx, 0);
118 SET_BPRCNSH(base, ctx, 0);
119 SET_BPSHCFG(base, ctx, 0);
120 SET_BPMTCFG(base, ctx, 0);
121 SET_ACTLR(base, ctx, 0);
122 SET_SCTLR(base, ctx, 0);
123 SET_FSRRESTORE(base, ctx, 0);
124 SET_TTBR0(base, ctx, 0);
125 SET_TTBR1(base, ctx, 0);
126 SET_TTBCR(base, ctx, 0);
127 SET_BFBCR(base, ctx, 0);
128 SET_PAR(base, ctx, 0);
129 SET_FAR(base, ctx, 0);
130 SET_CTX_TLBIALL(base, ctx, 0);
131 SET_TLBFLPTER(base, ctx, 0);
132 SET_TLBSLPTER(base, ctx, 0);
133 SET_TLBLKCR(base, ctx, 0);
134 SET_PRRR(base, ctx, 0);
135 SET_NMRR(base, ctx, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700136 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700137}
138
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700139static void __program_context(void __iomem *base, int ctx, int ncb,
140 phys_addr_t pgtable)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700141{
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800142 unsigned int prrr, nmrr;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700143 int i, j, found;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700144 __reset_context(base, ctx);
145
146 /* Set up HTW mode */
147 /* TLB miss configuration: perform HTW on miss */
148 SET_TLBMCFG(base, ctx, 0x3);
149
150 /* V2P configuration: HTW for access */
151 SET_V2PCFG(base, ctx, 0x3);
152
153 SET_TTBCR(base, ctx, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700154 SET_TTBR0_PA(base, ctx, (pgtable >> TTBR0_PA_SHIFT));
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700155
156 /* Invalidate the TLB for this context */
157 SET_CTX_TLBIALL(base, ctx, 0);
158
159 /* Set interrupt number to "secure" interrupt */
160 SET_IRPTNDX(base, ctx, 0);
161
162 /* Enable context fault interrupt */
163 SET_CFEIE(base, ctx, 1);
164
165 /* Stall access on a context fault and let the handler deal with it */
166 SET_CFCFG(base, ctx, 1);
167
168 /* Redirect all cacheable requests to L2 slave port. */
169 SET_RCISH(base, ctx, 1);
170 SET_RCOSH(base, ctx, 1);
171 SET_RCNSH(base, ctx, 1);
172
173 /* Turn on TEX Remap */
174 SET_TRE(base, ctx, 1);
175
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800176 /* Set TEX remap attributes */
177 RCP15_PRRR(prrr);
178 RCP15_NMRR(nmrr);
179 SET_PRRR(base, ctx, prrr);
180 SET_NMRR(base, ctx, nmrr);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700181
182 /* Turn on BFB prefetch */
183 SET_BFBDFE(base, ctx, 1);
184
185#ifdef CONFIG_IOMMU_PGTABLES_L2
186 /* Configure page tables as inner-cacheable and shareable to reduce
187 * the TLB miss penalty.
188 */
189 SET_TTBR0_SH(base, ctx, 1);
190 SET_TTBR1_SH(base, ctx, 1);
191
192 SET_TTBR0_NOS(base, ctx, 1);
193 SET_TTBR1_NOS(base, ctx, 1);
194
195 SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */
196 SET_TTBR0_IRGNL(base, ctx, 1);
197
198 SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */
199 SET_TTBR1_IRGNL(base, ctx, 1);
200
201 SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */
202 SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */
203#endif
204
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700205 /* Find if this page table is used elsewhere, and re-use ASID */
206 found = 0;
207 for (i = 0; i < ncb; i++)
208 if (GET_TTBR0_PA(base, i) == (pgtable >> TTBR0_PA_SHIFT) &&
209 i != ctx) {
210 SET_CONTEXTIDR_ASID(base, ctx, \
211 GET_CONTEXTIDR_ASID(base, i));
212 found = 1;
213 break;
214 }
215
216 /* If page table is new, find an unused ASID */
217 if (!found) {
218 for (i = 0; i < ncb; i++) {
219 found = 0;
220 for (j = 0; j < ncb; j++) {
221 if (GET_CONTEXTIDR_ASID(base, j) == i &&
222 j != ctx)
223 found = 1;
224 }
225
226 if (!found) {
227 SET_CONTEXTIDR_ASID(base, ctx, i);
228 break;
229 }
230 }
231 BUG_ON(found);
232 }
233
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700234 /* Enable the MMU */
235 SET_M(base, ctx, 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700236 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700237}
238
239static int msm_iommu_domain_init(struct iommu_domain *domain)
240{
241 struct msm_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
242
243 if (!priv)
244 goto fail_nomem;
245
246 INIT_LIST_HEAD(&priv->list_attached);
247 priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
248 get_order(SZ_16K));
249
250 if (!priv->pgtable)
251 goto fail_nomem;
252
253 memset(priv->pgtable, 0, SZ_16K);
254 domain->priv = priv;
255 return 0;
256
257fail_nomem:
258 kfree(priv);
259 return -ENOMEM;
260}
261
262static void msm_iommu_domain_destroy(struct iommu_domain *domain)
263{
264 struct msm_priv *priv;
265 unsigned long flags;
266 unsigned long *fl_table;
267 int i;
268
269 spin_lock_irqsave(&msm_iommu_lock, flags);
270 priv = domain->priv;
271 domain->priv = NULL;
272
273 if (priv) {
274 fl_table = priv->pgtable;
275
276 for (i = 0; i < NUM_FL_PTE; i++)
277 if ((fl_table[i] & 0x03) == FL_TYPE_TABLE)
278 free_page((unsigned long) __va(((fl_table[i]) &
279 FL_BASE_MASK)));
280
281 free_pages((unsigned long)priv->pgtable, get_order(SZ_16K));
282 priv->pgtable = NULL;
283 }
284
285 kfree(priv);
286 spin_unlock_irqrestore(&msm_iommu_lock, flags);
287}
288
289static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev)
290{
291 struct msm_priv *priv;
292 struct msm_iommu_ctx_dev *ctx_dev;
293 struct msm_iommu_drvdata *iommu_drvdata;
294 struct msm_iommu_ctx_drvdata *ctx_drvdata;
295 struct msm_iommu_ctx_drvdata *tmp_drvdata;
296 int ret = 0;
297 unsigned long flags;
298
299 spin_lock_irqsave(&msm_iommu_lock, flags);
300
301 priv = domain->priv;
302
303 if (!priv || !dev) {
304 ret = -EINVAL;
305 goto fail;
306 }
307
308 iommu_drvdata = dev_get_drvdata(dev->parent);
309 ctx_drvdata = dev_get_drvdata(dev);
310 ctx_dev = dev->platform_data;
311
312 if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) {
313 ret = -EINVAL;
314 goto fail;
315 }
316
Stepan Moskovchenko00d4b2b2010-11-12 19:29:56 -0800317 if (!list_empty(&ctx_drvdata->attached_elm)) {
318 ret = -EBUSY;
319 goto fail;
320 }
321
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700322 list_for_each_entry(tmp_drvdata, &priv->list_attached, attached_elm)
323 if (tmp_drvdata == ctx_drvdata) {
324 ret = -EBUSY;
325 goto fail;
326 }
327
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800328 ret = __enable_clocks(iommu_drvdata);
329 if (ret)
330 goto fail;
331
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700332 __program_context(iommu_drvdata->base, ctx_dev->num, iommu_drvdata->ncb,
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700333 __pa(priv->pgtable));
334
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800335 __disable_clocks(iommu_drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700336 list_add(&(ctx_drvdata->attached_elm), &priv->list_attached);
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800337 ret = __flush_iotlb(domain);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700338
339fail:
340 spin_unlock_irqrestore(&msm_iommu_lock, flags);
341 return ret;
342}
343
344static void msm_iommu_detach_dev(struct iommu_domain *domain,
345 struct device *dev)
346{
347 struct msm_priv *priv;
348 struct msm_iommu_ctx_dev *ctx_dev;
349 struct msm_iommu_drvdata *iommu_drvdata;
350 struct msm_iommu_ctx_drvdata *ctx_drvdata;
351 unsigned long flags;
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800352 int ret;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700353
354 spin_lock_irqsave(&msm_iommu_lock, flags);
355 priv = domain->priv;
356
357 if (!priv || !dev)
358 goto fail;
359
360 iommu_drvdata = dev_get_drvdata(dev->parent);
361 ctx_drvdata = dev_get_drvdata(dev);
362 ctx_dev = dev->platform_data;
363
364 if (!iommu_drvdata || !ctx_drvdata || !ctx_dev)
365 goto fail;
366
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800367 ret = __flush_iotlb(domain);
368 if (ret)
369 goto fail;
370
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800371 ret = __enable_clocks(iommu_drvdata);
372 if (ret)
373 goto fail;
374
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700375 __reset_context(iommu_drvdata->base, ctx_dev->num);
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800376 __disable_clocks(iommu_drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700377 list_del_init(&ctx_drvdata->attached_elm);
378
379fail:
380 spin_unlock_irqrestore(&msm_iommu_lock, flags);
381}
382
383static int msm_iommu_map(struct iommu_domain *domain, unsigned long va,
384 phys_addr_t pa, int order, int prot)
385{
386 struct msm_priv *priv;
387 unsigned long flags;
388 unsigned long *fl_table;
389 unsigned long *fl_pte;
390 unsigned long fl_offset;
391 unsigned long *sl_table;
392 unsigned long *sl_pte;
393 unsigned long sl_offset;
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800394 unsigned int pgprot;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700395 size_t len = 0x1000UL << order;
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800396 int ret = 0, tex, sh;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700397
398 spin_lock_irqsave(&msm_iommu_lock, flags);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700399
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800400 sh = (prot & MSM_IOMMU_ATTR_SH) ? 1 : 0;
401 tex = msm_iommu_tex_class[prot & MSM_IOMMU_CP_MASK];
402
403 if (tex < 0 || tex > NUM_TEX_CLASS - 1) {
404 ret = -EINVAL;
405 goto fail;
406 }
407
408 priv = domain->priv;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700409 if (!priv) {
410 ret = -EINVAL;
411 goto fail;
412 }
413
414 fl_table = priv->pgtable;
415
416 if (len != SZ_16M && len != SZ_1M &&
417 len != SZ_64K && len != SZ_4K) {
418 pr_debug("Bad size: %d\n", len);
419 ret = -EINVAL;
420 goto fail;
421 }
422
423 if (!fl_table) {
424 pr_debug("Null page table\n");
425 ret = -EINVAL;
426 goto fail;
427 }
428
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800429 if (len == SZ_16M || len == SZ_1M) {
430 pgprot = sh ? FL_SHARED : 0;
431 pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0;
432 pgprot |= tex & 0x02 ? FL_CACHEABLE : 0;
433 pgprot |= tex & 0x04 ? FL_TEX0 : 0;
434 } else {
435 pgprot = sh ? SL_SHARED : 0;
436 pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0;
437 pgprot |= tex & 0x02 ? SL_CACHEABLE : 0;
438 pgprot |= tex & 0x04 ? SL_TEX0 : 0;
439 }
440
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700441 fl_offset = FL_OFFSET(va); /* Upper 12 bits */
442 fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
443
444 if (len == SZ_16M) {
445 int i = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700446
447 for (i = 0; i < 16; i++)
448 if (*(fl_pte+i)) {
449 ret = -EBUSY;
450 goto fail;
451 }
452
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700453 for (i = 0; i < 16; i++)
454 *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION |
455 FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT |
Stepan Moskovchenko2e8c8ba2011-02-24 18:00:41 -0800456 FL_SHARED | FL_NG | pgprot;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700457 }
458
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700459 if (len == SZ_1M) {
460 if (*fl_pte) {
461 ret = -EBUSY;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700462 goto fail;
463 }
464
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700465 *fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG |
466 FL_TYPE_SECT | FL_SHARED | pgprot;
467 }
468
469 /* Need a 2nd level table */
470 if (len == SZ_4K || len == SZ_64K) {
471
472 if (*fl_pte == 0) {
473 unsigned long *sl;
474 sl = (unsigned long *) __get_free_pages(GFP_ATOMIC,
475 get_order(SZ_4K));
476
477 if (!sl) {
478 pr_debug("Could not allocate second level table\n");
479 ret = -ENOMEM;
480 goto fail;
481 }
482 memset(sl, 0, SZ_4K);
483
484 *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | \
485 FL_TYPE_TABLE);
486 }
487
488 if (!(*fl_pte & FL_TYPE_TABLE)) {
489 ret = -EBUSY;
490 goto fail;
491 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700492 }
493
494 sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
495 sl_offset = SL_OFFSET(va);
496 sl_pte = sl_table + sl_offset;
497
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700498 if (len == SZ_4K) {
499 if (*sl_pte) {
500 ret = -EBUSY;
501 goto fail;
502 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700503
Stepan Moskovchenko2e8c8ba2011-02-24 18:00:41 -0800504 *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_AP0 | SL_AP1 | SL_NG |
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800505 SL_SHARED | SL_TYPE_SMALL | pgprot;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700506 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700507
508 if (len == SZ_64K) {
509 int i;
510
511 for (i = 0; i < 16; i++)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700512 if (*(sl_pte+i)) {
513 ret = -EBUSY;
514 goto fail;
515 }
516
517 for (i = 0; i < 16; i++)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700518 *(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_AP0 |
Stepan Moskovchenko2e8c8ba2011-02-24 18:00:41 -0800519 SL_NG | SL_AP1 | SL_SHARED | SL_TYPE_LARGE | pgprot;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700520 }
521
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800522 ret = __flush_iotlb(domain);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700523fail:
524 spin_unlock_irqrestore(&msm_iommu_lock, flags);
525 return ret;
526}
527
528static int msm_iommu_unmap(struct iommu_domain *domain, unsigned long va,
529 int order)
530{
531 struct msm_priv *priv;
532 unsigned long flags;
533 unsigned long *fl_table;
534 unsigned long *fl_pte;
535 unsigned long fl_offset;
536 unsigned long *sl_table;
537 unsigned long *sl_pte;
538 unsigned long sl_offset;
539 size_t len = 0x1000UL << order;
540 int i, ret = 0;
541
542 spin_lock_irqsave(&msm_iommu_lock, flags);
543
544 priv = domain->priv;
545
546 if (!priv) {
547 ret = -ENODEV;
548 goto fail;
549 }
550
551 fl_table = priv->pgtable;
552
553 if (len != SZ_16M && len != SZ_1M &&
554 len != SZ_64K && len != SZ_4K) {
555 pr_debug("Bad length: %d\n", len);
556 ret = -EINVAL;
557 goto fail;
558 }
559
560 if (!fl_table) {
561 pr_debug("Null page table\n");
562 ret = -EINVAL;
563 goto fail;
564 }
565
566 fl_offset = FL_OFFSET(va); /* Upper 12 bits */
567 fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
568
569 if (*fl_pte == 0) {
570 pr_debug("First level PTE is 0\n");
571 ret = -ENODEV;
572 goto fail;
573 }
574
575 /* Unmap supersection */
576 if (len == SZ_16M)
577 for (i = 0; i < 16; i++)
578 *(fl_pte+i) = 0;
579
580 if (len == SZ_1M)
581 *fl_pte = 0;
582
583 sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
584 sl_offset = SL_OFFSET(va);
585 sl_pte = sl_table + sl_offset;
586
587 if (len == SZ_64K) {
588 for (i = 0; i < 16; i++)
589 *(sl_pte+i) = 0;
590 }
591
592 if (len == SZ_4K)
593 *sl_pte = 0;
594
595 if (len == SZ_4K || len == SZ_64K) {
596 int used = 0;
597
598 for (i = 0; i < NUM_SL_PTE; i++)
599 if (sl_table[i])
600 used = 1;
601 if (!used) {
602 free_page((unsigned long)sl_table);
603 *fl_pte = 0;
604 }
605 }
606
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800607 ret = __flush_iotlb(domain);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700608fail:
609 spin_unlock_irqrestore(&msm_iommu_lock, flags);
610 return ret;
611}
612
613static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
614 unsigned long va)
615{
616 struct msm_priv *priv;
617 struct msm_iommu_drvdata *iommu_drvdata;
618 struct msm_iommu_ctx_drvdata *ctx_drvdata;
619 unsigned int par;
620 unsigned long flags;
621 void __iomem *base;
622 phys_addr_t ret = 0;
623 int ctx;
624
625 spin_lock_irqsave(&msm_iommu_lock, flags);
626
627 priv = domain->priv;
628 if (list_empty(&priv->list_attached))
629 goto fail;
630
631 ctx_drvdata = list_entry(priv->list_attached.next,
632 struct msm_iommu_ctx_drvdata, attached_elm);
633 iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent);
634
635 base = iommu_drvdata->base;
636 ctx = ctx_drvdata->num;
637
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800638 ret = __enable_clocks(iommu_drvdata);
639 if (ret)
640 goto fail;
641
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700642 /* Invalidate context TLB */
643 SET_CTX_TLBIALL(base, ctx, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700644 mb();
Stepan Moskovchenkob0e78082011-02-28 16:04:55 -0800645 SET_V2PPR(base, ctx, va & V2Pxx_VA);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700646
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700647 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700648 par = GET_PAR(base, ctx);
649
650 /* We are dealing with a supersection */
651 if (GET_NOFAULT_SS(base, ctx))
652 ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
653 else /* Upper 20 bits from PAR, lower 12 from VA */
654 ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
655
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800656 if (GET_FAULT(base, ctx))
657 ret = 0;
658
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800659 __disable_clocks(iommu_drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700660fail:
661 spin_unlock_irqrestore(&msm_iommu_lock, flags);
662 return ret;
663}
664
665static int msm_iommu_domain_has_cap(struct iommu_domain *domain,
666 unsigned long cap)
667{
668 return 0;
669}
670
671static void print_ctx_regs(void __iomem *base, int ctx)
672{
673 unsigned int fsr = GET_FSR(base, ctx);
674 pr_err("FAR = %08x PAR = %08x\n",
675 GET_FAR(base, ctx), GET_PAR(base, ctx));
676 pr_err("FSR = %08x [%s%s%s%s%s%s%s%s%s%s]\n", fsr,
677 (fsr & 0x02) ? "TF " : "",
678 (fsr & 0x04) ? "AFF " : "",
679 (fsr & 0x08) ? "APF " : "",
680 (fsr & 0x10) ? "TLBMF " : "",
681 (fsr & 0x20) ? "HTWDEEF " : "",
682 (fsr & 0x40) ? "HTWSEEF " : "",
683 (fsr & 0x80) ? "MHF " : "",
684 (fsr & 0x10000) ? "SL " : "",
685 (fsr & 0x40000000) ? "SS " : "",
686 (fsr & 0x80000000) ? "MULTI " : "");
687
688 pr_err("FSYNR0 = %08x FSYNR1 = %08x\n",
689 GET_FSYNR0(base, ctx), GET_FSYNR1(base, ctx));
690 pr_err("TTBR0 = %08x TTBR1 = %08x\n",
691 GET_TTBR0(base, ctx), GET_TTBR1(base, ctx));
692 pr_err("SCTLR = %08x ACTLR = %08x\n",
693 GET_SCTLR(base, ctx), GET_ACTLR(base, ctx));
694 pr_err("PRRR = %08x NMRR = %08x\n",
695 GET_PRRR(base, ctx), GET_NMRR(base, ctx));
696}
697
698irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id)
699{
700 struct msm_iommu_drvdata *drvdata = dev_id;
701 void __iomem *base;
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800702 unsigned int fsr;
Stepan Moskovchenkoa43d8c12011-02-24 18:00:42 -0800703 int i, ret;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700704
705 spin_lock(&msm_iommu_lock);
706
707 if (!drvdata) {
708 pr_err("Invalid device ID in context interrupt handler\n");
709 goto fail;
710 }
711
712 base = drvdata->base;
713
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700714 pr_err("Unexpected IOMMU page fault!\n");
715 pr_err("base = %08x\n", (unsigned int) base);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700716 pr_err("name = %s\n", drvdata->name);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700717
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800718 ret = __enable_clocks(drvdata);
719 if (ret)
720 goto fail;
721
Stepan Moskovchenkoa43d8c12011-02-24 18:00:42 -0800722 for (i = 0; i < drvdata->ncb; i++) {
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700723 fsr = GET_FSR(base, i);
724 if (fsr) {
725 pr_err("Fault occurred in context %d.\n", i);
726 pr_err("Interesting registers:\n");
727 print_ctx_regs(base, i);
728 SET_FSR(base, i, 0x4000000F);
729 }
730 }
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800731 __disable_clocks(drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700732fail:
733 spin_unlock(&msm_iommu_lock);
734 return 0;
735}
736
737static struct iommu_ops msm_iommu_ops = {
738 .domain_init = msm_iommu_domain_init,
739 .domain_destroy = msm_iommu_domain_destroy,
740 .attach_dev = msm_iommu_attach_dev,
741 .detach_dev = msm_iommu_detach_dev,
742 .map = msm_iommu_map,
743 .unmap = msm_iommu_unmap,
744 .iova_to_phys = msm_iommu_iova_to_phys,
745 .domain_has_cap = msm_iommu_domain_has_cap
746};
747
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800748static int __init get_tex_class(int icp, int ocp, int mt, int nos)
749{
750 int i = 0;
751 unsigned int prrr = 0;
752 unsigned int nmrr = 0;
753 int c_icp, c_ocp, c_mt, c_nos;
754
755 RCP15_PRRR(prrr);
756 RCP15_NMRR(nmrr);
757
758 for (i = 0; i < NUM_TEX_CLASS; i++) {
759 c_nos = PRRR_NOS(prrr, i);
760 c_mt = PRRR_MT(prrr, i);
761 c_icp = NMRR_ICP(nmrr, i);
762 c_ocp = NMRR_OCP(nmrr, i);
763
764 if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos)
765 return i;
766 }
767
768 return -ENODEV;
769}
770
771static void __init setup_iommu_tex_classes(void)
772{
773 msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED] =
774 get_tex_class(CP_NONCACHED, CP_NONCACHED, MT_NORMAL, 1);
775
776 msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_WA] =
777 get_tex_class(CP_WB_WA, CP_WB_WA, MT_NORMAL, 1);
778
779 msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_NWA] =
780 get_tex_class(CP_WB_NWA, CP_WB_NWA, MT_NORMAL, 1);
781
782 msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WT] =
783 get_tex_class(CP_WT, CP_WT, MT_NORMAL, 1);
784}
785
Stepan Moskovchenko516cbc72010-11-12 19:29:53 -0800786static int __init msm_iommu_init(void)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700787{
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800788 setup_iommu_tex_classes();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700789 register_iommu(&msm_iommu_ops);
790 return 0;
791}
792
793subsys_initcall(msm_iommu_init);
794
795MODULE_LICENSE("GPL v2");
796MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>");