Commit | Line | Data |
---|---|---|
a750ba5f SP |
1 | /* |
2 | * skl-sst.c - HDA DSP library functions for SKL platform | |
3 | * | |
4 | * Copyright (C) 2014-15, Intel Corporation. | |
5 | * Author:Rafal Redzimski <rafal.f.redzimski@intel.com> | |
6 | * Jeeja KP <jeeja.kp@intel.com> | |
7 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as version 2, as | |
11 | * published by the Free Software Foundation. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | * General Public License for more details. | |
17 | */ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/delay.h> | |
21 | #include <linux/device.h> | |
53afce2c | 22 | #include <linux/err.h> |
09305da9 | 23 | #include <linux/uuid.h> |
a750ba5f SP |
24 | #include "../common/sst-dsp.h" |
25 | #include "../common/sst-dsp-priv.h" | |
26 | #include "../common/sst-ipc.h" | |
27 | #include "skl-sst-ipc.h" | |
28 | ||
29 | #define SKL_BASEFW_TIMEOUT 300 | |
30 | #define SKL_INIT_TIMEOUT 1000 | |
31 | ||
32 | /* Intel HD Audio SRAM Window 0*/ | |
33 | #define SKL_ADSP_SRAM0_BASE 0x8000 | |
34 | ||
35 | /* Firmware status window */ | |
36 | #define SKL_ADSP_FW_STATUS SKL_ADSP_SRAM0_BASE | |
37 | #define SKL_ADSP_ERROR_CODE (SKL_ADSP_FW_STATUS + 0x4) | |
38 | ||
6c5768b3 D |
39 | #define SKL_NUM_MODULES 1 |
40 | ||
a750ba5f SP |
41 | static bool skl_check_fw_status(struct sst_dsp *ctx, u32 status) |
42 | { | |
43 | u32 cur_sts; | |
44 | ||
45 | cur_sts = sst_dsp_shim_read(ctx, SKL_ADSP_FW_STATUS) & SKL_FW_STS_MASK; | |
46 | ||
47 | return (cur_sts == status); | |
48 | } | |
49 | ||
50 | static int skl_transfer_firmware(struct sst_dsp *ctx, | |
51 | const void *basefw, u32 base_fw_size) | |
52 | { | |
53 | int ret = 0; | |
54 | ||
55 | ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, basefw, base_fw_size); | |
56 | if (ret < 0) | |
57 | return ret; | |
58 | ||
59 | ret = sst_dsp_register_poll(ctx, | |
60 | SKL_ADSP_FW_STATUS, | |
61 | SKL_FW_STS_MASK, | |
62 | SKL_FW_RFW_START, | |
63 | SKL_BASEFW_TIMEOUT, | |
64 | "Firmware boot"); | |
65 | ||
66 | ctx->cl_dev.ops.cl_stop_dma(ctx); | |
67 | ||
68 | return ret; | |
69 | } | |
70 | ||
06711051 VK |
71 | #define SKL_ADSP_FW_BIN_HDR_OFFSET 0x284 |
72 | ||
a750ba5f SP |
73 | static int skl_load_base_firmware(struct sst_dsp *ctx) |
74 | { | |
75 | int ret = 0, i; | |
a750ba5f | 76 | struct skl_sst *skl = ctx->thread_context; |
cd63655e | 77 | struct firmware stripped_fw; |
a750ba5f SP |
78 | u32 reg; |
79 | ||
84c9e283 JK |
80 | skl->boot_complete = false; |
81 | init_waitqueue_head(&skl->boot_wait); | |
82 | ||
83 | if (ctx->fw == NULL) { | |
aecf6fd8 | 84 | ret = request_firmware(&ctx->fw, ctx->fw_name, ctx->dev); |
84c9e283 JK |
85 | if (ret < 0) { |
86 | dev_err(ctx->dev, "Request firmware failed %d\n", ret); | |
87 | skl_dsp_disable_core(ctx); | |
88 | return -EIO; | |
89 | } | |
06711051 VK |
90 | |
91 | } | |
92 | ||
93 | ret = snd_skl_parse_uuids(ctx, SKL_ADSP_FW_BIN_HDR_OFFSET); | |
94 | if (ret < 0) { | |
95 | dev_err(ctx->dev, | |
96 | "UUID parsing err: %d\n", ret); | |
97 | release_firmware(ctx->fw); | |
98 | skl_dsp_disable_core(ctx); | |
99 | return ret; | |
84c9e283 JK |
100 | } |
101 | ||
cd63655e VK |
102 | /* check for extended manifest */ |
103 | stripped_fw.data = ctx->fw->data; | |
104 | stripped_fw.size = ctx->fw->size; | |
105 | ||
106 | skl_dsp_strip_extended_manifest(&stripped_fw); | |
107 | ||
84c9e283 | 108 | ret = skl_dsp_boot(ctx); |
a750ba5f | 109 | if (ret < 0) { |
84c9e283 JK |
110 | dev_err(ctx->dev, "Boot dsp core failed ret: %d", ret); |
111 | goto skl_load_base_firmware_failed; | |
112 | } | |
113 | ||
114 | ret = skl_cldma_prepare(ctx); | |
115 | if (ret < 0) { | |
116 | dev_err(ctx->dev, "CL dma prepare failed : %d", ret); | |
117 | goto skl_load_base_firmware_failed; | |
a750ba5f SP |
118 | } |
119 | ||
120 | /* enable Interrupt */ | |
121 | skl_ipc_int_enable(ctx); | |
122 | skl_ipc_op_int_enable(ctx); | |
123 | ||
124 | /* check ROM Status */ | |
125 | for (i = SKL_INIT_TIMEOUT; i > 0; --i) { | |
126 | if (skl_check_fw_status(ctx, SKL_FW_INIT)) { | |
127 | dev_dbg(ctx->dev, | |
128 | "ROM loaded, we can continue with FW loading\n"); | |
129 | break; | |
130 | } | |
131 | mdelay(1); | |
132 | } | |
133 | if (!i) { | |
134 | reg = sst_dsp_shim_read(ctx, SKL_ADSP_FW_STATUS); | |
135 | dev_err(ctx->dev, | |
136 | "Timeout waiting for ROM init done, reg:0x%x\n", reg); | |
137 | ret = -EIO; | |
ae395937 | 138 | goto transfer_firmware_failed; |
a750ba5f SP |
139 | } |
140 | ||
cd63655e | 141 | ret = skl_transfer_firmware(ctx, stripped_fw.data, stripped_fw.size); |
a750ba5f SP |
142 | if (ret < 0) { |
143 | dev_err(ctx->dev, "Transfer firmware failed%d\n", ret); | |
ae395937 | 144 | goto transfer_firmware_failed; |
a750ba5f SP |
145 | } else { |
146 | ret = wait_event_timeout(skl->boot_wait, skl->boot_complete, | |
147 | msecs_to_jiffies(SKL_IPC_BOOT_MSECS)); | |
148 | if (ret == 0) { | |
149 | dev_err(ctx->dev, "DSP boot failed, FW Ready timed-out\n"); | |
150 | ret = -EIO; | |
ae395937 | 151 | goto transfer_firmware_failed; |
a750ba5f SP |
152 | } |
153 | ||
154 | dev_dbg(ctx->dev, "Download firmware successful%d\n", ret); | |
155 | skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); | |
1665c177 | 156 | skl->fw_loaded = true; |
a750ba5f | 157 | } |
a750ba5f | 158 | return 0; |
ae395937 JK |
159 | transfer_firmware_failed: |
160 | ctx->cl_dev.ops.cl_cleanup_controller(ctx); | |
a750ba5f SP |
161 | skl_load_base_firmware_failed: |
162 | skl_dsp_disable_core(ctx); | |
84c9e283 JK |
163 | release_firmware(ctx->fw); |
164 | ctx->fw = NULL; | |
a750ba5f SP |
165 | return ret; |
166 | } | |
167 | ||
168 | static int skl_set_dsp_D0(struct sst_dsp *ctx) | |
169 | { | |
170 | int ret; | |
171 | ||
172 | ret = skl_load_base_firmware(ctx); | |
173 | if (ret < 0) { | |
174 | dev_err(ctx->dev, "unable to load firmware\n"); | |
175 | return ret; | |
176 | } | |
177 | ||
178 | skl_dsp_set_state_locked(ctx, SKL_DSP_RUNNING); | |
179 | ||
180 | return ret; | |
181 | } | |
182 | ||
183 | static int skl_set_dsp_D3(struct sst_dsp *ctx) | |
184 | { | |
185 | int ret; | |
186 | struct skl_ipc_dxstate_info dx; | |
187 | struct skl_sst *skl = ctx->thread_context; | |
188 | ||
189 | dev_dbg(ctx->dev, "In %s:\n", __func__); | |
190 | mutex_lock(&ctx->mutex); | |
191 | if (!is_skl_dsp_running(ctx)) { | |
192 | mutex_unlock(&ctx->mutex); | |
193 | return 0; | |
194 | } | |
195 | mutex_unlock(&ctx->mutex); | |
196 | ||
197 | dx.core_mask = SKL_DSP_CORE0_MASK; | |
198 | dx.dx_mask = SKL_IPC_D3_MASK; | |
199 | ret = skl_ipc_set_dx(&skl->ipc, SKL_INSTANCE_ID, SKL_BASE_FW_MODULE_ID, &dx); | |
53afce2c JK |
200 | if (ret < 0) |
201 | dev_err(ctx->dev, | |
202 | "D3 request to FW failed, continuing reset: %d", ret); | |
203 | ||
204 | /* disable Interrupt */ | |
205 | ctx->cl_dev.ops.cl_cleanup_controller(ctx); | |
206 | skl_cldma_int_disable(ctx); | |
207 | skl_ipc_op_int_disable(ctx); | |
208 | skl_ipc_int_disable(ctx); | |
a750ba5f SP |
209 | |
210 | ret = skl_dsp_disable_core(ctx); | |
211 | if (ret < 0) { | |
212 | dev_err(ctx->dev, "disable dsp core failed ret: %d\n", ret); | |
213 | ret = -EIO; | |
214 | } | |
215 | skl_dsp_set_state_locked(ctx, SKL_DSP_RESET); | |
216 | ||
217 | return ret; | |
218 | } | |
219 | ||
220 | static unsigned int skl_get_errorcode(struct sst_dsp *ctx) | |
221 | { | |
222 | return sst_dsp_shim_read(ctx, SKL_ADSP_ERROR_CODE); | |
223 | } | |
224 | ||
6c5768b3 D |
225 | /* |
226 | * since get/set_module are called from DAPM context, | |
227 | * we don't need lock for usage count | |
228 | */ | |
db4e5613 | 229 | static int skl_get_module(struct sst_dsp *ctx, u16 mod_id) |
6c5768b3 D |
230 | { |
231 | struct skl_module_table *module; | |
232 | ||
233 | list_for_each_entry(module, &ctx->module_list, list) { | |
234 | if (module->mod_info->mod_id == mod_id) | |
235 | return ++module->usage_cnt; | |
236 | } | |
237 | ||
238 | return -EINVAL; | |
239 | } | |
240 | ||
db4e5613 | 241 | static int skl_put_module(struct sst_dsp *ctx, u16 mod_id) |
6c5768b3 D |
242 | { |
243 | struct skl_module_table *module; | |
244 | ||
245 | list_for_each_entry(module, &ctx->module_list, list) { | |
246 | if (module->mod_info->mod_id == mod_id) | |
247 | return --module->usage_cnt; | |
248 | } | |
249 | ||
250 | return -EINVAL; | |
251 | } | |
252 | ||
253 | static struct skl_module_table *skl_fill_module_table(struct sst_dsp *ctx, | |
254 | char *mod_name, int mod_id) | |
255 | { | |
256 | const struct firmware *fw; | |
257 | struct skl_module_table *skl_module; | |
258 | unsigned int size; | |
259 | int ret; | |
260 | ||
261 | ret = request_firmware(&fw, mod_name, ctx->dev); | |
262 | if (ret < 0) { | |
263 | dev_err(ctx->dev, "Request Module %s failed :%d\n", | |
264 | mod_name, ret); | |
265 | return NULL; | |
266 | } | |
267 | ||
268 | skl_module = devm_kzalloc(ctx->dev, sizeof(*skl_module), GFP_KERNEL); | |
269 | if (skl_module == NULL) { | |
270 | release_firmware(fw); | |
271 | return NULL; | |
272 | } | |
273 | ||
274 | size = sizeof(*skl_module->mod_info); | |
275 | skl_module->mod_info = devm_kzalloc(ctx->dev, size, GFP_KERNEL); | |
276 | if (skl_module->mod_info == NULL) { | |
277 | release_firmware(fw); | |
278 | return NULL; | |
279 | } | |
280 | ||
281 | skl_module->mod_info->mod_id = mod_id; | |
282 | skl_module->mod_info->fw = fw; | |
283 | list_add(&skl_module->list, &ctx->module_list); | |
284 | ||
285 | return skl_module; | |
286 | } | |
287 | ||
288 | /* get a module from it's unique ID */ | |
289 | static struct skl_module_table *skl_module_get_from_id( | |
290 | struct sst_dsp *ctx, u16 mod_id) | |
291 | { | |
292 | struct skl_module_table *module; | |
293 | ||
294 | if (list_empty(&ctx->module_list)) { | |
295 | dev_err(ctx->dev, "Module list is empty\n"); | |
296 | return NULL; | |
297 | } | |
298 | ||
299 | list_for_each_entry(module, &ctx->module_list, list) { | |
300 | if (module->mod_info->mod_id == mod_id) | |
301 | return module; | |
302 | } | |
303 | ||
304 | return NULL; | |
305 | } | |
306 | ||
307 | static int skl_transfer_module(struct sst_dsp *ctx, | |
308 | struct skl_load_module_info *module) | |
309 | { | |
310 | int ret; | |
311 | struct skl_sst *skl = ctx->thread_context; | |
312 | ||
313 | ret = ctx->cl_dev.ops.cl_copy_to_dmabuf(ctx, module->fw->data, | |
314 | module->fw->size); | |
315 | if (ret < 0) | |
316 | return ret; | |
317 | ||
318 | ret = skl_ipc_load_modules(&skl->ipc, SKL_NUM_MODULES, | |
319 | (void *)&module->mod_id); | |
320 | if (ret < 0) | |
321 | dev_err(ctx->dev, "Failed to Load module: %d\n", ret); | |
322 | ||
323 | ctx->cl_dev.ops.cl_stop_dma(ctx); | |
324 | ||
325 | return ret; | |
326 | } | |
327 | ||
09305da9 | 328 | static int skl_load_module(struct sst_dsp *ctx, u16 mod_id, u8 *guid) |
6c5768b3 D |
329 | { |
330 | struct skl_module_table *module_entry = NULL; | |
331 | int ret = 0; | |
332 | char mod_name[64]; /* guid str = 32 chars + 4 hyphens */ | |
09305da9 | 333 | uuid_le *uuid_mod; |
6c5768b3 | 334 | |
09305da9 SN |
335 | uuid_mod = (uuid_le *)guid; |
336 | snprintf(mod_name, sizeof(mod_name), "%s%pUL%s", | |
337 | "intel/dsp_fw_", uuid_mod, ".bin"); | |
6c5768b3 D |
338 | |
339 | module_entry = skl_module_get_from_id(ctx, mod_id); | |
340 | if (module_entry == NULL) { | |
341 | module_entry = skl_fill_module_table(ctx, mod_name, mod_id); | |
342 | if (module_entry == NULL) { | |
343 | dev_err(ctx->dev, "Failed to Load module\n"); | |
344 | return -EINVAL; | |
345 | } | |
346 | } | |
347 | ||
348 | if (!module_entry->usage_cnt) { | |
349 | ret = skl_transfer_module(ctx, module_entry->mod_info); | |
350 | if (ret < 0) { | |
351 | dev_err(ctx->dev, "Failed to Load module\n"); | |
352 | return ret; | |
353 | } | |
354 | } | |
355 | ||
356 | ret = skl_get_module(ctx, mod_id); | |
357 | ||
358 | return ret; | |
359 | } | |
360 | ||
361 | static int skl_unload_module(struct sst_dsp *ctx, u16 mod_id) | |
362 | { | |
db4e5613 | 363 | int usage_cnt; |
6c5768b3 D |
364 | struct skl_sst *skl = ctx->thread_context; |
365 | int ret = 0; | |
366 | ||
367 | usage_cnt = skl_put_module(ctx, mod_id); | |
368 | if (usage_cnt < 0) { | |
369 | dev_err(ctx->dev, "Module bad usage cnt!:%d\n", usage_cnt); | |
370 | return -EIO; | |
371 | } | |
372 | ret = skl_ipc_unload_modules(&skl->ipc, | |
373 | SKL_NUM_MODULES, &mod_id); | |
374 | if (ret < 0) { | |
375 | dev_err(ctx->dev, "Failed to UnLoad module\n"); | |
376 | skl_get_module(ctx, mod_id); | |
377 | return ret; | |
378 | } | |
379 | ||
380 | return ret; | |
381 | } | |
382 | ||
fe3f4442 D |
383 | void skl_clear_module_cnt(struct sst_dsp *ctx) |
384 | { | |
385 | struct skl_module_table *module; | |
386 | ||
387 | list_for_each_entry(module, &ctx->module_list, list) { | |
388 | module->usage_cnt = 0; | |
389 | } | |
390 | } | |
391 | EXPORT_SYMBOL_GPL(skl_clear_module_cnt); | |
392 | ||
6c5768b3 D |
393 | static void skl_clear_module_table(struct sst_dsp *ctx) |
394 | { | |
395 | struct skl_module_table *module, *tmp; | |
396 | ||
397 | if (list_empty(&ctx->module_list)) | |
398 | return; | |
399 | ||
400 | list_for_each_entry_safe(module, tmp, &ctx->module_list, list) { | |
401 | list_del(&module->list); | |
402 | release_firmware(module->mod_info->fw); | |
403 | } | |
404 | } | |
405 | ||
a750ba5f SP |
406 | static struct skl_dsp_fw_ops skl_fw_ops = { |
407 | .set_state_D0 = skl_set_dsp_D0, | |
408 | .set_state_D3 = skl_set_dsp_D3, | |
409 | .load_fw = skl_load_base_firmware, | |
410 | .get_fw_errcode = skl_get_errorcode, | |
6c5768b3 D |
411 | .load_mod = skl_load_module, |
412 | .unload_mod = skl_unload_module, | |
a750ba5f SP |
413 | }; |
414 | ||
415 | static struct sst_ops skl_ops = { | |
416 | .irq_handler = skl_dsp_sst_interrupt, | |
417 | .write = sst_shim32_write, | |
418 | .read = sst_shim32_read, | |
419 | .ram_read = sst_memcpy_fromio_32, | |
420 | .ram_write = sst_memcpy_toio_32, | |
421 | .free = skl_dsp_free, | |
422 | }; | |
423 | ||
424 | static struct sst_dsp_device skl_dev = { | |
425 | .thread = skl_dsp_irq_thread_handler, | |
426 | .ops = &skl_ops, | |
427 | }; | |
428 | ||
429 | int skl_sst_dsp_init(struct device *dev, void __iomem *mmio_base, int irq, | |
aecf6fd8 | 430 | const char *fw_name, struct skl_dsp_loader_ops dsp_ops, struct skl_sst **dsp) |
a750ba5f SP |
431 | { |
432 | struct skl_sst *skl; | |
433 | struct sst_dsp *sst; | |
434 | int ret; | |
435 | ||
436 | skl = devm_kzalloc(dev, sizeof(*skl), GFP_KERNEL); | |
437 | if (skl == NULL) | |
438 | return -ENOMEM; | |
439 | ||
440 | skl->dev = dev; | |
441 | skl_dev.thread_context = skl; | |
06711051 | 442 | INIT_LIST_HEAD(&skl->uuid_list); |
a750ba5f SP |
443 | |
444 | skl->dsp = skl_dsp_ctx_init(dev, &skl_dev, irq); | |
445 | if (!skl->dsp) { | |
446 | dev_err(skl->dev, "%s: no device\n", __func__); | |
447 | return -ENODEV; | |
448 | } | |
449 | ||
450 | sst = skl->dsp; | |
451 | ||
aecf6fd8 | 452 | sst->fw_name = fw_name; |
a750ba5f SP |
453 | sst->addr.lpe = mmio_base; |
454 | sst->addr.shim = mmio_base; | |
455 | sst_dsp_mailbox_init(sst, (SKL_ADSP_SRAM0_BASE + SKL_ADSP_W0_STAT_SZ), | |
456 | SKL_ADSP_W0_UP_SZ, SKL_ADSP_SRAM1_BASE, SKL_ADSP_W1_SZ); | |
457 | ||
6c5768b3 | 458 | INIT_LIST_HEAD(&sst->module_list); |
a750ba5f SP |
459 | sst->dsp_ops = dsp_ops; |
460 | sst->fw_ops = skl_fw_ops; | |
461 | ||
462 | ret = skl_ipc_init(dev, skl); | |
463 | if (ret) | |
464 | return ret; | |
465 | ||
a750ba5f SP |
466 | ret = sst->fw_ops.load_fw(sst); |
467 | if (ret < 0) { | |
468 | dev_err(dev, "Load base fw failed : %d", ret); | |
b4fe965f | 469 | goto cleanup; |
a750ba5f SP |
470 | } |
471 | ||
472 | if (dsp) | |
473 | *dsp = skl; | |
474 | ||
b4fe965f | 475 | return ret; |
a750ba5f | 476 | |
b4fe965f SP |
477 | cleanup: |
478 | skl_sst_dsp_cleanup(dev, skl); | |
a750ba5f SP |
479 | return ret; |
480 | } | |
481 | EXPORT_SYMBOL_GPL(skl_sst_dsp_init); | |
482 | ||
483 | void skl_sst_dsp_cleanup(struct device *dev, struct skl_sst *ctx) | |
484 | { | |
6c5768b3 | 485 | skl_clear_module_table(ctx->dsp); |
06711051 | 486 | skl_freeup_uuid_list(ctx); |
a750ba5f | 487 | skl_ipc_free(&ctx->ipc); |
a750ba5f | 488 | ctx->dsp->ops->free(ctx->dsp); |
95536d8c D |
489 | if (ctx->boot_complete) { |
490 | ctx->dsp->cl_dev.ops.cl_cleanup_controller(ctx->dsp); | |
491 | skl_cldma_int_disable(ctx->dsp); | |
492 | } | |
a750ba5f SP |
493 | } |
494 | EXPORT_SYMBOL_GPL(skl_sst_dsp_cleanup); | |
495 | ||
496 | MODULE_LICENSE("GPL v2"); | |
497 | MODULE_DESCRIPTION("Intel Skylake IPC driver"); |