blob: e331296148efa484e363603b422be1c706981a68 [file] [log] [blame]
Tianyi Gou828798d2012-05-02 21:12:38 -07001/* 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#include <linux/kernel.h>
14#include <linux/err.h>
15#include <linux/io.h>
16#include <linux/elf.h>
17#include <linux/delay.h>
18#include <linux/module.h>
19#include <linux/slab.h>
20#include <linux/platform_device.h>
21#include <linux/iommu.h>
22#include <linux/iopoll.h>
23#include <linux/of.h>
24#include <linux/regulator/consumer.h>
25
26#include <asm/page.h>
27#include <asm/sizes.h>
28
29#include <mach/iommu.h>
30#include <mach/iommu_domains.h>
31
32#include "peripheral-loader.h"
33#include "scm-pas.h"
34
35/* VENUS WRAPPER registers */
36#define VENUS_WRAPPER_CLOCK_CONFIG 0x4
37#define VENUS_WRAPPER_VBIF_SS_SEC_CPA_START_ADDR 0x1018
38#define VENUS_WRAPPER_VBIF_SS_SEC_CPA_END_ADDR 0x101C
39#define VENUS_WRAPPER_VBIF_SS_SEC_FW_START_ADDR 0x1020
40#define VENUS_WRAPPER_VBIF_SS_SEC_FW_END_ADDR 0x1024
41#define VENUS_WRAPPER_CPU_CLOCK_CONFIG 0x2000
42#define VENUS_WRAPPER_SW_RESET 0x3000
43
44/* VENUS VBIF registers */
45#define VENUS_VBIF_AXI_HALT_CTRL0 0x0
46#define VENUS_VBIF_AXI_HALT_CTRL0_HALT_REQ BIT(0)
47
48#define VENUS_VBIF_AXI_HALT_CTRL1 0x4
49#define VENUS_VBIF_AXI_HALT_CTRL1_HALT_ACK BIT(0)
50#define VENUS_VBIF_AXI_HALT_ACK_TIMEOUT_US 500000
51
52/* PIL proxy vote timeout */
53#define VENUS_PROXY_TIMEOUT 10000
54
55/* Poll interval in uS */
56#define POLL_INTERVAL_US 50
57
58static const char * const clk_names[] = {
59 "core_clk",
60 "iface_clk",
61 "bus_clk",
62 "mem_clk",
63};
64
65struct venus_data {
66 void __iomem *venus_wrapper_base;
67 void __iomem *venus_vbif_base;
68 struct pil_device *pil;
69 struct regulator *gdsc;
70 phys_addr_t start_addr;
71 struct clk *clks[ARRAY_SIZE(clk_names)];
72 struct device *iommu_fw_ctx;
73 struct iommu_domain *iommu_fw_domain;
74 int venus_domain_num;
75 bool is_booted;
76 u32 fw_sz;
77 u32 fw_min_paddr;
78 u32 fw_max_paddr;
79};
80
81static int venus_register_domain(u32 fw_max_sz)
82{
83 struct msm_iova_partition venus_fw_partition = {
84 .start = 0,
85 .size = fw_max_sz,
86 };
87 struct msm_iova_layout venus_fw_layout = {
88 .partitions = &venus_fw_partition,
89 .npartitions = 1,
90 .client_name = "pil_venus",
91 .domain_flags = 0,
92 };
93
94 return msm_register_domain(&venus_fw_layout);
95}
96
97/* Get venus clocks and set rates for rate-settable clocks */
98static int venus_clock_setup(struct device *dev)
99{
100 struct venus_data *drv = dev_get_drvdata(dev);
101 int i;
102
103 for (i = 0; i < ARRAY_SIZE(drv->clks); i++) {
104 drv->clks[i] = devm_clk_get(dev, clk_names[i]);
105 if (IS_ERR(drv->clks[i])) {
106 dev_err(dev, "failed to get %s\n",
107 clk_names[i]);
108 return PTR_ERR(drv->clks[i]);
109 }
110 /* Make sure rate-settable clocks' rates are set */
111 if (clk_get_rate(drv->clks[i]) == 0)
112 clk_set_rate(drv->clks[i],
113 clk_round_rate(drv->clks[i], 0));
114 }
115
116 return 0;
117}
118
119static int venus_clock_prepare_enable(struct device *dev)
120{
121 struct venus_data *drv = dev_get_drvdata(dev);
122 int rc, i;
123
124 for (i = 0; i < ARRAY_SIZE(drv->clks); i++) {
125 rc = clk_prepare_enable(drv->clks[i]);
126 if (rc) {
127 dev_err(dev, "failed to enable %s\n",
128 clk_names[i]);
129 for (i--; i >= 0; i--)
130 clk_disable_unprepare(drv->clks[i]);
131 return rc;
132 }
133 }
134
135 return 0;
136}
137
138static void venus_clock_disable_unprepare(struct device *dev)
139{
140 struct venus_data *drv = dev_get_drvdata(dev);
141 int i;
142
143 for (i = 0; i < ARRAY_SIZE(drv->clks); i++)
144 clk_disable_unprepare(drv->clks[i]);
145}
146
147static int pil_venus_make_proxy_vote(struct pil_desc *pil)
148{
149 struct venus_data *drv = dev_get_drvdata(pil->dev);
150 int rc;
151
152 /*
153 * Clocks need to be proxy voted to be able to pass control
154 * of clocks from PIL driver to the Venus driver. But GDSC
155 * needs to be turned on before clocks can be turned on. So
156 * enable the GDSC here.
157 */
158 rc = regulator_enable(drv->gdsc);
159 if (rc) {
160 dev_err(pil->dev, "GDSC enable failed\n");
161 return rc;
162 }
163
164 rc = venus_clock_prepare_enable(pil->dev);
165 if (rc)
166 regulator_disable(drv->gdsc);
167
168 return rc;
169}
170
171static void pil_venus_remove_proxy_vote(struct pil_desc *pil)
172{
173 struct venus_data *drv = dev_get_drvdata(pil->dev);
174
175 venus_clock_disable_unprepare(pil->dev);
176
177 /* Disable GDSC */
178 regulator_disable(drv->gdsc);
179}
180
181static int pil_venus_init_image(struct pil_desc *pil, const u8 *metadata,
182 size_t size)
183{
184 const struct elf32_hdr *ehdr = (struct elf32_hdr *)metadata;
185 struct venus_data *drv = dev_get_drvdata(pil->dev);
186
187 drv->start_addr = ehdr->e_entry;
188
189 if (drv->start_addr < drv->fw_min_paddr ||
190 drv->start_addr >= drv->fw_max_paddr) {
191 dev_err(pil->dev, "fw start addr is not within valid range\n");
192 return -EINVAL;
193 }
194
195 drv->fw_sz = drv->fw_max_paddr - drv->start_addr;
196
197 return 0;
198}
199
200static int pil_venus_reset(struct pil_desc *pil)
201{
202 int rc;
203 struct venus_data *drv = dev_get_drvdata(pil->dev);
204 void __iomem *wrapper_base = drv->venus_wrapper_base;
205 phys_addr_t pa = drv->start_addr;
206 unsigned long iova;
207
208 /*
209 * GDSC needs to remain on till Venus is shutdown. So, enable
210 * the GDSC here again to make sure it remains on beyond the
211 * expiry of the proxy vote timer.
212 */
213 rc = regulator_enable(drv->gdsc);
214 if (rc) {
215 dev_err(pil->dev, "GDSC enable failed\n");
216 return rc;
217 }
218
219 /* Program CPA start and end address */
220 writel_relaxed(0, wrapper_base +
221 VENUS_WRAPPER_VBIF_SS_SEC_CPA_START_ADDR);
222 writel_relaxed(drv->fw_sz, wrapper_base +
223 VENUS_WRAPPER_VBIF_SS_SEC_CPA_END_ADDR);
224
225 /* Program FW start and end address */
226 writel_relaxed(0, wrapper_base +
227 VENUS_WRAPPER_VBIF_SS_SEC_FW_START_ADDR);
228 writel_relaxed(drv->fw_sz, wrapper_base +
229 VENUS_WRAPPER_VBIF_SS_SEC_FW_END_ADDR);
230
Tianyi Gou828798d2012-05-02 21:12:38 -0700231 /* Enable all Venus internal clocks */
232 writel_relaxed(0, wrapper_base + VENUS_WRAPPER_CLOCK_CONFIG);
233 writel_relaxed(0, wrapper_base + VENUS_WRAPPER_CPU_CLOCK_CONFIG);
234
235 /* Make sure clocks are enabled */
236 mb();
237
238 /*
239 * Need to wait 10 cycles of internal clocks before bringing ARM9
240 * out of reset.
241 */
242 udelay(1);
243
Tianyi Gou79626242012-08-10 01:04:30 -0700244 rc = iommu_attach_device(drv->iommu_fw_domain, drv->iommu_fw_ctx);
245 if (rc) {
246 dev_err(pil->dev, "venus fw iommu attach failed\n");
247 goto err_iommu_attach;
248 }
249
Tianyi Gou828798d2012-05-02 21:12:38 -0700250 /* Map virtual addr space 0 - fw_sz to firmware physical addr space */
251 rc = msm_iommu_map_contig_buffer(pa, drv->venus_domain_num, 0,
252 drv->fw_sz, SZ_4K, 0, &iova);
253
254 if (rc || (iova != 0)) {
255 dev_err(pil->dev, "Failed to setup IOMMU\n");
256 goto err_iommu_map;
257 }
258
259 /* Bring Arm9 out of reset */
260 writel_relaxed(0, wrapper_base + VENUS_WRAPPER_SW_RESET);
261
262 drv->is_booted = 1;
263
264 return 0;
265
266err_iommu_map:
267 iommu_detach_device(drv->iommu_fw_domain, drv->iommu_fw_ctx);
268
269err_iommu_attach:
270 regulator_disable(drv->gdsc);
271
272 return rc;
273}
274
275static int pil_venus_shutdown(struct pil_desc *pil)
276{
277 struct venus_data *drv = dev_get_drvdata(pil->dev);
278 void __iomem *vbif_base = drv->venus_vbif_base;
279 void __iomem *wrapper_base = drv->venus_wrapper_base;
280 u32 reg;
281 int rc;
282
283 if (!drv->is_booted)
284 return 0;
285
286 venus_clock_prepare_enable(pil->dev);
287
Tianyi Gou828798d2012-05-02 21:12:38 -0700288 /* Assert the reset to ARM9 */
289 reg = readl_relaxed(wrapper_base + VENUS_WRAPPER_SW_RESET);
290 reg |= BIT(4);
291 writel_relaxed(reg, wrapper_base + VENUS_WRAPPER_SW_RESET);
292
293 /* Make sure reset is asserted before the mapping is removed */
294 mb();
295
296 msm_iommu_unmap_contig_buffer(0, drv->venus_domain_num,
297 0, drv->fw_sz);
298
299 iommu_detach_device(drv->iommu_fw_domain, drv->iommu_fw_ctx);
300
Tianyi Gou79626242012-08-10 01:04:30 -0700301 /* Halt AXI and AXI OCMEM VBIF Access */
302 reg = readl_relaxed(vbif_base + VENUS_VBIF_AXI_HALT_CTRL0);
303 reg |= VENUS_VBIF_AXI_HALT_CTRL0_HALT_REQ;
304 writel_relaxed(reg, vbif_base + VENUS_VBIF_AXI_HALT_CTRL0);
305
306 /* Request for AXI bus port halt */
307 rc = readl_poll_timeout(vbif_base + VENUS_VBIF_AXI_HALT_CTRL1,
308 reg, reg & VENUS_VBIF_AXI_HALT_CTRL1_HALT_ACK,
309 POLL_INTERVAL_US,
310 VENUS_VBIF_AXI_HALT_ACK_TIMEOUT_US);
311 if (rc)
312 dev_err(pil->dev, "Port halt timeout\n");
313
Tianyi Gou828798d2012-05-02 21:12:38 -0700314 venus_clock_disable_unprepare(pil->dev);
315
316 regulator_disable(drv->gdsc);
317
318 drv->is_booted = 0;
319
320 return 0;
321}
322
323static struct pil_reset_ops pil_venus_ops = {
324 .init_image = pil_venus_init_image,
325 .auth_and_reset = pil_venus_reset,
326 .shutdown = pil_venus_shutdown,
327 .proxy_vote = pil_venus_make_proxy_vote,
328 .proxy_unvote = pil_venus_remove_proxy_vote,
329};
330
Tianyi Gou8d5b8e12012-08-06 16:24:47 -0700331static int pil_venus_init_image_trusted(struct pil_desc *pil,
332 const u8 *metadata, size_t size)
333{
334 return pas_init_image(PAS_VIDC, metadata, size);
335}
336
337static int pil_venus_reset_trusted(struct pil_desc *pil)
338{
339 int rc;
340 struct venus_data *drv = dev_get_drvdata(pil->dev);
341
342 /*
343 * GDSC needs to remain on till Venus is shutdown. So, enable
344 * the GDSC here again to make sure it remains on beyond the
345 * expiry of the proxy vote timer.
346 */
347 rc = regulator_enable(drv->gdsc);
348 if (rc) {
349 dev_err(pil->dev, "GDSC enable failed\n");
350 return rc;
351 }
352
353 rc = pas_auth_and_reset(PAS_VIDC);
354 if (rc)
355 regulator_disable(drv->gdsc);
356
357 return rc;
358}
359
360static int pil_venus_shutdown_trusted(struct pil_desc *pil)
361{
362 int rc;
363 struct venus_data *drv = dev_get_drvdata(pil->dev);
364
365 venus_clock_prepare_enable(pil->dev);
366
367 rc = pas_shutdown(PAS_VIDC);
368
369 venus_clock_disable_unprepare(pil->dev);
370
371 regulator_disable(drv->gdsc);
372
373 return rc;
374}
375
376static struct pil_reset_ops pil_venus_ops_trusted = {
377 .init_image = pil_venus_init_image_trusted,
378 .auth_and_reset = pil_venus_reset_trusted,
379 .shutdown = pil_venus_shutdown_trusted,
380 .proxy_vote = pil_venus_make_proxy_vote,
381 .proxy_unvote = pil_venus_remove_proxy_vote,
382};
383
Tianyi Gou828798d2012-05-02 21:12:38 -0700384static int __devinit pil_venus_probe(struct platform_device *pdev)
385{
386 struct venus_data *drv;
387 struct resource *res;
388 struct pil_desc *desc;
389 int rc;
390
Matt Wagantall1f168152012-09-25 13:26:47 -0700391 res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
392 "wrapper_base");
Tianyi Gou828798d2012-05-02 21:12:38 -0700393 if (!res)
394 return -EINVAL;
395
396 drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
397 if (!drv)
398 return -ENOMEM;
399 platform_set_drvdata(pdev, drv);
400
401 drv->venus_wrapper_base = devm_ioremap(&pdev->dev, res->start,
402 resource_size(res));
403 if (!drv->venus_wrapper_base)
404 return -ENOMEM;
405
Matt Wagantall1f168152012-09-25 13:26:47 -0700406 res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vbif_base");
Tianyi Gou828798d2012-05-02 21:12:38 -0700407 if (!res)
408 return -EINVAL;
409
410 drv->venus_vbif_base = devm_ioremap(&pdev->dev, res->start,
411 resource_size(res));
412 if (!drv->venus_vbif_base)
413 return -ENOMEM;
414
415 drv->gdsc = devm_regulator_get(&pdev->dev, "vdd");
416 if (IS_ERR(drv->gdsc)) {
417 dev_err(&pdev->dev, "Failed to get Venus GDSC\n");
418 return -ENODEV;
419 }
420
421 rc = venus_clock_setup(&pdev->dev);
422 if (rc)
423 return rc;
424
425 drv->iommu_fw_ctx = msm_iommu_get_ctx("venus_fw");
426 if (!drv->iommu_fw_ctx) {
427 dev_err(&pdev->dev, "No iommu fw context found\n");
428 return -ENODEV;
429 }
430
431 /* Get fw address boundaries */
432 rc = of_property_read_u32(pdev->dev.of_node,
433 "qcom,firmware-max-paddr",
434 &drv->fw_max_paddr);
435 if (rc) {
436 dev_err(&pdev->dev, "Failed to get fw max paddr\n");
437 return rc;
438 }
439
440 rc = of_property_read_u32(pdev->dev.of_node,
441 "qcom,firmware-min-paddr",
442 &drv->fw_min_paddr);
443 if (rc) {
444 dev_err(&pdev->dev, "Failed to get fw min paddr\n");
445 return rc;
446 }
447
448 if (drv->fw_max_paddr <= drv->fw_min_paddr) {
449 dev_err(&pdev->dev, "Invalid fw max paddr or min paddr\n");
450 return -EINVAL;
451 }
452
453 drv->venus_domain_num =
454 venus_register_domain(drv->fw_max_paddr - drv->fw_min_paddr);
455 if (drv->venus_domain_num < 0) {
456 dev_err(&pdev->dev, "Venus fw iommu domain register failed\n");
457 return -ENODEV;
458 }
459
460 drv->iommu_fw_domain = msm_get_iommu_domain(drv->venus_domain_num);
461 if (!drv->iommu_fw_domain) {
462 dev_err(&pdev->dev, "No iommu fw domain found\n");
463 return -ENODEV;
464 }
465
466 desc = devm_kzalloc(&pdev->dev, sizeof(*desc), GFP_KERNEL);
467 if (!desc)
468 return -ENOMEM;
469
470 rc = of_property_read_string(pdev->dev.of_node, "qcom,firmware-name",
471 &desc->name);
472 if (rc)
473 return rc;
474
475 desc->dev = &pdev->dev;
476 desc->owner = THIS_MODULE;
477 desc->proxy_timeout = VENUS_PROXY_TIMEOUT;
478
Tianyi Gou8d5b8e12012-08-06 16:24:47 -0700479 if (pas_supported(PAS_VIDC) > 0) {
480 desc->ops = &pil_venus_ops_trusted;
481 dev_info(&pdev->dev, "using secure boot\n");
482 } else {
483 desc->ops = &pil_venus_ops;
484 dev_info(&pdev->dev, "using non-secure boot\n");
485 }
Tianyi Gou828798d2012-05-02 21:12:38 -0700486
487 drv->pil = msm_pil_register(desc);
488 if (IS_ERR(drv->pil))
489 return PTR_ERR(drv->pil);
490
491 return 0;
492}
493
494static int __devexit pil_venus_remove(struct platform_device *pdev)
495{
496 struct venus_data *drv = platform_get_drvdata(pdev);
497 msm_pil_unregister(drv->pil);
498
499 return 0;
500}
501
502#ifdef CONFIG_OF
503static const struct of_device_id msm_pil_venus_match[] = {
504 {.compatible = "qcom,pil-venus"},
505 {}
506};
507#endif
508
509static struct platform_driver pil_venus_driver = {
510 .probe = pil_venus_probe,
511 .remove = __devexit_p(pil_venus_remove),
512 .driver = {
513 .name = "pil_venus",
514 .owner = THIS_MODULE,
515 .of_match_table = of_match_ptr(msm_pil_venus_match),
516 },
517};
518
519module_platform_driver(pil_venus_driver);
520
521MODULE_DESCRIPTION("Support for booting VENUS processors");
522MODULE_LICENSE("GPL v2");