On Tue, Sep 15, 2020 at 06:29:34PM +0200, Cezary Rojewski wrote: > For Lynxpoint and Wildcat Point solution, is it host's responsibility to > allocate SRAM regions and ensure those already taken are not overwritten > with other data until released. Blocks are transferred to SRAM - either > IRAM or DRAM - via DW DMA controller. Once basefw is booted, ownership > of DMA transfer is lost in favour of DSP. > > Hosts reponsibilities don't end on initial block allocation and binary > transfer. During Dx transitions host must store FW runtime context from > DRAM before putting AudioDSP subsystem into lower power state. Said > context gets flashed after D0 entry to bring DSP right where it was just > before suspending. > > Load and restore procedures are finalized with SRAM power gating and > adequate clock level selection. This power gates unused EBBs and clock > speed effectively reducing power consumption. > > Signed-off-by: Cezary Rojewski <cezary.rojewski@xxxxxxxxx> > --- > sound/soc/intel/catpt/loader.c | 673 +++++++++++++++++++++++++++++++++ > 1 file changed, 673 insertions(+) > create mode 100644 sound/soc/intel/catpt/loader.c > > diff --git a/sound/soc/intel/catpt/loader.c b/sound/soc/intel/catpt/loader.c > new file mode 100644 > index 000000000000..9d21637215ab > --- /dev/null > +++ b/sound/soc/intel/catpt/loader.c > @@ -0,0 +1,673 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +// > +// Copyright(c) 2020 Intel Corporation. All rights reserved. > +// > +// Author: Cezary Rojewski <cezary.rojewski@xxxxxxxxx> > +// > + > +#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_MSECS 250 > + > +#define FW_SIGNATURE "$SST" > +#define FW_SIGNATURE_SIZE 4 > + > +/* some nice binary layout picture here */ > + > +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; > + enum catpt_module_id id:16; > + 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 { > + enum catpt_ram_type type __aligned(4); > + 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; > +} No user of this. Note I wrote this before looking closer into previous patches. So, I stopped reviewing the series due to the mess with function declarations and who knows what other W=1 or bisectability issues this series has. Hence waiting for v6 with all that addressed. > +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; > + } > +} Ditto. > +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); > +} Ditto. > +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 (!catpt_resource_overlapping(&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->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->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->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->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->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->id > CATPT_MODID_LAST) > + return -EINVAL; > + > + switch (mod->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->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 exit; > + > + if (!restore) > + goto exit; > + ret = catpt_restore_streams_context(cdev, chan); > + if (ret) > + dev_err(cdev->dev, "restore streams ctx failed: %d\n", ret); > +exit: > + dma_release_channel(chan); > + return ret; > +} > + > +int catpt_boot_firmware(struct catpt_dev *cdev, bool restore) static? > +{ > + 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_MSECS)); > + 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; > +} > -- > 2.17.1 > -- With Best Regards, Andy Shevchenko