blob: 68612ba06549c3b263e47916cae4d3c0ad32d3ff [file] [log] [blame]
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -08001/* Copyright (c) 2012 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
13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14
15#include <linux/kernel.h>
16#include <linux/module.h>
17#include <linux/platform_device.h>
18#include <linux/io.h>
19#include <linux/clk.h>
20#include <linux/iommu.h>
21#include <linux/interrupt.h>
22#include <linux/err.h>
23#include <linux/slab.h>
24#include <linux/atomic.h>
25#include <linux/of.h>
26#include <linux/of_address.h>
27#include <linux/of_device.h>
28
29#include <mach/iommu_hw-v2.h>
30#include <mach/iommu.h>
31
Stepan Moskovchenko880a3182012-10-01 12:35:24 -070032static int msm_iommu_parse_bfb_settings(struct platform_device *pdev,
33 struct msm_iommu_drvdata *drvdata)
34{
35 struct msm_iommu_bfb_settings *bfb_settings;
36 u32 nreg, nval;
37 int ret, i;
38
39 /*
40 * It is not valid for a device to have the qcom,iommu-bfb-regs
41 * property but not the qcom,iommu-bfb-data property, and vice versa.
42 */
43 if (!of_get_property(pdev->dev.of_node, "qcom,iommu-bfb-regs", &nreg)) {
44 if (of_get_property(pdev->dev.of_node, "qcom,iommu-bfb-data",
45 &nval))
46 return -EINVAL;
47 return 0;
48 }
49
50 if (!of_get_property(pdev->dev.of_node, "qcom,iommu-bfb-data", &nval))
51 return -EINVAL;
52
53 if (nreg >= sizeof(bfb_settings->regs))
54 return -EINVAL;
55
56 if (nval >= sizeof(bfb_settings->data))
57 return -EINVAL;
58
59 if (nval != nreg)
60 return -EINVAL;
61
62 bfb_settings = devm_kzalloc(&pdev->dev, sizeof(*bfb_settings),
63 GFP_KERNEL);
64 if (!bfb_settings)
65 return -ENOMEM;
66
67 ret = of_property_read_u32_array(pdev->dev.of_node,
68 "qcom,iommu-bfb-regs",
69 bfb_settings->regs,
70 nreg / sizeof(*bfb_settings->regs));
71 if (ret)
72 return ret;
73
74 ret = of_property_read_u32_array(pdev->dev.of_node,
75 "qcom,iommu-bfb-data",
76 bfb_settings->data,
77 nval / sizeof(*bfb_settings->data));
78 if (ret)
79 return ret;
80
81 bfb_settings->length = nreg / sizeof(*bfb_settings->regs);
82
83 for (i = 0; i < bfb_settings->length; i++)
84 if (bfb_settings->regs[i] < IMPLDEF_OFFSET ||
85 bfb_settings->regs[i] >= IMPLDEF_OFFSET + IMPLDEF_LENGTH)
86 return -EINVAL;
87
88 drvdata->bfb_settings = bfb_settings;
89 return 0;
90}
91
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -080092static int msm_iommu_parse_dt(struct platform_device *pdev,
93 struct msm_iommu_drvdata *drvdata)
94{
95 struct device_node *child;
Stepan Moskovchenko4575bdd2012-06-28 14:59:00 -070096 int ret = 0;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -080097
98 ret = device_move(&pdev->dev, &msm_iommu_root_dev->dev, DPM_ORDER_NONE);
99 if (ret)
Stepan Moskovchenko4575bdd2012-06-28 14:59:00 -0700100 goto fail;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800101
Stepan Moskovchenko880a3182012-10-01 12:35:24 -0700102 ret = msm_iommu_parse_bfb_settings(pdev, drvdata);
103 if (ret)
104 goto fail;
105
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800106 for_each_child_of_node(pdev->dev.of_node, child) {
107 drvdata->ncb++;
108 if (!of_platform_device_create(child, NULL, &pdev->dev))
109 pr_err("Failed to create %s device\n", child->name);
110 }
111
112 drvdata->name = dev_name(&pdev->dev);
Stepan Moskovchenko4575bdd2012-06-28 14:59:00 -0700113fail:
114 return ret;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800115}
116
117static atomic_t msm_iommu_next_id = ATOMIC_INIT(-1);
118
119static int __devinit msm_iommu_probe(struct platform_device *pdev)
120{
121 struct msm_iommu_drvdata *drvdata;
122 struct resource *r;
Stepan Moskovchenko17ae71e2012-07-24 19:24:14 -0700123 int ret, needs_alt_core_clk;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800124
125 if (msm_iommu_root_dev == pdev)
126 return 0;
127
128 if (pdev->id == -1)
129 pdev->id = atomic_inc_return(&msm_iommu_next_id) - 1;
130
131 drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
132 if (!drvdata)
133 return -ENOMEM;
134
135 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
136 if (!r)
137 return -EINVAL;
138
139 drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
140 if (!drvdata->base)
141 return -ENOMEM;
142
Stepan Moskovchenko6751acc2012-06-21 17:36:47 -0700143 drvdata->gdsc = devm_regulator_get(&pdev->dev, "vdd");
144 if (IS_ERR(drvdata->gdsc))
145 return -EINVAL;
146
Stepan Moskovchenko17ae71e2012-07-24 19:24:14 -0700147 drvdata->pclk = devm_clk_get(&pdev->dev, "iface_clk");
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800148 if (IS_ERR(drvdata->pclk))
149 return PTR_ERR(drvdata->pclk);
150
Stepan Moskovchenko17ae71e2012-07-24 19:24:14 -0700151 drvdata->clk = devm_clk_get(&pdev->dev, "core_clk");
152 if (IS_ERR(drvdata->clk))
153 return PTR_ERR(drvdata->clk);
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800154
Stepan Moskovchenko17ae71e2012-07-24 19:24:14 -0700155 needs_alt_core_clk = of_property_read_bool(pdev->dev.of_node,
156 "qcom,needs-alt-core-clk");
157 if (needs_alt_core_clk) {
158 drvdata->aclk = devm_clk_get(&pdev->dev, "alt_core_clk");
159 if (IS_ERR(drvdata->aclk))
160 return PTR_ERR(drvdata->aclk);
161 }
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800162
Stepan Moskovchenko17ae71e2012-07-24 19:24:14 -0700163 if (clk_get_rate(drvdata->clk) == 0) {
164 ret = clk_round_rate(drvdata->clk, 1);
165 clk_set_rate(drvdata->clk, ret);
166 }
167
168 if (drvdata->aclk && clk_get_rate(drvdata->aclk) == 0) {
169 ret = clk_round_rate(drvdata->aclk, 1);
170 clk_set_rate(drvdata->aclk, ret);
171 }
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800172
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800173 ret = msm_iommu_parse_dt(pdev, drvdata);
174 if (ret)
Stepan Moskovchenko17ae71e2012-07-24 19:24:14 -0700175 return ret;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800176
177 pr_info("device %s mapped at %p, with %d ctx banks\n",
178 drvdata->name, drvdata->base, drvdata->ncb);
179
180 platform_set_drvdata(pdev, drvdata);
181
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800182 return 0;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800183}
184
185static int __devexit msm_iommu_remove(struct platform_device *pdev)
186{
187 struct msm_iommu_drvdata *drv = NULL;
188
189 drv = platform_get_drvdata(pdev);
190 if (drv) {
191 if (drv->clk)
192 clk_put(drv->clk);
193 clk_put(drv->pclk);
194 platform_set_drvdata(pdev, NULL);
195 }
196 return 0;
197}
198
199static int msm_iommu_ctx_parse_dt(struct platform_device *pdev,
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800200 struct msm_iommu_ctx_drvdata *ctx_drvdata)
201{
202 struct resource *r, rp;
Sathish Ambleycf045e62012-06-07 12:56:50 -0700203 int irq, ret;
Stepan Moskovchenko4575bdd2012-06-28 14:59:00 -0700204 u32 nsid;
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800205
206 irq = platform_get_irq(pdev, 0);
207 if (irq > 0) {
208 ret = request_threaded_irq(irq, NULL,
209 msm_iommu_fault_handler_v2,
210 IRQF_ONESHOT | IRQF_SHARED,
211 "msm_iommu_nonsecure_irq", pdev);
212 if (ret) {
213 pr_err("Request IRQ %d failed with ret=%d\n", irq, ret);
214 return ret;
215 }
216 }
217
218 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
219 if (!r)
220 return -EINVAL;
221
222 ret = of_address_to_resource(pdev->dev.parent->of_node, 0, &rp);
223 if (ret)
224 return -EINVAL;
225
226 /* Calculate the context bank number using the base addresses. The
227 * first 8 pages belong to the global address space which is followed
228 * by the context banks, hence subtract by 8 to get the context bank
229 * number.
230 */
231 ctx_drvdata->num = ((r->start - rp.start) >> CTX_SHIFT) - 8;
232
Stepan Moskovchenkoce78fc22012-07-11 17:20:27 -0700233 if (of_property_read_string(pdev->dev.of_node, "label",
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800234 &ctx_drvdata->name))
235 ctx_drvdata->name = dev_name(&pdev->dev);
236
Stepan Moskovchenko4575bdd2012-06-28 14:59:00 -0700237 if (!of_get_property(pdev->dev.of_node, "qcom,iommu-ctx-sids", &nsid))
238 return -EINVAL;
239
240 if (nsid >= sizeof(ctx_drvdata->sids))
241 return -EINVAL;
242
243 if (of_property_read_u32_array(pdev->dev.of_node, "qcom,iommu-ctx-sids",
244 ctx_drvdata->sids,
245 nsid / sizeof(*ctx_drvdata->sids))) {
246 return -EINVAL;
247 }
248 ctx_drvdata->nsid = nsid;
249
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800250 return 0;
251}
252
253static int __devinit msm_iommu_ctx_probe(struct platform_device *pdev)
254{
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800255 struct msm_iommu_ctx_drvdata *ctx_drvdata = NULL;
256 int ret;
257
258 if (!pdev->dev.parent)
259 return -EINVAL;
260
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800261 ctx_drvdata = devm_kzalloc(&pdev->dev, sizeof(*ctx_drvdata),
262 GFP_KERNEL);
263 if (!ctx_drvdata)
264 return -ENOMEM;
265
266 ctx_drvdata->pdev = pdev;
267 INIT_LIST_HEAD(&ctx_drvdata->attached_elm);
268 platform_set_drvdata(pdev, ctx_drvdata);
269
Sathish Ambleycf045e62012-06-07 12:56:50 -0700270 ret = msm_iommu_ctx_parse_dt(pdev, ctx_drvdata);
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800271 if (!ret)
272 dev_info(&pdev->dev, "context %s using bank %d\n",
Stepan Moskovchenkoe14ca5c2012-07-11 12:40:51 -0700273 ctx_drvdata->name, ctx_drvdata->num);
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800274
Sathish Ambleyd1b89ed2012-02-07 21:47:47 -0800275 return ret;
276}
277
278static int __devexit msm_iommu_ctx_remove(struct platform_device *pdev)
279{
280 platform_set_drvdata(pdev, NULL);
281 return 0;
282}
283
284static struct of_device_id msm_iommu_match_table[] = {
285 { .compatible = "qcom,msm-smmu-v2", },
286 {}
287};
288
289static struct platform_driver msm_iommu_driver = {
290 .driver = {
291 .name = "msm_iommu_v2",
292 .of_match_table = msm_iommu_match_table,
293 },
294 .probe = msm_iommu_probe,
295 .remove = __devexit_p(msm_iommu_remove),
296};
297
298static struct of_device_id msm_iommu_ctx_match_table[] = {
299 { .name = "qcom,iommu-ctx", },
300 {}
301};
302
303static struct platform_driver msm_iommu_ctx_driver = {
304 .driver = {
305 .name = "msm_iommu_ctx_v2",
306 .of_match_table = msm_iommu_ctx_match_table,
307 },
308 .probe = msm_iommu_ctx_probe,
309 .remove = __devexit_p(msm_iommu_ctx_remove),
310};
311
312static int __init msm_iommu_driver_init(void)
313{
314 struct device_node *node;
315 int ret;
316
317 node = of_find_compatible_node(NULL, NULL, "qcom,msm-smmu-v2");
318 if (!node)
319 return -ENODEV;
320
321 of_node_put(node);
322
323 msm_iommu_root_dev = platform_device_register_simple(
324 "msm_iommu", -1, 0, 0);
325 if (!msm_iommu_root_dev) {
326 pr_err("Failed to create root IOMMU device\n");
327 ret = -ENODEV;
328 goto error;
329 }
330
331 atomic_inc(&msm_iommu_next_id);
332
333 ret = platform_driver_register(&msm_iommu_driver);
334 if (ret != 0) {
335 pr_err("Failed to register IOMMU driver\n");
336 goto error;
337 }
338
339 ret = platform_driver_register(&msm_iommu_ctx_driver);
340 if (ret != 0) {
341 pr_err("Failed to register IOMMU context driver\n");
342 goto error;
343 }
344
345error:
346 return ret;
347}
348
349static void __exit msm_iommu_driver_exit(void)
350{
351 platform_driver_unregister(&msm_iommu_ctx_driver);
352 platform_driver_unregister(&msm_iommu_driver);
353 platform_device_unregister(msm_iommu_root_dev);
354}
355
356subsys_initcall(msm_iommu_driver_init);
357module_exit(msm_iommu_driver_exit);
358
359MODULE_LICENSE("GPL v2");