linux/sound/soc/intel/catpt/loader.c
Cezary Rojewski a126750fc8
ASoC: Intel: catpt: PCM operations
DSP designed for Lynxpoint and Wildcat Point offers no dynamic topology
i.e. all pipelines are already defined within firmware and host is
relegated to allocing stream for predefined pins. This is represented by
'catpt_topology' member.

Implementation covers all available pin types:
- system playback and capture
- two offload streams
- loopback (reference)
- bluetooth playback and capture

PCM DAI operations differentiate between those pins as some (mainly
offload) are to be handled differently - DSP expects wp updates on each
notify_position notification.

System playback has no volume control capability as it is routed to
mixer stream directly. Other primary streams - capture and two offloads
- offer individual volume controls.

Compared to sound/soc/intel/haswell this configures SSP device format
automatically on pcm creation.

Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20200929141247.8058-7-cezary.rojewski@intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-10-02 15:32:32 +01:00

672 lines
15 KiB
C

// 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 (!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);
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;
}