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