blob: 442a1b4ba18ed5224ed7f0b556c163a6507ed63f [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 Moskovchenko04255ee2011-08-11 19:45:23 -070025#include <linux/scatterlist.h>
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070026
27#include <asm/cacheflush.h>
28#include <asm/sizes.h>
29
30#include <mach/iommu_hw-8xxx.h>
31#include <mach/iommu.h>
32
Stepan Moskovchenko100832c2010-11-15 18:20:08 -080033#define MRC(reg, processor, op1, crn, crm, op2) \
34__asm__ __volatile__ ( \
35" mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 "\n" \
36: "=r" (reg))
37
38#define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0)
39#define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1)
40
Stepan Moskovchenko094475d2011-08-03 13:38:29 -070041#ifndef CONFIG_IOMMU_PGTABLES_L2
42static inline void clean_pte(unsigned long *start, unsigned long *end)
43{
44 dmac_flush_range(start, end);
45}
46#else
47static inline void clean_pte(unsigned long *start, unsigned long *end) { }
48#endif
49
Stepan Moskovchenko100832c2010-11-15 18:20:08 -080050static int msm_iommu_tex_class[4];
51
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -070052DEFINE_SPINLOCK(msm_iommu_lock);
53
54struct msm_priv {
55 unsigned long *pgtable;
56 struct list_head list_attached;
57};
58
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -080059static int __enable_clocks(struct msm_iommu_drvdata *drvdata)
60{
61 int ret;
62
63 ret = clk_enable(drvdata->pclk);
64 if (ret)
65 goto fail;
66
67 if (drvdata->clk) {
68 ret = clk_enable(drvdata->clk);
69 if (ret)
70 clk_disable(drvdata->pclk);
71 }
72fail:
73 return ret;
74}
75
76static void __disable_clocks(struct msm_iommu_drvdata *drvdata)
77{
78 if (drvdata->clk)
79 clk_disable(drvdata->clk);
80 clk_disable(drvdata->pclk);
81}
82
Stepan Moskovchenkobd1ad612011-08-03 16:24:54 -070083static int __flush_iotlb_va(struct iommu_domain *domain, unsigned int va)
84{
85 struct msm_priv *priv = domain->priv;
86 struct msm_iommu_drvdata *iommu_drvdata;
87 struct msm_iommu_ctx_drvdata *ctx_drvdata;
88 int ret = 0;
89 int asid;
90
91 list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) {
92 if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent)
93 BUG();
94
95 iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent);
96 if (!iommu_drvdata)
97 BUG();
98
99 ret = __enable_clocks(iommu_drvdata);
100 if (ret)
101 goto fail;
102
103 asid = GET_CONTEXTIDR_ASID(iommu_drvdata->base,
104 ctx_drvdata->num);
105
106 SET_TLBIVA(iommu_drvdata->base, ctx_drvdata->num,
107 asid | (va & TLBIVA_VA));
108 mb();
109 __disable_clocks(iommu_drvdata);
110 }
111fail:
112 return ret;
113}
114
Stepan Moskovchenko04255ee2011-08-11 19:45:23 -0700115static int __flush_iotlb(struct iommu_domain *domain)
116{
117 struct msm_priv *priv = domain->priv;
118 struct msm_iommu_drvdata *iommu_drvdata;
119 struct msm_iommu_ctx_drvdata *ctx_drvdata;
120 int ret = 0;
121 int asid;
122
123 list_for_each_entry(ctx_drvdata, &priv->list_attached, attached_elm) {
124 if (!ctx_drvdata->pdev || !ctx_drvdata->pdev->dev.parent)
125 BUG();
126
127 iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent);
128 if (!iommu_drvdata)
129 BUG();
130
131 ret = __enable_clocks(iommu_drvdata);
132 if (ret)
133 goto fail;
134
135 asid = GET_CONTEXTIDR_ASID(iommu_drvdata->base,
136 ctx_drvdata->num);
137
138 SET_TLBIASID(iommu_drvdata->base, ctx_drvdata->num, asid);
139 mb();
140 __disable_clocks(iommu_drvdata);
141 }
142fail:
143 return ret;
144}
145
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700146static void __reset_context(void __iomem *base, int ctx)
147{
148 SET_BPRCOSH(base, ctx, 0);
149 SET_BPRCISH(base, ctx, 0);
150 SET_BPRCNSH(base, ctx, 0);
151 SET_BPSHCFG(base, ctx, 0);
152 SET_BPMTCFG(base, ctx, 0);
153 SET_ACTLR(base, ctx, 0);
154 SET_SCTLR(base, ctx, 0);
155 SET_FSRRESTORE(base, ctx, 0);
156 SET_TTBR0(base, ctx, 0);
157 SET_TTBR1(base, ctx, 0);
158 SET_TTBCR(base, ctx, 0);
159 SET_BFBCR(base, ctx, 0);
160 SET_PAR(base, ctx, 0);
161 SET_FAR(base, ctx, 0);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700162 SET_TLBFLPTER(base, ctx, 0);
163 SET_TLBSLPTER(base, ctx, 0);
164 SET_TLBLKCR(base, ctx, 0);
165 SET_PRRR(base, ctx, 0);
166 SET_NMRR(base, ctx, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700167 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700168}
169
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700170static void __program_context(void __iomem *base, int ctx, int ncb,
171 phys_addr_t pgtable)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700172{
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800173 unsigned int prrr, nmrr;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700174 int i, j, found;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700175 __reset_context(base, ctx);
176
177 /* Set up HTW mode */
178 /* TLB miss configuration: perform HTW on miss */
179 SET_TLBMCFG(base, ctx, 0x3);
180
181 /* V2P configuration: HTW for access */
182 SET_V2PCFG(base, ctx, 0x3);
183
184 SET_TTBCR(base, ctx, 0);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700185 SET_TTBR0_PA(base, ctx, (pgtable >> TTBR0_PA_SHIFT));
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700186
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700187 /* Enable context fault interrupt */
188 SET_CFEIE(base, ctx, 1);
189
190 /* Stall access on a context fault and let the handler deal with it */
191 SET_CFCFG(base, ctx, 1);
192
193 /* Redirect all cacheable requests to L2 slave port. */
194 SET_RCISH(base, ctx, 1);
195 SET_RCOSH(base, ctx, 1);
196 SET_RCNSH(base, ctx, 1);
197
198 /* Turn on TEX Remap */
199 SET_TRE(base, ctx, 1);
200
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800201 /* Set TEX remap attributes */
202 RCP15_PRRR(prrr);
203 RCP15_NMRR(nmrr);
204 SET_PRRR(base, ctx, prrr);
205 SET_NMRR(base, ctx, nmrr);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700206
207 /* Turn on BFB prefetch */
208 SET_BFBDFE(base, ctx, 1);
209
210#ifdef CONFIG_IOMMU_PGTABLES_L2
211 /* Configure page tables as inner-cacheable and shareable to reduce
212 * the TLB miss penalty.
213 */
214 SET_TTBR0_SH(base, ctx, 1);
215 SET_TTBR1_SH(base, ctx, 1);
216
217 SET_TTBR0_NOS(base, ctx, 1);
218 SET_TTBR1_NOS(base, ctx, 1);
219
220 SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */
221 SET_TTBR0_IRGNL(base, ctx, 1);
222
223 SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */
224 SET_TTBR1_IRGNL(base, ctx, 1);
225
226 SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */
227 SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */
228#endif
229
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700230 /* Find if this page table is used elsewhere, and re-use ASID */
231 found = 0;
232 for (i = 0; i < ncb; i++)
233 if (GET_TTBR0_PA(base, i) == (pgtable >> TTBR0_PA_SHIFT) &&
234 i != ctx) {
235 SET_CONTEXTIDR_ASID(base, ctx, \
236 GET_CONTEXTIDR_ASID(base, i));
237 found = 1;
238 break;
239 }
240
241 /* If page table is new, find an unused ASID */
242 if (!found) {
243 for (i = 0; i < ncb; i++) {
244 found = 0;
245 for (j = 0; j < ncb; j++) {
246 if (GET_CONTEXTIDR_ASID(base, j) == i &&
247 j != ctx)
248 found = 1;
249 }
250
251 if (!found) {
252 SET_CONTEXTIDR_ASID(base, ctx, i);
253 break;
254 }
255 }
256 BUG_ON(found);
257 }
258
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700259 /* Enable the MMU */
260 SET_M(base, ctx, 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700261 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700262}
263
Stepan Moskovchenkoff2d3662011-08-31 17:13:32 -0700264static int msm_iommu_domain_init(struct iommu_domain *domain, int flags)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700265{
266 struct msm_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
267
268 if (!priv)
269 goto fail_nomem;
270
271 INIT_LIST_HEAD(&priv->list_attached);
272 priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
273 get_order(SZ_16K));
274
275 if (!priv->pgtable)
276 goto fail_nomem;
277
278 memset(priv->pgtable, 0, SZ_16K);
279 domain->priv = priv;
280 return 0;
281
282fail_nomem:
283 kfree(priv);
284 return -ENOMEM;
285}
286
287static void msm_iommu_domain_destroy(struct iommu_domain *domain)
288{
289 struct msm_priv *priv;
290 unsigned long flags;
291 unsigned long *fl_table;
292 int i;
293
294 spin_lock_irqsave(&msm_iommu_lock, flags);
295 priv = domain->priv;
296 domain->priv = NULL;
297
298 if (priv) {
299 fl_table = priv->pgtable;
300
301 for (i = 0; i < NUM_FL_PTE; i++)
302 if ((fl_table[i] & 0x03) == FL_TYPE_TABLE)
303 free_page((unsigned long) __va(((fl_table[i]) &
304 FL_BASE_MASK)));
305
306 free_pages((unsigned long)priv->pgtable, get_order(SZ_16K));
307 priv->pgtable = NULL;
308 }
309
310 kfree(priv);
311 spin_unlock_irqrestore(&msm_iommu_lock, flags);
312}
313
314static int msm_iommu_attach_dev(struct iommu_domain *domain, struct device *dev)
315{
316 struct msm_priv *priv;
317 struct msm_iommu_ctx_dev *ctx_dev;
318 struct msm_iommu_drvdata *iommu_drvdata;
319 struct msm_iommu_ctx_drvdata *ctx_drvdata;
320 struct msm_iommu_ctx_drvdata *tmp_drvdata;
321 int ret = 0;
322 unsigned long flags;
323
324 spin_lock_irqsave(&msm_iommu_lock, flags);
325
326 priv = domain->priv;
327
328 if (!priv || !dev) {
329 ret = -EINVAL;
330 goto fail;
331 }
332
333 iommu_drvdata = dev_get_drvdata(dev->parent);
334 ctx_drvdata = dev_get_drvdata(dev);
335 ctx_dev = dev->platform_data;
336
337 if (!iommu_drvdata || !ctx_drvdata || !ctx_dev) {
338 ret = -EINVAL;
339 goto fail;
340 }
341
Stepan Moskovchenko00d4b2b2010-11-12 19:29:56 -0800342 if (!list_empty(&ctx_drvdata->attached_elm)) {
343 ret = -EBUSY;
344 goto fail;
345 }
346
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700347 list_for_each_entry(tmp_drvdata, &priv->list_attached, attached_elm)
348 if (tmp_drvdata == ctx_drvdata) {
349 ret = -EBUSY;
350 goto fail;
351 }
352
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800353 ret = __enable_clocks(iommu_drvdata);
354 if (ret)
355 goto fail;
356
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700357 __program_context(iommu_drvdata->base, ctx_dev->num, iommu_drvdata->ncb,
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700358 __pa(priv->pgtable));
359
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800360 __disable_clocks(iommu_drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700361 list_add(&(ctx_drvdata->attached_elm), &priv->list_attached);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700362
363fail:
364 spin_unlock_irqrestore(&msm_iommu_lock, flags);
365 return ret;
366}
367
368static void msm_iommu_detach_dev(struct iommu_domain *domain,
369 struct device *dev)
370{
371 struct msm_priv *priv;
372 struct msm_iommu_ctx_dev *ctx_dev;
373 struct msm_iommu_drvdata *iommu_drvdata;
374 struct msm_iommu_ctx_drvdata *ctx_drvdata;
375 unsigned long flags;
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800376 int ret;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700377
378 spin_lock_irqsave(&msm_iommu_lock, flags);
379 priv = domain->priv;
380
381 if (!priv || !dev)
382 goto fail;
383
384 iommu_drvdata = dev_get_drvdata(dev->parent);
385 ctx_drvdata = dev_get_drvdata(dev);
386 ctx_dev = dev->platform_data;
387
388 if (!iommu_drvdata || !ctx_drvdata || !ctx_dev)
389 goto fail;
390
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800391 ret = __enable_clocks(iommu_drvdata);
392 if (ret)
393 goto fail;
394
Stepan Moskovchenkof17c16c2011-08-05 12:16:39 -0700395 SET_TLBIASID(iommu_drvdata->base, ctx_dev->num,
396 GET_CONTEXTIDR_ASID(iommu_drvdata->base, ctx_dev->num));
397
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700398 __reset_context(iommu_drvdata->base, ctx_dev->num);
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800399 __disable_clocks(iommu_drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700400 list_del_init(&ctx_drvdata->attached_elm);
401
402fail:
403 spin_unlock_irqrestore(&msm_iommu_lock, flags);
404}
405
Stepan Moskovchenko04255ee2011-08-11 19:45:23 -0700406static int __get_pgprot(int prot, int len)
407{
408 unsigned int pgprot;
409 int tex, sh;
410
411 sh = (prot & MSM_IOMMU_ATTR_SH) ? 1 : 0;
412 tex = msm_iommu_tex_class[prot & MSM_IOMMU_CP_MASK];
413
414 if (tex < 0 || tex > NUM_TEX_CLASS - 1)
415 return 0;
416
417 if (len == SZ_16M || len == SZ_1M) {
418 pgprot = sh ? FL_SHARED : 0;
419 pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0;
420 pgprot |= tex & 0x02 ? FL_CACHEABLE : 0;
421 pgprot |= tex & 0x04 ? FL_TEX0 : 0;
422 } else {
423 pgprot = sh ? SL_SHARED : 0;
424 pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0;
425 pgprot |= tex & 0x02 ? SL_CACHEABLE : 0;
426 pgprot |= tex & 0x04 ? SL_TEX0 : 0;
427 }
428
429 return pgprot;
430}
431
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700432static int msm_iommu_map(struct iommu_domain *domain, unsigned long va,
433 phys_addr_t pa, int order, int prot)
434{
435 struct msm_priv *priv;
436 unsigned long flags;
437 unsigned long *fl_table;
438 unsigned long *fl_pte;
439 unsigned long fl_offset;
440 unsigned long *sl_table;
441 unsigned long *sl_pte;
442 unsigned long sl_offset;
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800443 unsigned int pgprot;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700444 size_t len = 0x1000UL << order;
Stepan Moskovchenko04255ee2011-08-11 19:45:23 -0700445 int ret = 0;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700446
447 spin_lock_irqsave(&msm_iommu_lock, flags);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700448
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800449 priv = domain->priv;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700450 if (!priv) {
451 ret = -EINVAL;
452 goto fail;
453 }
454
455 fl_table = priv->pgtable;
456
457 if (len != SZ_16M && len != SZ_1M &&
458 len != SZ_64K && len != SZ_4K) {
459 pr_debug("Bad size: %d\n", len);
460 ret = -EINVAL;
461 goto fail;
462 }
463
464 if (!fl_table) {
465 pr_debug("Null page table\n");
466 ret = -EINVAL;
467 goto fail;
468 }
469
Stepan Moskovchenko04255ee2011-08-11 19:45:23 -0700470 pgprot = __get_pgprot(prot, len);
471
472 if (!pgprot) {
473 ret = -EINVAL;
474 goto fail;
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800475 }
476
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700477 fl_offset = FL_OFFSET(va); /* Upper 12 bits */
478 fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
479
480 if (len == SZ_16M) {
481 int i = 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700482
483 for (i = 0; i < 16; i++)
484 if (*(fl_pte+i)) {
485 ret = -EBUSY;
486 goto fail;
487 }
488
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700489 for (i = 0; i < 16; i++)
490 *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION |
491 FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT |
Stepan Moskovchenko2e8c8ba2011-02-24 18:00:41 -0800492 FL_SHARED | FL_NG | pgprot;
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700493
494 clean_pte(fl_pte, fl_pte + 16);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700495 }
496
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700497 if (len == SZ_1M) {
498 if (*fl_pte) {
499 ret = -EBUSY;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700500 goto fail;
501 }
502
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700503 *fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG |
504 FL_TYPE_SECT | FL_SHARED | pgprot;
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700505
506 clean_pte(fl_pte, fl_pte + 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700507 }
508
509 /* Need a 2nd level table */
510 if (len == SZ_4K || len == SZ_64K) {
511
512 if (*fl_pte == 0) {
513 unsigned long *sl;
514 sl = (unsigned long *) __get_free_pages(GFP_ATOMIC,
515 get_order(SZ_4K));
516
517 if (!sl) {
518 pr_debug("Could not allocate second level table\n");
519 ret = -ENOMEM;
520 goto fail;
521 }
522 memset(sl, 0, SZ_4K);
523
524 *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | \
525 FL_TYPE_TABLE);
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700526
527 clean_pte(fl_pte, fl_pte + 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700528 }
529
530 if (!(*fl_pte & FL_TYPE_TABLE)) {
531 ret = -EBUSY;
532 goto fail;
533 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700534 }
535
536 sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
537 sl_offset = SL_OFFSET(va);
538 sl_pte = sl_table + sl_offset;
539
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700540 if (len == SZ_4K) {
541 if (*sl_pte) {
542 ret = -EBUSY;
543 goto fail;
544 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700545
Stepan Moskovchenko2e8c8ba2011-02-24 18:00:41 -0800546 *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_AP0 | SL_AP1 | SL_NG |
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800547 SL_SHARED | SL_TYPE_SMALL | pgprot;
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700548 clean_pte(sl_pte, sl_pte + 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700549 }
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700550
551 if (len == SZ_64K) {
552 int i;
553
554 for (i = 0; i < 16; i++)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700555 if (*(sl_pte+i)) {
556 ret = -EBUSY;
557 goto fail;
558 }
559
560 for (i = 0; i < 16; i++)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700561 *(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_AP0 |
Stepan Moskovchenko2e8c8ba2011-02-24 18:00:41 -0800562 SL_NG | SL_AP1 | SL_SHARED | SL_TYPE_LARGE | pgprot;
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700563
564 clean_pte(sl_pte, sl_pte + 16);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700565 }
566
Stepan Moskovchenkobd1ad612011-08-03 16:24:54 -0700567 ret = __flush_iotlb_va(domain, va);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700568fail:
569 spin_unlock_irqrestore(&msm_iommu_lock, flags);
570 return ret;
571}
572
573static int msm_iommu_unmap(struct iommu_domain *domain, unsigned long va,
574 int order)
575{
576 struct msm_priv *priv;
577 unsigned long flags;
578 unsigned long *fl_table;
579 unsigned long *fl_pte;
580 unsigned long fl_offset;
581 unsigned long *sl_table;
582 unsigned long *sl_pte;
583 unsigned long sl_offset;
584 size_t len = 0x1000UL << order;
585 int i, ret = 0;
586
587 spin_lock_irqsave(&msm_iommu_lock, flags);
588
589 priv = domain->priv;
590
591 if (!priv) {
592 ret = -ENODEV;
593 goto fail;
594 }
595
596 fl_table = priv->pgtable;
597
598 if (len != SZ_16M && len != SZ_1M &&
599 len != SZ_64K && len != SZ_4K) {
600 pr_debug("Bad length: %d\n", len);
601 ret = -EINVAL;
602 goto fail;
603 }
604
605 if (!fl_table) {
606 pr_debug("Null page table\n");
607 ret = -EINVAL;
608 goto fail;
609 }
610
611 fl_offset = FL_OFFSET(va); /* Upper 12 bits */
612 fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
613
614 if (*fl_pte == 0) {
615 pr_debug("First level PTE is 0\n");
616 ret = -ENODEV;
617 goto fail;
618 }
619
620 /* Unmap supersection */
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700621 if (len == SZ_16M) {
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700622 for (i = 0; i < 16; i++)
623 *(fl_pte+i) = 0;
624
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700625 clean_pte(fl_pte, fl_pte + 16);
626 }
627
628 if (len == SZ_1M) {
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700629 *fl_pte = 0;
630
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700631 clean_pte(fl_pte, fl_pte + 1);
632 }
633
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700634 sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
635 sl_offset = SL_OFFSET(va);
636 sl_pte = sl_table + sl_offset;
637
638 if (len == SZ_64K) {
639 for (i = 0; i < 16; i++)
640 *(sl_pte+i) = 0;
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700641
642 clean_pte(sl_pte, sl_pte + 16);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700643 }
644
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700645 if (len == SZ_4K) {
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700646 *sl_pte = 0;
647
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700648 clean_pte(sl_pte, sl_pte + 1);
649 }
650
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700651 if (len == SZ_4K || len == SZ_64K) {
652 int used = 0;
653
654 for (i = 0; i < NUM_SL_PTE; i++)
655 if (sl_table[i])
656 used = 1;
657 if (!used) {
658 free_page((unsigned long)sl_table);
659 *fl_pte = 0;
Stepan Moskovchenko094475d2011-08-03 13:38:29 -0700660
661 clean_pte(fl_pte, fl_pte + 1);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700662 }
663 }
664
Stepan Moskovchenkobd1ad612011-08-03 16:24:54 -0700665 ret = __flush_iotlb_va(domain, va);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700666fail:
667 spin_unlock_irqrestore(&msm_iommu_lock, flags);
668 return ret;
669}
670
Stepan Moskovchenko04255ee2011-08-11 19:45:23 -0700671static int msm_iommu_map_range(struct iommu_domain *domain, unsigned int va,
672 struct scatterlist *sg, unsigned int len,
673 int prot)
674{
675 unsigned int pa;
676 unsigned int offset = 0;
677 unsigned int pgprot;
678 unsigned long *fl_table;
679 unsigned long *fl_pte;
680 unsigned long fl_offset;
681 unsigned long *sl_table;
682 unsigned long sl_offset, sl_start;
683 unsigned long flags;
684 unsigned int chunk_offset = 0;
685 unsigned int chunk_pa;
686 int ret = 0;
687 struct msm_priv *priv;
688
689 spin_lock_irqsave(&msm_iommu_lock, flags);
690
691 BUG_ON(len & (SZ_4K - 1));
692
693 priv = domain->priv;
694 fl_table = priv->pgtable;
695
696 pgprot = __get_pgprot(prot, SZ_4K);
697
698 if (!pgprot) {
699 ret = -EINVAL;
700 goto fail;
701 }
702
703 fl_offset = FL_OFFSET(va); /* Upper 12 bits */
704 fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
705
706 sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
707 sl_offset = SL_OFFSET(va);
708
709 chunk_pa = sg_phys(sg);
710
711 while (offset < len) {
712 /* Set up a 2nd level page table if one doesn't exist */
713 if (*fl_pte == 0) {
714 sl_table = (unsigned long *)
715 __get_free_pages(GFP_ATOMIC, get_order(SZ_4K));
716
717 if (!sl_table) {
718 pr_debug("Could not allocate second level table\n");
719 ret = -ENOMEM;
720 goto fail;
721 }
722
723 memset(sl_table, 0, SZ_4K);
724 *fl_pte = ((((int)__pa(sl_table)) & FL_BASE_MASK) |
725 FL_TYPE_TABLE);
726 clean_pte(fl_pte, fl_pte + 1);
727 } else
728 sl_table = (unsigned long *)
729 __va(((*fl_pte) & FL_BASE_MASK));
730
731 /* Keep track of initial position so we
732 * don't clean more than we have to
733 */
734 sl_start = sl_offset;
735
736 /* Build the 2nd level page table */
737 while (offset < len && sl_offset < NUM_SL_PTE) {
738 pa = chunk_pa + chunk_offset;
739 sl_table[sl_offset] = (pa & SL_BASE_MASK_SMALL) |
740 pgprot | SL_AP0 | SL_AP1 | SL_NG |
741 SL_SHARED | SL_TYPE_SMALL;
742 sl_offset++;
743 offset += SZ_4K;
744
745 chunk_offset += SZ_4K;
746
747 if (chunk_offset >= sg->length && offset < len) {
748 chunk_offset = 0;
749 sg = sg_next(sg);
750 chunk_pa = sg_phys(sg);
751 }
752 }
753
754 clean_pte(sl_table + sl_start, sl_table + sl_offset);
755
756 fl_pte++;
757 sl_offset = 0;
758 }
759 __flush_iotlb(domain);
760fail:
761 spin_unlock_irqrestore(&msm_iommu_lock, flags);
762 return ret;
763}
764
765
766static int msm_iommu_unmap_range(struct iommu_domain *domain, unsigned int va,
767 unsigned int len)
768{
769 unsigned int offset = 0;
770 unsigned long *fl_table;
771 unsigned long *fl_pte;
772 unsigned long fl_offset;
773 unsigned long *sl_table;
774 unsigned long sl_start, sl_end;
775 unsigned long flags;
776 int used, i;
777 struct msm_priv *priv;
778
779 spin_lock_irqsave(&msm_iommu_lock, flags);
780
781 BUG_ON(len & (SZ_4K - 1));
782
783 priv = domain->priv;
784 fl_table = priv->pgtable;
785
786 fl_offset = FL_OFFSET(va); /* Upper 12 bits */
787 fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
788
789 sl_start = SL_OFFSET(va);
790
791 while (offset < len) {
792 sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
793 sl_end = ((len - offset) / SZ_4K) + sl_start;
794
795 if (sl_end > NUM_SL_PTE)
796 sl_end = NUM_SL_PTE;
797
798 memset(sl_table + sl_start, 0, (sl_end - sl_start) * 4);
799 clean_pte(sl_table + sl_start, sl_table + sl_end);
800
801 offset += (sl_end - sl_start) * SZ_4K;
802
803 /* Unmap and free the 2nd level table if all mappings in it
804 * were removed. This saves memory, but the table will need
805 * to be re-allocated the next time someone tries to map these
806 * VAs.
807 */
808 used = 0;
809
810 /* If we just unmapped the whole table, don't bother
811 * seeing if there are still used entries left.
812 */
813 if (sl_end - sl_start != NUM_SL_PTE)
814 for (i = 0; i < NUM_SL_PTE; i++)
815 if (sl_table[i]) {
816 used = 1;
817 break;
818 }
819 if (!used) {
820 free_page((unsigned long)sl_table);
821 *fl_pte = 0;
822
823 clean_pte(fl_pte, fl_pte + 1);
824 }
825
826 sl_start = 0;
827 fl_pte++;
828 }
829
830 __flush_iotlb(domain);
831 spin_unlock_irqrestore(&msm_iommu_lock, flags);
832 return 0;
833}
834
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700835static phys_addr_t msm_iommu_iova_to_phys(struct iommu_domain *domain,
836 unsigned long va)
837{
838 struct msm_priv *priv;
839 struct msm_iommu_drvdata *iommu_drvdata;
840 struct msm_iommu_ctx_drvdata *ctx_drvdata;
841 unsigned int par;
842 unsigned long flags;
843 void __iomem *base;
844 phys_addr_t ret = 0;
845 int ctx;
846
847 spin_lock_irqsave(&msm_iommu_lock, flags);
848
849 priv = domain->priv;
850 if (list_empty(&priv->list_attached))
851 goto fail;
852
853 ctx_drvdata = list_entry(priv->list_attached.next,
854 struct msm_iommu_ctx_drvdata, attached_elm);
855 iommu_drvdata = dev_get_drvdata(ctx_drvdata->pdev->dev.parent);
856
857 base = iommu_drvdata->base;
858 ctx = ctx_drvdata->num;
859
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800860 ret = __enable_clocks(iommu_drvdata);
861 if (ret)
862 goto fail;
863
Stepan Moskovchenkob0e78082011-02-28 16:04:55 -0800864 SET_V2PPR(base, ctx, va & V2Pxx_VA);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700865
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700866 mb();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700867 par = GET_PAR(base, ctx);
868
869 /* We are dealing with a supersection */
870 if (GET_NOFAULT_SS(base, ctx))
871 ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
872 else /* Upper 20 bits from PAR, lower 12 from VA */
873 ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
874
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800875 if (GET_FAULT(base, ctx))
876 ret = 0;
877
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800878 __disable_clocks(iommu_drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700879fail:
880 spin_unlock_irqrestore(&msm_iommu_lock, flags);
881 return ret;
882}
883
884static int msm_iommu_domain_has_cap(struct iommu_domain *domain,
885 unsigned long cap)
886{
887 return 0;
888}
889
890static void print_ctx_regs(void __iomem *base, int ctx)
891{
892 unsigned int fsr = GET_FSR(base, ctx);
893 pr_err("FAR = %08x PAR = %08x\n",
894 GET_FAR(base, ctx), GET_PAR(base, ctx));
895 pr_err("FSR = %08x [%s%s%s%s%s%s%s%s%s%s]\n", fsr,
896 (fsr & 0x02) ? "TF " : "",
897 (fsr & 0x04) ? "AFF " : "",
898 (fsr & 0x08) ? "APF " : "",
899 (fsr & 0x10) ? "TLBMF " : "",
900 (fsr & 0x20) ? "HTWDEEF " : "",
901 (fsr & 0x40) ? "HTWSEEF " : "",
902 (fsr & 0x80) ? "MHF " : "",
903 (fsr & 0x10000) ? "SL " : "",
904 (fsr & 0x40000000) ? "SS " : "",
905 (fsr & 0x80000000) ? "MULTI " : "");
906
907 pr_err("FSYNR0 = %08x FSYNR1 = %08x\n",
908 GET_FSYNR0(base, ctx), GET_FSYNR1(base, ctx));
909 pr_err("TTBR0 = %08x TTBR1 = %08x\n",
910 GET_TTBR0(base, ctx), GET_TTBR1(base, ctx));
911 pr_err("SCTLR = %08x ACTLR = %08x\n",
912 GET_SCTLR(base, ctx), GET_ACTLR(base, ctx));
913 pr_err("PRRR = %08x NMRR = %08x\n",
914 GET_PRRR(base, ctx), GET_NMRR(base, ctx));
915}
916
917irqreturn_t msm_iommu_fault_handler(int irq, void *dev_id)
918{
919 struct msm_iommu_drvdata *drvdata = dev_id;
920 void __iomem *base;
Stepan Moskovchenko33069732010-11-12 19:30:00 -0800921 unsigned int fsr;
Stepan Moskovchenkoa43d8c12011-02-24 18:00:42 -0800922 int i, ret;
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700923
924 spin_lock(&msm_iommu_lock);
925
926 if (!drvdata) {
927 pr_err("Invalid device ID in context interrupt handler\n");
928 goto fail;
929 }
930
931 base = drvdata->base;
932
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700933 pr_err("Unexpected IOMMU page fault!\n");
934 pr_err("base = %08x\n", (unsigned int) base);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700935 pr_err("name = %s\n", drvdata->name);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700936
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800937 ret = __enable_clocks(drvdata);
938 if (ret)
939 goto fail;
940
Stepan Moskovchenkoa43d8c12011-02-24 18:00:42 -0800941 for (i = 0; i < drvdata->ncb; i++) {
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700942 fsr = GET_FSR(base, i);
943 if (fsr) {
944 pr_err("Fault occurred in context %d.\n", i);
945 pr_err("Interesting registers:\n");
946 print_ctx_regs(base, i);
947 SET_FSR(base, i, 0x4000000F);
948 }
949 }
Stepan Moskovchenko41f3f512011-02-24 18:00:39 -0800950 __disable_clocks(drvdata);
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700951fail:
952 spin_unlock(&msm_iommu_lock);
953 return 0;
954}
955
956static struct iommu_ops msm_iommu_ops = {
957 .domain_init = msm_iommu_domain_init,
958 .domain_destroy = msm_iommu_domain_destroy,
959 .attach_dev = msm_iommu_attach_dev,
960 .detach_dev = msm_iommu_detach_dev,
961 .map = msm_iommu_map,
962 .unmap = msm_iommu_unmap,
Stepan Moskovchenko04255ee2011-08-11 19:45:23 -0700963 .map_range = msm_iommu_map_range,
964 .unmap_range = msm_iommu_unmap_range,
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -0700965 .iova_to_phys = msm_iommu_iova_to_phys,
966 .domain_has_cap = msm_iommu_domain_has_cap
967};
968
Stepan Moskovchenko100832c2010-11-15 18:20:08 -0800969static int __init get_tex_class(int icp, int ocp, int mt, int nos)
970{
971 int i = 0;
972 unsigned int prrr = 0;
973 unsigned int nmrr = 0;
974 int c_icp, c_ocp, c_mt, c_nos;
975
976 RCP15_PRRR(prrr);
977 RCP15_NMRR(nmrr);
978
979 for (i = 0; i < NUM_TEX_CLASS; i++) {
980 c_nos = PRRR_NOS(prrr, i);
981 c_mt = PRRR_MT(prrr, i);
982 c_icp = NMRR_ICP(nmrr, i);
983 c_ocp = NMRR_OCP(nmrr, i);
984
985 if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos)
986 return i;
987 }
988
989 return -ENODEV;
990}
991
992static void __init setup_iommu_tex_classes(void)
993{
994 msm_iommu_tex_class[MSM_IOMMU_ATTR_NONCACHED] =
995 get_tex_class(CP_NONCACHED, CP_NONCACHED, MT_NORMAL, 1);
996
997 msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_WA] =
998 get_tex_class(CP_WB_WA, CP_WB_WA, MT_NORMAL, 1);
999
1000 msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WB_NWA] =
1001 get_tex_class(CP_WB_NWA, CP_WB_NWA, MT_NORMAL, 1);
1002
1003 msm_iommu_tex_class[MSM_IOMMU_ATTR_CACHED_WT] =
1004 get_tex_class(CP_WT, CP_WT, MT_NORMAL, 1);
1005}
1006
Stepan Moskovchenko516cbc72010-11-12 19:29:53 -08001007static int __init msm_iommu_init(void)
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -07001008{
Stepan Moskovchenko15f209c2011-10-31 15:32:44 -07001009 if (!msm_soc_version_supports_iommu())
1010 return -ENODEV;
1011
Stepan Moskovchenko100832c2010-11-15 18:20:08 -08001012 setup_iommu_tex_classes();
Stepan Moskovchenko0720d1f2010-08-24 18:31:10 -07001013 register_iommu(&msm_iommu_ops);
1014 return 0;
1015}
1016
1017subsys_initcall(msm_iommu_init);
1018
1019MODULE_LICENSE("GPL v2");
1020MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>");