mirror of https://gitee.com/openkylin/linux.git
ASoC: Intel: mrfld - Add DSP load and management
This patch contains all dsp controlling functions like firmware download, setting/resetting dsp cores, etc. Signed-off-by: Subhransu S. Prusty <subhransu.s.prusty@intel.com> Signed-off-by: Vinod Koul <vinod.koul@intel.com> Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
parent
163d2089d2
commit
9012c9544e
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* sst_dsp.c - Intel SST Driver for audio engine
|
||||
*
|
||||
* Copyright (C) 2008-14 Intel Corp
|
||||
* Authors: Vinod Koul <vinod.koul@intel.com>
|
||||
* Harsha Priya <priya.harsha@intel.com>
|
||||
* Dharageswari R <dharageswari.r@intel.com>
|
||||
* KP Jeeja <jeeja.kp@intel.com>
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This file contains all dsp controlling functions like firmware download,
|
||||
* setting/resetting dsp cores, etc
|
||||
*/
|
||||
#include <linux/pci.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/compress_driver.h>
|
||||
#include <asm/platform_sst_audio.h>
|
||||
#include "../sst-mfld-platform.h"
|
||||
#include "sst.h"
|
||||
#include "../sst-dsp.h"
|
||||
|
||||
static void memcpy32_toio(void __iomem *dst, const void *src, int count)
|
||||
{
|
||||
int i;
|
||||
const u32 *src_32 = src;
|
||||
u32 *dst_32 = dst;
|
||||
|
||||
for (i = 0; i < count/sizeof(u32); i++)
|
||||
writel(*src_32++, dst_32++);
|
||||
}
|
||||
|
||||
/**
|
||||
* intel_sst_reset_dsp_mrfld - Resetting SST DSP
|
||||
*
|
||||
* This resets DSP in case of MRFLD platfroms
|
||||
*/
|
||||
int intel_sst_reset_dsp_mrfld(struct intel_sst_drv *sst_drv_ctx)
|
||||
{
|
||||
union config_status_reg_mrfld csr;
|
||||
|
||||
dev_dbg(sst_drv_ctx->dev, "sst: Resetting the DSP in mrfld\n");
|
||||
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
|
||||
|
||||
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
|
||||
|
||||
csr.full |= 0x7;
|
||||
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
|
||||
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
|
||||
|
||||
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
|
||||
|
||||
csr.full &= ~(0x1);
|
||||
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
|
||||
|
||||
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
|
||||
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sst_start_merrifield - Start the SST DSP processor
|
||||
*
|
||||
* This starts the DSP in MERRIFIELD platfroms
|
||||
*/
|
||||
int sst_start_mrfld(struct intel_sst_drv *sst_drv_ctx)
|
||||
{
|
||||
union config_status_reg_mrfld csr;
|
||||
|
||||
dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP in mrfld LALALALA\n");
|
||||
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
|
||||
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
|
||||
|
||||
csr.full |= 0x7;
|
||||
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
|
||||
|
||||
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
|
||||
dev_dbg(sst_drv_ctx->dev, "value:0x%llx\n", csr.full);
|
||||
|
||||
csr.part.xt_snoop = 1;
|
||||
csr.full &= ~(0x5);
|
||||
sst_shim_write64(sst_drv_ctx->shim, SST_CSR, csr.full);
|
||||
|
||||
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
|
||||
dev_dbg(sst_drv_ctx->dev, "sst: Starting the DSP_merrifield:%llx\n",
|
||||
csr.full);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sst_validate_fw_image(struct intel_sst_drv *ctx, unsigned long size,
|
||||
struct fw_module_header **module, u32 *num_modules)
|
||||
{
|
||||
struct sst_fw_header *header;
|
||||
const void *sst_fw_in_mem = ctx->fw_in_mem;
|
||||
|
||||
dev_dbg(ctx->dev, "Enter\n");
|
||||
|
||||
/* Read the header information from the data pointer */
|
||||
header = (struct sst_fw_header *)sst_fw_in_mem;
|
||||
dev_dbg(ctx->dev,
|
||||
"header sign=%s size=%x modules=%x fmt=%x size=%zx\n",
|
||||
header->signature, header->file_size, header->modules,
|
||||
header->file_format, sizeof(*header));
|
||||
|
||||
/* verify FW */
|
||||
if ((strncmp(header->signature, SST_FW_SIGN, 4) != 0) ||
|
||||
(size != header->file_size + sizeof(*header))) {
|
||||
/* Invalid FW signature */
|
||||
dev_err(ctx->dev, "InvalidFW sign/filesize mismatch\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
*num_modules = header->modules;
|
||||
*module = (void *)sst_fw_in_mem + sizeof(*header);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* sst_fill_memcpy_list - Fill the memcpy list
|
||||
*
|
||||
* @memcpy_list: List to be filled
|
||||
* @destn: Destination addr to be filled in the list
|
||||
* @src: Source addr to be filled in the list
|
||||
* @size: Size to be filled in the list
|
||||
*
|
||||
* Adds the node to the list after required fields
|
||||
* are populated in the node
|
||||
*/
|
||||
static int sst_fill_memcpy_list(struct list_head *memcpy_list,
|
||||
void *destn, const void *src, u32 size, bool is_io)
|
||||
{
|
||||
struct sst_memcpy_list *listnode;
|
||||
|
||||
listnode = kzalloc(sizeof(*listnode), GFP_KERNEL);
|
||||
if (listnode == NULL)
|
||||
return -ENOMEM;
|
||||
listnode->dstn = destn;
|
||||
listnode->src = src;
|
||||
listnode->size = size;
|
||||
listnode->is_io = is_io;
|
||||
list_add_tail(&listnode->memcpylist, memcpy_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list
|
||||
*
|
||||
* @sst_drv_ctx : driver context
|
||||
* @module : FW module header
|
||||
* @memcpy_list : Pointer to the list to be populated
|
||||
* Create the memcpy list as the number of block to be copied
|
||||
* returns error or 0 if module sizes are proper
|
||||
*/
|
||||
static int sst_parse_module_memcpy(struct intel_sst_drv *sst_drv_ctx,
|
||||
struct fw_module_header *module, struct list_head *memcpy_list)
|
||||
{
|
||||
struct fw_block_info *block;
|
||||
u32 count;
|
||||
int ret_val = 0;
|
||||
void __iomem *ram_iomem;
|
||||
|
||||
dev_dbg(sst_drv_ctx->dev, "module sign %s size %x blocks %x type %x\n",
|
||||
module->signature, module->mod_size,
|
||||
module->blocks, module->type);
|
||||
dev_dbg(sst_drv_ctx->dev, "module entrypoint 0x%x\n", module->entry_point);
|
||||
|
||||
block = (void *)module + sizeof(*module);
|
||||
|
||||
for (count = 0; count < module->blocks; count++) {
|
||||
if (block->size <= 0) {
|
||||
dev_err(sst_drv_ctx->dev, "block size invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
switch (block->type) {
|
||||
case SST_IRAM:
|
||||
ram_iomem = sst_drv_ctx->iram;
|
||||
break;
|
||||
case SST_DRAM:
|
||||
ram_iomem = sst_drv_ctx->dram;
|
||||
break;
|
||||
case SST_DDR:
|
||||
ram_iomem = sst_drv_ctx->ddr;
|
||||
break;
|
||||
case SST_CUSTOM_INFO:
|
||||
block = (void *)block + sizeof(*block) + block->size;
|
||||
continue;
|
||||
default:
|
||||
dev_err(sst_drv_ctx->dev, "wrong ram type0x%x in block0x%x\n",
|
||||
block->type, count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret_val = sst_fill_memcpy_list(memcpy_list,
|
||||
ram_iomem + block->ram_offset,
|
||||
(void *)block + sizeof(*block), block->size, 1);
|
||||
if (ret_val)
|
||||
return ret_val;
|
||||
|
||||
block = (void *)block + sizeof(*block) + block->size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sst_parse_fw_memcpy - parse the firmware image & populate the list for memcpy
|
||||
*
|
||||
* @ctx : pointer to drv context
|
||||
* @size : size of the firmware
|
||||
* @fw_list : pointer to list_head to be populated
|
||||
* This function parses the FW image and saves the parsed image in the list
|
||||
* for memcpy
|
||||
*/
|
||||
static int sst_parse_fw_memcpy(struct intel_sst_drv *ctx, unsigned long size,
|
||||
struct list_head *fw_list)
|
||||
{
|
||||
struct fw_module_header *module;
|
||||
u32 count, num_modules;
|
||||
int ret_val;
|
||||
|
||||
ret_val = sst_validate_fw_image(ctx, size, &module, &num_modules);
|
||||
if (ret_val)
|
||||
return ret_val;
|
||||
|
||||
for (count = 0; count < num_modules; count++) {
|
||||
ret_val = sst_parse_module_memcpy(ctx, module, fw_list);
|
||||
if (ret_val)
|
||||
return ret_val;
|
||||
module = (void *)module + sizeof(*module) + module->mod_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sst_do_memcpy - function initiates the memcpy
|
||||
*
|
||||
* @memcpy_list: Pter to memcpy list on which the memcpy needs to be initiated
|
||||
*
|
||||
* Triggers the memcpy
|
||||
*/
|
||||
static void sst_do_memcpy(struct list_head *memcpy_list)
|
||||
{
|
||||
struct sst_memcpy_list *listnode;
|
||||
|
||||
list_for_each_entry(listnode, memcpy_list, memcpylist) {
|
||||
if (listnode->is_io == true)
|
||||
memcpy32_toio((void __iomem *)listnode->dstn,
|
||||
listnode->src, listnode->size);
|
||||
else
|
||||
memcpy(listnode->dstn, listnode->src, listnode->size);
|
||||
}
|
||||
}
|
||||
|
||||
void sst_memcpy_free_resources(struct intel_sst_drv *sst_drv_ctx)
|
||||
{
|
||||
struct sst_memcpy_list *listnode, *tmplistnode;
|
||||
|
||||
/* Free the list */
|
||||
if (!list_empty(&sst_drv_ctx->memcpy_list)) {
|
||||
list_for_each_entry_safe(listnode, tmplistnode,
|
||||
&sst_drv_ctx->memcpy_list, memcpylist) {
|
||||
list_del(&listnode->memcpylist);
|
||||
kfree(listnode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int sst_cache_and_parse_fw(struct intel_sst_drv *sst,
|
||||
const struct firmware *fw)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL);
|
||||
if (!sst->fw_in_mem) {
|
||||
retval = -ENOMEM;
|
||||
goto end_release;
|
||||
}
|
||||
dev_dbg(sst->dev, "copied fw to %p", sst->fw_in_mem);
|
||||
dev_dbg(sst->dev, "phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem));
|
||||
memcpy(sst->fw_in_mem, fw->data, fw->size);
|
||||
retval = sst_parse_fw_memcpy(sst, fw->size, &sst->memcpy_list);
|
||||
if (retval) {
|
||||
dev_err(sst->dev, "Failed to parse fw\n");
|
||||
kfree(sst->fw_in_mem);
|
||||
sst->fw_in_mem = NULL;
|
||||
}
|
||||
|
||||
end_release:
|
||||
release_firmware(fw);
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
void sst_firmware_load_cb(const struct firmware *fw, void *context)
|
||||
{
|
||||
struct intel_sst_drv *ctx = context;
|
||||
|
||||
dev_dbg(ctx->dev, "Enter\n");
|
||||
|
||||
if (fw == NULL) {
|
||||
dev_err(ctx->dev, "request fw failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->sst_lock);
|
||||
|
||||
if (ctx->sst_state != SST_RESET ||
|
||||
ctx->fw_in_mem != NULL) {
|
||||
if (fw != NULL)
|
||||
release_firmware(fw);
|
||||
mutex_unlock(&ctx->sst_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
dev_dbg(ctx->dev, "Request Fw completed\n");
|
||||
sst_cache_and_parse_fw(ctx, fw);
|
||||
mutex_unlock(&ctx->sst_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* sst_request_fw - requests audio fw from kernel and saves a copy
|
||||
*
|
||||
* This function requests the SST FW from the kernel, parses it and
|
||||
* saves a copy in the driver context
|
||||
*/
|
||||
static int sst_request_fw(struct intel_sst_drv *sst)
|
||||
{
|
||||
int retval = 0;
|
||||
char name[20];
|
||||
const struct firmware *fw;
|
||||
|
||||
dev_dbg(sst->dev, "Requesting FW %s now...\n", name);
|
||||
|
||||
retval = request_firmware(&fw, name, sst->dev);
|
||||
if (fw == NULL) {
|
||||
dev_err(sst->dev, "fw is returning as null\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (retval) {
|
||||
dev_err(sst->dev, "request fw failed %d\n", retval);
|
||||
return retval;
|
||||
}
|
||||
mutex_lock(&sst->sst_lock);
|
||||
retval = sst_cache_and_parse_fw(sst, fw);
|
||||
mutex_unlock(&sst->sst_lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writing the DDR physical base to DCCM offset
|
||||
* so that FW can use it to setup TLB
|
||||
*/
|
||||
static void sst_dccm_config_write(void __iomem *dram_base,
|
||||
unsigned int ddr_base)
|
||||
{
|
||||
void __iomem *addr;
|
||||
u32 bss_reset = 0;
|
||||
|
||||
addr = (void __iomem *)(dram_base + MRFLD_FW_DDR_BASE_OFFSET);
|
||||
memcpy32_toio(addr, (void *)&ddr_base, sizeof(u32));
|
||||
bss_reset |= (1 << MRFLD_FW_BSS_RESET_BIT);
|
||||
addr = (void __iomem *)(dram_base + MRFLD_FW_FEATURE_BASE_OFFSET);
|
||||
memcpy32_toio(addr, &bss_reset, sizeof(u32));
|
||||
|
||||
}
|
||||
|
||||
void sst_post_download_mrfld(struct intel_sst_drv *ctx)
|
||||
{
|
||||
sst_dccm_config_write(ctx->dram, ctx->ddr_base);
|
||||
dev_dbg(ctx->dev, "config written to DCCM\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* sst_load_fw - function to load FW into DSP
|
||||
* Transfers the FW to DSP using dma/memcpy
|
||||
*/
|
||||
int sst_load_fw(struct intel_sst_drv *sst_drv_ctx)
|
||||
{
|
||||
int ret_val = 0;
|
||||
struct sst_block *block;
|
||||
|
||||
dev_dbg(sst_drv_ctx->dev, "sst_load_fw\n");
|
||||
|
||||
if (sst_drv_ctx->sst_state != SST_RESET ||
|
||||
sst_drv_ctx->sst_state == SST_SHUTDOWN)
|
||||
return -EAGAIN;
|
||||
|
||||
if (!sst_drv_ctx->fw_in_mem) {
|
||||
dev_dbg(sst_drv_ctx->dev, "sst: FW not in memory retry to download\n");
|
||||
ret_val = sst_request_fw(sst_drv_ctx);
|
||||
if (ret_val)
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
BUG_ON(!sst_drv_ctx->fw_in_mem);
|
||||
block = sst_create_block(sst_drv_ctx, 0, FW_DWNL_ID);
|
||||
if (block == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Prevent C-states beyond C6 */
|
||||
pm_qos_update_request(sst_drv_ctx->qos, 0);
|
||||
|
||||
sst_drv_ctx->sst_state = SST_FW_LOADING;
|
||||
|
||||
ret_val = sst_drv_ctx->ops->reset(sst_drv_ctx);
|
||||
if (ret_val)
|
||||
goto restore;
|
||||
|
||||
sst_do_memcpy(&sst_drv_ctx->memcpy_list);
|
||||
|
||||
/* Write the DRAM/DCCM config before enabling FW */
|
||||
if (sst_drv_ctx->ops->post_download)
|
||||
sst_drv_ctx->ops->post_download(sst_drv_ctx);
|
||||
|
||||
/* bring sst out of reset */
|
||||
ret_val = sst_drv_ctx->ops->start(sst_drv_ctx);
|
||||
if (ret_val)
|
||||
goto restore;
|
||||
|
||||
ret_val = sst_wait_timeout(sst_drv_ctx, block);
|
||||
if (ret_val) {
|
||||
dev_err(sst_drv_ctx->dev, "fw download failed %d\n" , ret_val);
|
||||
/* FW download failed due to timeout */
|
||||
ret_val = -EBUSY;
|
||||
|
||||
}
|
||||
|
||||
|
||||
restore:
|
||||
/* Re-enable Deeper C-states beyond C6 */
|
||||
pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE);
|
||||
sst_free_block(sst_drv_ctx, block);
|
||||
dev_dbg(sst_drv_ctx->dev, "fw load successful!!!\n");
|
||||
|
||||
if (sst_drv_ctx->ops->restore_dsp_context)
|
||||
sst_drv_ctx->ops->restore_dsp_context();
|
||||
sst_drv_ctx->sst_state = SST_FW_RUNNING;
|
||||
return ret_val;
|
||||
}
|
||||
|
Loading…
Reference in New Issue