Commit | Line | Data |
---|---|---|
c6a5951e SM |
1 | /* Copyright (c) 2010, Code Aurora Forum. All rights reserved. |
2 | * | |
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. | |
11 | * | |
12 | * You should have received a copy of the GNU General Public License | |
13 | * along with this program; if not, write to the Free Software | |
14 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
15 | * 02110-1301, USA. | |
16 | */ | |
17 | ||
18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
19 | ||
20 | #include <linux/kernel.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/io.h> | |
24 | #include <linux/clk.h> | |
25 | #include <linux/iommu.h> | |
26 | #include <linux/interrupt.h> | |
27 | #include <linux/err.h> | |
28 | #include <linux/slab.h> | |
29 | ||
30 | #include <mach/iommu_hw-8xxx.h> | |
31 | #include <mach/iommu.h> | |
32 | ||
33 | struct iommu_ctx_iter_data { | |
34 | /* input */ | |
35 | const char *name; | |
36 | ||
37 | /* output */ | |
38 | struct device *dev; | |
39 | }; | |
40 | ||
41 | static struct platform_device *msm_iommu_root_dev; | |
42 | ||
43 | static int each_iommu_ctx(struct device *dev, void *data) | |
44 | { | |
45 | struct iommu_ctx_iter_data *res = data; | |
46 | struct msm_iommu_ctx_dev *c = dev->platform_data; | |
47 | ||
48 | if (!res || !c || !c->name || !res->name) | |
49 | return -EINVAL; | |
50 | ||
51 | if (!strcmp(res->name, c->name)) { | |
52 | res->dev = dev; | |
53 | return 1; | |
54 | } | |
55 | return 0; | |
56 | } | |
57 | ||
58 | static int each_iommu(struct device *dev, void *data) | |
59 | { | |
60 | return device_for_each_child(dev, data, each_iommu_ctx); | |
61 | } | |
62 | ||
63 | struct device *msm_iommu_get_ctx(const char *ctx_name) | |
64 | { | |
65 | struct iommu_ctx_iter_data r; | |
66 | int found; | |
67 | ||
68 | if (!msm_iommu_root_dev) { | |
69 | pr_err("No root IOMMU device.\n"); | |
70 | goto fail; | |
71 | } | |
72 | ||
73 | r.name = ctx_name; | |
74 | found = device_for_each_child(&msm_iommu_root_dev->dev, &r, each_iommu); | |
75 | ||
76 | if (!found) { | |
77 | pr_err("Could not find context <%s>\n", ctx_name); | |
78 | goto fail; | |
79 | } | |
80 | ||
81 | return r.dev; | |
82 | fail: | |
83 | return NULL; | |
84 | } | |
85 | EXPORT_SYMBOL(msm_iommu_get_ctx); | |
86 | ||
87 | static void msm_iommu_reset(void __iomem *base) | |
88 | { | |
89 | int ctx, ncb; | |
90 | ||
91 | SET_RPUE(base, 0); | |
92 | SET_RPUEIE(base, 0); | |
93 | SET_ESRRESTORE(base, 0); | |
94 | SET_TBE(base, 0); | |
95 | SET_CR(base, 0); | |
96 | SET_SPDMBE(base, 0); | |
97 | SET_TESTBUSCR(base, 0); | |
98 | SET_TLBRSW(base, 0); | |
99 | SET_GLOBAL_TLBIALL(base, 0); | |
100 | SET_RPU_ACR(base, 0); | |
101 | SET_TLBLKCRWE(base, 1); | |
102 | ncb = GET_NCB(base)+1; | |
103 | ||
104 | for (ctx = 0; ctx < ncb; ctx++) { | |
105 | SET_BPRCOSH(base, ctx, 0); | |
106 | SET_BPRCISH(base, ctx, 0); | |
107 | SET_BPRCNSH(base, ctx, 0); | |
108 | SET_BPSHCFG(base, ctx, 0); | |
109 | SET_BPMTCFG(base, ctx, 0); | |
110 | SET_ACTLR(base, ctx, 0); | |
111 | SET_SCTLR(base, ctx, 0); | |
112 | SET_FSRRESTORE(base, ctx, 0); | |
113 | SET_TTBR0(base, ctx, 0); | |
114 | SET_TTBR1(base, ctx, 0); | |
115 | SET_TTBCR(base, ctx, 0); | |
116 | SET_BFBCR(base, ctx, 0); | |
117 | SET_PAR(base, ctx, 0); | |
118 | SET_FAR(base, ctx, 0); | |
119 | SET_CTX_TLBIALL(base, ctx, 0); | |
120 | SET_TLBFLPTER(base, ctx, 0); | |
121 | SET_TLBSLPTER(base, ctx, 0); | |
122 | SET_TLBLKCR(base, ctx, 0); | |
123 | SET_PRRR(base, ctx, 0); | |
124 | SET_NMRR(base, ctx, 0); | |
125 | SET_CONTEXTIDR(base, ctx, 0); | |
126 | } | |
127 | } | |
128 | ||
129 | static int msm_iommu_probe(struct platform_device *pdev) | |
130 | { | |
a86c44d4 | 131 | struct resource *r, *r2; |
c6a5951e SM |
132 | struct clk *iommu_clk; |
133 | struct msm_iommu_drvdata *drvdata; | |
134 | struct msm_iommu_dev *iommu_dev = pdev->dev.platform_data; | |
135 | void __iomem *regs_base; | |
136 | resource_size_t len; | |
137 | int ret = 0, ncb, nm2v, irq; | |
138 | ||
139 | if (pdev->id != -1) { | |
140 | drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); | |
141 | ||
142 | if (!drvdata) { | |
143 | ret = -ENOMEM; | |
144 | goto fail; | |
145 | } | |
146 | ||
147 | if (!iommu_dev) { | |
148 | ret = -ENODEV; | |
149 | goto fail; | |
150 | } | |
151 | ||
152 | if (iommu_dev->clk_rate != 0) { | |
153 | iommu_clk = clk_get(&pdev->dev, "iommu_clk"); | |
154 | ||
155 | if (IS_ERR(iommu_clk)) { | |
156 | ret = -ENODEV; | |
157 | goto fail; | |
158 | } | |
159 | ||
160 | if (iommu_dev->clk_rate > 0) { | |
161 | ret = clk_set_rate(iommu_clk, | |
162 | iommu_dev->clk_rate); | |
163 | if (ret) { | |
164 | clk_put(iommu_clk); | |
165 | goto fail; | |
166 | } | |
167 | } | |
168 | ||
169 | ret = clk_enable(iommu_clk); | |
170 | if (ret) { | |
171 | clk_put(iommu_clk); | |
172 | goto fail; | |
173 | } | |
174 | clk_put(iommu_clk); | |
175 | } | |
176 | ||
177 | r = platform_get_resource_byname(pdev, IORESOURCE_MEM, | |
178 | "physbase"); | |
179 | if (!r) { | |
180 | ret = -ENODEV; | |
181 | goto fail; | |
182 | } | |
183 | ||
184 | len = r->end - r->start + 1; | |
185 | ||
a86c44d4 VK |
186 | r2 = request_mem_region(r->start, len, r->name); |
187 | if (!r2) { | |
c6a5951e SM |
188 | pr_err("Could not request memory region: " |
189 | "start=%p, len=%d\n", (void *) r->start, len); | |
190 | ret = -EBUSY; | |
191 | goto fail; | |
192 | } | |
193 | ||
a86c44d4 | 194 | regs_base = ioremap(r2->start, len); |
c6a5951e SM |
195 | |
196 | if (!regs_base) { | |
197 | pr_err("Could not ioremap: start=%p, len=%d\n", | |
a86c44d4 | 198 | (void *) r2->start, len); |
c6a5951e | 199 | ret = -EBUSY; |
a86c44d4 | 200 | goto fail_mem; |
c6a5951e SM |
201 | } |
202 | ||
203 | irq = platform_get_irq_byname(pdev, "secure_irq"); | |
204 | if (irq < 0) { | |
205 | ret = -ENODEV; | |
a86c44d4 | 206 | goto fail_io; |
c6a5951e SM |
207 | } |
208 | ||
209 | mb(); | |
210 | ||
211 | if (GET_IDR(regs_base) == 0) { | |
212 | pr_err("Invalid IDR value detected\n"); | |
213 | ret = -ENODEV; | |
a86c44d4 | 214 | goto fail_io; |
c6a5951e SM |
215 | } |
216 | ||
217 | ret = request_irq(irq, msm_iommu_fault_handler, 0, | |
218 | "msm_iommu_secure_irpt_handler", drvdata); | |
219 | if (ret) { | |
220 | pr_err("Request IRQ %d failed with ret=%d\n", irq, ret); | |
a86c44d4 | 221 | goto fail_io; |
c6a5951e SM |
222 | } |
223 | ||
224 | msm_iommu_reset(regs_base); | |
225 | drvdata->base = regs_base; | |
226 | drvdata->irq = irq; | |
227 | ||
228 | nm2v = GET_NM2VCBMT((unsigned long) regs_base); | |
229 | ncb = GET_NCB((unsigned long) regs_base); | |
230 | ||
231 | pr_info("device %s mapped at %p, irq %d with %d ctx banks\n", | |
232 | iommu_dev->name, regs_base, irq, ncb+1); | |
233 | ||
234 | platform_set_drvdata(pdev, drvdata); | |
235 | } else | |
236 | msm_iommu_root_dev = pdev; | |
237 | ||
238 | return 0; | |
239 | ||
a86c44d4 VK |
240 | fail_io: |
241 | iounmap(regs_base); | |
242 | fail_mem: | |
243 | release_mem_region(r->start, len); | |
c6a5951e SM |
244 | fail: |
245 | kfree(drvdata); | |
246 | return ret; | |
247 | } | |
248 | ||
249 | static int msm_iommu_remove(struct platform_device *pdev) | |
250 | { | |
251 | struct msm_iommu_drvdata *drv = NULL; | |
252 | ||
253 | drv = platform_get_drvdata(pdev); | |
254 | if (drv) { | |
255 | memset(drv, 0, sizeof(struct msm_iommu_drvdata)); | |
256 | kfree(drv); | |
257 | platform_set_drvdata(pdev, NULL); | |
258 | } | |
259 | return 0; | |
260 | } | |
261 | ||
262 | static int msm_iommu_ctx_probe(struct platform_device *pdev) | |
263 | { | |
264 | struct msm_iommu_ctx_dev *c = pdev->dev.platform_data; | |
265 | struct msm_iommu_drvdata *drvdata; | |
266 | struct msm_iommu_ctx_drvdata *ctx_drvdata = NULL; | |
267 | int i, ret = 0; | |
268 | if (!c || !pdev->dev.parent) { | |
269 | ret = -EINVAL; | |
270 | goto fail; | |
271 | } | |
272 | ||
273 | drvdata = dev_get_drvdata(pdev->dev.parent); | |
274 | ||
275 | if (!drvdata) { | |
276 | ret = -ENODEV; | |
277 | goto fail; | |
278 | } | |
279 | ||
280 | ctx_drvdata = kzalloc(sizeof(*ctx_drvdata), GFP_KERNEL); | |
281 | if (!ctx_drvdata) { | |
282 | ret = -ENOMEM; | |
283 | goto fail; | |
284 | } | |
285 | ctx_drvdata->num = c->num; | |
286 | ctx_drvdata->pdev = pdev; | |
287 | ||
288 | INIT_LIST_HEAD(&ctx_drvdata->attached_elm); | |
289 | platform_set_drvdata(pdev, ctx_drvdata); | |
290 | ||
291 | /* Program the M2V tables for this context */ | |
292 | for (i = 0; i < MAX_NUM_MIDS; i++) { | |
293 | int mid = c->mids[i]; | |
294 | if (mid == -1) | |
295 | break; | |
296 | ||
297 | SET_M2VCBR_N(drvdata->base, mid, 0); | |
298 | SET_CBACR_N(drvdata->base, c->num, 0); | |
299 | ||
300 | /* Set VMID = MID */ | |
301 | SET_VMID(drvdata->base, mid, mid); | |
302 | ||
303 | /* Set the context number for that MID to this context */ | |
304 | SET_CBNDX(drvdata->base, mid, c->num); | |
305 | ||
306 | /* Set MID associated with this context bank */ | |
307 | SET_CBVMID(drvdata->base, c->num, mid); | |
308 | ||
309 | /* Set security bit override to be Non-secure */ | |
310 | SET_NSCFG(drvdata->base, mid, 3); | |
311 | } | |
312 | ||
313 | pr_info("context device %s with bank index %d\n", c->name, c->num); | |
314 | ||
315 | return 0; | |
316 | fail: | |
317 | kfree(ctx_drvdata); | |
318 | return ret; | |
319 | } | |
320 | ||
321 | static int msm_iommu_ctx_remove(struct platform_device *pdev) | |
322 | { | |
323 | struct msm_iommu_ctx_drvdata *drv = NULL; | |
324 | drv = platform_get_drvdata(pdev); | |
325 | if (drv) { | |
326 | memset(drv, 0, sizeof(struct msm_iommu_ctx_drvdata)); | |
327 | kfree(drv); | |
328 | platform_set_drvdata(pdev, NULL); | |
329 | } | |
330 | return 0; | |
331 | } | |
332 | ||
333 | static struct platform_driver msm_iommu_driver = { | |
334 | .driver = { | |
335 | .name = "msm_iommu", | |
336 | }, | |
337 | .probe = msm_iommu_probe, | |
338 | .remove = msm_iommu_remove, | |
339 | }; | |
340 | ||
341 | static struct platform_driver msm_iommu_ctx_driver = { | |
342 | .driver = { | |
343 | .name = "msm_iommu_ctx", | |
344 | }, | |
345 | .probe = msm_iommu_ctx_probe, | |
346 | .remove = msm_iommu_ctx_remove, | |
347 | }; | |
348 | ||
516cbc79 | 349 | static int __init msm_iommu_driver_init(void) |
c6a5951e SM |
350 | { |
351 | int ret; | |
352 | ret = platform_driver_register(&msm_iommu_driver); | |
353 | if (ret != 0) { | |
354 | pr_err("Failed to register IOMMU driver\n"); | |
355 | goto error; | |
356 | } | |
357 | ||
358 | ret = platform_driver_register(&msm_iommu_ctx_driver); | |
359 | if (ret != 0) { | |
360 | pr_err("Failed to register IOMMU context driver\n"); | |
361 | goto error; | |
362 | } | |
363 | ||
364 | error: | |
365 | return ret; | |
366 | } | |
367 | ||
516cbc79 | 368 | static void __exit msm_iommu_driver_exit(void) |
c6a5951e SM |
369 | { |
370 | platform_driver_unregister(&msm_iommu_ctx_driver); | |
371 | platform_driver_unregister(&msm_iommu_driver); | |
372 | } | |
373 | ||
374 | subsys_initcall(msm_iommu_driver_init); | |
375 | module_exit(msm_iommu_driver_exit); | |
376 | ||
377 | MODULE_LICENSE("GPL v2"); | |
378 | MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>"); |