Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
b9e718e9 | 2 | /* |
90a068ed | 3 | * Qualcomm ADSP/SLPI Peripheral Image Loader for MSM8974 and MSM8996 |
b9e718e9 BA |
4 | * |
5 | * Copyright (C) 2016 Linaro Ltd | |
6 | * Copyright (C) 2014 Sony Mobile Communications AB | |
7 | * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. | |
b9e718e9 BA |
8 | */ |
9 | ||
f33a7358 | 10 | #include <linux/clk.h> |
b9e718e9 BA |
11 | #include <linux/firmware.h> |
12 | #include <linux/interrupt.h> | |
13 | #include <linux/kernel.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_address.h> | |
16 | #include <linux/of_device.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/qcom_scm.h> | |
19 | #include <linux/regulator/consumer.h> | |
20 | #include <linux/remoteproc.h> | |
2aad40d9 | 21 | #include <linux/soc/qcom/mdt_loader.h> |
b9e718e9 BA |
22 | #include <linux/soc/qcom/smem.h> |
23 | #include <linux/soc/qcom/smem_state.h> | |
24 | ||
bde440ee | 25 | #include "qcom_common.h" |
6103b1a6 | 26 | #include "qcom_q6v5.h" |
b9e718e9 BA |
27 | #include "remoteproc_internal.h" |
28 | ||
c7715e47 AKD |
29 | struct adsp_data { |
30 | int crash_reason_smem; | |
31 | const char *firmware_name; | |
32 | int pas_id; | |
e323fc03 | 33 | bool has_aggre2_clk; |
1fb82ee8 | 34 | |
1e140df0 | 35 | const char *ssr_name; |
1fb82ee8 BA |
36 | const char *sysmon_name; |
37 | int ssctl_id; | |
c7715e47 | 38 | }; |
b9e718e9 BA |
39 | |
40 | struct qcom_adsp { | |
41 | struct device *dev; | |
42 | struct rproc *rproc; | |
43 | ||
6103b1a6 | 44 | struct qcom_q6v5 q6v5; |
b9e718e9 | 45 | |
f33a7358 | 46 | struct clk *xo; |
e323fc03 | 47 | struct clk *aggre2_clk; |
f33a7358 | 48 | |
b9e718e9 | 49 | struct regulator *cx_supply; |
e323fc03 | 50 | struct regulator *px_supply; |
b9e718e9 | 51 | |
c7715e47 AKD |
52 | int pas_id; |
53 | int crash_reason_smem; | |
e323fc03 | 54 | bool has_aggre2_clk; |
c7715e47 | 55 | |
b9e718e9 BA |
56 | struct completion start_done; |
57 | struct completion stop_done; | |
58 | ||
59 | phys_addr_t mem_phys; | |
60 | phys_addr_t mem_reloc; | |
61 | void *mem_region; | |
62 | size_t mem_size; | |
4b48921a | 63 | |
eea07023 | 64 | struct qcom_rproc_glink glink_subdev; |
4b48921a | 65 | struct qcom_rproc_subdev smd_subdev; |
1e140df0 | 66 | struct qcom_rproc_ssr ssr_subdev; |
1fb82ee8 | 67 | struct qcom_sysmon *sysmon; |
b9e718e9 BA |
68 | }; |
69 | ||
70 | static int adsp_load(struct rproc *rproc, const struct firmware *fw) | |
71 | { | |
72 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | |
b9e718e9 | 73 | |
7f0dd07a | 74 | return qcom_mdt_load(adsp->dev, fw, rproc->firmware, adsp->pas_id, |
4dd27f54 BA |
75 | adsp->mem_region, adsp->mem_phys, adsp->mem_size, |
76 | &adsp->mem_reloc); | |
77 | ||
b9e718e9 BA |
78 | } |
79 | ||
b9e718e9 BA |
80 | static int adsp_start(struct rproc *rproc) |
81 | { | |
82 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | |
83 | int ret; | |
84 | ||
6103b1a6 BA |
85 | qcom_q6v5_prepare(&adsp->q6v5); |
86 | ||
f33a7358 | 87 | ret = clk_prepare_enable(adsp->xo); |
b9e718e9 BA |
88 | if (ret) |
89 | return ret; | |
90 | ||
e323fc03 AKD |
91 | ret = clk_prepare_enable(adsp->aggre2_clk); |
92 | if (ret) | |
93 | goto disable_xo_clk; | |
94 | ||
f33a7358 SJ |
95 | ret = regulator_enable(adsp->cx_supply); |
96 | if (ret) | |
e323fc03 AKD |
97 | goto disable_aggre2_clk; |
98 | ||
99 | ret = regulator_enable(adsp->px_supply); | |
100 | if (ret) | |
101 | goto disable_cx_supply; | |
f33a7358 | 102 | |
c7715e47 | 103 | ret = qcom_scm_pas_auth_and_reset(adsp->pas_id); |
b9e718e9 BA |
104 | if (ret) { |
105 | dev_err(adsp->dev, | |
106 | "failed to authenticate image and release reset\n"); | |
e323fc03 | 107 | goto disable_px_supply; |
b9e718e9 BA |
108 | } |
109 | ||
6103b1a6 BA |
110 | ret = qcom_q6v5_wait_for_start(&adsp->q6v5, msecs_to_jiffies(5000)); |
111 | if (ret == -ETIMEDOUT) { | |
b9e718e9 | 112 | dev_err(adsp->dev, "start timed out\n"); |
c7715e47 | 113 | qcom_scm_pas_shutdown(adsp->pas_id); |
e323fc03 | 114 | goto disable_px_supply; |
b9e718e9 BA |
115 | } |
116 | ||
6103b1a6 | 117 | return 0; |
b9e718e9 | 118 | |
e323fc03 AKD |
119 | disable_px_supply: |
120 | regulator_disable(adsp->px_supply); | |
121 | disable_cx_supply: | |
b9e718e9 | 122 | regulator_disable(adsp->cx_supply); |
e323fc03 AKD |
123 | disable_aggre2_clk: |
124 | clk_disable_unprepare(adsp->aggre2_clk); | |
125 | disable_xo_clk: | |
f33a7358 | 126 | clk_disable_unprepare(adsp->xo); |
b9e718e9 BA |
127 | |
128 | return ret; | |
129 | } | |
130 | ||
6103b1a6 BA |
131 | static void qcom_pas_handover(struct qcom_q6v5 *q6v5) |
132 | { | |
133 | struct qcom_adsp *adsp = container_of(q6v5, struct qcom_adsp, q6v5); | |
134 | ||
135 | regulator_disable(adsp->px_supply); | |
136 | regulator_disable(adsp->cx_supply); | |
137 | clk_disable_unprepare(adsp->aggre2_clk); | |
138 | clk_disable_unprepare(adsp->xo); | |
139 | } | |
140 | ||
b9e718e9 BA |
141 | static int adsp_stop(struct rproc *rproc) |
142 | { | |
143 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | |
6103b1a6 | 144 | int handover; |
b9e718e9 BA |
145 | int ret; |
146 | ||
6103b1a6 BA |
147 | ret = qcom_q6v5_request_stop(&adsp->q6v5); |
148 | if (ret == -ETIMEDOUT) | |
b9e718e9 BA |
149 | dev_err(adsp->dev, "timed out on wait\n"); |
150 | ||
c7715e47 | 151 | ret = qcom_scm_pas_shutdown(adsp->pas_id); |
b9e718e9 BA |
152 | if (ret) |
153 | dev_err(adsp->dev, "failed to shutdown: %d\n", ret); | |
154 | ||
6103b1a6 BA |
155 | handover = qcom_q6v5_unprepare(&adsp->q6v5); |
156 | if (handover) | |
157 | qcom_pas_handover(&adsp->q6v5); | |
158 | ||
b9e718e9 BA |
159 | return ret; |
160 | } | |
161 | ||
162 | static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) | |
163 | { | |
164 | struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; | |
165 | int offset; | |
166 | ||
167 | offset = da - adsp->mem_reloc; | |
168 | if (offset < 0 || offset + len > adsp->mem_size) | |
169 | return NULL; | |
170 | ||
171 | return adsp->mem_region + offset; | |
172 | } | |
173 | ||
174 | static const struct rproc_ops adsp_ops = { | |
175 | .start = adsp_start, | |
176 | .stop = adsp_stop, | |
177 | .da_to_va = adsp_da_to_va, | |
dcb57ed4 | 178 | .parse_fw = qcom_register_dump_segments, |
0f21f9cc | 179 | .load = adsp_load, |
b9e718e9 BA |
180 | }; |
181 | ||
f33a7358 SJ |
182 | static int adsp_init_clock(struct qcom_adsp *adsp) |
183 | { | |
184 | int ret; | |
185 | ||
186 | adsp->xo = devm_clk_get(adsp->dev, "xo"); | |
187 | if (IS_ERR(adsp->xo)) { | |
188 | ret = PTR_ERR(adsp->xo); | |
189 | if (ret != -EPROBE_DEFER) | |
190 | dev_err(adsp->dev, "failed to get xo clock"); | |
191 | return ret; | |
192 | } | |
193 | ||
e323fc03 AKD |
194 | if (adsp->has_aggre2_clk) { |
195 | adsp->aggre2_clk = devm_clk_get(adsp->dev, "aggre2"); | |
196 | if (IS_ERR(adsp->aggre2_clk)) { | |
197 | ret = PTR_ERR(adsp->aggre2_clk); | |
198 | if (ret != -EPROBE_DEFER) | |
199 | dev_err(adsp->dev, | |
200 | "failed to get aggre2 clock"); | |
201 | return ret; | |
202 | } | |
203 | } | |
204 | ||
f33a7358 SJ |
205 | return 0; |
206 | } | |
207 | ||
b9e718e9 BA |
208 | static int adsp_init_regulator(struct qcom_adsp *adsp) |
209 | { | |
210 | adsp->cx_supply = devm_regulator_get(adsp->dev, "cx"); | |
211 | if (IS_ERR(adsp->cx_supply)) | |
212 | return PTR_ERR(adsp->cx_supply); | |
213 | ||
214 | regulator_set_load(adsp->cx_supply, 100000); | |
215 | ||
e323fc03 | 216 | adsp->px_supply = devm_regulator_get(adsp->dev, "px"); |
c76929b3 | 217 | return PTR_ERR_OR_ZERO(adsp->px_supply); |
b9e718e9 BA |
218 | } |
219 | ||
b9e718e9 BA |
220 | static int adsp_alloc_memory_region(struct qcom_adsp *adsp) |
221 | { | |
222 | struct device_node *node; | |
223 | struct resource r; | |
224 | int ret; | |
225 | ||
226 | node = of_parse_phandle(adsp->dev->of_node, "memory-region", 0); | |
227 | if (!node) { | |
228 | dev_err(adsp->dev, "no memory-region specified\n"); | |
229 | return -EINVAL; | |
230 | } | |
231 | ||
232 | ret = of_address_to_resource(node, 0, &r); | |
233 | if (ret) | |
234 | return ret; | |
235 | ||
236 | adsp->mem_phys = adsp->mem_reloc = r.start; | |
237 | adsp->mem_size = resource_size(&r); | |
238 | adsp->mem_region = devm_ioremap_wc(adsp->dev, adsp->mem_phys, adsp->mem_size); | |
239 | if (!adsp->mem_region) { | |
240 | dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n", | |
241 | &r.start, adsp->mem_size); | |
242 | return -EBUSY; | |
243 | } | |
244 | ||
245 | return 0; | |
246 | } | |
247 | ||
248 | static int adsp_probe(struct platform_device *pdev) | |
249 | { | |
c7715e47 | 250 | const struct adsp_data *desc; |
b9e718e9 BA |
251 | struct qcom_adsp *adsp; |
252 | struct rproc *rproc; | |
a5a4e02d | 253 | const char *fw_name; |
b9e718e9 BA |
254 | int ret; |
255 | ||
c7715e47 AKD |
256 | desc = of_device_get_match_data(&pdev->dev); |
257 | if (!desc) | |
258 | return -EINVAL; | |
259 | ||
b9e718e9 BA |
260 | if (!qcom_scm_is_available()) |
261 | return -EPROBE_DEFER; | |
262 | ||
a5a4e02d SS |
263 | fw_name = desc->firmware_name; |
264 | ret = of_property_read_string(pdev->dev.of_node, "firmware-name", | |
265 | &fw_name); | |
266 | if (ret < 0 && ret != -EINVAL) | |
267 | return ret; | |
268 | ||
b9e718e9 | 269 | rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops, |
a5a4e02d | 270 | fw_name, sizeof(*adsp)); |
b9e718e9 BA |
271 | if (!rproc) { |
272 | dev_err(&pdev->dev, "unable to allocate remoteproc\n"); | |
273 | return -ENOMEM; | |
274 | } | |
275 | ||
b9e718e9 BA |
276 | adsp = (struct qcom_adsp *)rproc->priv; |
277 | adsp->dev = &pdev->dev; | |
278 | adsp->rproc = rproc; | |
c7715e47 | 279 | adsp->pas_id = desc->pas_id; |
e323fc03 | 280 | adsp->has_aggre2_clk = desc->has_aggre2_clk; |
b9e718e9 BA |
281 | platform_set_drvdata(pdev, adsp); |
282 | ||
b9e718e9 BA |
283 | ret = adsp_alloc_memory_region(adsp); |
284 | if (ret) | |
285 | goto free_rproc; | |
286 | ||
f33a7358 SJ |
287 | ret = adsp_init_clock(adsp); |
288 | if (ret) | |
289 | goto free_rproc; | |
290 | ||
b9e718e9 BA |
291 | ret = adsp_init_regulator(adsp); |
292 | if (ret) | |
293 | goto free_rproc; | |
294 | ||
6103b1a6 BA |
295 | ret = qcom_q6v5_init(&adsp->q6v5, pdev, rproc, desc->crash_reason_smem, |
296 | qcom_pas_handover); | |
297 | if (ret) | |
b9e718e9 | 298 | goto free_rproc; |
b9e718e9 | 299 | |
eea07023 | 300 | qcom_add_glink_subdev(rproc, &adsp->glink_subdev); |
4b48921a | 301 | qcom_add_smd_subdev(rproc, &adsp->smd_subdev); |
1e140df0 | 302 | qcom_add_ssr_subdev(rproc, &adsp->ssr_subdev, desc->ssr_name); |
1fb82ee8 BA |
303 | adsp->sysmon = qcom_add_sysmon_subdev(rproc, |
304 | desc->sysmon_name, | |
305 | desc->ssctl_id); | |
027045a6 SS |
306 | if (IS_ERR(adsp->sysmon)) { |
307 | ret = PTR_ERR(adsp->sysmon); | |
308 | goto free_rproc; | |
309 | } | |
4b48921a | 310 | |
b9e718e9 BA |
311 | ret = rproc_add(rproc); |
312 | if (ret) | |
313 | goto free_rproc; | |
314 | ||
315 | return 0; | |
316 | ||
317 | free_rproc: | |
90a80d88 | 318 | rproc_free(rproc); |
b9e718e9 BA |
319 | |
320 | return ret; | |
321 | } | |
322 | ||
323 | static int adsp_remove(struct platform_device *pdev) | |
324 | { | |
325 | struct qcom_adsp *adsp = platform_get_drvdata(pdev); | |
326 | ||
b9e718e9 | 327 | rproc_del(adsp->rproc); |
4b48921a | 328 | |
eea07023 | 329 | qcom_remove_glink_subdev(adsp->rproc, &adsp->glink_subdev); |
1fb82ee8 | 330 | qcom_remove_sysmon_subdev(adsp->sysmon); |
4b48921a | 331 | qcom_remove_smd_subdev(adsp->rproc, &adsp->smd_subdev); |
1e140df0 | 332 | qcom_remove_ssr_subdev(adsp->rproc, &adsp->ssr_subdev); |
90a80d88 | 333 | rproc_free(adsp->rproc); |
b9e718e9 BA |
334 | |
335 | return 0; | |
336 | } | |
337 | ||
c7715e47 AKD |
338 | static const struct adsp_data adsp_resource_init = { |
339 | .crash_reason_smem = 423, | |
340 | .firmware_name = "adsp.mdt", | |
341 | .pas_id = 1, | |
e323fc03 | 342 | .has_aggre2_clk = false, |
1e140df0 | 343 | .ssr_name = "lpass", |
1fb82ee8 BA |
344 | .sysmon_name = "adsp", |
345 | .ssctl_id = 0x14, | |
c7715e47 AKD |
346 | }; |
347 | ||
3b0d1b65 BA |
348 | static const struct adsp_data cdsp_resource_init = { |
349 | .crash_reason_smem = 601, | |
350 | .firmware_name = "cdsp.mdt", | |
351 | .pas_id = 18, | |
352 | .has_aggre2_clk = false, | |
353 | .ssr_name = "cdsp", | |
354 | .sysmon_name = "cdsp", | |
355 | .ssctl_id = 0x17, | |
356 | }; | |
357 | ||
90a068ed AKD |
358 | static const struct adsp_data slpi_resource_init = { |
359 | .crash_reason_smem = 424, | |
360 | .firmware_name = "slpi.mdt", | |
361 | .pas_id = 12, | |
362 | .has_aggre2_clk = true, | |
1e140df0 | 363 | .ssr_name = "dsps", |
1fb82ee8 BA |
364 | .sysmon_name = "slpi", |
365 | .ssctl_id = 0x16, | |
90a068ed AKD |
366 | }; |
367 | ||
0af93682 BA |
368 | static const struct adsp_data wcss_resource_init = { |
369 | .crash_reason_smem = 421, | |
370 | .firmware_name = "wcnss.mdt", | |
371 | .pas_id = 6, | |
372 | .ssr_name = "mpss", | |
373 | .sysmon_name = "wcnss", | |
374 | .ssctl_id = 0x12, | |
375 | }; | |
376 | ||
b9e718e9 | 377 | static const struct of_device_id adsp_of_match[] = { |
c7715e47 AKD |
378 | { .compatible = "qcom,msm8974-adsp-pil", .data = &adsp_resource_init}, |
379 | { .compatible = "qcom,msm8996-adsp-pil", .data = &adsp_resource_init}, | |
90a068ed | 380 | { .compatible = "qcom,msm8996-slpi-pil", .data = &slpi_resource_init}, |
0af93682 BA |
381 | { .compatible = "qcom,qcs404-adsp-pas", .data = &adsp_resource_init }, |
382 | { .compatible = "qcom,qcs404-cdsp-pas", .data = &cdsp_resource_init }, | |
383 | { .compatible = "qcom,qcs404-wcss-pas", .data = &wcss_resource_init }, | |
3b0d1b65 BA |
384 | { .compatible = "qcom,sdm845-adsp-pas", .data = &adsp_resource_init}, |
385 | { .compatible = "qcom,sdm845-cdsp-pas", .data = &cdsp_resource_init}, | |
b9e718e9 BA |
386 | { }, |
387 | }; | |
62423472 | 388 | MODULE_DEVICE_TABLE(of, adsp_of_match); |
b9e718e9 BA |
389 | |
390 | static struct platform_driver adsp_driver = { | |
391 | .probe = adsp_probe, | |
392 | .remove = adsp_remove, | |
393 | .driver = { | |
9e004f97 | 394 | .name = "qcom_q6v5_pas", |
b9e718e9 BA |
395 | .of_match_table = adsp_of_match, |
396 | }, | |
397 | }; | |
398 | ||
399 | module_platform_driver(adsp_driver); | |
9e004f97 | 400 | MODULE_DESCRIPTION("Qualcomm Hexagon v5 Peripheral Authentication Service driver"); |
b9e718e9 | 401 | MODULE_LICENSE("GPL v2"); |