| // SPDX-License-Identifier: GPL-2.0-only |
| // |
| // Copyright(c) 2020 Intel Corporation. All rights reserved. |
| // |
| // Author: Cezary Rojewski <cezary.rojewski@intel.com> |
| // |
| |
| #include <linux/dma-mapping.h> |
| #include <linux/firmware.h> |
| #include <linux/slab.h> |
| #include "core.h" |
| #include "registers.h" |
| |
| /* FW load (200ms) plus operational delays */ |
| #define FW_READY_TIMEOUT_MS 250 |
| |
| #define FW_SIGNATURE "$SST" |
| #define FW_SIGNATURE_SIZE 4 |
| |
| struct catpt_fw_hdr { |
| char signature[FW_SIGNATURE_SIZE]; |
| u32 file_size; |
| u32 modules; |
| u32 file_format; |
| u32 reserved[4]; |
| } __packed; |
| |
| struct catpt_fw_mod_hdr { |
| char signature[FW_SIGNATURE_SIZE]; |
| u32 mod_size; |
| u32 blocks; |
| u16 slot; |
| u16 module_id; |
| u32 entry_point; |
| u32 persistent_size; |
| u32 scratch_size; |
| } __packed; |
| |
| enum catpt_ram_type { |
| CATPT_RAM_TYPE_IRAM = 1, |
| CATPT_RAM_TYPE_DRAM = 2, |
| /* DRAM with module's initial state */ |
| CATPT_RAM_TYPE_INSTANCE = 3, |
| }; |
| |
| struct catpt_fw_block_hdr { |
| u32 ram_type; |
| u32 size; |
| u32 ram_offset; |
| u32 rsvd; |
| } __packed; |
| |
| void catpt_sram_init(struct resource *sram, u32 start, u32 size) |
| { |
| sram->start = start; |
| sram->end = start + size - 1; |
| } |
| |
| void catpt_sram_free(struct resource *sram) |
| { |
| struct resource *res, *save; |
| |
| for (res = sram->child; res;) { |
| save = res->sibling; |
| release_resource(res); |
| kfree(res); |
| res = save; |
| } |
| } |
| |
| struct resource * |
| catpt_request_region(struct resource *root, resource_size_t size) |
| { |
| struct resource *res = root->child; |
| resource_size_t addr = root->start; |
| |
| for (;;) { |
| if (res->start - addr >= size) |
| break; |
| addr = res->end + 1; |
| res = res->sibling; |
| if (!res) |
| return NULL; |
| } |
| |
| return __request_region(root, addr, size, NULL, 0); |
| } |
| |
| int catpt_store_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) |
| { |
| struct catpt_stream_runtime *stream; |
| |
| list_for_each_entry(stream, &cdev->stream_list, node) { |
| u32 off, size; |
| int ret; |
| |
| off = stream->persistent->start; |
| size = resource_size(stream->persistent); |
| dev_dbg(cdev->dev, "storing stream %d ctx: off 0x%08x size %d\n", |
| stream->info.stream_hw_id, off, size); |
| |
| ret = catpt_dma_memcpy_fromdsp(cdev, chan, |
| cdev->dxbuf_paddr + off, |
| cdev->lpe_base + off, |
| ALIGN(size, 4)); |
| if (ret) { |
| dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int catpt_store_module_states(struct catpt_dev *cdev, struct dma_chan *chan) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(cdev->modules); i++) { |
| struct catpt_module_type *type; |
| u32 off; |
| int ret; |
| |
| type = &cdev->modules[i]; |
| if (!type->loaded || !type->state_size) |
| continue; |
| |
| off = type->state_offset; |
| dev_dbg(cdev->dev, "storing mod %d state: off 0x%08x size %d\n", |
| i, off, type->state_size); |
| |
| ret = catpt_dma_memcpy_fromdsp(cdev, chan, |
| cdev->dxbuf_paddr + off, |
| cdev->lpe_base + off, |
| ALIGN(type->state_size, 4)); |
| if (ret) { |
| dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int catpt_store_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) |
| { |
| int i; |
| |
| for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) { |
| struct catpt_save_meminfo *info; |
| u32 off; |
| int ret; |
| |
| info = &cdev->dx_ctx.meminfo[i]; |
| if (info->source != CATPT_DX_TYPE_MEMORY_DUMP) |
| continue; |
| |
| off = catpt_to_host_offset(info->offset); |
| if (off < cdev->dram.start || off > cdev->dram.end) |
| continue; |
| |
| dev_dbg(cdev->dev, "storing memdump: off 0x%08x size %d\n", |
| off, info->size); |
| |
| ret = catpt_dma_memcpy_fromdsp(cdev, chan, |
| cdev->dxbuf_paddr + off, |
| cdev->lpe_base + off, |
| ALIGN(info->size, 4)); |
| if (ret) { |
| dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| catpt_restore_streams_context(struct catpt_dev *cdev, struct dma_chan *chan) |
| { |
| struct catpt_stream_runtime *stream; |
| |
| list_for_each_entry(stream, &cdev->stream_list, node) { |
| u32 off, size; |
| int ret; |
| |
| off = stream->persistent->start; |
| size = resource_size(stream->persistent); |
| dev_dbg(cdev->dev, "restoring stream %d ctx: off 0x%08x size %d\n", |
| stream->info.stream_hw_id, off, size); |
| |
| ret = catpt_dma_memcpy_todsp(cdev, chan, |
| cdev->lpe_base + off, |
| cdev->dxbuf_paddr + off, |
| ALIGN(size, 4)); |
| if (ret) { |
| dev_err(cdev->dev, "memcpy fromdsp failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int catpt_restore_memdumps(struct catpt_dev *cdev, struct dma_chan *chan) |
| { |
| int i; |
| |
| for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) { |
| struct catpt_save_meminfo *info; |
| u32 off; |
| int ret; |
| |
| info = &cdev->dx_ctx.meminfo[i]; |
| if (info->source != CATPT_DX_TYPE_MEMORY_DUMP) |
| continue; |
| |
| off = catpt_to_host_offset(info->offset); |
| if (off < cdev->dram.start || off > cdev->dram.end) |
| continue; |
| |
| dev_dbg(cdev->dev, "restoring memdump: off 0x%08x size %d\n", |
| off, info->size); |
| |
| ret = catpt_dma_memcpy_todsp(cdev, chan, |
| cdev->lpe_base + off, |
| cdev->dxbuf_paddr + off, |
| ALIGN(info->size, 4)); |
| if (ret) { |
| dev_err(cdev->dev, "restore block failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int catpt_restore_fwimage(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_block_hdr *blk) |
| { |
| struct resource r1, r2, common; |
| int i; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| blk, sizeof(*blk), false); |
| |
| r1.start = cdev->dram.start + blk->ram_offset; |
| r1.end = r1.start + blk->size - 1; |
| /* advance to data area */ |
| paddr += sizeof(*blk); |
| |
| for (i = 0; i < cdev->dx_ctx.num_meminfo; i++) { |
| struct catpt_save_meminfo *info; |
| u32 off; |
| int ret; |
| |
| info = &cdev->dx_ctx.meminfo[i]; |
| |
| if (info->source != CATPT_DX_TYPE_FW_IMAGE) |
| continue; |
| |
| off = catpt_to_host_offset(info->offset); |
| if (off < cdev->dram.start || off > cdev->dram.end) |
| continue; |
| |
| r2.start = off; |
| r2.end = r2.start + info->size - 1; |
| |
| if (!resource_intersection(&r2, &r1, &common)) |
| continue; |
| /* calculate start offset of common data area */ |
| off = common.start - r1.start; |
| |
| dev_dbg(cdev->dev, "restoring fwimage: %pr\n", &common); |
| |
| ret = catpt_dma_memcpy_todsp(cdev, chan, common.start, |
| paddr + off, |
| resource_size(&common)); |
| if (ret) { |
| dev_err(cdev->dev, "memcpy todsp failed: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int catpt_load_block(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_block_hdr *blk, bool alloc) |
| { |
| struct resource *sram, *res; |
| dma_addr_t dst_addr; |
| int ret; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| blk, sizeof(*blk), false); |
| |
| switch (blk->ram_type) { |
| case CATPT_RAM_TYPE_IRAM: |
| sram = &cdev->iram; |
| break; |
| default: |
| sram = &cdev->dram; |
| break; |
| } |
| |
| dst_addr = sram->start + blk->ram_offset; |
| if (alloc) { |
| res = __request_region(sram, dst_addr, blk->size, NULL, 0); |
| if (!res) |
| return -EBUSY; |
| } |
| |
| /* advance to data area */ |
| paddr += sizeof(*blk); |
| |
| ret = catpt_dma_memcpy_todsp(cdev, chan, dst_addr, paddr, blk->size); |
| if (ret) { |
| dev_err(cdev->dev, "memcpy error: %d\n", ret); |
| __release_region(sram, dst_addr, blk->size); |
| } |
| |
| return ret; |
| } |
| |
| static int catpt_restore_basefw(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_mod_hdr *basefw) |
| { |
| u32 offset = sizeof(*basefw); |
| int ret, i; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| basefw, sizeof(*basefw), false); |
| |
| /* restore basefw image */ |
| for (i = 0; i < basefw->blocks; i++) { |
| struct catpt_fw_block_hdr *blk; |
| |
| blk = (struct catpt_fw_block_hdr *)((u8 *)basefw + offset); |
| |
| switch (blk->ram_type) { |
| case CATPT_RAM_TYPE_IRAM: |
| ret = catpt_load_block(cdev, chan, paddr + offset, |
| blk, false); |
| break; |
| default: |
| ret = catpt_restore_fwimage(cdev, chan, paddr + offset, |
| blk); |
| break; |
| } |
| |
| if (ret) { |
| dev_err(cdev->dev, "restore block failed: %d\n", ret); |
| return ret; |
| } |
| |
| offset += sizeof(*blk) + blk->size; |
| } |
| |
| /* then proceed with memory dumps */ |
| ret = catpt_restore_memdumps(cdev, chan); |
| if (ret) |
| dev_err(cdev->dev, "restore memdumps failed: %d\n", ret); |
| |
| return ret; |
| } |
| |
| static int catpt_restore_module(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_mod_hdr *mod) |
| { |
| u32 offset = sizeof(*mod); |
| int i; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| mod, sizeof(*mod), false); |
| |
| for (i = 0; i < mod->blocks; i++) { |
| struct catpt_fw_block_hdr *blk; |
| int ret; |
| |
| blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); |
| |
| switch (blk->ram_type) { |
| case CATPT_RAM_TYPE_INSTANCE: |
| /* restore module state */ |
| ret = catpt_dma_memcpy_todsp(cdev, chan, |
| cdev->lpe_base + blk->ram_offset, |
| cdev->dxbuf_paddr + blk->ram_offset, |
| ALIGN(blk->size, 4)); |
| break; |
| default: |
| ret = catpt_load_block(cdev, chan, paddr + offset, |
| blk, false); |
| break; |
| } |
| |
| if (ret) { |
| dev_err(cdev->dev, "restore block failed: %d\n", ret); |
| return ret; |
| } |
| |
| offset += sizeof(*blk) + blk->size; |
| } |
| |
| return 0; |
| } |
| |
| static int catpt_load_module(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_mod_hdr *mod) |
| { |
| struct catpt_module_type *type; |
| u32 offset = sizeof(*mod); |
| int i; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| mod, sizeof(*mod), false); |
| |
| type = &cdev->modules[mod->module_id]; |
| |
| for (i = 0; i < mod->blocks; i++) { |
| struct catpt_fw_block_hdr *blk; |
| int ret; |
| |
| blk = (struct catpt_fw_block_hdr *)((u8 *)mod + offset); |
| |
| ret = catpt_load_block(cdev, chan, paddr + offset, blk, true); |
| if (ret) { |
| dev_err(cdev->dev, "load block failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* |
| * Save state window coordinates - these will be |
| * used to capture module state on D0 exit. |
| */ |
| if (blk->ram_type == CATPT_RAM_TYPE_INSTANCE) { |
| type->state_offset = blk->ram_offset; |
| type->state_size = blk->size; |
| } |
| |
| offset += sizeof(*blk) + blk->size; |
| } |
| |
| /* init module type static info */ |
| type->loaded = true; |
| /* DSP expects address from module header substracted by 4 */ |
| type->entry_point = mod->entry_point - 4; |
| type->persistent_size = mod->persistent_size; |
| type->scratch_size = mod->scratch_size; |
| |
| return 0; |
| } |
| |
| static int catpt_restore_firmware(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_hdr *fw) |
| { |
| u32 offset = sizeof(*fw); |
| int i; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| fw, sizeof(*fw), false); |
| |
| for (i = 0; i < fw->modules; i++) { |
| struct catpt_fw_mod_hdr *mod; |
| int ret; |
| |
| mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); |
| if (strncmp(fw->signature, mod->signature, |
| FW_SIGNATURE_SIZE)) { |
| dev_err(cdev->dev, "module signature mismatch\n"); |
| return -EINVAL; |
| } |
| |
| if (mod->module_id > CATPT_MODID_LAST) |
| return -EINVAL; |
| |
| switch (mod->module_id) { |
| case CATPT_MODID_BASE_FW: |
| ret = catpt_restore_basefw(cdev, chan, paddr + offset, |
| mod); |
| break; |
| default: |
| ret = catpt_restore_module(cdev, chan, paddr + offset, |
| mod); |
| break; |
| } |
| |
| if (ret) { |
| dev_err(cdev->dev, "restore module failed: %d\n", ret); |
| return ret; |
| } |
| |
| offset += sizeof(*mod) + mod->mod_size; |
| } |
| |
| return 0; |
| } |
| |
| static int catpt_load_firmware(struct catpt_dev *cdev, |
| struct dma_chan *chan, dma_addr_t paddr, |
| struct catpt_fw_hdr *fw) |
| { |
| u32 offset = sizeof(*fw); |
| int i; |
| |
| print_hex_dump_debug(__func__, DUMP_PREFIX_OFFSET, 8, 4, |
| fw, sizeof(*fw), false); |
| |
| for (i = 0; i < fw->modules; i++) { |
| struct catpt_fw_mod_hdr *mod; |
| int ret; |
| |
| mod = (struct catpt_fw_mod_hdr *)((u8 *)fw + offset); |
| if (strncmp(fw->signature, mod->signature, |
| FW_SIGNATURE_SIZE)) { |
| dev_err(cdev->dev, "module signature mismatch\n"); |
| return -EINVAL; |
| } |
| |
| if (mod->module_id > CATPT_MODID_LAST) |
| return -EINVAL; |
| |
| ret = catpt_load_module(cdev, chan, paddr + offset, mod); |
| if (ret) { |
| dev_err(cdev->dev, "load module failed: %d\n", ret); |
| return ret; |
| } |
| |
| offset += sizeof(*mod) + mod->mod_size; |
| } |
| |
| return 0; |
| } |
| |
| static int catpt_load_image(struct catpt_dev *cdev, struct dma_chan *chan, |
| const char *name, const char *signature, |
| bool restore) |
| { |
| struct catpt_fw_hdr *fw; |
| struct firmware *img; |
| dma_addr_t paddr; |
| void *vaddr; |
| int ret; |
| |
| ret = request_firmware((const struct firmware **)&img, name, cdev->dev); |
| if (ret) |
| return ret; |
| |
| fw = (struct catpt_fw_hdr *)img->data; |
| if (strncmp(fw->signature, signature, FW_SIGNATURE_SIZE)) { |
| dev_err(cdev->dev, "firmware signature mismatch\n"); |
| ret = -EINVAL; |
| goto release_fw; |
| } |
| |
| vaddr = dma_alloc_coherent(cdev->dev, img->size, &paddr, GFP_KERNEL); |
| if (!vaddr) { |
| ret = -ENOMEM; |
| goto release_fw; |
| } |
| |
| memcpy(vaddr, img->data, img->size); |
| fw = (struct catpt_fw_hdr *)vaddr; |
| if (restore) |
| ret = catpt_restore_firmware(cdev, chan, paddr, fw); |
| else |
| ret = catpt_load_firmware(cdev, chan, paddr, fw); |
| |
| dma_free_coherent(cdev->dev, img->size, vaddr, paddr); |
| release_fw: |
| release_firmware(img); |
| return ret; |
| } |
| |
| static int catpt_load_images(struct catpt_dev *cdev, bool restore) |
| { |
| static const char *const names[] = { |
| "intel/IntcSST1.bin", |
| "intel/IntcSST2.bin", |
| }; |
| struct dma_chan *chan; |
| int ret; |
| |
| chan = catpt_dma_request_config_chan(cdev); |
| if (IS_ERR(chan)) |
| return PTR_ERR(chan); |
| |
| ret = catpt_load_image(cdev, chan, names[cdev->spec->core_id - 1], |
| FW_SIGNATURE, restore); |
| if (ret) |
| goto release_dma_chan; |
| |
| if (!restore) |
| goto release_dma_chan; |
| ret = catpt_restore_streams_context(cdev, chan); |
| if (ret) |
| dev_err(cdev->dev, "restore streams ctx failed: %d\n", ret); |
| release_dma_chan: |
| dma_release_channel(chan); |
| return ret; |
| } |
| |
| int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) |
| { |
| int ret; |
| |
| catpt_dsp_stall(cdev, true); |
| |
| ret = catpt_load_images(cdev, restore); |
| if (ret) { |
| dev_err(cdev->dev, "load binaries failed: %d\n", ret); |
| return ret; |
| } |
| |
| reinit_completion(&cdev->fw_ready); |
| catpt_dsp_stall(cdev, false); |
| |
| ret = wait_for_completion_timeout(&cdev->fw_ready, |
| msecs_to_jiffies(FW_READY_TIMEOUT_MS)); |
| if (!ret) { |
| dev_err(cdev->dev, "firmware ready timeout\n"); |
| return -ETIMEDOUT; |
| } |
| |
| /* update sram pg & clock once done booting */ |
| catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); |
| catpt_dsp_update_srampge(cdev, &cdev->iram, cdev->spec->iram_mask); |
| |
| return catpt_dsp_update_lpclock(cdev); |
| } |
| |
| int catpt_first_boot_firmware(struct catpt_dev *cdev) |
| { |
| struct resource *res; |
| int ret; |
| |
| ret = catpt_boot_firmware(cdev, false); |
| if (ret) { |
| dev_err(cdev->dev, "basefw boot failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* restrict FW Core dump area */ |
| __request_region(&cdev->dram, 0, 0x200, NULL, 0); |
| /* restrict entire area following BASE_FW - highest offset in DRAM */ |
| for (res = cdev->dram.child; res->sibling; res = res->sibling) |
| ; |
| __request_region(&cdev->dram, res->end + 1, |
| cdev->dram.end - res->end, NULL, 0); |
| |
| ret = catpt_ipc_get_mixer_stream_info(cdev, &cdev->mixer); |
| if (ret) |
| return CATPT_IPC_ERROR(ret); |
| |
| ret = catpt_arm_stream_templates(cdev); |
| if (ret) { |
| dev_err(cdev->dev, "arm templates failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* update dram pg for scratch and restricted regions */ |
| catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask); |
| |
| return 0; |
| } |