1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2016-2018 Linaro Ltd. |
4 | * Copyright (C) 2014 Sony Mobile Communications AB |
5 | * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. |
6 | */ |
7 | #include <linux/clk.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/io.h> |
10 | #include <linux/iopoll.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/mfd/syscon.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/of_reserved_mem.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/regulator/consumer.h> |
19 | #include <linux/reset.h> |
20 | #include <linux/soc/qcom/mdt_loader.h> |
21 | #include "qcom_common.h" |
22 | #include "qcom_pil_info.h" |
23 | #include "qcom_q6v5.h" |
24 | |
25 | #define WCSS_CRASH_REASON 421 |
26 | |
27 | /* Q6SS Register Offsets */ |
28 | #define Q6SS_RESET_REG 0x014 |
29 | #define Q6SS_GFMUX_CTL_REG 0x020 |
30 | #define Q6SS_PWR_CTL_REG 0x030 |
31 | #define Q6SS_MEM_PWR_CTL 0x0B0 |
32 | #define Q6SS_STRAP_ACC 0x110 |
33 | #define Q6SS_CGC_OVERRIDE 0x034 |
34 | #define Q6SS_BCR_REG 0x6000 |
35 | |
36 | /* AXI Halt Register Offsets */ |
37 | #define AXI_HALTREQ_REG 0x0 |
38 | #define AXI_HALTACK_REG 0x4 |
39 | #define AXI_IDLE_REG 0x8 |
40 | |
41 | #define HALT_ACK_TIMEOUT_MS 100 |
42 | |
43 | /* Q6SS_RESET */ |
44 | #define Q6SS_STOP_CORE BIT(0) |
45 | #define Q6SS_CORE_ARES BIT(1) |
46 | #define Q6SS_BUS_ARES_ENABLE BIT(2) |
47 | |
48 | /* Q6SS_BRC_RESET */ |
49 | #define Q6SS_BRC_BLK_ARES BIT(0) |
50 | |
51 | /* Q6SS_GFMUX_CTL */ |
52 | #define Q6SS_CLK_ENABLE BIT(1) |
53 | #define Q6SS_SWITCH_CLK_SRC BIT(8) |
54 | |
55 | /* Q6SS_PWR_CTL */ |
56 | #define Q6SS_L2DATA_STBY_N BIT(18) |
57 | #define Q6SS_SLP_RET_N BIT(19) |
58 | #define Q6SS_CLAMP_IO BIT(20) |
59 | #define QDSS_BHS_ON BIT(21) |
60 | #define QDSS_Q6_MEMORIES GENMASK(15, 0) |
61 | |
62 | /* Q6SS parameters */ |
63 | #define Q6SS_LDO_BYP BIT(25) |
64 | #define Q6SS_BHS_ON BIT(24) |
65 | #define Q6SS_CLAMP_WL BIT(21) |
66 | #define Q6SS_CLAMP_QMC_MEM BIT(22) |
67 | #define HALT_CHECK_MAX_LOOPS 200 |
68 | #define Q6SS_XO_CBCR GENMASK(5, 3) |
69 | #define Q6SS_SLEEP_CBCR GENMASK(5, 2) |
70 | |
71 | /* Q6SS config/status registers */ |
72 | #define TCSR_GLOBAL_CFG0 0x0 |
73 | #define TCSR_GLOBAL_CFG1 0x4 |
74 | #define SSCAON_CONFIG 0x8 |
75 | #define SSCAON_STATUS 0xc |
76 | #define Q6SS_BHS_STATUS 0x78 |
77 | #define Q6SS_RST_EVB 0x10 |
78 | |
79 | #define BHS_EN_REST_ACK BIT(0) |
80 | #define SSCAON_ENABLE BIT(13) |
81 | #define SSCAON_BUS_EN BIT(15) |
82 | #define SSCAON_BUS_MUX_MASK GENMASK(18, 16) |
83 | |
84 | #define MEM_BANKS 19 |
85 | #define TCSR_WCSS_CLK_MASK 0x1F |
86 | #define TCSR_WCSS_CLK_ENABLE 0x14 |
87 | |
88 | #define MAX_HALT_REG 3 |
89 | enum { |
90 | WCSS_IPQ8074, |
91 | WCSS_QCS404, |
92 | }; |
93 | |
94 | struct wcss_data { |
95 | const char *firmware_name; |
96 | unsigned int crash_reason_smem; |
97 | u32 version; |
98 | bool aon_reset_required; |
99 | bool wcss_q6_reset_required; |
100 | const char *ssr_name; |
101 | const char *sysmon_name; |
102 | int ssctl_id; |
103 | const struct rproc_ops *ops; |
104 | bool requires_force_stop; |
105 | }; |
106 | |
107 | struct q6v5_wcss { |
108 | struct device *dev; |
109 | |
110 | void __iomem *reg_base; |
111 | void __iomem *rmb_base; |
112 | |
113 | struct regmap *halt_map; |
114 | u32 halt_q6; |
115 | u32 halt_wcss; |
116 | u32 halt_nc; |
117 | |
118 | struct clk *xo; |
119 | struct clk *ahbfabric_cbcr_clk; |
120 | struct clk *gcc_abhs_cbcr; |
121 | struct clk *gcc_axim_cbcr; |
122 | struct clk *lcc_csr_cbcr; |
123 | struct clk *ahbs_cbcr; |
124 | struct clk *tcm_slave_cbcr; |
125 | struct clk *qdsp6ss_abhm_cbcr; |
126 | struct clk *qdsp6ss_sleep_cbcr; |
127 | struct clk *qdsp6ss_axim_cbcr; |
128 | struct clk *qdsp6ss_xo_cbcr; |
129 | struct clk *qdsp6ss_core_gfmux; |
130 | struct clk *lcc_bcr_sleep; |
131 | struct regulator *cx_supply; |
132 | struct qcom_sysmon *sysmon; |
133 | |
134 | struct reset_control *wcss_aon_reset; |
135 | struct reset_control *wcss_reset; |
136 | struct reset_control *wcss_q6_reset; |
137 | struct reset_control *wcss_q6_bcr_reset; |
138 | |
139 | struct qcom_q6v5 q6v5; |
140 | |
141 | phys_addr_t mem_phys; |
142 | phys_addr_t mem_reloc; |
143 | void *mem_region; |
144 | size_t mem_size; |
145 | |
146 | unsigned int crash_reason_smem; |
147 | u32 version; |
148 | bool requires_force_stop; |
149 | |
150 | struct qcom_rproc_glink glink_subdev; |
151 | struct qcom_rproc_ssr ssr_subdev; |
152 | }; |
153 | |
154 | static int q6v5_wcss_reset(struct q6v5_wcss *wcss) |
155 | { |
156 | int ret; |
157 | u32 val; |
158 | int i; |
159 | |
160 | /* Assert resets, stop core */ |
161 | val = readl(addr: wcss->reg_base + Q6SS_RESET_REG); |
162 | val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; |
163 | writel(val, addr: wcss->reg_base + Q6SS_RESET_REG); |
164 | |
165 | /* BHS require xo cbcr to be enabled */ |
166 | val = readl(addr: wcss->reg_base + Q6SS_XO_CBCR); |
167 | val |= 0x1; |
168 | writel(val, addr: wcss->reg_base + Q6SS_XO_CBCR); |
169 | |
170 | /* Read CLKOFF bit to go low indicating CLK is enabled */ |
171 | ret = readl_poll_timeout(wcss->reg_base + Q6SS_XO_CBCR, |
172 | val, !(val & BIT(31)), 1, |
173 | HALT_CHECK_MAX_LOOPS); |
174 | if (ret) { |
175 | dev_err(wcss->dev, |
176 | "xo cbcr enabling timed out (rc:%d)\n" , ret); |
177 | return ret; |
178 | } |
179 | /* Enable power block headswitch and wait for it to stabilize */ |
180 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
181 | val |= Q6SS_BHS_ON; |
182 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
183 | udelay(1); |
184 | |
185 | /* Put LDO in bypass mode */ |
186 | val |= Q6SS_LDO_BYP; |
187 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
188 | |
189 | /* Deassert Q6 compiler memory clamp */ |
190 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
191 | val &= ~Q6SS_CLAMP_QMC_MEM; |
192 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
193 | |
194 | /* Deassert memory peripheral sleep and L2 memory standby */ |
195 | val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N; |
196 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
197 | |
198 | /* Turn on L1, L2, ETB and JU memories 1 at a time */ |
199 | val = readl(addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
200 | for (i = MEM_BANKS; i >= 0; i--) { |
201 | val |= BIT(i); |
202 | writel(val, addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
203 | /* |
204 | * Read back value to ensure the write is done then |
205 | * wait for 1us for both memory peripheral and data |
206 | * array to turn on. |
207 | */ |
208 | val |= readl(addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
209 | udelay(1); |
210 | } |
211 | /* Remove word line clamp */ |
212 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
213 | val &= ~Q6SS_CLAMP_WL; |
214 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
215 | |
216 | /* Remove IO clamp */ |
217 | val &= ~Q6SS_CLAMP_IO; |
218 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
219 | |
220 | /* Bring core out of reset */ |
221 | val = readl(addr: wcss->reg_base + Q6SS_RESET_REG); |
222 | val &= ~Q6SS_CORE_ARES; |
223 | writel(val, addr: wcss->reg_base + Q6SS_RESET_REG); |
224 | |
225 | /* Turn on core clock */ |
226 | val = readl(addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
227 | val |= Q6SS_CLK_ENABLE; |
228 | writel(val, addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
229 | |
230 | /* Start core execution */ |
231 | val = readl(addr: wcss->reg_base + Q6SS_RESET_REG); |
232 | val &= ~Q6SS_STOP_CORE; |
233 | writel(val, addr: wcss->reg_base + Q6SS_RESET_REG); |
234 | |
235 | return 0; |
236 | } |
237 | |
238 | static int q6v5_wcss_start(struct rproc *rproc) |
239 | { |
240 | struct q6v5_wcss *wcss = rproc->priv; |
241 | int ret; |
242 | |
243 | qcom_q6v5_prepare(q6v5: &wcss->q6v5); |
244 | |
245 | /* Release Q6 and WCSS reset */ |
246 | ret = reset_control_deassert(rstc: wcss->wcss_reset); |
247 | if (ret) { |
248 | dev_err(wcss->dev, "wcss_reset failed\n" ); |
249 | return ret; |
250 | } |
251 | |
252 | ret = reset_control_deassert(rstc: wcss->wcss_q6_reset); |
253 | if (ret) { |
254 | dev_err(wcss->dev, "wcss_q6_reset failed\n" ); |
255 | goto wcss_reset; |
256 | } |
257 | |
258 | /* Lithium configuration - clock gating and bus arbitration */ |
259 | ret = regmap_update_bits(map: wcss->halt_map, |
260 | reg: wcss->halt_nc + TCSR_GLOBAL_CFG0, |
261 | TCSR_WCSS_CLK_MASK, |
262 | TCSR_WCSS_CLK_ENABLE); |
263 | if (ret) |
264 | goto wcss_q6_reset; |
265 | |
266 | ret = regmap_update_bits(map: wcss->halt_map, |
267 | reg: wcss->halt_nc + TCSR_GLOBAL_CFG1, |
268 | mask: 1, val: 0); |
269 | if (ret) |
270 | goto wcss_q6_reset; |
271 | |
272 | /* Write bootaddr to EVB so that Q6WCSS will jump there after reset */ |
273 | writel(val: rproc->bootaddr >> 4, addr: wcss->reg_base + Q6SS_RST_EVB); |
274 | |
275 | ret = q6v5_wcss_reset(wcss); |
276 | if (ret) |
277 | goto wcss_q6_reset; |
278 | |
279 | ret = qcom_q6v5_wait_for_start(q6v5: &wcss->q6v5, timeout: 5 * HZ); |
280 | if (ret == -ETIMEDOUT) |
281 | dev_err(wcss->dev, "start timed out\n" ); |
282 | |
283 | return ret; |
284 | |
285 | wcss_q6_reset: |
286 | reset_control_assert(rstc: wcss->wcss_q6_reset); |
287 | |
288 | wcss_reset: |
289 | reset_control_assert(rstc: wcss->wcss_reset); |
290 | |
291 | return ret; |
292 | } |
293 | |
294 | static int q6v5_wcss_qcs404_power_on(struct q6v5_wcss *wcss) |
295 | { |
296 | unsigned long val; |
297 | int ret, idx; |
298 | |
299 | /* Toggle the restart */ |
300 | reset_control_assert(rstc: wcss->wcss_reset); |
301 | usleep_range(min: 200, max: 300); |
302 | reset_control_deassert(rstc: wcss->wcss_reset); |
303 | usleep_range(min: 200, max: 300); |
304 | |
305 | /* Enable GCC_WDSP_Q6SS_AHBS_CBCR clock */ |
306 | ret = clk_prepare_enable(clk: wcss->gcc_abhs_cbcr); |
307 | if (ret) |
308 | return ret; |
309 | |
310 | /* Remove reset to the WCNSS QDSP6SS */ |
311 | reset_control_deassert(rstc: wcss->wcss_q6_bcr_reset); |
312 | |
313 | /* Enable Q6SSTOP_AHBFABRIC_CBCR clock */ |
314 | ret = clk_prepare_enable(clk: wcss->ahbfabric_cbcr_clk); |
315 | if (ret) |
316 | goto disable_gcc_abhs_cbcr_clk; |
317 | |
318 | /* Enable the LCCCSR CBC clock, Q6SSTOP_Q6SSTOP_LCC_CSR_CBCR clock */ |
319 | ret = clk_prepare_enable(clk: wcss->lcc_csr_cbcr); |
320 | if (ret) |
321 | goto disable_ahbfabric_cbcr_clk; |
322 | |
323 | /* Enable the Q6AHBS CBC, Q6SSTOP_Q6SS_AHBS_CBCR clock */ |
324 | ret = clk_prepare_enable(clk: wcss->ahbs_cbcr); |
325 | if (ret) |
326 | goto disable_csr_cbcr_clk; |
327 | |
328 | /* Enable the TCM slave CBC, Q6SSTOP_Q6SS_TCM_SLAVE_CBCR clock */ |
329 | ret = clk_prepare_enable(clk: wcss->tcm_slave_cbcr); |
330 | if (ret) |
331 | goto disable_ahbs_cbcr_clk; |
332 | |
333 | /* Enable the Q6SS AHB master CBC, Q6SSTOP_Q6SS_AHBM_CBCR clock */ |
334 | ret = clk_prepare_enable(clk: wcss->qdsp6ss_abhm_cbcr); |
335 | if (ret) |
336 | goto disable_tcm_slave_cbcr_clk; |
337 | |
338 | /* Enable the Q6SS AXI master CBC, Q6SSTOP_Q6SS_AXIM_CBCR clock */ |
339 | ret = clk_prepare_enable(clk: wcss->qdsp6ss_axim_cbcr); |
340 | if (ret) |
341 | goto disable_abhm_cbcr_clk; |
342 | |
343 | /* Enable the Q6SS XO CBC */ |
344 | val = readl(addr: wcss->reg_base + Q6SS_XO_CBCR); |
345 | val |= BIT(0); |
346 | writel(val, addr: wcss->reg_base + Q6SS_XO_CBCR); |
347 | /* Read CLKOFF bit to go low indicating CLK is enabled */ |
348 | ret = readl_poll_timeout(wcss->reg_base + Q6SS_XO_CBCR, |
349 | val, !(val & BIT(31)), 1, |
350 | HALT_CHECK_MAX_LOOPS); |
351 | if (ret) { |
352 | dev_err(wcss->dev, |
353 | "xo cbcr enabling timed out (rc:%d)\n" , ret); |
354 | goto disable_xo_cbcr_clk; |
355 | } |
356 | |
357 | writel(val: 0, addr: wcss->reg_base + Q6SS_CGC_OVERRIDE); |
358 | |
359 | /* Enable QDSP6 sleep clock clock */ |
360 | val = readl(addr: wcss->reg_base + Q6SS_SLEEP_CBCR); |
361 | val |= BIT(0); |
362 | writel(val, addr: wcss->reg_base + Q6SS_SLEEP_CBCR); |
363 | |
364 | /* Enable the Enable the Q6 AXI clock, GCC_WDSP_Q6SS_AXIM_CBCR*/ |
365 | ret = clk_prepare_enable(clk: wcss->gcc_axim_cbcr); |
366 | if (ret) |
367 | goto disable_sleep_cbcr_clk; |
368 | |
369 | /* Assert resets, stop core */ |
370 | val = readl(addr: wcss->reg_base + Q6SS_RESET_REG); |
371 | val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; |
372 | writel(val, addr: wcss->reg_base + Q6SS_RESET_REG); |
373 | |
374 | /* Program the QDSP6SS PWR_CTL register */ |
375 | writel(val: 0x01700000, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
376 | |
377 | writel(val: 0x03700000, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
378 | |
379 | writel(val: 0x03300000, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
380 | |
381 | writel(val: 0x033C0000, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
382 | |
383 | /* |
384 | * Enable memories by turning on the QDSP6 memory foot/head switch, one |
385 | * bank at a time to avoid in-rush current |
386 | */ |
387 | for (idx = 28; idx >= 0; idx--) { |
388 | writel(val: (readl(addr: wcss->reg_base + Q6SS_MEM_PWR_CTL) | |
389 | (1 << idx)), addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
390 | } |
391 | |
392 | writel(val: 0x031C0000, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
393 | writel(val: 0x030C0000, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
394 | |
395 | val = readl(addr: wcss->reg_base + Q6SS_RESET_REG); |
396 | val &= ~Q6SS_CORE_ARES; |
397 | writel(val, addr: wcss->reg_base + Q6SS_RESET_REG); |
398 | |
399 | /* Enable the Q6 core clock at the GFM, Q6SSTOP_QDSP6SS_GFMUX_CTL */ |
400 | val = readl(addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
401 | val |= Q6SS_CLK_ENABLE | Q6SS_SWITCH_CLK_SRC; |
402 | writel(val, addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
403 | |
404 | /* Enable sleep clock branch needed for BCR circuit */ |
405 | ret = clk_prepare_enable(clk: wcss->lcc_bcr_sleep); |
406 | if (ret) |
407 | goto disable_core_gfmux_clk; |
408 | |
409 | return 0; |
410 | |
411 | disable_core_gfmux_clk: |
412 | val = readl(addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
413 | val &= ~(Q6SS_CLK_ENABLE | Q6SS_SWITCH_CLK_SRC); |
414 | writel(val, addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
415 | clk_disable_unprepare(clk: wcss->gcc_axim_cbcr); |
416 | disable_sleep_cbcr_clk: |
417 | val = readl(addr: wcss->reg_base + Q6SS_SLEEP_CBCR); |
418 | val &= ~Q6SS_CLK_ENABLE; |
419 | writel(val, addr: wcss->reg_base + Q6SS_SLEEP_CBCR); |
420 | disable_xo_cbcr_clk: |
421 | val = readl(addr: wcss->reg_base + Q6SS_XO_CBCR); |
422 | val &= ~Q6SS_CLK_ENABLE; |
423 | writel(val, addr: wcss->reg_base + Q6SS_XO_CBCR); |
424 | clk_disable_unprepare(clk: wcss->qdsp6ss_axim_cbcr); |
425 | disable_abhm_cbcr_clk: |
426 | clk_disable_unprepare(clk: wcss->qdsp6ss_abhm_cbcr); |
427 | disable_tcm_slave_cbcr_clk: |
428 | clk_disable_unprepare(clk: wcss->tcm_slave_cbcr); |
429 | disable_ahbs_cbcr_clk: |
430 | clk_disable_unprepare(clk: wcss->ahbs_cbcr); |
431 | disable_csr_cbcr_clk: |
432 | clk_disable_unprepare(clk: wcss->lcc_csr_cbcr); |
433 | disable_ahbfabric_cbcr_clk: |
434 | clk_disable_unprepare(clk: wcss->ahbfabric_cbcr_clk); |
435 | disable_gcc_abhs_cbcr_clk: |
436 | clk_disable_unprepare(clk: wcss->gcc_abhs_cbcr); |
437 | |
438 | return ret; |
439 | } |
440 | |
441 | static inline int q6v5_wcss_qcs404_reset(struct q6v5_wcss *wcss) |
442 | { |
443 | unsigned long val; |
444 | |
445 | writel(val: 0x80800000, addr: wcss->reg_base + Q6SS_STRAP_ACC); |
446 | |
447 | /* Start core execution */ |
448 | val = readl(addr: wcss->reg_base + Q6SS_RESET_REG); |
449 | val &= ~Q6SS_STOP_CORE; |
450 | writel(val, addr: wcss->reg_base + Q6SS_RESET_REG); |
451 | |
452 | return 0; |
453 | } |
454 | |
455 | static int q6v5_qcs404_wcss_start(struct rproc *rproc) |
456 | { |
457 | struct q6v5_wcss *wcss = rproc->priv; |
458 | int ret; |
459 | |
460 | ret = clk_prepare_enable(clk: wcss->xo); |
461 | if (ret) |
462 | return ret; |
463 | |
464 | ret = regulator_enable(regulator: wcss->cx_supply); |
465 | if (ret) |
466 | goto disable_xo_clk; |
467 | |
468 | qcom_q6v5_prepare(q6v5: &wcss->q6v5); |
469 | |
470 | ret = q6v5_wcss_qcs404_power_on(wcss); |
471 | if (ret) { |
472 | dev_err(wcss->dev, "wcss clk_enable failed\n" ); |
473 | goto disable_cx_supply; |
474 | } |
475 | |
476 | writel(val: rproc->bootaddr >> 4, addr: wcss->reg_base + Q6SS_RST_EVB); |
477 | |
478 | q6v5_wcss_qcs404_reset(wcss); |
479 | |
480 | ret = qcom_q6v5_wait_for_start(q6v5: &wcss->q6v5, timeout: 5 * HZ); |
481 | if (ret == -ETIMEDOUT) { |
482 | dev_err(wcss->dev, "start timed out\n" ); |
483 | goto disable_cx_supply; |
484 | } |
485 | |
486 | return 0; |
487 | |
488 | disable_cx_supply: |
489 | regulator_disable(regulator: wcss->cx_supply); |
490 | disable_xo_clk: |
491 | clk_disable_unprepare(clk: wcss->xo); |
492 | |
493 | return ret; |
494 | } |
495 | |
496 | static void q6v5_wcss_halt_axi_port(struct q6v5_wcss *wcss, |
497 | struct regmap *halt_map, |
498 | u32 offset) |
499 | { |
500 | unsigned long timeout; |
501 | unsigned int val; |
502 | int ret; |
503 | |
504 | /* Check if we're already idle */ |
505 | ret = regmap_read(map: halt_map, reg: offset + AXI_IDLE_REG, val: &val); |
506 | if (!ret && val) |
507 | return; |
508 | |
509 | /* Assert halt request */ |
510 | regmap_write(map: halt_map, reg: offset + AXI_HALTREQ_REG, val: 1); |
511 | |
512 | /* Wait for halt */ |
513 | timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS); |
514 | for (;;) { |
515 | ret = regmap_read(map: halt_map, reg: offset + AXI_HALTACK_REG, val: &val); |
516 | if (ret || val || time_after(jiffies, timeout)) |
517 | break; |
518 | |
519 | msleep(msecs: 1); |
520 | } |
521 | |
522 | ret = regmap_read(map: halt_map, reg: offset + AXI_IDLE_REG, val: &val); |
523 | if (ret || !val) |
524 | dev_err(wcss->dev, "port failed halt\n" ); |
525 | |
526 | /* Clear halt request (port will remain halted until reset) */ |
527 | regmap_write(map: halt_map, reg: offset + AXI_HALTREQ_REG, val: 0); |
528 | } |
529 | |
530 | static int q6v5_qcs404_wcss_shutdown(struct q6v5_wcss *wcss) |
531 | { |
532 | unsigned long val; |
533 | int ret; |
534 | |
535 | q6v5_wcss_halt_axi_port(wcss, halt_map: wcss->halt_map, offset: wcss->halt_wcss); |
536 | |
537 | /* assert clamps to avoid MX current inrush */ |
538 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
539 | val |= (Q6SS_CLAMP_IO | Q6SS_CLAMP_WL | Q6SS_CLAMP_QMC_MEM); |
540 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
541 | |
542 | /* Disable memories by turning off memory foot/headswitch */ |
543 | writel(val: (readl(addr: wcss->reg_base + Q6SS_MEM_PWR_CTL) & |
544 | ~QDSS_Q6_MEMORIES), |
545 | addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
546 | |
547 | /* Clear the BHS_ON bit */ |
548 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
549 | val &= ~Q6SS_BHS_ON; |
550 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
551 | |
552 | clk_disable_unprepare(clk: wcss->ahbfabric_cbcr_clk); |
553 | clk_disable_unprepare(clk: wcss->lcc_csr_cbcr); |
554 | clk_disable_unprepare(clk: wcss->tcm_slave_cbcr); |
555 | clk_disable_unprepare(clk: wcss->qdsp6ss_abhm_cbcr); |
556 | clk_disable_unprepare(clk: wcss->qdsp6ss_axim_cbcr); |
557 | |
558 | val = readl(addr: wcss->reg_base + Q6SS_SLEEP_CBCR); |
559 | val &= ~BIT(0); |
560 | writel(val, addr: wcss->reg_base + Q6SS_SLEEP_CBCR); |
561 | |
562 | val = readl(addr: wcss->reg_base + Q6SS_XO_CBCR); |
563 | val &= ~BIT(0); |
564 | writel(val, addr: wcss->reg_base + Q6SS_XO_CBCR); |
565 | |
566 | clk_disable_unprepare(clk: wcss->ahbs_cbcr); |
567 | clk_disable_unprepare(clk: wcss->lcc_bcr_sleep); |
568 | |
569 | val = readl(addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
570 | val &= ~(Q6SS_CLK_ENABLE | Q6SS_SWITCH_CLK_SRC); |
571 | writel(val, addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
572 | |
573 | clk_disable_unprepare(clk: wcss->gcc_abhs_cbcr); |
574 | |
575 | ret = reset_control_assert(rstc: wcss->wcss_reset); |
576 | if (ret) { |
577 | dev_err(wcss->dev, "wcss_reset failed\n" ); |
578 | return ret; |
579 | } |
580 | usleep_range(min: 200, max: 300); |
581 | |
582 | ret = reset_control_deassert(rstc: wcss->wcss_reset); |
583 | if (ret) { |
584 | dev_err(wcss->dev, "wcss_reset failed\n" ); |
585 | return ret; |
586 | } |
587 | usleep_range(min: 200, max: 300); |
588 | |
589 | clk_disable_unprepare(clk: wcss->gcc_axim_cbcr); |
590 | |
591 | return 0; |
592 | } |
593 | |
594 | static int q6v5_wcss_powerdown(struct q6v5_wcss *wcss) |
595 | { |
596 | int ret; |
597 | u32 val; |
598 | |
599 | /* 1 - Assert WCSS/Q6 HALTREQ */ |
600 | q6v5_wcss_halt_axi_port(wcss, halt_map: wcss->halt_map, offset: wcss->halt_wcss); |
601 | |
602 | /* 2 - Enable WCSSAON_CONFIG */ |
603 | val = readl(addr: wcss->rmb_base + SSCAON_CONFIG); |
604 | val |= SSCAON_ENABLE; |
605 | writel(val, addr: wcss->rmb_base + SSCAON_CONFIG); |
606 | |
607 | /* 3 - Set SSCAON_CONFIG */ |
608 | val |= SSCAON_BUS_EN; |
609 | val &= ~SSCAON_BUS_MUX_MASK; |
610 | writel(val, addr: wcss->rmb_base + SSCAON_CONFIG); |
611 | |
612 | /* 4 - SSCAON_CONFIG 1 */ |
613 | val |= BIT(1); |
614 | writel(val, addr: wcss->rmb_base + SSCAON_CONFIG); |
615 | |
616 | /* 5 - wait for SSCAON_STATUS */ |
617 | ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS, |
618 | val, (val & 0xffff) == 0x400, 1000, |
619 | HALT_CHECK_MAX_LOOPS); |
620 | if (ret) { |
621 | dev_err(wcss->dev, |
622 | "can't get SSCAON_STATUS rc:%d)\n" , ret); |
623 | return ret; |
624 | } |
625 | |
626 | /* 6 - De-assert WCSS_AON reset */ |
627 | reset_control_assert(rstc: wcss->wcss_aon_reset); |
628 | |
629 | /* 7 - Disable WCSSAON_CONFIG 13 */ |
630 | val = readl(addr: wcss->rmb_base + SSCAON_CONFIG); |
631 | val &= ~SSCAON_ENABLE; |
632 | writel(val, addr: wcss->rmb_base + SSCAON_CONFIG); |
633 | |
634 | /* 8 - De-assert WCSS/Q6 HALTREQ */ |
635 | reset_control_assert(rstc: wcss->wcss_reset); |
636 | |
637 | return 0; |
638 | } |
639 | |
640 | static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) |
641 | { |
642 | int ret; |
643 | u32 val; |
644 | int i; |
645 | |
646 | /* 1 - Halt Q6 bus interface */ |
647 | q6v5_wcss_halt_axi_port(wcss, halt_map: wcss->halt_map, offset: wcss->halt_q6); |
648 | |
649 | /* 2 - Disable Q6 Core clock */ |
650 | val = readl(addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
651 | val &= ~Q6SS_CLK_ENABLE; |
652 | writel(val, addr: wcss->reg_base + Q6SS_GFMUX_CTL_REG); |
653 | |
654 | /* 3 - Clamp I/O */ |
655 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
656 | val |= Q6SS_CLAMP_IO; |
657 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
658 | |
659 | /* 4 - Clamp WL */ |
660 | val |= QDSS_BHS_ON; |
661 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
662 | |
663 | /* 5 - Clear Erase standby */ |
664 | val &= ~Q6SS_L2DATA_STBY_N; |
665 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
666 | |
667 | /* 6 - Clear Sleep RTN */ |
668 | val &= ~Q6SS_SLP_RET_N; |
669 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
670 | |
671 | /* 7 - turn off Q6 memory foot/head switch one bank at a time */ |
672 | for (i = 0; i < 20; i++) { |
673 | val = readl(addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
674 | val &= ~BIT(i); |
675 | writel(val, addr: wcss->reg_base + Q6SS_MEM_PWR_CTL); |
676 | mdelay(1); |
677 | } |
678 | |
679 | /* 8 - Assert QMC memory RTN */ |
680 | val = readl(addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
681 | val |= Q6SS_CLAMP_QMC_MEM; |
682 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
683 | |
684 | /* 9 - Turn off BHS */ |
685 | val &= ~Q6SS_BHS_ON; |
686 | writel(val, addr: wcss->reg_base + Q6SS_PWR_CTL_REG); |
687 | udelay(1); |
688 | |
689 | /* 10 - Wait till BHS Reset is done */ |
690 | ret = readl_poll_timeout(wcss->reg_base + Q6SS_BHS_STATUS, |
691 | val, !(val & BHS_EN_REST_ACK), 1000, |
692 | HALT_CHECK_MAX_LOOPS); |
693 | if (ret) { |
694 | dev_err(wcss->dev, "BHS_STATUS not OFF (rc:%d)\n" , ret); |
695 | return ret; |
696 | } |
697 | |
698 | /* 11 - Assert WCSS reset */ |
699 | reset_control_assert(rstc: wcss->wcss_reset); |
700 | |
701 | /* 12 - Assert Q6 reset */ |
702 | reset_control_assert(rstc: wcss->wcss_q6_reset); |
703 | |
704 | return 0; |
705 | } |
706 | |
707 | static int q6v5_wcss_stop(struct rproc *rproc) |
708 | { |
709 | struct q6v5_wcss *wcss = rproc->priv; |
710 | int ret; |
711 | |
712 | /* WCSS powerdown */ |
713 | if (wcss->requires_force_stop) { |
714 | ret = qcom_q6v5_request_stop(q6v5: &wcss->q6v5, NULL); |
715 | if (ret == -ETIMEDOUT) { |
716 | dev_err(wcss->dev, "timed out on wait\n" ); |
717 | return ret; |
718 | } |
719 | } |
720 | |
721 | if (wcss->version == WCSS_QCS404) { |
722 | ret = q6v5_qcs404_wcss_shutdown(wcss); |
723 | if (ret) |
724 | return ret; |
725 | } else { |
726 | ret = q6v5_wcss_powerdown(wcss); |
727 | if (ret) |
728 | return ret; |
729 | |
730 | /* Q6 Power down */ |
731 | ret = q6v5_q6_powerdown(wcss); |
732 | if (ret) |
733 | return ret; |
734 | } |
735 | |
736 | qcom_q6v5_unprepare(q6v5: &wcss->q6v5); |
737 | |
738 | return 0; |
739 | } |
740 | |
741 | static void *q6v5_wcss_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem) |
742 | { |
743 | struct q6v5_wcss *wcss = rproc->priv; |
744 | int offset; |
745 | |
746 | offset = da - wcss->mem_reloc; |
747 | if (offset < 0 || offset + len > wcss->mem_size) |
748 | return NULL; |
749 | |
750 | return wcss->mem_region + offset; |
751 | } |
752 | |
753 | static int q6v5_wcss_load(struct rproc *rproc, const struct firmware *fw) |
754 | { |
755 | struct q6v5_wcss *wcss = rproc->priv; |
756 | int ret; |
757 | |
758 | ret = qcom_mdt_load_no_init(dev: wcss->dev, fw, fw_name: rproc->firmware, |
759 | pas_id: 0, mem_region: wcss->mem_region, mem_phys: wcss->mem_phys, |
760 | mem_size: wcss->mem_size, reloc_base: &wcss->mem_reloc); |
761 | if (ret) |
762 | return ret; |
763 | |
764 | qcom_pil_info_store(image: "wcnss" , base: wcss->mem_phys, size: wcss->mem_size); |
765 | |
766 | return ret; |
767 | } |
768 | |
769 | static const struct rproc_ops q6v5_wcss_ipq8074_ops = { |
770 | .start = q6v5_wcss_start, |
771 | .stop = q6v5_wcss_stop, |
772 | .da_to_va = q6v5_wcss_da_to_va, |
773 | .load = q6v5_wcss_load, |
774 | .get_boot_addr = rproc_elf_get_boot_addr, |
775 | }; |
776 | |
777 | static const struct rproc_ops q6v5_wcss_qcs404_ops = { |
778 | .start = q6v5_qcs404_wcss_start, |
779 | .stop = q6v5_wcss_stop, |
780 | .da_to_va = q6v5_wcss_da_to_va, |
781 | .load = q6v5_wcss_load, |
782 | .get_boot_addr = rproc_elf_get_boot_addr, |
783 | .parse_fw = qcom_register_dump_segments, |
784 | }; |
785 | |
786 | static int q6v5_wcss_init_reset(struct q6v5_wcss *wcss, |
787 | const struct wcss_data *desc) |
788 | { |
789 | struct device *dev = wcss->dev; |
790 | |
791 | if (desc->aon_reset_required) { |
792 | wcss->wcss_aon_reset = devm_reset_control_get_exclusive(dev, id: "wcss_aon_reset" ); |
793 | if (IS_ERR(ptr: wcss->wcss_aon_reset)) { |
794 | dev_err(wcss->dev, "fail to acquire wcss_aon_reset\n" ); |
795 | return PTR_ERR(ptr: wcss->wcss_aon_reset); |
796 | } |
797 | } |
798 | |
799 | wcss->wcss_reset = devm_reset_control_get_exclusive(dev, id: "wcss_reset" ); |
800 | if (IS_ERR(ptr: wcss->wcss_reset)) { |
801 | dev_err(wcss->dev, "unable to acquire wcss_reset\n" ); |
802 | return PTR_ERR(ptr: wcss->wcss_reset); |
803 | } |
804 | |
805 | if (desc->wcss_q6_reset_required) { |
806 | wcss->wcss_q6_reset = devm_reset_control_get_exclusive(dev, id: "wcss_q6_reset" ); |
807 | if (IS_ERR(ptr: wcss->wcss_q6_reset)) { |
808 | dev_err(wcss->dev, "unable to acquire wcss_q6_reset\n" ); |
809 | return PTR_ERR(ptr: wcss->wcss_q6_reset); |
810 | } |
811 | } |
812 | |
813 | wcss->wcss_q6_bcr_reset = devm_reset_control_get_exclusive(dev, id: "wcss_q6_bcr_reset" ); |
814 | if (IS_ERR(ptr: wcss->wcss_q6_bcr_reset)) { |
815 | dev_err(wcss->dev, "unable to acquire wcss_q6_bcr_reset\n" ); |
816 | return PTR_ERR(ptr: wcss->wcss_q6_bcr_reset); |
817 | } |
818 | |
819 | return 0; |
820 | } |
821 | |
822 | static int q6v5_wcss_init_mmio(struct q6v5_wcss *wcss, |
823 | struct platform_device *pdev) |
824 | { |
825 | unsigned int halt_reg[MAX_HALT_REG] = {0}; |
826 | struct device_node *syscon; |
827 | struct resource *res; |
828 | int ret; |
829 | |
830 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6" ); |
831 | if (!res) |
832 | return -EINVAL; |
833 | |
834 | wcss->reg_base = devm_ioremap(dev: &pdev->dev, offset: res->start, |
835 | size: resource_size(res)); |
836 | if (!wcss->reg_base) |
837 | return -ENOMEM; |
838 | |
839 | if (wcss->version == WCSS_IPQ8074) { |
840 | wcss->rmb_base = devm_platform_ioremap_resource_byname(pdev, name: "rmb" ); |
841 | if (IS_ERR(ptr: wcss->rmb_base)) |
842 | return PTR_ERR(ptr: wcss->rmb_base); |
843 | } |
844 | |
845 | syscon = of_parse_phandle(np: pdev->dev.of_node, |
846 | phandle_name: "qcom,halt-regs" , index: 0); |
847 | if (!syscon) { |
848 | dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n" ); |
849 | return -EINVAL; |
850 | } |
851 | |
852 | wcss->halt_map = syscon_node_to_regmap(np: syscon); |
853 | of_node_put(node: syscon); |
854 | if (IS_ERR(ptr: wcss->halt_map)) |
855 | return PTR_ERR(ptr: wcss->halt_map); |
856 | |
857 | ret = of_property_read_variable_u32_array(np: pdev->dev.of_node, |
858 | propname: "qcom,halt-regs" , |
859 | out_values: halt_reg, sz_min: 0, |
860 | MAX_HALT_REG); |
861 | if (ret < 0) { |
862 | dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n" ); |
863 | return -EINVAL; |
864 | } |
865 | |
866 | wcss->halt_q6 = halt_reg[0]; |
867 | wcss->halt_wcss = halt_reg[1]; |
868 | wcss->halt_nc = halt_reg[2]; |
869 | |
870 | return 0; |
871 | } |
872 | |
873 | static int q6v5_alloc_memory_region(struct q6v5_wcss *wcss) |
874 | { |
875 | struct reserved_mem *rmem = NULL; |
876 | struct device_node *node; |
877 | struct device *dev = wcss->dev; |
878 | |
879 | node = of_parse_phandle(np: dev->of_node, phandle_name: "memory-region" , index: 0); |
880 | if (node) |
881 | rmem = of_reserved_mem_lookup(np: node); |
882 | of_node_put(node); |
883 | |
884 | if (!rmem) { |
885 | dev_err(dev, "unable to acquire memory-region\n" ); |
886 | return -EINVAL; |
887 | } |
888 | |
889 | wcss->mem_phys = rmem->base; |
890 | wcss->mem_reloc = rmem->base; |
891 | wcss->mem_size = rmem->size; |
892 | wcss->mem_region = devm_ioremap_wc(dev, offset: wcss->mem_phys, size: wcss->mem_size); |
893 | if (!wcss->mem_region) { |
894 | dev_err(dev, "unable to map memory region: %pa+%pa\n" , |
895 | &rmem->base, &rmem->size); |
896 | return -EBUSY; |
897 | } |
898 | |
899 | return 0; |
900 | } |
901 | |
902 | static int q6v5_wcss_init_clock(struct q6v5_wcss *wcss) |
903 | { |
904 | int ret; |
905 | |
906 | wcss->xo = devm_clk_get(dev: wcss->dev, id: "xo" ); |
907 | if (IS_ERR(ptr: wcss->xo)) { |
908 | ret = PTR_ERR(ptr: wcss->xo); |
909 | if (ret != -EPROBE_DEFER) |
910 | dev_err(wcss->dev, "failed to get xo clock" ); |
911 | return ret; |
912 | } |
913 | |
914 | wcss->gcc_abhs_cbcr = devm_clk_get(dev: wcss->dev, id: "gcc_abhs_cbcr" ); |
915 | if (IS_ERR(ptr: wcss->gcc_abhs_cbcr)) { |
916 | ret = PTR_ERR(ptr: wcss->gcc_abhs_cbcr); |
917 | if (ret != -EPROBE_DEFER) |
918 | dev_err(wcss->dev, "failed to get gcc abhs clock" ); |
919 | return ret; |
920 | } |
921 | |
922 | wcss->gcc_axim_cbcr = devm_clk_get(dev: wcss->dev, id: "gcc_axim_cbcr" ); |
923 | if (IS_ERR(ptr: wcss->gcc_axim_cbcr)) { |
924 | ret = PTR_ERR(ptr: wcss->gcc_axim_cbcr); |
925 | if (ret != -EPROBE_DEFER) |
926 | dev_err(wcss->dev, "failed to get gcc axim clock\n" ); |
927 | return ret; |
928 | } |
929 | |
930 | wcss->ahbfabric_cbcr_clk = devm_clk_get(dev: wcss->dev, |
931 | id: "lcc_ahbfabric_cbc" ); |
932 | if (IS_ERR(ptr: wcss->ahbfabric_cbcr_clk)) { |
933 | ret = PTR_ERR(ptr: wcss->ahbfabric_cbcr_clk); |
934 | if (ret != -EPROBE_DEFER) |
935 | dev_err(wcss->dev, "failed to get ahbfabric clock\n" ); |
936 | return ret; |
937 | } |
938 | |
939 | wcss->lcc_csr_cbcr = devm_clk_get(dev: wcss->dev, id: "tcsr_lcc_cbc" ); |
940 | if (IS_ERR(ptr: wcss->lcc_csr_cbcr)) { |
941 | ret = PTR_ERR(ptr: wcss->lcc_csr_cbcr); |
942 | if (ret != -EPROBE_DEFER) |
943 | dev_err(wcss->dev, "failed to get csr cbcr clk\n" ); |
944 | return ret; |
945 | } |
946 | |
947 | wcss->ahbs_cbcr = devm_clk_get(dev: wcss->dev, |
948 | id: "lcc_abhs_cbc" ); |
949 | if (IS_ERR(ptr: wcss->ahbs_cbcr)) { |
950 | ret = PTR_ERR(ptr: wcss->ahbs_cbcr); |
951 | if (ret != -EPROBE_DEFER) |
952 | dev_err(wcss->dev, "failed to get ahbs_cbcr clk\n" ); |
953 | return ret; |
954 | } |
955 | |
956 | wcss->tcm_slave_cbcr = devm_clk_get(dev: wcss->dev, |
957 | id: "lcc_tcm_slave_cbc" ); |
958 | if (IS_ERR(ptr: wcss->tcm_slave_cbcr)) { |
959 | ret = PTR_ERR(ptr: wcss->tcm_slave_cbcr); |
960 | if (ret != -EPROBE_DEFER) |
961 | dev_err(wcss->dev, "failed to get tcm cbcr clk\n" ); |
962 | return ret; |
963 | } |
964 | |
965 | wcss->qdsp6ss_abhm_cbcr = devm_clk_get(dev: wcss->dev, id: "lcc_abhm_cbc" ); |
966 | if (IS_ERR(ptr: wcss->qdsp6ss_abhm_cbcr)) { |
967 | ret = PTR_ERR(ptr: wcss->qdsp6ss_abhm_cbcr); |
968 | if (ret != -EPROBE_DEFER) |
969 | dev_err(wcss->dev, "failed to get abhm cbcr clk\n" ); |
970 | return ret; |
971 | } |
972 | |
973 | wcss->qdsp6ss_axim_cbcr = devm_clk_get(dev: wcss->dev, id: "lcc_axim_cbc" ); |
974 | if (IS_ERR(ptr: wcss->qdsp6ss_axim_cbcr)) { |
975 | ret = PTR_ERR(ptr: wcss->qdsp6ss_axim_cbcr); |
976 | if (ret != -EPROBE_DEFER) |
977 | dev_err(wcss->dev, "failed to get axim cbcr clk\n" ); |
978 | return ret; |
979 | } |
980 | |
981 | wcss->lcc_bcr_sleep = devm_clk_get(dev: wcss->dev, id: "lcc_bcr_sleep" ); |
982 | if (IS_ERR(ptr: wcss->lcc_bcr_sleep)) { |
983 | ret = PTR_ERR(ptr: wcss->lcc_bcr_sleep); |
984 | if (ret != -EPROBE_DEFER) |
985 | dev_err(wcss->dev, "failed to get bcr cbcr clk\n" ); |
986 | return ret; |
987 | } |
988 | |
989 | return 0; |
990 | } |
991 | |
992 | static int q6v5_wcss_init_regulator(struct q6v5_wcss *wcss) |
993 | { |
994 | wcss->cx_supply = devm_regulator_get(dev: wcss->dev, id: "cx" ); |
995 | if (IS_ERR(ptr: wcss->cx_supply)) |
996 | return PTR_ERR(ptr: wcss->cx_supply); |
997 | |
998 | regulator_set_load(regulator: wcss->cx_supply, load_uA: 100000); |
999 | |
1000 | return 0; |
1001 | } |
1002 | |
1003 | static int q6v5_wcss_probe(struct platform_device *pdev) |
1004 | { |
1005 | const struct wcss_data *desc; |
1006 | struct q6v5_wcss *wcss; |
1007 | struct rproc *rproc; |
1008 | int ret; |
1009 | |
1010 | desc = device_get_match_data(dev: &pdev->dev); |
1011 | if (!desc) |
1012 | return -EINVAL; |
1013 | |
1014 | rproc = rproc_alloc(dev: &pdev->dev, name: pdev->name, ops: desc->ops, |
1015 | firmware: desc->firmware_name, len: sizeof(*wcss)); |
1016 | if (!rproc) { |
1017 | dev_err(&pdev->dev, "failed to allocate rproc\n" ); |
1018 | return -ENOMEM; |
1019 | } |
1020 | |
1021 | wcss = rproc->priv; |
1022 | wcss->dev = &pdev->dev; |
1023 | wcss->version = desc->version; |
1024 | |
1025 | wcss->version = desc->version; |
1026 | wcss->requires_force_stop = desc->requires_force_stop; |
1027 | |
1028 | ret = q6v5_wcss_init_mmio(wcss, pdev); |
1029 | if (ret) |
1030 | goto free_rproc; |
1031 | |
1032 | ret = q6v5_alloc_memory_region(wcss); |
1033 | if (ret) |
1034 | goto free_rproc; |
1035 | |
1036 | if (wcss->version == WCSS_QCS404) { |
1037 | ret = q6v5_wcss_init_clock(wcss); |
1038 | if (ret) |
1039 | goto free_rproc; |
1040 | |
1041 | ret = q6v5_wcss_init_regulator(wcss); |
1042 | if (ret) |
1043 | goto free_rproc; |
1044 | } |
1045 | |
1046 | ret = q6v5_wcss_init_reset(wcss, desc); |
1047 | if (ret) |
1048 | goto free_rproc; |
1049 | |
1050 | ret = qcom_q6v5_init(q6v5: &wcss->q6v5, pdev, rproc, crash_reason: desc->crash_reason_smem, NULL, NULL); |
1051 | if (ret) |
1052 | goto free_rproc; |
1053 | |
1054 | qcom_add_glink_subdev(rproc, glink: &wcss->glink_subdev, ssr_name: "q6wcss" ); |
1055 | qcom_add_ssr_subdev(rproc, ssr: &wcss->ssr_subdev, ssr_name: "q6wcss" ); |
1056 | |
1057 | if (desc->ssctl_id) |
1058 | wcss->sysmon = qcom_add_sysmon_subdev(rproc, |
1059 | name: desc->sysmon_name, |
1060 | ssctl_instance: desc->ssctl_id); |
1061 | |
1062 | ret = rproc_add(rproc); |
1063 | if (ret) |
1064 | goto free_rproc; |
1065 | |
1066 | platform_set_drvdata(pdev, data: rproc); |
1067 | |
1068 | return 0; |
1069 | |
1070 | free_rproc: |
1071 | rproc_free(rproc); |
1072 | |
1073 | return ret; |
1074 | } |
1075 | |
1076 | static void q6v5_wcss_remove(struct platform_device *pdev) |
1077 | { |
1078 | struct rproc *rproc = platform_get_drvdata(pdev); |
1079 | struct q6v5_wcss *wcss = rproc->priv; |
1080 | |
1081 | qcom_q6v5_deinit(q6v5: &wcss->q6v5); |
1082 | rproc_del(rproc); |
1083 | rproc_free(rproc); |
1084 | } |
1085 | |
1086 | static const struct wcss_data wcss_ipq8074_res_init = { |
1087 | .firmware_name = "IPQ8074/q6_fw.mdt" , |
1088 | .crash_reason_smem = WCSS_CRASH_REASON, |
1089 | .aon_reset_required = true, |
1090 | .wcss_q6_reset_required = true, |
1091 | .ops = &q6v5_wcss_ipq8074_ops, |
1092 | .requires_force_stop = true, |
1093 | }; |
1094 | |
1095 | static const struct wcss_data wcss_qcs404_res_init = { |
1096 | .crash_reason_smem = WCSS_CRASH_REASON, |
1097 | .firmware_name = "wcnss.mdt" , |
1098 | .version = WCSS_QCS404, |
1099 | .aon_reset_required = false, |
1100 | .wcss_q6_reset_required = false, |
1101 | .ssr_name = "mpss" , |
1102 | .sysmon_name = "wcnss" , |
1103 | .ssctl_id = 0x12, |
1104 | .ops = &q6v5_wcss_qcs404_ops, |
1105 | .requires_force_stop = false, |
1106 | }; |
1107 | |
1108 | static const struct of_device_id q6v5_wcss_of_match[] = { |
1109 | { .compatible = "qcom,ipq8074-wcss-pil" , .data = &wcss_ipq8074_res_init }, |
1110 | { .compatible = "qcom,qcs404-wcss-pil" , .data = &wcss_qcs404_res_init }, |
1111 | { }, |
1112 | }; |
1113 | MODULE_DEVICE_TABLE(of, q6v5_wcss_of_match); |
1114 | |
1115 | static struct platform_driver q6v5_wcss_driver = { |
1116 | .probe = q6v5_wcss_probe, |
1117 | .remove_new = q6v5_wcss_remove, |
1118 | .driver = { |
1119 | .name = "qcom-q6v5-wcss-pil" , |
1120 | .of_match_table = q6v5_wcss_of_match, |
1121 | }, |
1122 | }; |
1123 | module_platform_driver(q6v5_wcss_driver); |
1124 | |
1125 | MODULE_DESCRIPTION("Hexagon WCSS Peripheral Image Loader" ); |
1126 | MODULE_LICENSE("GPL v2" ); |
1127 | |