//
 
 #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)
 {
 
        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 (!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->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);
+
+       /* update dram pg for scratch and restricted regions */
+       catpt_dsp_update_srampge(cdev, &cdev->dram, cdev->spec->dram_mask);
+
+       return 0;
+}