1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Qualcomm Technology Inc. ADSP Peripheral Image Loader for SDM845. |
4 | * Copyright (c) 2018, The Linux Foundation. All rights reserved. |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/firmware.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/io.h> |
12 | #include <linux/iommu.h> |
13 | #include <linux/iopoll.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_reserved_mem.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/pm_domain.h> |
21 | #include <linux/pm_runtime.h> |
22 | #include <linux/regmap.h> |
23 | #include <linux/remoteproc.h> |
24 | #include <linux/reset.h> |
25 | #include <linux/soc/qcom/mdt_loader.h> |
26 | #include <linux/soc/qcom/smem.h> |
27 | #include <linux/soc/qcom/smem_state.h> |
28 | |
29 | #include "qcom_common.h" |
30 | #include "qcom_pil_info.h" |
31 | #include "qcom_q6v5.h" |
32 | #include "remoteproc_internal.h" |
33 | |
34 | /* time out value */ |
35 | #define ACK_TIMEOUT 1000 |
36 | #define ACK_TIMEOUT_US 1000000 |
37 | #define BOOT_FSM_TIMEOUT 10000 |
38 | /* mask values */ |
39 | #define EVB_MASK GENMASK(27, 4) |
40 | /*QDSP6SS register offsets*/ |
41 | #define RST_EVB_REG 0x10 |
42 | #define CORE_START_REG 0x400 |
43 | #define BOOT_CMD_REG 0x404 |
44 | #define BOOT_STATUS_REG 0x408 |
45 | #define RET_CFG_REG 0x1C |
46 | /*TCSR register offsets*/ |
47 | #define LPASS_MASTER_IDLE_REG 0x8 |
48 | #define LPASS_HALTACK_REG 0x4 |
49 | #define LPASS_PWR_ON_REG 0x10 |
50 | #define LPASS_HALTREQ_REG 0x0 |
51 | |
52 | #define SID_MASK_DEFAULT 0xF |
53 | |
54 | #define QDSP6SS_XO_CBCR 0x38 |
55 | #define QDSP6SS_CORE_CBCR 0x20 |
56 | #define QDSP6SS_SLEEP_CBCR 0x3c |
57 | |
58 | #define QCOM_Q6V5_RPROC_PROXY_PD_MAX 3 |
59 | |
60 | #define LPASS_BOOT_CORE_START BIT(0) |
61 | #define LPASS_BOOT_CMD_START BIT(0) |
62 | #define LPASS_EFUSE_Q6SS_EVB_SEL 0x0 |
63 | |
64 | struct adsp_pil_data { |
65 | int crash_reason_smem; |
66 | const char *firmware_name; |
67 | |
68 | const char *ssr_name; |
69 | const char *sysmon_name; |
70 | int ssctl_id; |
71 | bool is_wpss; |
72 | bool has_iommu; |
73 | bool auto_boot; |
74 | |
75 | const char **clk_ids; |
76 | int num_clks; |
77 | const char **proxy_pd_names; |
78 | const char *load_state; |
79 | }; |
80 | |
81 | struct qcom_adsp { |
82 | struct device *dev; |
83 | struct rproc *rproc; |
84 | |
85 | struct qcom_q6v5 q6v5; |
86 | |
87 | struct clk *xo; |
88 | |
89 | int num_clks; |
90 | struct clk_bulk_data *clks; |
91 | |
92 | void __iomem *qdsp6ss_base; |
93 | void __iomem *lpass_efuse; |
94 | |
95 | struct reset_control *pdc_sync_reset; |
96 | struct reset_control *restart; |
97 | |
98 | struct regmap *halt_map; |
99 | unsigned int halt_lpass; |
100 | |
101 | int crash_reason_smem; |
102 | const char *info_name; |
103 | |
104 | struct completion start_done; |
105 | struct completion stop_done; |
106 | |
107 | phys_addr_t mem_phys; |
108 | phys_addr_t mem_reloc; |
109 | void *mem_region; |
110 | size_t mem_size; |
111 | bool has_iommu; |
112 | |
113 | struct device *proxy_pds[QCOM_Q6V5_RPROC_PROXY_PD_MAX]; |
114 | size_t proxy_pd_count; |
115 | |
116 | struct qcom_rproc_glink glink_subdev; |
117 | struct qcom_rproc_ssr ssr_subdev; |
118 | struct qcom_sysmon *sysmon; |
119 | |
120 | int (*shutdown)(struct qcom_adsp *adsp); |
121 | }; |
122 | |
123 | static int qcom_rproc_pds_attach(struct device *dev, struct qcom_adsp *adsp, |
124 | const char **pd_names) |
125 | { |
126 | struct device **devs = adsp->proxy_pds; |
127 | size_t num_pds = 0; |
128 | int ret; |
129 | int i; |
130 | |
131 | if (!pd_names) |
132 | return 0; |
133 | |
134 | /* Handle single power domain */ |
135 | if (dev->pm_domain) { |
136 | devs[0] = dev; |
137 | pm_runtime_enable(dev); |
138 | return 1; |
139 | } |
140 | |
141 | while (pd_names[num_pds]) |
142 | num_pds++; |
143 | |
144 | if (num_pds > ARRAY_SIZE(adsp->proxy_pds)) |
145 | return -E2BIG; |
146 | |
147 | for (i = 0; i < num_pds; i++) { |
148 | devs[i] = dev_pm_domain_attach_by_name(dev, name: pd_names[i]); |
149 | if (IS_ERR_OR_NULL(ptr: devs[i])) { |
150 | ret = PTR_ERR(ptr: devs[i]) ? : -ENODATA; |
151 | goto unroll_attach; |
152 | } |
153 | } |
154 | |
155 | return num_pds; |
156 | |
157 | unroll_attach: |
158 | for (i--; i >= 0; i--) |
159 | dev_pm_domain_detach(dev: devs[i], power_off: false); |
160 | |
161 | return ret; |
162 | } |
163 | |
164 | static void qcom_rproc_pds_detach(struct qcom_adsp *adsp, struct device **pds, |
165 | size_t pd_count) |
166 | { |
167 | struct device *dev = adsp->dev; |
168 | int i; |
169 | |
170 | /* Handle single power domain */ |
171 | if (dev->pm_domain && pd_count) { |
172 | pm_runtime_disable(dev); |
173 | return; |
174 | } |
175 | |
176 | for (i = 0; i < pd_count; i++) |
177 | dev_pm_domain_detach(dev: pds[i], power_off: false); |
178 | } |
179 | |
180 | static int qcom_rproc_pds_enable(struct qcom_adsp *adsp, struct device **pds, |
181 | size_t pd_count) |
182 | { |
183 | int ret; |
184 | int i; |
185 | |
186 | for (i = 0; i < pd_count; i++) { |
187 | dev_pm_genpd_set_performance_state(dev: pds[i], INT_MAX); |
188 | ret = pm_runtime_resume_and_get(dev: pds[i]); |
189 | if (ret < 0) { |
190 | dev_pm_genpd_set_performance_state(dev: pds[i], state: 0); |
191 | goto unroll_pd_votes; |
192 | } |
193 | } |
194 | |
195 | return 0; |
196 | |
197 | unroll_pd_votes: |
198 | for (i--; i >= 0; i--) { |
199 | dev_pm_genpd_set_performance_state(dev: pds[i], state: 0); |
200 | pm_runtime_put(dev: pds[i]); |
201 | } |
202 | |
203 | return ret; |
204 | } |
205 | |
206 | static void qcom_rproc_pds_disable(struct qcom_adsp *adsp, struct device **pds, |
207 | size_t pd_count) |
208 | { |
209 | int i; |
210 | |
211 | for (i = 0; i < pd_count; i++) { |
212 | dev_pm_genpd_set_performance_state(dev: pds[i], state: 0); |
213 | pm_runtime_put(dev: pds[i]); |
214 | } |
215 | } |
216 | |
217 | static int qcom_wpss_shutdown(struct qcom_adsp *adsp) |
218 | { |
219 | unsigned int val; |
220 | |
221 | regmap_write(map: adsp->halt_map, reg: adsp->halt_lpass + LPASS_HALTREQ_REG, val: 1); |
222 | |
223 | /* Wait for halt ACK from QDSP6 */ |
224 | regmap_read_poll_timeout(adsp->halt_map, |
225 | adsp->halt_lpass + LPASS_HALTACK_REG, val, |
226 | val, 1000, ACK_TIMEOUT_US); |
227 | |
228 | /* Assert the WPSS PDC Reset */ |
229 | reset_control_assert(rstc: adsp->pdc_sync_reset); |
230 | |
231 | /* Place the WPSS processor into reset */ |
232 | reset_control_assert(rstc: adsp->restart); |
233 | |
234 | /* wait after asserting subsystem restart from AOSS */ |
235 | usleep_range(min: 200, max: 205); |
236 | |
237 | /* Remove the WPSS reset */ |
238 | reset_control_deassert(rstc: adsp->restart); |
239 | |
240 | /* De-assert the WPSS PDC Reset */ |
241 | reset_control_deassert(rstc: adsp->pdc_sync_reset); |
242 | |
243 | usleep_range(min: 100, max: 105); |
244 | |
245 | clk_bulk_disable_unprepare(num_clks: adsp->num_clks, clks: adsp->clks); |
246 | |
247 | regmap_write(map: adsp->halt_map, reg: adsp->halt_lpass + LPASS_HALTREQ_REG, val: 0); |
248 | |
249 | /* Wait for halt ACK from QDSP6 */ |
250 | regmap_read_poll_timeout(adsp->halt_map, |
251 | adsp->halt_lpass + LPASS_HALTACK_REG, val, |
252 | !val, 1000, ACK_TIMEOUT_US); |
253 | |
254 | return 0; |
255 | } |
256 | |
257 | static int qcom_adsp_shutdown(struct qcom_adsp *adsp) |
258 | { |
259 | unsigned long timeout; |
260 | unsigned int val; |
261 | int ret; |
262 | |
263 | /* Reset the retention logic */ |
264 | val = readl(addr: adsp->qdsp6ss_base + RET_CFG_REG); |
265 | val |= 0x1; |
266 | writel(val, addr: adsp->qdsp6ss_base + RET_CFG_REG); |
267 | |
268 | clk_bulk_disable_unprepare(num_clks: adsp->num_clks, clks: adsp->clks); |
269 | |
270 | /* QDSP6 master port needs to be explicitly halted */ |
271 | ret = regmap_read(map: adsp->halt_map, |
272 | reg: adsp->halt_lpass + LPASS_PWR_ON_REG, val: &val); |
273 | if (ret || !val) |
274 | goto reset; |
275 | |
276 | ret = regmap_read(map: adsp->halt_map, |
277 | reg: adsp->halt_lpass + LPASS_MASTER_IDLE_REG, |
278 | val: &val); |
279 | if (ret || val) |
280 | goto reset; |
281 | |
282 | regmap_write(map: adsp->halt_map, |
283 | reg: adsp->halt_lpass + LPASS_HALTREQ_REG, val: 1); |
284 | |
285 | /* Wait for halt ACK from QDSP6 */ |
286 | timeout = jiffies + msecs_to_jiffies(ACK_TIMEOUT); |
287 | for (;;) { |
288 | ret = regmap_read(map: adsp->halt_map, |
289 | reg: adsp->halt_lpass + LPASS_HALTACK_REG, val: &val); |
290 | if (ret || val || time_after(jiffies, timeout)) |
291 | break; |
292 | |
293 | usleep_range(min: 1000, max: 1100); |
294 | } |
295 | |
296 | ret = regmap_read(map: adsp->halt_map, |
297 | reg: adsp->halt_lpass + LPASS_MASTER_IDLE_REG, val: &val); |
298 | if (ret || !val) |
299 | dev_err(adsp->dev, "port failed halt\n" ); |
300 | |
301 | reset: |
302 | /* Assert the LPASS PDC Reset */ |
303 | reset_control_assert(rstc: adsp->pdc_sync_reset); |
304 | /* Place the LPASS processor into reset */ |
305 | reset_control_assert(rstc: adsp->restart); |
306 | /* wait after asserting subsystem restart from AOSS */ |
307 | usleep_range(min: 200, max: 300); |
308 | |
309 | /* Clear the halt request for the AXIM and AHBM for Q6 */ |
310 | regmap_write(map: adsp->halt_map, reg: adsp->halt_lpass + LPASS_HALTREQ_REG, val: 0); |
311 | |
312 | /* De-assert the LPASS PDC Reset */ |
313 | reset_control_deassert(rstc: adsp->pdc_sync_reset); |
314 | /* Remove the LPASS reset */ |
315 | reset_control_deassert(rstc: adsp->restart); |
316 | /* wait after de-asserting subsystem restart from AOSS */ |
317 | usleep_range(min: 200, max: 300); |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | static int adsp_load(struct rproc *rproc, const struct firmware *fw) |
323 | { |
324 | struct qcom_adsp *adsp = rproc->priv; |
325 | int ret; |
326 | |
327 | ret = qcom_mdt_load_no_init(dev: adsp->dev, fw, fw_name: rproc->firmware, pas_id: 0, |
328 | mem_region: adsp->mem_region, mem_phys: adsp->mem_phys, |
329 | mem_size: adsp->mem_size, reloc_base: &adsp->mem_reloc); |
330 | if (ret) |
331 | return ret; |
332 | |
333 | qcom_pil_info_store(image: adsp->info_name, base: adsp->mem_phys, size: adsp->mem_size); |
334 | |
335 | return 0; |
336 | } |
337 | |
338 | static void adsp_unmap_carveout(struct rproc *rproc) |
339 | { |
340 | struct qcom_adsp *adsp = rproc->priv; |
341 | |
342 | if (adsp->has_iommu) |
343 | iommu_unmap(domain: rproc->domain, iova: adsp->mem_phys, size: adsp->mem_size); |
344 | } |
345 | |
346 | static int adsp_map_carveout(struct rproc *rproc) |
347 | { |
348 | struct qcom_adsp *adsp = rproc->priv; |
349 | struct of_phandle_args args; |
350 | long long sid; |
351 | unsigned long iova; |
352 | int ret; |
353 | |
354 | if (!adsp->has_iommu) |
355 | return 0; |
356 | |
357 | if (!rproc->domain) |
358 | return -EINVAL; |
359 | |
360 | ret = of_parse_phandle_with_args(np: adsp->dev->of_node, list_name: "iommus" , cells_name: "#iommu-cells" , index: 0, out_args: &args); |
361 | if (ret < 0) |
362 | return ret; |
363 | |
364 | sid = args.args[0] & SID_MASK_DEFAULT; |
365 | |
366 | /* Add SID configuration for ADSP Firmware to SMMU */ |
367 | iova = adsp->mem_phys | (sid << 32); |
368 | |
369 | ret = iommu_map(domain: rproc->domain, iova, paddr: adsp->mem_phys, |
370 | size: adsp->mem_size, IOMMU_READ | IOMMU_WRITE, |
371 | GFP_KERNEL); |
372 | if (ret) { |
373 | dev_err(adsp->dev, "Unable to map ADSP Physical Memory\n" ); |
374 | return ret; |
375 | } |
376 | |
377 | return 0; |
378 | } |
379 | |
380 | static int adsp_start(struct rproc *rproc) |
381 | { |
382 | struct qcom_adsp *adsp = rproc->priv; |
383 | int ret; |
384 | unsigned int val; |
385 | |
386 | ret = qcom_q6v5_prepare(q6v5: &adsp->q6v5); |
387 | if (ret) |
388 | return ret; |
389 | |
390 | ret = adsp_map_carveout(rproc); |
391 | if (ret) { |
392 | dev_err(adsp->dev, "ADSP smmu mapping failed\n" ); |
393 | goto disable_irqs; |
394 | } |
395 | |
396 | ret = clk_prepare_enable(clk: adsp->xo); |
397 | if (ret) |
398 | goto adsp_smmu_unmap; |
399 | |
400 | ret = qcom_rproc_pds_enable(adsp, pds: adsp->proxy_pds, |
401 | pd_count: adsp->proxy_pd_count); |
402 | if (ret < 0) |
403 | goto disable_xo_clk; |
404 | |
405 | ret = clk_bulk_prepare_enable(num_clks: adsp->num_clks, clks: adsp->clks); |
406 | if (ret) { |
407 | dev_err(adsp->dev, "adsp clk_enable failed\n" ); |
408 | goto disable_power_domain; |
409 | } |
410 | |
411 | /* Enable the XO clock */ |
412 | writel(val: 1, addr: adsp->qdsp6ss_base + QDSP6SS_XO_CBCR); |
413 | |
414 | /* Enable the QDSP6SS sleep clock */ |
415 | writel(val: 1, addr: adsp->qdsp6ss_base + QDSP6SS_SLEEP_CBCR); |
416 | |
417 | /* Enable the QDSP6 core clock */ |
418 | writel(val: 1, addr: adsp->qdsp6ss_base + QDSP6SS_CORE_CBCR); |
419 | |
420 | /* Program boot address */ |
421 | writel(val: adsp->mem_phys >> 4, addr: adsp->qdsp6ss_base + RST_EVB_REG); |
422 | |
423 | if (adsp->lpass_efuse) |
424 | writel(LPASS_EFUSE_Q6SS_EVB_SEL, addr: adsp->lpass_efuse); |
425 | |
426 | /* De-assert QDSP6 stop core. QDSP6 will execute after out of reset */ |
427 | writel(LPASS_BOOT_CORE_START, addr: adsp->qdsp6ss_base + CORE_START_REG); |
428 | |
429 | /* Trigger boot FSM to start QDSP6 */ |
430 | writel(LPASS_BOOT_CMD_START, addr: adsp->qdsp6ss_base + BOOT_CMD_REG); |
431 | |
432 | /* Wait for core to come out of reset */ |
433 | ret = readl_poll_timeout(adsp->qdsp6ss_base + BOOT_STATUS_REG, |
434 | val, (val & BIT(0)) != 0, 10, BOOT_FSM_TIMEOUT); |
435 | if (ret) { |
436 | dev_err(adsp->dev, "failed to bootup adsp\n" ); |
437 | goto disable_adsp_clks; |
438 | } |
439 | |
440 | ret = qcom_q6v5_wait_for_start(q6v5: &adsp->q6v5, timeout: msecs_to_jiffies(m: 5 * HZ)); |
441 | if (ret == -ETIMEDOUT) { |
442 | dev_err(adsp->dev, "start timed out\n" ); |
443 | goto disable_adsp_clks; |
444 | } |
445 | |
446 | return 0; |
447 | |
448 | disable_adsp_clks: |
449 | clk_bulk_disable_unprepare(num_clks: adsp->num_clks, clks: adsp->clks); |
450 | disable_power_domain: |
451 | qcom_rproc_pds_disable(adsp, pds: adsp->proxy_pds, pd_count: adsp->proxy_pd_count); |
452 | disable_xo_clk: |
453 | clk_disable_unprepare(clk: adsp->xo); |
454 | adsp_smmu_unmap: |
455 | adsp_unmap_carveout(rproc); |
456 | disable_irqs: |
457 | qcom_q6v5_unprepare(q6v5: &adsp->q6v5); |
458 | |
459 | return ret; |
460 | } |
461 | |
462 | static void qcom_adsp_pil_handover(struct qcom_q6v5 *q6v5) |
463 | { |
464 | struct qcom_adsp *adsp = container_of(q6v5, struct qcom_adsp, q6v5); |
465 | |
466 | clk_disable_unprepare(clk: adsp->xo); |
467 | qcom_rproc_pds_disable(adsp, pds: adsp->proxy_pds, pd_count: adsp->proxy_pd_count); |
468 | } |
469 | |
470 | static int adsp_stop(struct rproc *rproc) |
471 | { |
472 | struct qcom_adsp *adsp = rproc->priv; |
473 | int handover; |
474 | int ret; |
475 | |
476 | ret = qcom_q6v5_request_stop(q6v5: &adsp->q6v5, sysmon: adsp->sysmon); |
477 | if (ret == -ETIMEDOUT) |
478 | dev_err(adsp->dev, "timed out on wait\n" ); |
479 | |
480 | ret = adsp->shutdown(adsp); |
481 | if (ret) |
482 | dev_err(adsp->dev, "failed to shutdown: %d\n" , ret); |
483 | |
484 | adsp_unmap_carveout(rproc); |
485 | |
486 | handover = qcom_q6v5_unprepare(q6v5: &adsp->q6v5); |
487 | if (handover) |
488 | qcom_adsp_pil_handover(q6v5: &adsp->q6v5); |
489 | |
490 | return ret; |
491 | } |
492 | |
493 | static void *adsp_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem) |
494 | { |
495 | struct qcom_adsp *adsp = rproc->priv; |
496 | int offset; |
497 | |
498 | offset = da - adsp->mem_reloc; |
499 | if (offset < 0 || offset + len > adsp->mem_size) |
500 | return NULL; |
501 | |
502 | return adsp->mem_region + offset; |
503 | } |
504 | |
505 | static int adsp_parse_firmware(struct rproc *rproc, const struct firmware *fw) |
506 | { |
507 | struct qcom_adsp *adsp = rproc->priv; |
508 | int ret; |
509 | |
510 | ret = qcom_register_dump_segments(rproc, fw); |
511 | if (ret) { |
512 | dev_err(&rproc->dev, "Error in registering dump segments\n" ); |
513 | return ret; |
514 | } |
515 | |
516 | if (adsp->has_iommu) { |
517 | ret = rproc_elf_load_rsc_table(rproc, fw); |
518 | if (ret) { |
519 | dev_err(&rproc->dev, "Error in loading resource table\n" ); |
520 | return ret; |
521 | } |
522 | } |
523 | return 0; |
524 | } |
525 | |
526 | static unsigned long adsp_panic(struct rproc *rproc) |
527 | { |
528 | struct qcom_adsp *adsp = rproc->priv; |
529 | |
530 | return qcom_q6v5_panic(q6v5: &adsp->q6v5); |
531 | } |
532 | |
533 | static const struct rproc_ops adsp_ops = { |
534 | .start = adsp_start, |
535 | .stop = adsp_stop, |
536 | .da_to_va = adsp_da_to_va, |
537 | .parse_fw = adsp_parse_firmware, |
538 | .load = adsp_load, |
539 | .panic = adsp_panic, |
540 | }; |
541 | |
542 | static int adsp_init_clock(struct qcom_adsp *adsp, const char **clk_ids) |
543 | { |
544 | int num_clks = 0; |
545 | int i, ret; |
546 | |
547 | adsp->xo = devm_clk_get(dev: adsp->dev, id: "xo" ); |
548 | if (IS_ERR(ptr: adsp->xo)) { |
549 | ret = PTR_ERR(ptr: adsp->xo); |
550 | if (ret != -EPROBE_DEFER) |
551 | dev_err(adsp->dev, "failed to get xo clock" ); |
552 | return ret; |
553 | } |
554 | |
555 | for (i = 0; clk_ids[i]; i++) |
556 | num_clks++; |
557 | |
558 | adsp->num_clks = num_clks; |
559 | adsp->clks = devm_kcalloc(dev: adsp->dev, n: adsp->num_clks, |
560 | size: sizeof(*adsp->clks), GFP_KERNEL); |
561 | if (!adsp->clks) |
562 | return -ENOMEM; |
563 | |
564 | for (i = 0; i < adsp->num_clks; i++) |
565 | adsp->clks[i].id = clk_ids[i]; |
566 | |
567 | return devm_clk_bulk_get(dev: adsp->dev, num_clks: adsp->num_clks, clks: adsp->clks); |
568 | } |
569 | |
570 | static int adsp_init_reset(struct qcom_adsp *adsp) |
571 | { |
572 | adsp->pdc_sync_reset = devm_reset_control_get_optional_exclusive(dev: adsp->dev, |
573 | id: "pdc_sync" ); |
574 | if (IS_ERR(ptr: adsp->pdc_sync_reset)) { |
575 | dev_err(adsp->dev, "failed to acquire pdc_sync reset\n" ); |
576 | return PTR_ERR(ptr: adsp->pdc_sync_reset); |
577 | } |
578 | |
579 | adsp->restart = devm_reset_control_get_optional_exclusive(dev: adsp->dev, id: "restart" ); |
580 | |
581 | /* Fall back to the old "cc_lpass" if "restart" is absent */ |
582 | if (!adsp->restart) |
583 | adsp->restart = devm_reset_control_get_exclusive(dev: adsp->dev, id: "cc_lpass" ); |
584 | |
585 | if (IS_ERR(ptr: adsp->restart)) { |
586 | dev_err(adsp->dev, "failed to acquire restart\n" ); |
587 | return PTR_ERR(ptr: adsp->restart); |
588 | } |
589 | |
590 | return 0; |
591 | } |
592 | |
593 | static int adsp_init_mmio(struct qcom_adsp *adsp, |
594 | struct platform_device *pdev) |
595 | { |
596 | struct resource *efuse_region; |
597 | struct device_node *syscon; |
598 | int ret; |
599 | |
600 | adsp->qdsp6ss_base = devm_platform_ioremap_resource(pdev, index: 0); |
601 | if (IS_ERR(ptr: adsp->qdsp6ss_base)) { |
602 | dev_err(adsp->dev, "failed to map QDSP6SS registers\n" ); |
603 | return PTR_ERR(ptr: adsp->qdsp6ss_base); |
604 | } |
605 | |
606 | efuse_region = platform_get_resource(pdev, IORESOURCE_MEM, 1); |
607 | if (!efuse_region) { |
608 | adsp->lpass_efuse = NULL; |
609 | dev_dbg(adsp->dev, "failed to get efuse memory region\n" ); |
610 | } else { |
611 | adsp->lpass_efuse = devm_ioremap_resource(dev: &pdev->dev, res: efuse_region); |
612 | if (IS_ERR(ptr: adsp->lpass_efuse)) { |
613 | dev_err(adsp->dev, "failed to map efuse registers\n" ); |
614 | return PTR_ERR(ptr: adsp->lpass_efuse); |
615 | } |
616 | } |
617 | syscon = of_parse_phandle(np: pdev->dev.of_node, phandle_name: "qcom,halt-regs" , index: 0); |
618 | if (!syscon) { |
619 | dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n" ); |
620 | return -EINVAL; |
621 | } |
622 | |
623 | adsp->halt_map = syscon_node_to_regmap(np: syscon); |
624 | of_node_put(node: syscon); |
625 | if (IS_ERR(ptr: adsp->halt_map)) |
626 | return PTR_ERR(ptr: adsp->halt_map); |
627 | |
628 | ret = of_property_read_u32_index(np: pdev->dev.of_node, propname: "qcom,halt-regs" , |
629 | index: 1, out_value: &adsp->halt_lpass); |
630 | if (ret < 0) { |
631 | dev_err(&pdev->dev, "no offset in syscon\n" ); |
632 | return ret; |
633 | } |
634 | |
635 | return 0; |
636 | } |
637 | |
638 | static int adsp_alloc_memory_region(struct qcom_adsp *adsp) |
639 | { |
640 | struct reserved_mem *rmem = NULL; |
641 | struct device_node *node; |
642 | |
643 | node = of_parse_phandle(np: adsp->dev->of_node, phandle_name: "memory-region" , index: 0); |
644 | if (node) |
645 | rmem = of_reserved_mem_lookup(np: node); |
646 | of_node_put(node); |
647 | |
648 | if (!rmem) { |
649 | dev_err(adsp->dev, "unable to resolve memory-region\n" ); |
650 | return -EINVAL; |
651 | } |
652 | |
653 | adsp->mem_phys = adsp->mem_reloc = rmem->base; |
654 | adsp->mem_size = rmem->size; |
655 | adsp->mem_region = devm_ioremap_wc(dev: adsp->dev, |
656 | offset: adsp->mem_phys, size: adsp->mem_size); |
657 | if (!adsp->mem_region) { |
658 | dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n" , |
659 | &rmem->base, adsp->mem_size); |
660 | return -EBUSY; |
661 | } |
662 | |
663 | return 0; |
664 | } |
665 | |
666 | static int adsp_probe(struct platform_device *pdev) |
667 | { |
668 | const struct adsp_pil_data *desc; |
669 | const char *firmware_name; |
670 | struct qcom_adsp *adsp; |
671 | struct rproc *rproc; |
672 | int ret; |
673 | |
674 | desc = of_device_get_match_data(dev: &pdev->dev); |
675 | if (!desc) |
676 | return -EINVAL; |
677 | |
678 | firmware_name = desc->firmware_name; |
679 | ret = of_property_read_string(np: pdev->dev.of_node, propname: "firmware-name" , |
680 | out_string: &firmware_name); |
681 | if (ret < 0 && ret != -EINVAL) { |
682 | dev_err(&pdev->dev, "unable to read firmware-name\n" ); |
683 | return ret; |
684 | } |
685 | |
686 | rproc = rproc_alloc(dev: &pdev->dev, name: pdev->name, ops: &adsp_ops, |
687 | firmware: firmware_name, len: sizeof(*adsp)); |
688 | if (!rproc) { |
689 | dev_err(&pdev->dev, "unable to allocate remoteproc\n" ); |
690 | return -ENOMEM; |
691 | } |
692 | |
693 | rproc->auto_boot = desc->auto_boot; |
694 | rproc->has_iommu = desc->has_iommu; |
695 | rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); |
696 | |
697 | adsp = rproc->priv; |
698 | adsp->dev = &pdev->dev; |
699 | adsp->rproc = rproc; |
700 | adsp->info_name = desc->sysmon_name; |
701 | adsp->has_iommu = desc->has_iommu; |
702 | |
703 | platform_set_drvdata(pdev, data: adsp); |
704 | |
705 | if (desc->is_wpss) |
706 | adsp->shutdown = qcom_wpss_shutdown; |
707 | else |
708 | adsp->shutdown = qcom_adsp_shutdown; |
709 | |
710 | ret = adsp_alloc_memory_region(adsp); |
711 | if (ret) |
712 | goto free_rproc; |
713 | |
714 | ret = adsp_init_clock(adsp, clk_ids: desc->clk_ids); |
715 | if (ret) |
716 | goto free_rproc; |
717 | |
718 | ret = qcom_rproc_pds_attach(dev: adsp->dev, adsp, |
719 | pd_names: desc->proxy_pd_names); |
720 | if (ret < 0) { |
721 | dev_err(&pdev->dev, "Failed to attach proxy power domains\n" ); |
722 | goto free_rproc; |
723 | } |
724 | adsp->proxy_pd_count = ret; |
725 | |
726 | ret = adsp_init_reset(adsp); |
727 | if (ret) |
728 | goto disable_pm; |
729 | |
730 | ret = adsp_init_mmio(adsp, pdev); |
731 | if (ret) |
732 | goto disable_pm; |
733 | |
734 | ret = qcom_q6v5_init(q6v5: &adsp->q6v5, pdev, rproc, crash_reason: desc->crash_reason_smem, |
735 | load_state: desc->load_state, handover: qcom_adsp_pil_handover); |
736 | if (ret) |
737 | goto disable_pm; |
738 | |
739 | qcom_add_glink_subdev(rproc, glink: &adsp->glink_subdev, ssr_name: desc->ssr_name); |
740 | qcom_add_ssr_subdev(rproc, ssr: &adsp->ssr_subdev, ssr_name: desc->ssr_name); |
741 | adsp->sysmon = qcom_add_sysmon_subdev(rproc, |
742 | name: desc->sysmon_name, |
743 | ssctl_instance: desc->ssctl_id); |
744 | if (IS_ERR(ptr: adsp->sysmon)) { |
745 | ret = PTR_ERR(ptr: adsp->sysmon); |
746 | goto disable_pm; |
747 | } |
748 | |
749 | ret = rproc_add(rproc); |
750 | if (ret) |
751 | goto disable_pm; |
752 | |
753 | return 0; |
754 | |
755 | disable_pm: |
756 | qcom_rproc_pds_detach(adsp, pds: adsp->proxy_pds, pd_count: adsp->proxy_pd_count); |
757 | |
758 | free_rproc: |
759 | rproc_free(rproc); |
760 | |
761 | return ret; |
762 | } |
763 | |
764 | static void adsp_remove(struct platform_device *pdev) |
765 | { |
766 | struct qcom_adsp *adsp = platform_get_drvdata(pdev); |
767 | |
768 | rproc_del(rproc: adsp->rproc); |
769 | |
770 | qcom_q6v5_deinit(q6v5: &adsp->q6v5); |
771 | qcom_remove_glink_subdev(rproc: adsp->rproc, glink: &adsp->glink_subdev); |
772 | qcom_remove_sysmon_subdev(sysmon: adsp->sysmon); |
773 | qcom_remove_ssr_subdev(rproc: adsp->rproc, ssr: &adsp->ssr_subdev); |
774 | qcom_rproc_pds_detach(adsp, pds: adsp->proxy_pds, pd_count: adsp->proxy_pd_count); |
775 | rproc_free(rproc: adsp->rproc); |
776 | } |
777 | |
778 | static const struct adsp_pil_data adsp_resource_init = { |
779 | .crash_reason_smem = 423, |
780 | .firmware_name = "adsp.mdt" , |
781 | .ssr_name = "lpass" , |
782 | .sysmon_name = "adsp" , |
783 | .ssctl_id = 0x14, |
784 | .is_wpss = false, |
785 | .auto_boot = true, |
786 | .clk_ids = (const char*[]) { |
787 | "sway_cbcr" , "lpass_ahbs_aon_cbcr" , "lpass_ahbm_aon_cbcr" , |
788 | "qdsp6ss_xo" , "qdsp6ss_sleep" , "qdsp6ss_core" , NULL |
789 | }, |
790 | .num_clks = 7, |
791 | .proxy_pd_names = (const char*[]) { |
792 | "cx" , NULL |
793 | }, |
794 | }; |
795 | |
796 | static const struct adsp_pil_data adsp_sc7280_resource_init = { |
797 | .crash_reason_smem = 423, |
798 | .firmware_name = "adsp.pbn" , |
799 | .load_state = "adsp" , |
800 | .ssr_name = "lpass" , |
801 | .sysmon_name = "adsp" , |
802 | .ssctl_id = 0x14, |
803 | .has_iommu = true, |
804 | .auto_boot = true, |
805 | .clk_ids = (const char*[]) { |
806 | "gcc_cfg_noc_lpass" , NULL |
807 | }, |
808 | .num_clks = 1, |
809 | }; |
810 | |
811 | static const struct adsp_pil_data cdsp_resource_init = { |
812 | .crash_reason_smem = 601, |
813 | .firmware_name = "cdsp.mdt" , |
814 | .ssr_name = "cdsp" , |
815 | .sysmon_name = "cdsp" , |
816 | .ssctl_id = 0x17, |
817 | .is_wpss = false, |
818 | .auto_boot = true, |
819 | .clk_ids = (const char*[]) { |
820 | "sway" , "tbu" , "bimc" , "ahb_aon" , "q6ss_slave" , "q6ss_master" , |
821 | "q6_axim" , NULL |
822 | }, |
823 | .num_clks = 7, |
824 | .proxy_pd_names = (const char*[]) { |
825 | "cx" , NULL |
826 | }, |
827 | }; |
828 | |
829 | static const struct adsp_pil_data wpss_resource_init = { |
830 | .crash_reason_smem = 626, |
831 | .firmware_name = "wpss.mdt" , |
832 | .ssr_name = "wpss" , |
833 | .sysmon_name = "wpss" , |
834 | .ssctl_id = 0x19, |
835 | .is_wpss = true, |
836 | .auto_boot = false, |
837 | .load_state = "wpss" , |
838 | .clk_ids = (const char*[]) { |
839 | "ahb_bdg" , "ahb" , "rscp" , NULL |
840 | }, |
841 | .num_clks = 3, |
842 | .proxy_pd_names = (const char*[]) { |
843 | "cx" , "mx" , NULL |
844 | }, |
845 | }; |
846 | |
847 | static const struct of_device_id adsp_of_match[] = { |
848 | { .compatible = "qcom,qcs404-cdsp-pil" , .data = &cdsp_resource_init }, |
849 | { .compatible = "qcom,sc7280-adsp-pil" , .data = &adsp_sc7280_resource_init }, |
850 | { .compatible = "qcom,sc7280-wpss-pil" , .data = &wpss_resource_init }, |
851 | { .compatible = "qcom,sdm845-adsp-pil" , .data = &adsp_resource_init }, |
852 | { }, |
853 | }; |
854 | MODULE_DEVICE_TABLE(of, adsp_of_match); |
855 | |
856 | static struct platform_driver adsp_pil_driver = { |
857 | .probe = adsp_probe, |
858 | .remove_new = adsp_remove, |
859 | .driver = { |
860 | .name = "qcom_q6v5_adsp" , |
861 | .of_match_table = adsp_of_match, |
862 | }, |
863 | }; |
864 | |
865 | module_platform_driver(adsp_pil_driver); |
866 | MODULE_DESCRIPTION("QTI SDM845 ADSP Peripheral Image Loader" ); |
867 | MODULE_LICENSE("GPL v2" ); |
868 | |