diff options
Diffstat (limited to 'sound/soc/intel/atom/sst/sst_loader.c')
-rw-r--r-- | sound/soc/intel/atom/sst/sst_loader.c | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/sound/soc/intel/atom/sst/sst_loader.c b/sound/soc/intel/atom/sst/sst_loader.c new file mode 100644 index 000000000000..33917146d9c4 --- /dev/null +++ b/sound/soc/intel/atom/sst/sst_loader.c | |||
@@ -0,0 +1,463 @@ | |||
1 | /* | ||
2 | * sst_dsp.c - Intel SST Driver for audio engine | ||
3 | * | ||
4 | * Copyright (C) 2008-14 Intel Corp | ||
5 | * Authors: Vinod Koul <vinod.koul@intel.com> | ||
6 | * Harsha Priya <priya.harsha@intel.com> | ||
7 | * Dharageswari R <dharageswari.r@intel.com> | ||
8 | * KP Jeeja <jeeja.kp@intel.com> | ||
9 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; version 2 of the License. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, but | ||
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
18 | * General Public License for more details. | ||
19 | * | ||
20 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
21 | * | ||
22 | * This file contains all dsp controlling functions like firmware download, | ||
23 | * setting/resetting dsp cores, etc | ||
24 | */ | ||
25 | #include <linux/pci.h> | ||
26 | #include <linux/delay.h> | ||
27 | #include <linux/fs.h> | ||
28 | #include <linux/sched.h> | ||
29 | #include <linux/firmware.h> | ||
30 | #include <linux/dmaengine.h> | ||
31 | #include <linux/pm_runtime.h> | ||
32 | #include <linux/pm_qos.h> | ||
33 | #include <sound/core.h> | ||
34 | #include <sound/pcm.h> | ||
35 | #include <sound/soc.h> | ||
36 | #include <sound/compress_driver.h> | ||
37 | #include <asm/platform_sst_audio.h> | ||
38 | #include "../sst-mfld-platform.h" | ||
39 | #include "sst.h" | ||
40 | #include "../../common/sst-dsp.h" | ||
41 | |||
42 | void memcpy32_toio(void __iomem *dst, const void *src, int count) | ||
43 | { | ||
44 | /* __iowrite32_copy uses 32-bit count values so divide by 4 for | ||
45 | * right count in words | ||
46 | */ | ||
47 | __iowrite32_copy(dst, src, count/4); | ||
48 | } | ||
49 | |||
50 | void memcpy32_fromio(void *dst, const void __iomem *src, int count) | ||
51 | { | ||
52 | /* __iowrite32_copy uses 32-bit count values so divide by 4 for | ||
53 | * right count in words | ||
54 | */ | ||
55 | __iowrite32_copy(dst, src, count/4); | ||
56 | } | ||
57 | |||
58 | /** | ||
59 | * intel_sst_reset_dsp_mrfld - Resetting SST DSP | ||
60 | * | ||
61 | * This resets DSP in case of MRFLD platfroms | ||
62 | */ | ||
63 | int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx) | ||
64 | { | ||
65 | union config_status_reg_mrfld csr; | ||
66 | |||
67 | dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n"); | ||
68 | csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); | ||
69 | |||
70 | dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); | ||
71 | |||
72 | csr.full |= 0x7; | ||
73 | sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); | ||
74 | csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); | ||
75 | |||
76 | dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); | ||
77 | |||
78 | csr.full &= ~(0x1); | ||
79 | sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); | ||
80 | |||
81 | csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); | ||
82 | dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); | ||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * sst_start_merrifield - Start the SST DSP processor | ||
88 | * | ||
89 | * This starts the DSP in MERRIFIELD platfroms | ||
90 | */ | ||
91 | int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx) | ||
92 | { | ||
93 | union config_status_reg_mrfld csr; | ||
94 | |||
95 | dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n"); | ||
96 | csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); | ||
97 | dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); | ||
98 | |||
99 | csr.full |= 0x7; | ||
100 | sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); | ||
101 | |||
102 | csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); | ||
103 | dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full); | ||
104 | |||
105 | csr.part.xt_snoop = 1; | ||
106 | csr.full &= ~(0x5); | ||
107 | sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full); | ||
108 | |||
109 | csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR); | ||
110 | dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n", | ||
111 | csr.full); | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size, | ||
116 | struct fw_module_header **module, u32 *num_modules) | ||
117 | { | ||
118 | struct sst_fw_header *header; | ||
119 | const void *sst_fw_in_mem = ctx->fw_in_mem; | ||
120 | |||
121 | dev_dbg(ctx->dev, "Enter\n"); | ||
122 | |||
123 | /* Read the header information from the data pointer */ | ||
124 | header = (struct sst_fw_header *)sst_fw_in_mem; | ||
125 | dev_dbg(ctx->dev, | ||
126 | "header sign=%s size=%x modules=%x fmt=%x size=%zx\n", | ||
127 | header->signature, header->file_size, header->modules, | ||
128 | header->file_format, sizeof(*header)); | ||
129 | |||
130 | /* verify FW */ | ||
131 | if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) || | ||
132 | (size != header->file_size + sizeof(*header))) { | ||
133 | /* Invalid FW signature */ | ||
134 | dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n"); | ||
135 | return -EINVAL; | ||
136 | } | ||
137 | *num_modules = header->modules; | ||
138 | *module = (void *)sst_fw_in_mem + sizeof(*header); | ||
139 | |||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | /* | ||
144 | * sst_fill_memcpy_list - Fill the memcpy list | ||
145 | * | ||
146 | * @memcpy_list: List to be filled | ||
147 | * @destn: Destination addr to be filled in the list | ||
148 | * @src: Source addr to be filled in the list | ||
149 | * @size: Size to be filled in the list | ||
150 | * | ||
151 | * Adds the node to the list after required fields | ||
152 | * are populated in the node | ||
153 | */ | ||
154 | static int sst_fill_memcpy_list(struct list_head *memcpy_list, | ||
155 | void *destn, const void *src, u32 size, bool is_io) | ||
156 | { | ||
157 | struct sst_memcpy_list *listnode; | ||
158 | |||
159 | listnode = kzalloc(sizeof(*listnode), GFP_KERNEL); | ||
160 | if (listnode == NULL) | ||
161 | return -ENOMEM; | ||
162 | listnode->dstn = destn; | ||
163 | listnode->src = src; | ||
164 | listnode->size = size; | ||
165 | listnode->is_io = is_io; | ||
166 | list_add_tail(&listnode->memcpylist, memcpy_list); | ||
167 | |||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | /** | ||
172 | * sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list | ||
173 | * | ||
174 | * @sst_drv_ctx : driver context | ||
175 | * @module : FW module header | ||
176 | * @memcpy_list : Pointer to the list to be populated | ||
177 | * Create the memcpy list as the number of block to be copied | ||
178 | * returns error or 0 if module sizes are proper | ||
179 | */ | ||
180 | static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx, | ||
181 | struct fw_module_header *module, struct list_head *memcpy_list) | ||
182 | { | ||
183 | struct fw_block_info *block; | ||
184 | u32 count; | ||
185 | int ret_val = 0; | ||
186 | void __iomem *ram_iomem; | ||
187 | |||
188 | dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n", | ||
189 | module->signature, module->mod_size, | ||
190 | module->blocks, module->type); | ||
191 | dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point); | ||
192 | |||
193 | block = (void *)module + sizeof(*module); | ||
194 | |||
195 | for (count = 0; count < module->blocks; count++) { | ||
196 | if (block->size <= 0) { | ||
197 | dev_err(sst_drv_ctx->dev, "block size invalid\n"); | ||
198 | return -EINVAL; | ||
199 | } | ||
200 | switch (block->type) { | ||
201 | case SST_IRAM: | ||
202 | ram_iomem = sst_drv_ctx->iram; | ||
203 | break; | ||
204 | case SST_DRAM: | ||
205 | ram_iomem = sst_drv_ctx->dram; | ||
206 | break; | ||
207 | case SST_DDR: | ||
208 | ram_iomem = sst_drv_ctx->ddr; | ||
209 | break; | ||
210 | case SST_CUSTOM_INFO: | ||
211 | block = (void *)block + sizeof(*block) + block->size; | ||
212 | continue; | ||
213 | default: | ||
214 | dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n", | ||
215 | block->type, count); | ||
216 | return -EINVAL; | ||
217 | } | ||
218 | |||
219 | ret_val = sst_fill_memcpy_list(memcpy_list, | ||
220 | ram_iomem + block->ram_offset, | ||
221 | (void *)block + sizeof(*block), block->size, 1); | ||
222 | if (ret_val) | ||
223 | return ret_val; | ||
224 | |||
225 | block = (void *)block + sizeof(*block) + block->size; | ||
226 | } | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | /** | ||
231 | * sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy | ||
232 | * | ||
233 | * @ctx : pointer to drv context | ||
234 | * @size : size of the firmware | ||
235 | * @fw_list : pointer to list_head to be populated | ||
236 | * This function parses the FW image and saves the parsed image in the list | ||
237 | * for memcpy | ||
238 | */ | ||
239 | static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size, | ||
240 | struct list_head *fw_list) | ||
241 | { | ||
242 | struct fw_module_header *module; | ||
243 | u32 count, num_modules; | ||
244 | int ret_val; | ||
245 | |||
246 | ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules); | ||
247 | if (ret_val) | ||
248 | return ret_val; | ||
249 | |||
250 | for (count = 0; count < num_modules; count++) { | ||
251 | ret_val = sst_parse_module_memcpy(ctx, module, fw_list); | ||
252 | if (ret_val) | ||
253 | return ret_val; | ||
254 | module = (void *)module + sizeof(*module) + module->mod_size; | ||
255 | } | ||
256 | |||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | /** | ||
261 | * sst_do_memcpy - function initiates the memcpy | ||
262 | * | ||
263 | * @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated | ||
264 | * | ||
265 | * Triggers the memcpy | ||
266 | */ | ||
267 | static void sst_do_memcpy(struct list_head *memcpy_list) | ||
268 | { | ||
269 | struct sst_memcpy_list *listnode; | ||
270 | |||
271 | list_for_each_entry(listnode, memcpy_list, memcpylist) { | ||
272 | if (listnode->is_io == true) | ||
273 | memcpy32_toio((void __iomem *)listnode->dstn, | ||
274 | listnode->src, listnode->size); | ||
275 | else | ||
276 | memcpy(listnode->dstn, listnode->src, listnode->size); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx) | ||
281 | { | ||
282 | struct sst_memcpy_list *listnode, *tmplistnode; | ||
283 | |||
284 | /* Free the list */ | ||
285 | if (!list_empty(&sst_drv_ctx->memcpy_list)) { | ||
286 | list_for_each_entry_safe(listnode, tmplistnode, | ||
287 | &sst_drv_ctx->memcpy_list, memcpylist) { | ||
288 | list_del(&listnode->memcpylist); | ||
289 | kfree(listnode); | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | |||
294 | static int sst_cache_and_parse_fw(struct intel_sst_drv *sst, | ||
295 | const struct firmware *fw) | ||
296 | { | ||
297 | int retval = 0; | ||
298 | |||
299 | sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL); | ||
300 | if (!sst->fw_in_mem) { | ||
301 | retval = -ENOMEM; | ||
302 | goto end_release; | ||
303 | } | ||
304 | dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem); | ||
305 | dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem)); | ||
306 | memcpy(sst->fw_in_mem, fw->data, fw->size); | ||
307 | retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list); | ||
308 | if (retval) { | ||
309 | dev_err(sst->dev, "Failed to parse fw\n"); | ||
310 | kfree(sst->fw_in_mem); | ||
311 | sst->fw_in_mem = NULL; | ||
312 | } | ||
313 | |||
314 | end_release: | ||
315 | release_firmware(fw); | ||
316 | return retval; | ||
317 | |||
318 | } | ||
319 | |||
320 | void sst_firmware_load_cb(const struct firmware *fw, void *context) | ||
321 | { | ||
322 | struct intel_sst_drv *ctx = context; | ||
323 | |||
324 | dev_dbg(ctx->dev, "Enter\n"); | ||
325 | |||
326 | if (fw == NULL) { | ||
327 | dev_err(ctx->dev, "request fw failed\n"); | ||
328 | return; | ||
329 | } | ||
330 | |||
331 | mutex_lock(&ctx->sst_lock); | ||
332 | |||
333 | if (ctx->sst_state != SST_RESET || | ||
334 | ctx->fw_in_mem != NULL) { | ||
335 | release_firmware(fw); | ||
336 | mutex_unlock(&ctx->sst_lock); | ||
337 | return; | ||
338 | } | ||
339 | |||
340 | dev_dbg(ctx->dev, "Request Fw completed\n"); | ||
341 | sst_cache_and_parse_fw(ctx, fw); | ||
342 | mutex_unlock(&ctx->sst_lock); | ||
343 | } | ||
344 | |||
345 | /* | ||
346 | * sst_request_fw - requests audio fw from kernel and saves a copy | ||
347 | * | ||
348 | * This function requests the SST FW from the kernel, parses it and | ||
349 | * saves a copy in the driver context | ||
350 | */ | ||
351 | static int sst_request_fw(struct intel_sst_drv *sst) | ||
352 | { | ||
353 | int retval = 0; | ||
354 | const struct firmware *fw; | ||
355 | |||
356 | retval = request_firmware(&fw, sst->firmware_name, sst->dev); | ||
357 | if (fw == NULL) { | ||
358 | dev_err(sst->dev, "fw is returning as null\n"); | ||
359 | return -EINVAL; | ||
360 | } | ||
361 | if (retval) { | ||
362 | dev_err(sst->dev, "request fw failed %d\n", retval); | ||
363 | return retval; | ||
364 | } | ||
365 | mutex_lock(&sst->sst_lock); | ||
366 | retval = sst_cache_and_parse_fw(sst, fw); | ||
367 | mutex_unlock(&sst->sst_lock); | ||
368 | |||
369 | return retval; | ||
370 | } | ||
371 | |||
372 | /* | ||
373 | * Writing the DDR physical base to DCCM offset | ||
374 | * so that FW can use it to setup TLB | ||
375 | */ | ||
376 | static void sst_dccm_config_write(void __iomem *dram_base, | ||
377 | unsigned int ddr_base) | ||
378 | { | ||
379 | void __iomem *addr; | ||
380 | u32 bss_reset = 0; | ||
381 | |||
382 | addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET); | ||
383 | memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32)); | ||
384 | bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT); | ||
385 | addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET); | ||
386 | memcpy32_toio(addr, &bss_reset, sizeof(u32)); | ||
387 | |||
388 | } | ||
389 | |||
390 | void sst_post_download_mrfld(struct intel_sst_drv *ctx) | ||
391 | { | ||
392 | sst_dccm_config_write(ctx->dram, ctx->ddr_base); | ||
393 | dev_dbg(ctx->dev, "config written to DCCM\n"); | ||
394 | } | ||
395 | |||
396 | /** | ||
397 | * sst_load_fw - function to load FW into DSP | ||
398 | * Transfers the FW to DSP using dma/memcpy | ||
399 | */ | ||
400 | int sst_load_fw(struct intel_sst_drv *sst_drv_ctx) | ||
401 | { | ||
402 | int ret_val = 0; | ||
403 | struct sst_block *block; | ||
404 | |||
405 | dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n"); | ||
406 | |||
407 | if (sst_drv_ctx->sst_state != SST_RESET || | ||
408 | sst_drv_ctx->sst_state == SST_SHUTDOWN) | ||
409 | return -EAGAIN; | ||
410 | |||
411 | if (!sst_drv_ctx->fw_in_mem) { | ||
412 | dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n"); | ||
413 | ret_val = sst_request_fw(sst_drv_ctx); | ||
414 | if (ret_val) | ||
415 | return ret_val; | ||
416 | } | ||
417 | |||
418 | BUG_ON(!sst_drv_ctx->fw_in_mem); | ||
419 | block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID); | ||
420 | if (block == NULL) | ||
421 | return -ENOMEM; | ||
422 | |||
423 | /* Prevent C-states beyond C6 */ | ||
424 | pm_qos_update_request(sst_drv_ctx->qos, 0); | ||
425 | |||
426 | sst_drv_ctx->sst_state = SST_FW_LOADING; | ||
427 | |||
428 | ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx); | ||
429 | if (ret_val) | ||
430 | goto restore; | ||
431 | |||
432 | sst_do_memcpy(&sst_drv_ctx->memcpy_list); | ||
433 | |||
434 | /* Write the DRAM/DCCM config before enabling FW */ | ||
435 | if (sst_drv_ctx->ops->post_download) | ||
436 | sst_drv_ctx->ops->post_download(sst_drv_ctx); | ||
437 | |||
438 | /* bring sst out of reset */ | ||
439 | ret_val = sst_drv_ctx->ops->start(sst_drv_ctx); | ||
440 | if (ret_val) | ||
441 | goto restore; | ||
442 | |||
443 | ret_val = sst_wait_timeout(sst_drv_ctx, block); | ||
444 | if (ret_val) { | ||
445 | dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val); | ||
446 | /* FW download failed due to timeout */ | ||
447 | ret_val = -EBUSY; | ||
448 | |||
449 | } | ||
450 | |||
451 | |||
452 | restore: | ||
453 | /* Re-enable Deeper C-states beyond C6 */ | ||
454 | pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE); | ||
455 | sst_free_block(sst_drv_ctx, block); | ||
456 | dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n"); | ||
457 | |||
458 | if (sst_drv_ctx->ops->restore_dsp_context) | ||
459 | sst_drv_ctx->ops->restore_dsp_context(); | ||
460 | sst_drv_ctx->sst_state = SST_FW_RUNNING; | ||
461 | return ret_val; | ||
462 | } | ||
463 | |||