Commit | Line | Data |
---|---|---|
dc160e44 R |
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> | |
f22eedff | 12 | #include <linux/iommu.h> |
dc160e44 R |
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_address.h> | |
18 | #include <linux/of_device.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" | |
d4c78d21 | 30 | #include "qcom_pil_info.h" |
dc160e44 R |
31 | #include "qcom_q6v5.h" |
32 | #include "remoteproc_internal.h" | |
33 | ||
34 | /* time out value */ | |
35 | #define ACK_TIMEOUT 1000 | |
358b586f | 36 | #define ACK_TIMEOUT_US 1000000 |
dc160e44 R |
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 | ||
f22eedff SRM |
52 | #define SID_MASK_DEFAULT 0xF |
53 | ||
0c6de4c2 BA |
54 | #define QDSP6SS_XO_CBCR 0x38 |
55 | #define QDSP6SS_CORE_CBCR 0x20 | |
56 | #define QDSP6SS_SLEEP_CBCR 0x3c | |
dc160e44 | 57 | |
358b586f RP |
58 | #define QCOM_Q6V5_RPROC_PROXY_PD_MAX 3 |
59 | ||
c36d6aa6 SRM |
60 | #define LPASS_BOOT_CORE_START BIT(0) |
61 | #define LPASS_BOOT_CMD_START BIT(0) | |
9ece9619 | 62 | #define LPASS_EFUSE_Q6SS_EVB_SEL 0x0 |
c36d6aa6 | 63 | |
dc160e44 R |
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; | |
358b586f | 71 | bool is_wpss; |
272dca8d | 72 | bool has_iommu; |
358b586f | 73 | bool auto_boot; |
0c6de4c2 BA |
74 | |
75 | const char **clk_ids; | |
76 | int num_clks; | |
358b586f RP |
77 | const char **proxy_pd_names; |
78 | const char *load_state; | |
dc160e44 R |
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; | |
9ece9619 | 93 | void __iomem *lpass_efuse; |
dc160e44 R |
94 | |
95 | struct reset_control *pdc_sync_reset; | |
0c6de4c2 | 96 | struct reset_control *restart; |
dc160e44 R |
97 | |
98 | struct regmap *halt_map; | |
99 | unsigned int halt_lpass; | |
100 | ||
101 | int crash_reason_smem; | |
d4c78d21 | 102 | const char *info_name; |
dc160e44 R |
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; | |
272dca8d | 111 | bool has_iommu; |
dc160e44 | 112 | |
358b586f RP |
113 | struct device *proxy_pds[QCOM_Q6V5_RPROC_PROXY_PD_MAX]; |
114 | size_t proxy_pd_count; | |
115 | ||
dc160e44 R |
116 | struct qcom_rproc_glink glink_subdev; |
117 | struct qcom_rproc_ssr ssr_subdev; | |
118 | struct qcom_sysmon *sysmon; | |
358b586f RP |
119 | |
120 | int (*shutdown)(struct qcom_adsp *adsp); | |
dc160e44 R |
121 | }; |
122 | ||
358b586f RP |
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, pd_names[i]); | |
149 | if (IS_ERR_OR_NULL(devs[i])) { | |
150 | ret = PTR_ERR(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(devs[i], 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(pds[i], 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(pds[i], INT_MAX); | |
8672e79d | 188 | ret = pm_runtime_resume_and_get(pds[i]); |
358b586f | 189 | if (ret < 0) { |
358b586f RP |
190 | dev_pm_genpd_set_performance_state(pds[i], 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(pds[i], 0); | |
200 | pm_runtime_put(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(pds[i], 0); | |
213 | pm_runtime_put(pds[i]); | |
214 | } | |
215 | } | |
216 | ||
217 | static int qcom_wpss_shutdown(struct qcom_adsp *adsp) | |
218 | { | |
219 | unsigned int val; | |
220 | ||
221 | regmap_write(adsp->halt_map, adsp->halt_lpass + LPASS_HALTREQ_REG, 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(adsp->pdc_sync_reset); | |
230 | ||
231 | /* Place the WPSS processor into reset */ | |
232 | reset_control_assert(adsp->restart); | |
233 | ||
234 | /* wait after asserting subsystem restart from AOSS */ | |
235 | usleep_range(200, 205); | |
236 | ||
237 | /* Remove the WPSS reset */ | |
238 | reset_control_deassert(adsp->restart); | |
239 | ||
240 | /* De-assert the WPSS PDC Reset */ | |
241 | reset_control_deassert(adsp->pdc_sync_reset); | |
242 | ||
243 | usleep_range(100, 105); | |
244 | ||
245 | clk_bulk_disable_unprepare(adsp->num_clks, adsp->clks); | |
246 | ||
247 | regmap_write(adsp->halt_map, adsp->halt_lpass + LPASS_HALTREQ_REG, 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 | ||
dc160e44 R |
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(adsp->qdsp6ss_base + RET_CFG_REG); | |
265 | val |= 0x1; | |
266 | writel(val, adsp->qdsp6ss_base + RET_CFG_REG); | |
267 | ||
268 | clk_bulk_disable_unprepare(adsp->num_clks, adsp->clks); | |
269 | ||
270 | /* QDSP6 master port needs to be explicitly halted */ | |
271 | ret = regmap_read(adsp->halt_map, | |
272 | adsp->halt_lpass + LPASS_PWR_ON_REG, &val); | |
273 | if (ret || !val) | |
274 | goto reset; | |
275 | ||
276 | ret = regmap_read(adsp->halt_map, | |
277 | adsp->halt_lpass + LPASS_MASTER_IDLE_REG, | |
278 | &val); | |
279 | if (ret || val) | |
280 | goto reset; | |
281 | ||
282 | regmap_write(adsp->halt_map, | |
283 | adsp->halt_lpass + LPASS_HALTREQ_REG, 1); | |
284 | ||
285 | /* Wait for halt ACK from QDSP6 */ | |
286 | timeout = jiffies + msecs_to_jiffies(ACK_TIMEOUT); | |
287 | for (;;) { | |
288 | ret = regmap_read(adsp->halt_map, | |
289 | adsp->halt_lpass + LPASS_HALTACK_REG, &val); | |
290 | if (ret || val || time_after(jiffies, timeout)) | |
291 | break; | |
292 | ||
293 | usleep_range(1000, 1100); | |
294 | } | |
295 | ||
296 | ret = regmap_read(adsp->halt_map, | |
297 | adsp->halt_lpass + LPASS_MASTER_IDLE_REG, &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(adsp->pdc_sync_reset); | |
304 | /* Place the LPASS processor into reset */ | |
0c6de4c2 | 305 | reset_control_assert(adsp->restart); |
dc160e44 R |
306 | /* wait after asserting subsystem restart from AOSS */ |
307 | usleep_range(200, 300); | |
308 | ||
309 | /* Clear the halt request for the AXIM and AHBM for Q6 */ | |
310 | regmap_write(adsp->halt_map, adsp->halt_lpass + LPASS_HALTREQ_REG, 0); | |
311 | ||
312 | /* De-assert the LPASS PDC Reset */ | |
313 | reset_control_deassert(adsp->pdc_sync_reset); | |
314 | /* Remove the LPASS reset */ | |
0c6de4c2 | 315 | reset_control_deassert(adsp->restart); |
dc160e44 R |
316 | /* wait after de-asserting subsystem restart from AOSS */ |
317 | usleep_range(200, 300); | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
322 | static int adsp_load(struct rproc *rproc, const struct firmware *fw) | |
323 | { | |
86660713 | 324 | struct qcom_adsp *adsp = rproc->priv; |
d4c78d21 BA |
325 | int ret; |
326 | ||
327 | ret = qcom_mdt_load_no_init(adsp->dev, fw, rproc->firmware, 0, | |
328 | adsp->mem_region, adsp->mem_phys, | |
329 | adsp->mem_size, &adsp->mem_reloc); | |
330 | if (ret) | |
331 | return ret; | |
332 | ||
333 | qcom_pil_info_store(adsp->info_name, adsp->mem_phys, adsp->mem_size); | |
dc160e44 | 334 | |
d4c78d21 | 335 | return 0; |
dc160e44 R |
336 | } |
337 | ||
f22eedff SRM |
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(rproc->domain, adsp->mem_phys, 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(adsp->dev->of_node, "iommus", "#iommu-cells", 0, &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(rproc->domain, iova, adsp->mem_phys, | |
f3a2439f LT |
370 | adsp->mem_size, IOMMU_READ | IOMMU_WRITE, |
371 | GFP_KERNEL); | |
f22eedff SRM |
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 | ||
dc160e44 R |
380 | static int adsp_start(struct rproc *rproc) |
381 | { | |
86660713 | 382 | struct qcom_adsp *adsp = rproc->priv; |
dc160e44 R |
383 | int ret; |
384 | unsigned int val; | |
385 | ||
c1fe10d2 SS |
386 | ret = qcom_q6v5_prepare(&adsp->q6v5); |
387 | if (ret) | |
388 | return ret; | |
dc160e44 | 389 | |
f22eedff SRM |
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 | ||
dc160e44 R |
396 | ret = clk_prepare_enable(adsp->xo); |
397 | if (ret) | |
f22eedff | 398 | goto adsp_smmu_unmap; |
dc160e44 | 399 | |
358b586f RP |
400 | ret = qcom_rproc_pds_enable(adsp, adsp->proxy_pds, |
401 | adsp->proxy_pd_count); | |
402 | if (ret < 0) | |
dc160e44 R |
403 | goto disable_xo_clk; |
404 | ||
405 | ret = clk_bulk_prepare_enable(adsp->num_clks, adsp->clks); | |
406 | if (ret) { | |
407 | dev_err(adsp->dev, "adsp clk_enable failed\n"); | |
408 | goto disable_power_domain; | |
409 | } | |
410 | ||
0c6de4c2 BA |
411 | /* Enable the XO clock */ |
412 | writel(1, adsp->qdsp6ss_base + QDSP6SS_XO_CBCR); | |
413 | ||
414 | /* Enable the QDSP6SS sleep clock */ | |
415 | writel(1, adsp->qdsp6ss_base + QDSP6SS_SLEEP_CBCR); | |
416 | ||
417 | /* Enable the QDSP6 core clock */ | |
418 | writel(1, adsp->qdsp6ss_base + QDSP6SS_CORE_CBCR); | |
419 | ||
dc160e44 R |
420 | /* Program boot address */ |
421 | writel(adsp->mem_phys >> 4, adsp->qdsp6ss_base + RST_EVB_REG); | |
422 | ||
9ece9619 SRM |
423 | if (adsp->lpass_efuse) |
424 | writel(LPASS_EFUSE_Q6SS_EVB_SEL, adsp->lpass_efuse); | |
425 | ||
dc160e44 | 426 | /* De-assert QDSP6 stop core. QDSP6 will execute after out of reset */ |
c36d6aa6 | 427 | writel(LPASS_BOOT_CORE_START, adsp->qdsp6ss_base + CORE_START_REG); |
dc160e44 R |
428 | |
429 | /* Trigger boot FSM to start QDSP6 */ | |
c36d6aa6 | 430 | writel(LPASS_BOOT_CMD_START, adsp->qdsp6ss_base + BOOT_CMD_REG); |
dc160e44 R |
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(&adsp->q6v5, msecs_to_jiffies(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(adsp->num_clks, adsp->clks); | |
450 | disable_power_domain: | |
358b586f | 451 | qcom_rproc_pds_disable(adsp, adsp->proxy_pds, adsp->proxy_pd_count); |
dc160e44 R |
452 | disable_xo_clk: |
453 | clk_disable_unprepare(adsp->xo); | |
f22eedff SRM |
454 | adsp_smmu_unmap: |
455 | adsp_unmap_carveout(rproc); | |
dc160e44 R |
456 | disable_irqs: |
457 | qcom_q6v5_unprepare(&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(adsp->xo); | |
358b586f | 467 | qcom_rproc_pds_disable(adsp, adsp->proxy_pds, adsp->proxy_pd_count); |
dc160e44 R |
468 | } |
469 | ||
470 | static int adsp_stop(struct rproc *rproc) | |
471 | { | |
86660713 | 472 | struct qcom_adsp *adsp = rproc->priv; |
dc160e44 R |
473 | int handover; |
474 | int ret; | |
475 | ||
ed5da808 | 476 | ret = qcom_q6v5_request_stop(&adsp->q6v5, adsp->sysmon); |
dc160e44 R |
477 | if (ret == -ETIMEDOUT) |
478 | dev_err(adsp->dev, "timed out on wait\n"); | |
479 | ||
358b586f | 480 | ret = adsp->shutdown(adsp); |
dc160e44 R |
481 | if (ret) |
482 | dev_err(adsp->dev, "failed to shutdown: %d\n", ret); | |
483 | ||
f22eedff SRM |
484 | adsp_unmap_carveout(rproc); |
485 | ||
dc160e44 R |
486 | handover = qcom_q6v5_unprepare(&adsp->q6v5); |
487 | if (handover) | |
488 | qcom_adsp_pil_handover(&adsp->q6v5); | |
489 | ||
490 | return ret; | |
491 | } | |
492 | ||
40df0a91 | 493 | static void *adsp_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem) |
dc160e44 | 494 | { |
86660713 | 495 | struct qcom_adsp *adsp = rproc->priv; |
dc160e44 R |
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 | ||
48ab209c SRM |
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 | ||
717c21ba BA |
526 | static unsigned long adsp_panic(struct rproc *rproc) |
527 | { | |
528 | struct qcom_adsp *adsp = rproc->priv; | |
529 | ||
530 | return qcom_q6v5_panic(&adsp->q6v5); | |
531 | } | |
532 | ||
dc160e44 R |
533 | static const struct rproc_ops adsp_ops = { |
534 | .start = adsp_start, | |
535 | .stop = adsp_stop, | |
536 | .da_to_va = adsp_da_to_va, | |
48ab209c | 537 | .parse_fw = adsp_parse_firmware, |
dc160e44 | 538 | .load = adsp_load, |
717c21ba | 539 | .panic = adsp_panic, |
dc160e44 R |
540 | }; |
541 | ||
0c6de4c2 | 542 | static int adsp_init_clock(struct qcom_adsp *adsp, const char **clk_ids) |
dc160e44 | 543 | { |
0c6de4c2 | 544 | int num_clks = 0; |
dc160e44 R |
545 | int i, ret; |
546 | ||
547 | adsp->xo = devm_clk_get(adsp->dev, "xo"); | |
548 | if (IS_ERR(adsp->xo)) { | |
549 | ret = PTR_ERR(adsp->xo); | |
550 | if (ret != -EPROBE_DEFER) | |
551 | dev_err(adsp->dev, "failed to get xo clock"); | |
552 | return ret; | |
553 | } | |
554 | ||
0c6de4c2 BA |
555 | for (i = 0; clk_ids[i]; i++) |
556 | num_clks++; | |
557 | ||
558 | adsp->num_clks = num_clks; | |
dc160e44 R |
559 | adsp->clks = devm_kcalloc(adsp->dev, adsp->num_clks, |
560 | sizeof(*adsp->clks), GFP_KERNEL); | |
6e6b1ada WY |
561 | if (!adsp->clks) |
562 | return -ENOMEM; | |
dc160e44 R |
563 | |
564 | for (i = 0; i < adsp->num_clks; i++) | |
0c6de4c2 | 565 | adsp->clks[i].id = clk_ids[i]; |
dc160e44 R |
566 | |
567 | return devm_clk_bulk_get(adsp->dev, adsp->num_clks, adsp->clks); | |
568 | } | |
569 | ||
570 | static int adsp_init_reset(struct qcom_adsp *adsp) | |
571 | { | |
0c6de4c2 | 572 | adsp->pdc_sync_reset = devm_reset_control_get_optional_exclusive(adsp->dev, |
dc160e44 R |
573 | "pdc_sync"); |
574 | if (IS_ERR(adsp->pdc_sync_reset)) { | |
575 | dev_err(adsp->dev, "failed to acquire pdc_sync reset\n"); | |
576 | return PTR_ERR(adsp->pdc_sync_reset); | |
577 | } | |
578 | ||
0c6de4c2 BA |
579 | adsp->restart = devm_reset_control_get_optional_exclusive(adsp->dev, "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(adsp->dev, "cc_lpass"); | |
584 | ||
585 | if (IS_ERR(adsp->restart)) { | |
586 | dev_err(adsp->dev, "failed to acquire restart\n"); | |
587 | return PTR_ERR(adsp->restart); | |
dc160e44 R |
588 | } |
589 | ||
590 | return 0; | |
591 | } | |
592 | ||
593 | static int adsp_init_mmio(struct qcom_adsp *adsp, | |
594 | struct platform_device *pdev) | |
595 | { | |
9ece9619 | 596 | struct resource *efuse_region; |
dc160e44 | 597 | struct device_node *syscon; |
dc160e44 R |
598 | int ret; |
599 | ||
c3d4e5b1 ZC |
600 | adsp->qdsp6ss_base = devm_platform_ioremap_resource(pdev, 0); |
601 | if (IS_ERR(adsp->qdsp6ss_base)) { | |
dc160e44 | 602 | dev_err(adsp->dev, "failed to map QDSP6SS registers\n"); |
c3d4e5b1 | 603 | return PTR_ERR(adsp->qdsp6ss_base); |
dc160e44 R |
604 | } |
605 | ||
9ece9619 SRM |
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(&pdev->dev, efuse_region); | |
612 | if (IS_ERR(adsp->lpass_efuse)) { | |
613 | dev_err(adsp->dev, "failed to map efuse registers\n"); | |
614 | return PTR_ERR(adsp->lpass_efuse); | |
615 | } | |
616 | } | |
dc160e44 R |
617 | syscon = of_parse_phandle(pdev->dev.of_node, "qcom,halt-regs", 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(syscon); | |
624 | of_node_put(syscon); | |
625 | if (IS_ERR(adsp->halt_map)) | |
626 | return PTR_ERR(adsp->halt_map); | |
627 | ||
628 | ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,halt-regs", | |
629 | 1, &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 device_node *node; | |
641 | struct resource r; | |
642 | int ret; | |
643 | ||
644 | node = of_parse_phandle(adsp->dev->of_node, "memory-region", 0); | |
645 | if (!node) { | |
646 | dev_err(adsp->dev, "no memory-region specified\n"); | |
647 | return -EINVAL; | |
648 | } | |
649 | ||
650 | ret = of_address_to_resource(node, 0, &r); | |
505b5b16 | 651 | of_node_put(node); |
dc160e44 R |
652 | if (ret) |
653 | return ret; | |
654 | ||
655 | adsp->mem_phys = adsp->mem_reloc = r.start; | |
656 | adsp->mem_size = resource_size(&r); | |
657 | adsp->mem_region = devm_ioremap_wc(adsp->dev, | |
658 | adsp->mem_phys, adsp->mem_size); | |
659 | if (!adsp->mem_region) { | |
660 | dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n", | |
661 | &r.start, adsp->mem_size); | |
662 | return -EBUSY; | |
663 | } | |
664 | ||
665 | return 0; | |
666 | } | |
667 | ||
668 | static int adsp_probe(struct platform_device *pdev) | |
669 | { | |
670 | const struct adsp_pil_data *desc; | |
358b586f | 671 | const char *firmware_name; |
dc160e44 R |
672 | struct qcom_adsp *adsp; |
673 | struct rproc *rproc; | |
674 | int ret; | |
675 | ||
676 | desc = of_device_get_match_data(&pdev->dev); | |
677 | if (!desc) | |
678 | return -EINVAL; | |
679 | ||
358b586f RP |
680 | firmware_name = desc->firmware_name; |
681 | ret = of_property_read_string(pdev->dev.of_node, "firmware-name", | |
682 | &firmware_name); | |
683 | if (ret < 0 && ret != -EINVAL) { | |
684 | dev_err(&pdev->dev, "unable to read firmware-name\n"); | |
685 | return ret; | |
686 | } | |
687 | ||
dc160e44 | 688 | rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops, |
358b586f | 689 | firmware_name, sizeof(*adsp)); |
dc160e44 R |
690 | if (!rproc) { |
691 | dev_err(&pdev->dev, "unable to allocate remoteproc\n"); | |
692 | return -ENOMEM; | |
693 | } | |
358b586f RP |
694 | |
695 | rproc->auto_boot = desc->auto_boot; | |
272dca8d | 696 | rproc->has_iommu = desc->has_iommu; |
3898fc99 | 697 | rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); |
dc160e44 | 698 | |
86660713 | 699 | adsp = rproc->priv; |
dc160e44 R |
700 | adsp->dev = &pdev->dev; |
701 | adsp->rproc = rproc; | |
d4c78d21 | 702 | adsp->info_name = desc->sysmon_name; |
272dca8d SRM |
703 | adsp->has_iommu = desc->has_iommu; |
704 | ||
dc160e44 R |
705 | platform_set_drvdata(pdev, adsp); |
706 | ||
358b586f RP |
707 | if (desc->is_wpss) |
708 | adsp->shutdown = qcom_wpss_shutdown; | |
709 | else | |
710 | adsp->shutdown = qcom_adsp_shutdown; | |
711 | ||
dc160e44 R |
712 | ret = adsp_alloc_memory_region(adsp); |
713 | if (ret) | |
714 | goto free_rproc; | |
715 | ||
0c6de4c2 | 716 | ret = adsp_init_clock(adsp, desc->clk_ids); |
dc160e44 R |
717 | if (ret) |
718 | goto free_rproc; | |
719 | ||
358b586f RP |
720 | ret = qcom_rproc_pds_attach(adsp->dev, adsp, |
721 | desc->proxy_pd_names); | |
722 | if (ret < 0) { | |
723 | dev_err(&pdev->dev, "Failed to attach proxy power domains\n"); | |
724 | goto free_rproc; | |
725 | } | |
726 | adsp->proxy_pd_count = ret; | |
dc160e44 R |
727 | |
728 | ret = adsp_init_reset(adsp); | |
729 | if (ret) | |
730 | goto disable_pm; | |
731 | ||
732 | ret = adsp_init_mmio(adsp, pdev); | |
733 | if (ret) | |
734 | goto disable_pm; | |
735 | ||
358b586f RP |
736 | ret = qcom_q6v5_init(&adsp->q6v5, pdev, rproc, desc->crash_reason_smem, |
737 | desc->load_state, qcom_adsp_pil_handover); | |
dc160e44 R |
738 | if (ret) |
739 | goto disable_pm; | |
740 | ||
cd9fc8f1 | 741 | qcom_add_glink_subdev(rproc, &adsp->glink_subdev, desc->ssr_name); |
dc160e44 R |
742 | qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name); |
743 | adsp->sysmon = qcom_add_sysmon_subdev(rproc, | |
744 | desc->sysmon_name, | |
745 | desc->ssctl_id); | |
027045a6 SS |
746 | if (IS_ERR(adsp->sysmon)) { |
747 | ret = PTR_ERR(adsp->sysmon); | |
748 | goto disable_pm; | |
749 | } | |
dc160e44 R |
750 | |
751 | ret = rproc_add(rproc); | |
752 | if (ret) | |
753 | goto disable_pm; | |
754 | ||
755 | return 0; | |
756 | ||
757 | disable_pm: | |
358b586f RP |
758 | qcom_rproc_pds_detach(adsp, adsp->proxy_pds, adsp->proxy_pd_count); |
759 | ||
dc160e44 R |
760 | free_rproc: |
761 | rproc_free(rproc); | |
762 | ||
763 | return ret; | |
764 | } | |
765 | ||
766 | static int adsp_remove(struct platform_device *pdev) | |
767 | { | |
768 | struct qcom_adsp *adsp = platform_get_drvdata(pdev); | |
769 | ||
770 | rproc_del(adsp->rproc); | |
771 | ||
c1fe10d2 | 772 | qcom_q6v5_deinit(&adsp->q6v5); |
dc160e44 R |
773 | qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev); |
774 | qcom_remove_sysmon_subdev(adsp->sysmon); | |
775 | qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev); | |
358b586f | 776 | qcom_rproc_pds_detach(adsp, adsp->proxy_pds, adsp->proxy_pd_count); |
dc160e44 R |
777 | rproc_free(adsp->rproc); |
778 | ||
779 | return 0; | |
780 | } | |
781 | ||
782 | static const struct adsp_pil_data adsp_resource_init = { | |
783 | .crash_reason_smem = 423, | |
784 | .firmware_name = "adsp.mdt", | |
785 | .ssr_name = "lpass", | |
786 | .sysmon_name = "adsp", | |
787 | .ssctl_id = 0x14, | |
358b586f RP |
788 | .is_wpss = false, |
789 | .auto_boot = true, | |
0c6de4c2 BA |
790 | .clk_ids = (const char*[]) { |
791 | "sway_cbcr", "lpass_ahbs_aon_cbcr", "lpass_ahbm_aon_cbcr", | |
792 | "qdsp6ss_xo", "qdsp6ss_sleep", "qdsp6ss_core", NULL | |
793 | }, | |
794 | .num_clks = 7, | |
358b586f RP |
795 | .proxy_pd_names = (const char*[]) { |
796 | "cx", NULL | |
797 | }, | |
0c6de4c2 BA |
798 | }; |
799 | ||
66cab0c5 SRM |
800 | static const struct adsp_pil_data adsp_sc7280_resource_init = { |
801 | .crash_reason_smem = 423, | |
802 | .firmware_name = "adsp.pbn", | |
803 | .load_state = "adsp", | |
804 | .ssr_name = "lpass", | |
805 | .sysmon_name = "adsp", | |
806 | .ssctl_id = 0x14, | |
807 | .has_iommu = true, | |
808 | .auto_boot = true, | |
809 | .clk_ids = (const char*[]) { | |
810 | "gcc_cfg_noc_lpass", NULL | |
811 | }, | |
812 | .num_clks = 1, | |
813 | }; | |
814 | ||
0c6de4c2 BA |
815 | static const struct adsp_pil_data cdsp_resource_init = { |
816 | .crash_reason_smem = 601, | |
817 | .firmware_name = "cdsp.mdt", | |
818 | .ssr_name = "cdsp", | |
819 | .sysmon_name = "cdsp", | |
820 | .ssctl_id = 0x17, | |
358b586f RP |
821 | .is_wpss = false, |
822 | .auto_boot = true, | |
0c6de4c2 BA |
823 | .clk_ids = (const char*[]) { |
824 | "sway", "tbu", "bimc", "ahb_aon", "q6ss_slave", "q6ss_master", | |
825 | "q6_axim", NULL | |
826 | }, | |
827 | .num_clks = 7, | |
358b586f RP |
828 | .proxy_pd_names = (const char*[]) { |
829 | "cx", NULL | |
830 | }, | |
831 | }; | |
832 | ||
833 | static const struct adsp_pil_data wpss_resource_init = { | |
834 | .crash_reason_smem = 626, | |
835 | .firmware_name = "wpss.mdt", | |
836 | .ssr_name = "wpss", | |
837 | .sysmon_name = "wpss", | |
838 | .ssctl_id = 0x19, | |
839 | .is_wpss = true, | |
840 | .auto_boot = false, | |
841 | .load_state = "wpss", | |
842 | .clk_ids = (const char*[]) { | |
843 | "ahb_bdg", "ahb", "rscp", NULL | |
844 | }, | |
845 | .num_clks = 3, | |
846 | .proxy_pd_names = (const char*[]) { | |
847 | "cx", "mx", NULL | |
848 | }, | |
dc160e44 R |
849 | }; |
850 | ||
851 | static const struct of_device_id adsp_of_match[] = { | |
0c6de4c2 | 852 | { .compatible = "qcom,qcs404-cdsp-pil", .data = &cdsp_resource_init }, |
66cab0c5 | 853 | { .compatible = "qcom,sc7280-adsp-pil", .data = &adsp_sc7280_resource_init }, |
358b586f | 854 | { .compatible = "qcom,sc7280-wpss-pil", .data = &wpss_resource_init }, |
dc160e44 R |
855 | { .compatible = "qcom,sdm845-adsp-pil", .data = &adsp_resource_init }, |
856 | { }, | |
857 | }; | |
858 | MODULE_DEVICE_TABLE(of, adsp_of_match); | |
859 | ||
860 | static struct platform_driver adsp_pil_driver = { | |
861 | .probe = adsp_probe, | |
862 | .remove = adsp_remove, | |
863 | .driver = { | |
864 | .name = "qcom_q6v5_adsp", | |
865 | .of_match_table = adsp_of_match, | |
866 | }, | |
867 | }; | |
868 | ||
869 | module_platform_driver(adsp_pil_driver); | |
870 | MODULE_DESCRIPTION("QTI SDM845 ADSP Peripheral Image Loader"); | |
871 | MODULE_LICENSE("GPL v2"); |