diff options
author | Bjorn Andersson <bjorn.andersson@sonymobile.com> | 2016-08-23 01:57:44 -0400 |
---|---|---|
committer | Bjorn Andersson <bjorn.andersson@linaro.org> | 2016-11-15 21:44:53 -0500 |
commit | b9e718e950c3dfa458bbf9180a8d8691e55413ae (patch) | |
tree | 95a0f2611e69d4ac1d7c6dca4a721be7f77e9e8d /drivers/remoteproc | |
parent | f904e7245b8b6d777acf565a0c831f58e78bc8a1 (diff) |
remoteproc: Introduce Qualcomm ADSP PIL
The Qualcomm ADSP Peripheral Image Loader is used on a variety of
different Qualcomm platforms for loading firmware into and controlling
the Hexagon based ADSP.
Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Diffstat (limited to 'drivers/remoteproc')
-rw-r--r-- | drivers/remoteproc/Kconfig | 11 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 1 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_adsp_pil.c | 399 |
3 files changed, 411 insertions, 0 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index 5fcbefcb8636..c199b360067f 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig | |||
@@ -77,6 +77,17 @@ config DA8XX_REMOTEPROC | |||
77 | It's safe to say n here if you're not interested in multimedia | 77 | It's safe to say n here if you're not interested in multimedia |
78 | offloading. | 78 | offloading. |
79 | 79 | ||
80 | config QCOM_ADSP_PIL | ||
81 | tristate "Qualcomm ADSP Peripheral Image Loader" | ||
82 | depends on OF && ARCH_QCOM | ||
83 | depends on QCOM_SMEM | ||
84 | select MFD_SYSCON | ||
85 | select QCOM_MDT_LOADER | ||
86 | select REMOTEPROC | ||
87 | help | ||
88 | Say y here to support the TrustZone based Peripherial Image Loader | ||
89 | for the Qualcomm ADSP remote processors. | ||
90 | |||
80 | config QCOM_MDT_LOADER | 91 | config QCOM_MDT_LOADER |
81 | tristate | 92 | tristate |
82 | 93 | ||
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index d79eeeae6366..523f5128a1f1 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile | |||
@@ -12,6 +12,7 @@ obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o | |||
12 | obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o | 12 | obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o |
13 | obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o | 13 | obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o |
14 | obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o | 14 | obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o |
15 | obj-$(CONFIG_QCOM_ADSP_PIL) += qcom_adsp_pil.o | ||
15 | obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o | 16 | obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o |
16 | obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o | 17 | obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o |
17 | obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o | 18 | obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o |
diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c new file mode 100644 index 000000000000..914163315091 --- /dev/null +++ b/drivers/remoteproc/qcom_adsp_pil.c | |||
@@ -0,0 +1,399 @@ | |||
1 | /* | ||
2 | * Qualcomm ADSP Peripheral Image Loader for MSM8974 and MSM8996 | ||
3 | * | ||
4 | * Copyright (C) 2016 Linaro Ltd | ||
5 | * Copyright (C) 2014 Sony Mobile Communications AB | ||
6 | * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or | ||
9 | * modify it under the terms of the GNU General Public License | ||
10 | * version 2 as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #include <linux/firmware.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/of_address.h> | ||
23 | #include <linux/of_device.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/qcom_scm.h> | ||
26 | #include <linux/regulator/consumer.h> | ||
27 | #include <linux/remoteproc.h> | ||
28 | #include <linux/soc/qcom/smem.h> | ||
29 | #include <linux/soc/qcom/smem_state.h> | ||
30 | |||
31 | #include "qcom_mdt_loader.h" | ||
32 | #include "remoteproc_internal.h" | ||
33 | |||
34 | #define ADSP_CRASH_REASON_SMEM 423 | ||
35 | #define ADSP_FIRMWARE_NAME "adsp.mdt" | ||
36 | #define ADSP_PAS_ID 1 | ||
37 | |||
38 | struct qcom_adsp { | ||
39 | struct device *dev; | ||
40 | struct rproc *rproc; | ||
41 | |||
42 | int wdog_irq; | ||
43 | int fatal_irq; | ||
44 | int ready_irq; | ||
45 | int handover_irq; | ||
46 | int stop_ack_irq; | ||
47 | |||
48 | struct qcom_smem_state *state; | ||
49 | unsigned stop_bit; | ||
50 | |||
51 | struct regulator *cx_supply; | ||
52 | |||
53 | struct completion start_done; | ||
54 | struct completion stop_done; | ||
55 | |||
56 | phys_addr_t mem_phys; | ||
57 | phys_addr_t mem_reloc; | ||
58 | void *mem_region; | ||
59 | size_t mem_size; | ||
60 | }; | ||
61 | |||
62 | static int adsp_load(struct rproc *rproc, const struct firmware *fw) | ||
63 | { | ||
64 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | ||
65 | phys_addr_t fw_addr; | ||
66 | size_t fw_size; | ||
67 | bool relocate; | ||
68 | int ret; | ||
69 | |||
70 | ret = qcom_scm_pas_init_image(ADSP_PAS_ID, fw->data, fw->size); | ||
71 | if (ret) { | ||
72 | dev_err(&rproc->dev, "invalid firmware metadata\n"); | ||
73 | return ret; | ||
74 | } | ||
75 | |||
76 | ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate); | ||
77 | if (ret) { | ||
78 | dev_err(&rproc->dev, "failed to parse mdt header\n"); | ||
79 | return ret; | ||
80 | } | ||
81 | |||
82 | if (relocate) { | ||
83 | adsp->mem_reloc = fw_addr; | ||
84 | |||
85 | ret = qcom_scm_pas_mem_setup(ADSP_PAS_ID, adsp->mem_phys, fw_size); | ||
86 | if (ret) { | ||
87 | dev_err(&rproc->dev, "unable to setup memory for image\n"); | ||
88 | return ret; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | return qcom_mdt_load(rproc, fw, rproc->firmware); | ||
93 | } | ||
94 | |||
95 | static const struct rproc_fw_ops adsp_fw_ops = { | ||
96 | .find_rsc_table = qcom_mdt_find_rsc_table, | ||
97 | .load = adsp_load, | ||
98 | }; | ||
99 | |||
100 | static int adsp_start(struct rproc *rproc) | ||
101 | { | ||
102 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | ||
103 | int ret; | ||
104 | |||
105 | ret = regulator_enable(adsp->cx_supply); | ||
106 | if (ret) | ||
107 | return ret; | ||
108 | |||
109 | ret = qcom_scm_pas_auth_and_reset(ADSP_PAS_ID); | ||
110 | if (ret) { | ||
111 | dev_err(adsp->dev, | ||
112 | "failed to authenticate image and release reset\n"); | ||
113 | goto disable_regulators; | ||
114 | } | ||
115 | |||
116 | ret = wait_for_completion_timeout(&adsp->start_done, | ||
117 | msecs_to_jiffies(5000)); | ||
118 | if (!ret) { | ||
119 | dev_err(adsp->dev, "start timed out\n"); | ||
120 | qcom_scm_pas_shutdown(ADSP_PAS_ID); | ||
121 | ret = -ETIMEDOUT; | ||
122 | goto disable_regulators; | ||
123 | } | ||
124 | |||
125 | ret = 0; | ||
126 | |||
127 | disable_regulators: | ||
128 | regulator_disable(adsp->cx_supply); | ||
129 | |||
130 | return ret; | ||
131 | } | ||
132 | |||
133 | static int adsp_stop(struct rproc *rproc) | ||
134 | { | ||
135 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | ||
136 | int ret; | ||
137 | |||
138 | qcom_smem_state_update_bits(adsp->state, | ||
139 | BIT(adsp->stop_bit), | ||
140 | BIT(adsp->stop_bit)); | ||
141 | |||
142 | ret = wait_for_completion_timeout(&adsp->stop_done, | ||
143 | msecs_to_jiffies(5000)); | ||
144 | if (ret == 0) | ||
145 | dev_err(adsp->dev, "timed out on wait\n"); | ||
146 | |||
147 | qcom_smem_state_update_bits(adsp->state, | ||
148 | BIT(adsp->stop_bit), | ||
149 | 0); | ||
150 | |||
151 | ret = qcom_scm_pas_shutdown(ADSP_PAS_ID); | ||
152 | if (ret) | ||
153 | dev_err(adsp->dev, "failed to shutdown: %d\n", ret); | ||
154 | |||
155 | return ret; | ||
156 | } | ||
157 | |||
158 | static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) | ||
159 | { | ||
160 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | ||
161 | int offset; | ||
162 | |||
163 | offset = da - adsp->mem_reloc; | ||
164 | if (offset < 0 || offset + len > adsp->mem_size) | ||
165 | return NULL; | ||
166 | |||
167 | return adsp->mem_region + offset; | ||
168 | } | ||
169 | |||
170 | static const struct rproc_ops adsp_ops = { | ||
171 | .start = adsp_start, | ||
172 | .stop = adsp_stop, | ||
173 | .da_to_va = adsp_da_to_va, | ||
174 | }; | ||
175 | |||
176 | static irqreturn_t adsp_wdog_interrupt(int irq, void *dev) | ||
177 | { | ||
178 | struct qcom_adsp *adsp = dev; | ||
179 | |||
180 | rproc_report_crash(adsp->rproc, RPROC_WATCHDOG); | ||
181 | |||
182 | return IRQ_HANDLED; | ||
183 | } | ||
184 | |||
185 | static irqreturn_t adsp_fatal_interrupt(int irq, void *dev) | ||
186 | { | ||
187 | struct qcom_adsp *adsp = dev; | ||
188 | size_t len; | ||
189 | char *msg; | ||
190 | |||
191 | msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, ADSP_CRASH_REASON_SMEM, &len); | ||
192 | if (!IS_ERR(msg) && len > 0 && msg[0]) | ||
193 | dev_err(adsp->dev, "fatal error received: %s\n", msg); | ||
194 | |||
195 | rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR); | ||
196 | |||
197 | if (!IS_ERR(msg)) | ||
198 | msg[0] = '\0'; | ||
199 | |||
200 | return IRQ_HANDLED; | ||
201 | } | ||
202 | |||
203 | static irqreturn_t adsp_ready_interrupt(int irq, void *dev) | ||
204 | { | ||
205 | return IRQ_HANDLED; | ||
206 | } | ||
207 | |||
208 | static irqreturn_t adsp_handover_interrupt(int irq, void *dev) | ||
209 | { | ||
210 | struct qcom_adsp *adsp = dev; | ||
211 | |||
212 | complete(&adsp->start_done); | ||
213 | |||
214 | return IRQ_HANDLED; | ||
215 | } | ||
216 | |||
217 | static irqreturn_t adsp_stop_ack_interrupt(int irq, void *dev) | ||
218 | { | ||
219 | struct qcom_adsp *adsp = dev; | ||
220 | |||
221 | complete(&adsp->stop_done); | ||
222 | |||
223 | return IRQ_HANDLED; | ||
224 | } | ||
225 | |||
226 | static int adsp_init_regulator(struct qcom_adsp *adsp) | ||
227 | { | ||
228 | adsp->cx_supply = devm_regulator_get(adsp->dev, "cx"); | ||
229 | if (IS_ERR(adsp->cx_supply)) | ||
230 | return PTR_ERR(adsp->cx_supply); | ||
231 | |||
232 | regulator_set_load(adsp->cx_supply, 100000); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static int adsp_request_irq(struct qcom_adsp *adsp, | ||
238 | struct platform_device *pdev, | ||
239 | const char *name, | ||
240 | irq_handler_t thread_fn) | ||
241 | { | ||
242 | int ret; | ||
243 | |||
244 | ret = platform_get_irq_byname(pdev, name); | ||
245 | if (ret < 0) { | ||
246 | dev_err(&pdev->dev, "no %s IRQ defined\n", name); | ||
247 | return ret; | ||
248 | } | ||
249 | |||
250 | ret = devm_request_threaded_irq(&pdev->dev, ret, | ||
251 | NULL, thread_fn, | ||
252 | IRQF_ONESHOT, | ||
253 | "adsp", adsp); | ||
254 | if (ret) | ||
255 | dev_err(&pdev->dev, "request %s IRQ failed\n", name); | ||
256 | |||
257 | return ret; | ||
258 | } | ||
259 | |||
260 | static int adsp_alloc_memory_region(struct qcom_adsp *adsp) | ||
261 | { | ||
262 | struct device_node *node; | ||
263 | struct resource r; | ||
264 | int ret; | ||
265 | |||
266 | node = of_parse_phandle(adsp->dev->of_node, "memory-region", 0); | ||
267 | if (!node) { | ||
268 | dev_err(adsp->dev, "no memory-region specified\n"); | ||
269 | return -EINVAL; | ||
270 | } | ||
271 | |||
272 | ret = of_address_to_resource(node, 0, &r); | ||
273 | if (ret) | ||
274 | return ret; | ||
275 | |||
276 | adsp->mem_phys = adsp->mem_reloc = r.start; | ||
277 | adsp->mem_size = resource_size(&r); | ||
278 | adsp->mem_region = devm_ioremap_wc(adsp->dev, adsp->mem_phys, adsp->mem_size); | ||
279 | if (!adsp->mem_region) { | ||
280 | dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n", | ||
281 | &r.start, adsp->mem_size); | ||
282 | return -EBUSY; | ||
283 | } | ||
284 | |||
285 | return 0; | ||
286 | } | ||
287 | |||
288 | static int adsp_probe(struct platform_device *pdev) | ||
289 | { | ||
290 | struct qcom_adsp *adsp; | ||
291 | struct rproc *rproc; | ||
292 | int ret; | ||
293 | |||
294 | if (!qcom_scm_is_available()) | ||
295 | return -EPROBE_DEFER; | ||
296 | |||
297 | if (!qcom_scm_pas_supported(ADSP_PAS_ID)) { | ||
298 | dev_err(&pdev->dev, "PAS is not available for ADSP\n"); | ||
299 | return -ENXIO; | ||
300 | } | ||
301 | |||
302 | rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops, | ||
303 | ADSP_FIRMWARE_NAME, sizeof(*adsp)); | ||
304 | if (!rproc) { | ||
305 | dev_err(&pdev->dev, "unable to allocate remoteproc\n"); | ||
306 | return -ENOMEM; | ||
307 | } | ||
308 | |||
309 | rproc->fw_ops = &adsp_fw_ops; | ||
310 | |||
311 | adsp = (struct qcom_adsp *)rproc->priv; | ||
312 | adsp->dev = &pdev->dev; | ||
313 | adsp->rproc = rproc; | ||
314 | platform_set_drvdata(pdev, adsp); | ||
315 | |||
316 | init_completion(&adsp->start_done); | ||
317 | init_completion(&adsp->stop_done); | ||
318 | |||
319 | ret = adsp_alloc_memory_region(adsp); | ||
320 | if (ret) | ||
321 | goto free_rproc; | ||
322 | |||
323 | ret = adsp_init_regulator(adsp); | ||
324 | if (ret) | ||
325 | goto free_rproc; | ||
326 | |||
327 | ret = adsp_request_irq(adsp, pdev, "wdog", adsp_wdog_interrupt); | ||
328 | if (ret < 0) | ||
329 | goto free_rproc; | ||
330 | adsp->wdog_irq = ret; | ||
331 | |||
332 | ret = adsp_request_irq(adsp, pdev, "fatal", adsp_fatal_interrupt); | ||
333 | if (ret < 0) | ||
334 | goto free_rproc; | ||
335 | adsp->fatal_irq = ret; | ||
336 | |||
337 | ret = adsp_request_irq(adsp, pdev, "ready", adsp_ready_interrupt); | ||
338 | if (ret < 0) | ||
339 | goto free_rproc; | ||
340 | adsp->ready_irq = ret; | ||
341 | |||
342 | ret = adsp_request_irq(adsp, pdev, "handover", adsp_handover_interrupt); | ||
343 | if (ret < 0) | ||
344 | goto free_rproc; | ||
345 | adsp->handover_irq = ret; | ||
346 | |||
347 | ret = adsp_request_irq(adsp, pdev, "stop-ack", adsp_stop_ack_interrupt); | ||
348 | if (ret < 0) | ||
349 | goto free_rproc; | ||
350 | adsp->stop_ack_irq = ret; | ||
351 | |||
352 | adsp->state = qcom_smem_state_get(&pdev->dev, "stop", | ||
353 | &adsp->stop_bit); | ||
354 | if (IS_ERR(adsp->state)) { | ||
355 | ret = PTR_ERR(adsp->state); | ||
356 | goto free_rproc; | ||
357 | } | ||
358 | |||
359 | ret = rproc_add(rproc); | ||
360 | if (ret) | ||
361 | goto free_rproc; | ||
362 | |||
363 | return 0; | ||
364 | |||
365 | free_rproc: | ||
366 | rproc_put(rproc); | ||
367 | |||
368 | return ret; | ||
369 | } | ||
370 | |||
371 | static int adsp_remove(struct platform_device *pdev) | ||
372 | { | ||
373 | struct qcom_adsp *adsp = platform_get_drvdata(pdev); | ||
374 | |||
375 | qcom_smem_state_put(adsp->state); | ||
376 | rproc_del(adsp->rproc); | ||
377 | rproc_put(adsp->rproc); | ||
378 | |||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | static const struct of_device_id adsp_of_match[] = { | ||
383 | { .compatible = "qcom,msm8974-adsp-pil" }, | ||
384 | { .compatible = "qcom,msm8996-adsp-pil" }, | ||
385 | { }, | ||
386 | }; | ||
387 | |||
388 | static struct platform_driver adsp_driver = { | ||
389 | .probe = adsp_probe, | ||
390 | .remove = adsp_remove, | ||
391 | .driver = { | ||
392 | .name = "qcom_adsp_pil", | ||
393 | .of_match_table = adsp_of_match, | ||
394 | }, | ||
395 | }; | ||
396 | |||
397 | module_platform_driver(adsp_driver); | ||
398 | MODULE_DESCRIPTION("Qualcomm MSM8974/MSM8996 ADSP Peripherial Image Loader"); | ||
399 | MODULE_LICENSE("GPL v2"); | ||