blob: 29950f9843fb85bd660f42b7d95862a57b9583aa [file] [log] [blame]
/*
* sst_dsp.c - Intel SST Driver for audio engine
*
* Copyright (C) 2008-10 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This file contains all dsp controlling functions like firmware download,
* setting/resetting dsp cores, etc
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#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/intel_mid_dma.h>
#include <linux/pm_qos.h>
#include <linux/intel_mid_pm.h>
#include <linux/elf.h>
#include "../sst_platform.h"
#include "../platform_ipc_v2.h"
#include "sst.h"
#include "sst_trace.h"
#ifndef CONFIG_X86_64
#define MEMCPY_TOIO memcpy_toio
#else
#define MEMCPY_TOIO memcpy32_toio
#endif
/**
* Add here all the static librairies to be downloaded at bootup
*/
static struct sst_module_info sst_modules_mrfld[] = {};
static struct sst_module_info sst_modules_byt[] = {
{"mp3_dec", SST_CODEC_TYPE_MP3, 0, SST_LIB_NOT_FOUND},
{"aac_dec", SST_CODEC_TYPE_AAC, 0, SST_LIB_NOT_FOUND},
};
/**
* memcpy32_toio: Copy using writel commands
*
* This is needed because the hardware does not support
* 64-bit moveq insructions while writing to PCI MMIO
*/
void memcpy32_toio(void *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_medfield - Resetting SST DSP
*
* This resets DSP in case of Medfield platfroms
*/
int intel_sst_reset_dsp_mfld(void)
{
union config_status_reg csr;
pr_debug("Resetting the DSP in medfield\n");
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.full |= 0x382;
csr.part.run_stall = 0x1;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
mutex_unlock(&sst_drv_ctx->csr_lock);
return 0;
}
/**
* sst_start_medfield - Start the SST DSP processor
*
* This starts the DSP in MRST platfroms
*/
int sst_start_mfld(void)
{
union config_status_reg csr;
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.part.bypass = 0;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.part.mfld_strb = 1;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.part.run_stall = 0;
csr.part.sst_reset = 0;
pr_debug("Starting the DSP_medfld %x\n", csr.full);
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
pr_debug("Starting the DSP_medfld\n");
mutex_unlock(&sst_drv_ctx->csr_lock);
return 0;
}
/**
* intel_sst_reset_dsp_mrfld - Resetting SST DSP
*
* This resets DSP in case of MRFLD platfroms
*/
int intel_sst_reset_dsp_mrfld(void)
{
union config_status_reg_mrfld csr;
pr_debug("sst: Resetting the DSP in mrfld\n");
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
pr_debug("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);
pr_debug("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);
pr_debug("value:0x%llx\n", csr.full);
mutex_unlock(&sst_drv_ctx->csr_lock);
return 0;
}
/**
* sst_start_merrifield - Start the SST DSP processor
*
* This starts the DSP in MERRIFIELD platfroms
*/
int sst_start_mrfld(void)
{
union config_status_reg_mrfld csr;
pr_debug("sst: Starting the DSP in mrfld LALALALA\n");
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = sst_shim_read64(sst_drv_ctx->shim, SST_CSR);
pr_debug("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);
pr_debug("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);
pr_debug("sst: Starting the DSP_merrifield:%llx\n", csr.full);
mutex_unlock(&sst_drv_ctx->csr_lock);
return 0;
}
/**
* intel_sst_set_bypass - Sets/clears the bypass bits
*
* This sets/clears the bypass bits
*/
void intel_sst_set_bypass_mfld(bool set)
{
union config_status_reg csr;
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
if (set == true)
csr.full |= 0x380;
else
csr.part.bypass = 0;
pr_debug("SetupByPass set %d Val 0x%x\n", set, csr.full);
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
mutex_unlock(&sst_drv_ctx->csr_lock);
}
#define SST_CALC_DMA_DSTN(lpe_viewpt_rqd, ia_viewpt_addr, elf_paddr, \
lpe_viewpt_addr) ((lpe_viewpt_rqd) ? \
elf_paddr : (ia_viewpt_addr + elf_paddr - lpe_viewpt_addr))
static int sst_fill_dstn(struct intel_sst_drv *sst, struct sst_info info,
Elf32_Phdr *pr, void **dstn, unsigned int *dstn_phys, int *mem_type)
{
#ifdef MRFLD_WORD_WA
/* work arnd-since only 4 byte align copying is only allowed for ICCM */
if ((pr->p_paddr >= info.iram_start) && (pr->p_paddr < info.iram_end)) {
size_t data_size = pr->p_filesz % SST_ICCM_BOUNDARY;
if (data_size)
pr->p_filesz += 4 - data_size;
*dstn = sst->iram + (pr->p_paddr - info.iram_start);
*dstn_phys = SST_CALC_DMA_DSTN(info.lpe_viewpt_rqd,
sst->iram_base, pr->p_paddr, info.iram_start);
*mem_type = 1;
}
#else
if ((pr->p_paddr >= info.iram_start) &&
(pr->p_paddr < info.iram_end)) {
*dstn = sst->iram + (pr->p_paddr - info.iram_start);
*dstn_phys = SST_CALC_DMA_DSTN(info.lpe_viewpt_rqd,
sst->iram_base, pr->p_paddr, info.iram_start);
*mem_type = 1;
}
#endif
else if ((pr->p_paddr >= info.dram_start) &&
(pr->p_paddr < info.dram_end)) {
*dstn = sst->dram + (pr->p_paddr - info.dram_start);
*dstn_phys = SST_CALC_DMA_DSTN(info.lpe_viewpt_rqd,
sst->dram_base, pr->p_paddr, info.dram_start);
*mem_type = 1;
} else if ((pr->p_paddr >= info.imr_start) &&
(pr->p_paddr < info.imr_end)) {
*dstn = sst->ddr + (pr->p_paddr - info.imr_start);
*dstn_phys = sst->ddr_base + pr->p_paddr - info.imr_start;
*mem_type = 0;
} else {
return -EINVAL;
}
return 0;
}
static void sst_fill_info(struct intel_sst_drv *sst,
struct sst_info *info)
{
/* first we setup addresses to be used for elf sections */
if (sst->info.iram_use) {
info->iram_start = sst->info.iram_start;
info->iram_end = sst->info.iram_end;
} else {
info->iram_start = sst->iram_base;
info->iram_end = sst->iram_end;
}
if (sst->info.dram_use) {
info->dram_start = sst->info.dram_start;
info->dram_end = sst->info.dram_end;
} else {
info->dram_start = sst->dram_base;
info->dram_end = sst->dram_end;
}
if (sst->info.imr_use) {
info->imr_start = sst->info.imr_start;
info->imr_end = sst->info.imr_end;
} else {
info->imr_start = relocate_imr_addr_mrfld(sst->ddr_base);
info->imr_end = relocate_imr_addr_mrfld(sst->ddr_end);
}
info->lpe_viewpt_rqd = sst->info.lpe_viewpt_rqd;
info->dma_max_len = sst->info.dma_max_len;
pr_debug("%s: dma_max_len 0x%x", __func__, info->dma_max_len);
}
static inline int sst_validate_elf(const struct firmware *sst_bin, bool dynamic)
{
Elf32_Ehdr *elf;
BUG_ON(!sst_bin);
pr_debug("IN %s\n", __func__);
elf = (Elf32_Ehdr *)sst_bin->data;
if ((elf->e_ident[0] != 0x7F) || (elf->e_ident[1] != 'E') ||
(elf->e_ident[2] != 'L') || (elf->e_ident[3] != 'F')) {
pr_debug("ELF Header Not found!%zu\n", sst_bin->size);
return -EINVAL;
}
if (dynamic == true) {
if (elf->e_type != ET_DYN) {
pr_err("Not a dynamic loadable library\n");
return -EINVAL;
}
}
pr_debug("Valid ELF Header...%zu\n", sst_bin->size);
return 0;
}
/**
* sst_validate_fw_image - validates the firmware signature
*
* @sst_fw_in_mem : pointer to audio FW
* @size : size of the firmware
* @module : points to the FW modules
* @num_modules : points to the num of modules
* This function validates the header signature in the FW image
*/
static int sst_validate_fw_image(const void *sst_fw_in_mem, unsigned long size,
struct fw_module_header **module, u32 *num_modules)
{
struct fw_header *header;
pr_debug("%s\n", __func__);
/* Read the header information from the data pointer */
header = (struct fw_header *)sst_fw_in_mem;
pr_debug("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 */
pr_err("InvalidFW sign/filesize mismatch\n");
return -EINVAL;
}
*num_modules = header->modules;
*module = (void *)sst_fw_in_mem + sizeof(*header);
return 0;
}
/**
* sst_validate_library - validates the library signature
*
* @fw_lib : pointer to FW library
* @slot : pointer to the lib slot info
* @entry_point : out param, which contains the module entry point
* This function is called before downloading the codec/postprocessing
* library
*/
static int sst_validate_library(const struct firmware *fw_lib,
struct lib_slot_info *slot,
u32 *entry_point)
{
struct fw_header *header;
struct fw_module_header *module;
struct fw_block_info *block;
unsigned int n_blk, isize = 0, dsize = 0;
int err = 0;
header = (struct fw_header *)fw_lib->data;
if (header->modules != 1) {
pr_err("Module no mismatch found\n");
err = -EINVAL;
goto exit;
}
module = (void *)fw_lib->data + sizeof(*header);
*entry_point = module->entry_point;
pr_debug("Module entry point 0x%x\n", *entry_point);
pr_debug("Module Sign %s, Size 0x%x, Blocks 0x%x Type 0x%x\n",
module->signature, module->mod_size,
module->blocks, module->type);
block = (void *)module + sizeof(*module);
for (n_blk = 0; n_blk < module->blocks; n_blk++) {
switch (block->type) {
case SST_IRAM:
isize += block->size;
break;
case SST_DRAM:
dsize += block->size;
break;
default:
pr_err("Invalid block type for 0x%x\n", n_blk);
err = -EINVAL;
goto exit;
}
block = (void *)block + sizeof(*block) + block->size;
}
if (isize > slot->iram_size || dsize > slot->dram_size) {
pr_err("library exceeds size allocated\n");
err = -EINVAL;
goto exit;
} else
pr_debug("Library is safe for download...\n");
pr_debug("iram 0x%x, dram 0x%x, iram 0x%x, dram 0x%x\n",
isize, dsize, slot->iram_size, slot->dram_size);
exit:
return err;
}
static bool chan_filter(struct dma_chan *chan, void *param)
{
struct sst_dma *dma = (struct sst_dma *)param;
/* we only need MID_DMAC1 as that can access DSP RAMs*/
if (chan->device->dev == dma->dev)
return true;
return false;
}
static unsigned int
sst_get_elf_sg_len(struct intel_sst_drv *sst, Elf32_Ehdr *elf, Elf32_Phdr *pr,
struct sst_info info)
{
unsigned int i = 0, count = 0;
pr_debug("in %s: dma_max_len 0x%x\n", __func__, info.dma_max_len);
while (i < elf->e_phnum) {
if (pr[i].p_type == PT_LOAD) {
if ((pr[i].p_paddr >= info.iram_start) &&
(pr[i].p_paddr < info.iram_end &&
pr[i].p_filesz)) {
count += (pr[i].p_filesz) / info.dma_max_len;
if ((pr[i].p_filesz) % info.dma_max_len)
count++;
} else if ((pr[i].p_paddr >= info.dram_start) &&
(pr[i].p_paddr < info.dram_end &&
pr[i].p_filesz)) {
count += (pr[i].p_filesz) / info.dma_max_len;
if ((pr[i].p_filesz) % info.dma_max_len)
count++;
} else if ((pr[i].p_paddr >= info.imr_start) &&
(pr[i].p_paddr < info.imr_end &&
pr[i].p_filesz)) {
count += (pr[i].p_filesz) / info.dma_max_len;
if ((pr[i].p_filesz) % info.dma_max_len)
count++;
}
}
i++;
}
pr_debug("gotcha count %d\n", count);
return count;
}
static int
sst_init_dma_sg_list(struct intel_sst_drv *sst, unsigned int len,
struct scatterlist **src, struct scatterlist **dstn)
{
struct scatterlist *sg_src = NULL, *sg_dst = NULL;
sg_src = kzalloc(sizeof(*sg_src)*(len), GFP_KERNEL);
if (NULL == sg_src)
return -ENOMEM;
sg_init_table(sg_src, len);
sg_dst = kzalloc(sizeof(*sg_dst)*(len), GFP_KERNEL);
if (NULL == sg_dst) {
kfree(sg_src);
return -ENOMEM;
}
sg_init_table(sg_dst, len);
*src = sg_src;
*dstn = sg_dst;
return 0;
}
static int sst_alloc_dma_chan(struct sst_dma *dma)
{
dma_cap_mask_t mask;
struct intel_mid_dma_slave *slave = &dma->slave;
int retval;
struct pci_dev *dmac = NULL;
const char *hid;
pr_debug("%s\n", __func__);
dma->dev = NULL;
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask);
if (sst_drv_ctx->pci_id == SST_CLV_PCI_ID)
dmac = pci_get_device(PCI_VENDOR_ID_INTEL,
PCI_DMAC_CLV_ID, NULL);
else if (sst_drv_ctx->pci_id == SST_MRFLD_PCI_ID)
dmac = pci_get_device(PCI_VENDOR_ID_INTEL,
PCI_DMAC_MRFLD_ID, NULL);
else if (sst_drv_ctx->pci_id == SST_BYT_PCI_ID) {
hid = sst_drv_ctx->hid;
if (!strncmp(hid, "LPE0F281", 8))
dma->dev = intel_mid_get_acpi_dma("DMA0F28");
else if (!strncmp(hid, "80860F28", 8))
dma->dev = intel_mid_get_acpi_dma("ADMA0F28");
else if (!strncmp(hid, "808622A8", 8))
dma->dev = intel_mid_get_acpi_dma("ADMA22A8");
else if (!strncmp(hid, "LPE0F28", 7))
dma->dev = intel_mid_get_acpi_dma("DMA0F28");
}
if (!dmac && !dma->dev) {
pr_err("Can't find DMAC\n");
return -ENODEV;
}
if (dmac)
dma->dev = &dmac->dev;
dma->ch = dma_request_channel(mask, chan_filter, dma);
if (!dma->ch) {
pr_err("unable to request dma channel\n");
return -EIO;
}
slave->dma_slave.direction = DMA_MEM_TO_MEM;
slave->hs_mode = 0;
slave->cfg_mode = LNW_DMA_MEM_TO_MEM;
slave->dma_slave.src_addr_width = slave->dma_slave.dst_addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
slave->dma_slave.src_maxburst = slave->dma_slave.dst_maxburst =
LNW_DMA_MSIZE_16;
retval = dmaengine_slave_config(dma->ch, &slave->dma_slave);
if (retval) {
pr_err("unable to set slave config, err %d\n", retval);
dma_release_channel(dma->ch);
return -EIO;
}
return retval;
}
static void sst_dma_transfer_complete(void *arg)
{
sst_drv_ctx = (struct intel_sst_drv *)arg;
pr_debug(" sst_dma_transfer_complete\n");
sst_wake_up_block(sst_drv_ctx, 0, FW_DWNL_ID, FW_DWNL_ID, NULL, 0);
}
static inline int sst_dma_wait_for_completion(struct intel_sst_drv *sst)
{
int ret = 0;
struct sst_block *block;
/* call prep and wait */
sst->desc->callback = sst_dma_transfer_complete;
sst->desc->callback_param = sst;
block = sst_create_block(sst, FW_DWNL_ID, FW_DWNL_ID);
if (block == NULL)
return -ENOMEM;
sst->desc->tx_submit(sst_drv_ctx->desc);
ret = sst_wait_timeout(sst, block);
if (ret)
dma_wait_for_async_tx(sst_drv_ctx->desc);
sst_free_block(sst, block);
return ret;
}
static int sst_dma_firmware(struct sst_dma *dma, struct sst_sg_list *sg_list)
{
int retval = 0;
enum dma_ctrl_flags flag = DMA_CTRL_ACK;
struct scatterlist *sg_src_list, *sg_dst_list;
int length;
pr_debug("%s: use_lli %d\n", __func__, sst_drv_ctx->use_lli);
sg_src_list = sg_list->src;
sg_dst_list = sg_list->dst;
length = sg_list->list_len;
/* BY default PIMR is unsmasked
* FW gets unmaksed dma intr too, so mask it for FW to execute on mrfld
*/
if (sst_drv_ctx->pci_id == SST_MRFLD_PCI_ID ||
sst_drv_ctx->pci_id == SST_BYT_PCI_ID)
sst_shim_write(sst_drv_ctx->shim, SST_PIMR, 0xFFFF0034);
if (sst_drv_ctx->use_lli) {
sst_drv_ctx->desc = dma->ch->device->device_prep_dma_sg(dma->ch,
sg_dst_list, length,
sg_src_list, length, flag);
if (!sst_drv_ctx->desc)
return -EFAULT;
retval = sst_dma_wait_for_completion(sst_drv_ctx);
if (retval)
pr_err("sst_dma_firmware..timeout!\n");
} else {
struct scatterlist *sg;
dma_addr_t src_addr, dstn_addr;
int i = 0;
/* dma single block mode */
for_each_sg(sg_src_list, sg, length, i) {
pr_debug("dma desc %d, length %d\n", i, sg->length);
src_addr = sg_phys(sg);
dstn_addr = sg_phys(sg_dst_list);
if (sg_dst_list)
sg_dst_list = sg_next(sg_dst_list);
sst_drv_ctx->desc = dma->ch->device->device_prep_dma_memcpy(
dma->ch, dstn_addr, src_addr, sg->length, flag);
if (!sst_drv_ctx->desc)
return -EFAULT;
retval = sst_dma_wait_for_completion(sst_drv_ctx);
if (retval)
pr_err("sst_dma_firmware..timeout!\n");
}
}
return retval;
}
/*
* sst_fill_sglist - Fill the sg list
*
* @from: src address of the fw
* @to: virtual address of IRAM/DRAM
* @block_size: size of the block
* @sg_src: source scatterlist pointer
* @sg_dst: Destination scatterlist pointer
* @fw_sg_list: Pointer to the sg_list
* @dma_max_len: maximum len of the DMA block
*
* Parses modules that need to be placed in SST IRAM and DRAM
* and stores them in a sg list for transfer
* returns error or 0 if list creation fails or pass.
*/
static int sst_fill_sglist(unsigned long from, unsigned long to,
u32 block_size, struct scatterlist **sg_src, struct scatterlist **sg_dstn,
struct sst_sg_list *fw_sg_list, u32 dma_max_len)
{
u32 offset = 0;
int len = 0;
unsigned long dstn, src;
pr_debug("%s entry", __func__);
if (!sg_src || !sg_dstn)
return -EINVAL;
do {
dstn = (unsigned long) (to + offset);
src = (unsigned long) (from + offset);
/* split blocks to dma_max_len */
len = block_size - offset;
pr_debug("DMA blk src %lx,dstn %lx,len %d,offset %d, size %d\n",
src, dstn, len, offset, block_size);
if (len > dma_max_len) {
pr_debug("block size exceeds %d\n", dma_max_len);
len = dma_max_len;
offset += len;
} else {
pr_debug("Node length less that %d\n", dma_max_len);
offset = 0;
}
if (!(*sg_src) || !(*sg_dstn))
return -ENOMEM;
sg_set_page(*sg_src, virt_to_page((void *) src), len,
offset_in_page((void *) src));
sg_set_page(*sg_dstn, virt_to_page((void *) dstn), len,
offset_in_page((void *) dstn));
*sg_src = sg_next(*sg_src);
*sg_dstn = sg_next(*sg_dstn);
/* TODO: is sg_idx required? */
if (sst_drv_ctx->info.use_elf == true)
fw_sg_list->sg_idx++;
} while (offset > 0);
return 0;
}
static int sst_parse_elf_module_dma(struct intel_sst_drv *sst, const void *fw,
struct sst_info info, Elf32_Phdr *pr,
struct scatterlist **sg_src, struct scatterlist **sg_dstn,
struct sst_sg_list *fw_sg_list)
{
unsigned long dstn, src;
unsigned int dstn_phys;
int ret_val = 0;
int mem_type;
ret_val = sst_fill_dstn(sst, info, pr, (void *)&dstn, &dstn_phys, &mem_type);
if (ret_val)
return ret_val;
dstn = (unsigned long) phys_to_virt(dstn_phys);
src = (unsigned long) (fw + pr->p_offset);
ret_val = sst_fill_sglist(src, dstn, pr->p_filesz,
sg_src, sg_dstn, fw_sg_list, sst->info.dma_max_len);
return ret_val;
}
static int
sst_parse_elf_fw_dma(struct intel_sst_drv *sst, const void *fw_in_mem,
struct sst_sg_list *fw_sg_list)
{
int i = 0, ret = 0;
Elf32_Ehdr *elf;
Elf32_Phdr *pr;
struct sst_info info;
struct scatterlist *sg_src = NULL, *sg_dst = NULL;
unsigned int sg_len;
BUG_ON(!fw_in_mem);
elf = (Elf32_Ehdr *)fw_in_mem;
pr = (Elf32_Phdr *) (fw_in_mem + elf->e_phoff);
pr_debug("%s entry\n", __func__);
sst_fill_info(sst, &info);
sg_len = sst_get_elf_sg_len(sst, elf, pr, info);
if (sg_len == 0) {
pr_err("we got NULL sz ELF, abort\n");
return -EIO;
}
if (sst_init_dma_sg_list(sst, sg_len, &sg_src, &sg_dst)) {
ret = -ENOMEM;
goto err1;
}
fw_sg_list->src = sg_src;
fw_sg_list->dst = sg_dst;
fw_sg_list->list_len = sg_len;
fw_sg_list->sg_idx = 0;
while (i < elf->e_phnum) {
if ((pr[i].p_type == PT_LOAD) && (pr[i].p_filesz)) {
ret = sst_parse_elf_module_dma(sst, fw_in_mem, info,
&pr[i], &sg_src, &sg_dst, fw_sg_list);
if (ret)
goto err;
}
i++;
}
return 0;
err:
kfree(fw_sg_list->src);
kfree(fw_sg_list->dst);
err1:
fw_sg_list->src = NULL;
fw_sg_list->dst = NULL;
fw_sg_list->list_len = 0;
fw_sg_list->sg_idx = 0;
return ret;
}
/**
* sst_parse_module_dma - Parse audio FW modules and populate the dma list
*
* @sst_ctx : sst driver context
* @module : FW module header
* @sg_list : Pointer to the sg_list to be populated
* Count the length for scattergather list
* and create the scattergather list of same length
* returns error or 0 if module sizes are proper
*/
static int sst_parse_module_dma(struct intel_sst_drv *sst_ctx,
struct fw_module_header *module,
struct sst_sg_list *sg_list)
{
struct fw_block_info *block;
u32 count;
unsigned long ram, src;
int retval, sg_len = 0;
struct scatterlist *sg_src, *sg_dst;
pr_debug("module sign %s size %x blocks %x type %x\n",
module->signature, module->mod_size,
module->blocks, module->type);
pr_debug("module entrypoint 0x%x\n", module->entry_point);
block = (void *)module + sizeof(*module);
for (count = 0; count < module->blocks; count++) {
sg_len += (block->size) / sst_drv_ctx->info.dma_max_len;
if (block->size % sst_drv_ctx->info.dma_max_len)
sg_len = sg_len + 1;
block = (void *)block + sizeof(*block) + block->size;
}
if (sst_init_dma_sg_list(sst_ctx, sg_len, &sg_src, &sg_dst)) {
retval = -ENOMEM;
goto err1;
}
sg_list->src = sg_src;
sg_list->dst = sg_dst;
sg_list->list_len = sg_len;
block = (void *)module + sizeof(*module);
for (count = 0; count < module->blocks; count++) {
if (block->size <= 0) {
pr_err("block size invalid\n");
retval = -EINVAL;
goto err;
}
switch (block->type) {
case SST_IRAM:
ram = sst_ctx->iram_base;
break;
case SST_DRAM:
ram = sst_ctx->dram_base;
break;
default:
pr_err("wrong ram type0x%x in block0x%x\n",
block->type, count);
retval = -EINVAL;
goto err;
}
/*converting from physical to virtual because
scattergather list works on virtual pointers*/
ram = (unsigned long) phys_to_virt(ram);
ram = (unsigned long)(ram + block->ram_offset);
src = (unsigned long) (void *)block + sizeof(*block);
retval = sst_fill_sglist(src, ram,
block->size, &sg_src, &sg_dst,
sg_list, sst_ctx->info.dma_max_len);
if (retval)
goto err;
block = (void *)block + sizeof(*block) + block->size;
}
return 0;
err:
kfree(sg_list->src);
kfree(sg_list->dst);
err1:
sg_list->src = NULL;
sg_list->dst = NULL;
sg_list->list_len = 0;
return retval;
}
/**
* sst_parse_fw_dma - parse the firmware image & populate the list for dma
*
* @sst_fw_in_mem : pointer to audio fw
* @size : size of the firmware
* @fw_list : pointer to sst_sg_list to be populated
* This function parses the FW image and saves the parsed image in the list
* for dma
*/
static int sst_parse_fw_dma(const void *sst_fw_in_mem, unsigned long size,
struct sst_sg_list *fw_list)
{
struct fw_module_header *module;
u32 count, num_modules;
int ret_val;
ret_val = sst_validate_fw_image(sst_fw_in_mem, size,
&module, &num_modules);
if (ret_val)
return ret_val;
for (count = 0; count < num_modules; count++) {
/* module */
ret_val = sst_parse_module_dma(sst_drv_ctx, module, fw_list);
if (ret_val)
return ret_val;
module = (void *)module + sizeof(*module) + module->mod_size ;
}
return 0;
}
static void sst_dma_free_resources(struct sst_dma *dma)
{
pr_debug("entry:%s\n", __func__);
dma_release_channel(dma->ch);
}
void sst_fill_config(struct intel_sst_drv *sst_ctx, unsigned int offset)
{
struct sst_fill_config sst_config;
if (!(sst_ctx->pdata->bdata && sst_ctx->pdata->pdata))
return;
sst_config.sign = SST_CONFIG_SSP_SIGN;
memcpy(&sst_config.sst_bdata, sst_ctx->pdata->bdata, sizeof(struct sst_board_config_data));
memcpy(&sst_config.sst_pdata, sst_ctx->pdata->pdata, sizeof(struct sst_platform_config_data));
sst_config.shim_phy_add = sst_ctx->shim_phy_add;
sst_config.mailbox_add = sst_ctx->mailbox_add;
MEMCPY_TOIO(sst_ctx->dram + offset, &sst_config, sizeof(sst_config));
}
/**
* sst_do_dma - function allocs and initiates the DMA
*
* @sg_list: Pointer to dma list on which the dma needs to be initiated
*
* Triggers the DMA
*/
static int sst_do_dma(struct sst_sg_list *sg_list)
{
int ret_val;
/* get a dmac channel */
ret_val = sst_alloc_dma_chan(&sst_drv_ctx->dma);
if (ret_val)
return ret_val;
/* allocate desc for transfer and submit */
ret_val = sst_dma_firmware(&sst_drv_ctx->dma, sg_list);
sst_dma_free_resources(&sst_drv_ctx->dma);
return ret_val;
}
/*
* 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;
}
static int sst_parse_elf_module_memcpy(struct intel_sst_drv *sst,
const void *fw, struct sst_info info, Elf32_Phdr *pr,
struct list_head *memcpy_list)
{
void *dstn;
unsigned int dstn_phys;
int ret_val = 0;
int mem_type;
ret_val = sst_fill_dstn(sst, info, pr, &dstn, &dstn_phys, &mem_type);
if (ret_val)
return ret_val;
ret_val = sst_fill_memcpy_list(memcpy_list, dstn,
(void *)fw + pr->p_offset, pr->p_filesz, mem_type);
if (ret_val)
return ret_val;
return 0;
}
static int
sst_parse_elf_fw_memcpy(struct intel_sst_drv *sst, const void *fw_in_mem,
struct list_head *memcpy_list)
{
int i = 0;
Elf32_Ehdr *elf;
Elf32_Phdr *pr;
struct sst_info info;
BUG_ON(!fw_in_mem);
elf = (Elf32_Ehdr *)fw_in_mem;
pr = (Elf32_Phdr *) (fw_in_mem + elf->e_phoff);
pr_debug("%s entry\n", __func__);
sst_fill_info(sst, &info);
while (i < elf->e_phnum) {
if (pr[i].p_type == PT_LOAD)
sst_parse_elf_module_memcpy(sst, fw_in_mem, info,
&pr[i], memcpy_list);
i++;
}
return 0;
}
/**
* sst_parse_module_memcpy - Parse audio FW modules and populate the memcpy list
*
* @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 fw_module_header *module,
struct list_head *memcpy_list)
{
struct fw_block_info *block;
u32 count;
int ret_val = 0;
void __iomem *ram_iomem;
pr_debug("module sign %s size %x blocks %x type %x\n",
module->signature, module->mod_size,
module->blocks, module->type);
pr_debug("module entrypoint 0x%x\n", module->entry_point);
block = (void *)module + sizeof(*module);
for (count = 0; count < module->blocks; count++) {
if (block->size <= 0) {
pr_err("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;
default:
pr_err("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
*
* @sst_fw_in_mem : pointer to audio fw
* @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(const void *sst_fw_in_mem, 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(sst_fw_in_mem, size,
&module, &num_modules);
if (ret_val)
return ret_val;
for (count = 0; count < num_modules; count++) {
/* module */
ret_val = sst_parse_module_memcpy(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)
MEMCPY_TOIO((void __iomem *)listnode->dstn, listnode->src,
listnode->size);
else
memcpy(listnode->dstn, listnode->src, listnode->size);
}
}
static void sst_memcpy_free_lib_resources(void)
{
struct sst_memcpy_list *listnode, *tmplistnode;
pr_debug("entry:%s\n", __func__);
/*Free the list*/
if (!list_empty(&sst_drv_ctx->libmemcpy_list)) {
list_for_each_entry_safe(listnode, tmplistnode,
&sst_drv_ctx->libmemcpy_list, memcpylist) {
list_del(&listnode->memcpylist);
kfree(listnode);
}
}
}
void sst_memcpy_free_resources(void)
{
struct sst_memcpy_list *listnode, *tmplistnode;
pr_debug("entry:%s\n", __func__);
/*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);
}
}
sst_memcpy_free_lib_resources();
}
void sst_firmware_load_cb(const struct firmware *fw, void *context)
{
struct intel_sst_drv *ctx = context;
int ret = 0;
pr_debug("In %s\n", __func__);
if (fw == NULL) {
pr_err("request fw failed\n");
goto out;
}
if (sst_drv_ctx->sst_state != SST_UN_INIT ||
ctx->fw_in_mem != NULL)
goto exit;
pr_debug("Request Fw completed\n");
trace_sst_fw_download("End of FW request", ctx->sst_state);
if (ctx->info.use_elf == true)
ret = sst_validate_elf(fw, false);
if (ret != 0) {
pr_err("FW image invalid...\n");
goto out;
}
ctx->fw_in_mem = kzalloc(fw->size, GFP_KERNEL);
if (!ctx->fw_in_mem) {
pr_err("%s unable to allocate memory\n", __func__);
goto out;
}
pr_debug("copied fw to %p", ctx->fw_in_mem);
pr_debug("phys: %lx", (unsigned long)virt_to_phys(ctx->fw_in_mem));
memcpy(ctx->fw_in_mem, fw->data, fw->size);
trace_sst_fw_download("Start FW parsing", ctx->sst_state);
if (ctx->use_dma) {
if (ctx->info.use_elf == true)
ret = sst_parse_elf_fw_dma(ctx, ctx->fw_in_mem,
&ctx->fw_sg_list);
else
ret = sst_parse_fw_dma(ctx->fw_in_mem, fw->size,
&ctx->fw_sg_list);
} else {
if (ctx->info.use_elf == true)
ret = sst_parse_elf_fw_memcpy(ctx, ctx->fw_in_mem,
&ctx->memcpy_list);
else
ret = sst_parse_fw_memcpy(ctx->fw_in_mem, fw->size,
&ctx->memcpy_list);
}
trace_sst_fw_download("End FW parsing", ctx->sst_state);
if (ret) {
kfree(ctx->fw_in_mem);
ctx->fw_in_mem = NULL;
goto out;
}
/* If static module download(download at boot time) is supported,
* set the flag to indicate lib download is to be done
*/
if (ctx->pdata->lib_info)
if (ctx->pdata->lib_info->mod_ddr_dnld)
ctx->lib_dwnld_reqd = true;
sst_set_fw_state_locked(sst_drv_ctx, SST_FW_LIB_LOAD);
goto exit;
out:
sst_set_fw_state_locked(sst_drv_ctx, SST_UN_INIT);
exit:
if (fw != NULL)
release_firmware(fw);
}
/*
* 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;
snprintf(name, sizeof(name), "%s%04x%s", "fw_sst_",
sst->pci_id, ".bin");
pr_debug("Requesting FW %s now...\n", name);
retval = request_firmware(&fw, name, sst->dev);
if (fw == NULL) {
pr_err("fw is returning as null\n");
return -EINVAL;
}
if (retval) {
pr_err("request fw failed %d\n", retval);
return retval;
}
trace_sst_fw_download("End of FW request", sst->sst_state);
if (sst->info.use_elf == true)
retval = sst_validate_elf(fw, false);
if (retval != 0) {
pr_err("FW image invalid...\n");
goto end_release;
}
sst->fw_in_mem = kzalloc(fw->size, GFP_KERNEL);
if (!sst->fw_in_mem) {
pr_err("%s unable to allocate memory\n", __func__);
retval = -ENOMEM;
goto end_release;
}
pr_debug("copied fw to %p", sst->fw_in_mem);
pr_debug("phys: %lx", (unsigned long)virt_to_phys(sst->fw_in_mem));
memcpy(sst->fw_in_mem, fw->data, fw->size);
trace_sst_fw_download("Start FW parsing", sst->sst_state);
if (sst->use_dma) {
if (sst->info.use_elf == true)
retval = sst_parse_elf_fw_dma(sst, sst->fw_in_mem,
&sst->fw_sg_list);
else
retval = sst_parse_fw_dma(sst->fw_in_mem, fw->size,
&sst->fw_sg_list);
} else {
if (sst->info.use_elf == true)
retval = sst_parse_elf_fw_memcpy(sst, sst->fw_in_mem,
&sst->memcpy_list);
else
retval = sst_parse_fw_memcpy(sst->fw_in_mem, fw->size,
&sst->memcpy_list);
}
trace_sst_fw_download("End FW parsing", sst->sst_state);
if (retval) {
kfree(sst->fw_in_mem);
sst->fw_in_mem = NULL;
}
/* If static module download(download at boot time) is supported,
* set the flag to indicate lib download is to be done
*/
if (sst->pdata->lib_info)
if (sst->pdata->lib_info->mod_ddr_dnld)
sst->lib_dwnld_reqd = true;
end_release:
release_firmware(fw);
return retval;
}
static inline void print_lib_info(struct snd_sst_lib_download_info *resp)
{
pr_debug("codec Type %d Ver %d Built %s: %s\n",
resp->dload_lib.lib_info.lib_type,
resp->dload_lib.lib_info.lib_version,
resp->dload_lib.lib_info.b_date,
resp->dload_lib.lib_info.b_time);
}
/* sst_download_library - This function is called when any
codec/post processing library needs to be downloaded */
static int sst_download_library(const struct firmware *fw_lib,
struct snd_sst_lib_download_info *lib)
{
int ret_val = 0;
/* send IPC message and wait */
u8 pvt_id;
struct ipc_post *msg = NULL;
union config_status_reg csr;
struct snd_sst_str_type str_type = {0};
int retval = 0;
void *codec_fw;
struct sst_block *block;
pvt_id = sst_assign_pvt_id(sst_drv_ctx);
ret_val = sst_create_block_and_ipc_msg(&msg, true, sst_drv_ctx, &block,
IPC_IA_PREP_LIB_DNLD, pvt_id);
if (ret_val) {
pr_err("library download failed\n");
return ret_val;
}
sst_fill_header(&msg->header, IPC_IA_PREP_LIB_DNLD, 1, pvt_id);
msg->header.part.data = sizeof(u32) + sizeof(str_type);
str_type.codec_type = lib->dload_lib.lib_info.lib_type;
/*str_type.pvt_id = pvt_id;*/
memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
memcpy(msg->mailbox_data + sizeof(u32), &str_type, sizeof(str_type));
sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg);
retval = sst_wait_timeout(sst_drv_ctx, block);
if (block->data) {
struct snd_sst_str_type *str_type =
(struct snd_sst_str_type *)block->data;
if (str_type->result) {
/* error */
pr_err("Prep codec downloaded failed %d\n",
str_type->result);
retval = -EIO;
goto free_block;
}
kfree(block->data);
} else if (retval != 0) {
retval = -EIO;
goto free_block;
}
pr_debug("FW responded, ready for download now...\n");
codec_fw = kzalloc(fw_lib->size, GFP_KERNEL);
if (!codec_fw) {
memset(lib, 0, sizeof(*lib));
retval = -ENOMEM;
goto send_ipc;
}
memcpy(codec_fw, fw_lib->data, fw_lib->size);
if (sst_drv_ctx->use_dma)
retval = sst_parse_fw_dma(codec_fw, fw_lib->size,
&sst_drv_ctx->library_list);
else
retval = sst_parse_fw_memcpy(codec_fw, fw_lib->size,
&sst_drv_ctx->libmemcpy_list);
if (retval) {
memset(lib, 0, sizeof(*lib));
goto send_ipc;
}
/* downloading on success */
mutex_lock(&sst_drv_ctx->sst_lock);
sst_drv_ctx->sst_state = SST_FW_LOADED;
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = readl(sst_drv_ctx->shim + SST_CSR);
csr.part.run_stall = 1;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.part.bypass = 0x7;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
mutex_unlock(&sst_drv_ctx->csr_lock);
if (sst_drv_ctx->use_dma) {
ret_val = sst_do_dma(&sst_drv_ctx->library_list);
if (ret_val) {
pr_err("sst_do_dma failed, abort\n");
memset(lib, 0, sizeof(*lib));
}
} else
sst_do_memcpy(&sst_drv_ctx->libmemcpy_list);
/* set the FW to running again */
mutex_lock(&sst_drv_ctx->csr_lock);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.part.bypass = 0x0;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
csr.full = sst_shim_read(sst_drv_ctx->shim, SST_CSR);
csr.part.run_stall = 0;
sst_shim_write(sst_drv_ctx->shim, SST_CSR, csr.full);
mutex_unlock(&sst_drv_ctx->csr_lock);
send_ipc:
/* send download complete and wait */
if (sst_create_ipc_msg(&msg, true)) {
retval = -ENOMEM;
goto free_resources;
}
block->condition = false;
block->msg_id = IPC_IA_LIB_DNLD_CMPLT;
sst_fill_header(&msg->header, IPC_IA_LIB_DNLD_CMPLT, 1, pvt_id);
msg->header.part.data = sizeof(u32) + sizeof(*lib);
lib->pvt_id = pvt_id;
memcpy(msg->mailbox_data, &msg->header, sizeof(u32));
memcpy(msg->mailbox_data + sizeof(u32), lib, sizeof(*lib));
sst_add_to_dispatch_list_and_post(sst_drv_ctx, msg);
pr_debug("Waiting for FW response Download complete\n");
retval = sst_wait_timeout(sst_drv_ctx, block);
sst_drv_ctx->sst_state = SST_FW_RUNNING;
if (block->data) {
struct snd_sst_lib_download_info *resp = block->data;
retval = resp->result;
if (retval) {
pr_err("err in lib dload %x\n", resp->result);
goto free_resources;
} else {
pr_debug("Codec download complete...\n");
print_lib_info(resp);
}
} else if (retval) {
/* error */
retval = -EIO;
goto free_resources;
}
pr_debug("FW success on Download complete\n");
free_resources:
if (sst_drv_ctx->use_dma) {
kfree(sst_drv_ctx->library_list.src);
kfree(sst_drv_ctx->library_list.dst);
sst_drv_ctx->library_list.list_len = 0;
}
kfree(codec_fw);
mutex_unlock(&sst_drv_ctx->sst_lock);
free_block:
sst_free_block(sst_drv_ctx, block);
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);
MEMCPY_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);
MEMCPY_TOIO(addr, &bss_reset, sizeof(u32));
pr_debug("%s: config written to DCCM\n", __func__);
}
void sst_post_download_mrfld(struct intel_sst_drv *ctx)
{
sst_dccm_config_write(ctx->dram, ctx->ddr_base);
/* For mrfld, download all libraries the first time fw is
* downloaded */
pr_debug("%s: lib_dwnld = %u\n", __func__, ctx->lib_dwnld_reqd);
if (ctx->lib_dwnld_reqd) {
sst_load_all_modules_elf(ctx, sst_modules_mrfld, ARRAY_SIZE(sst_modules_mrfld));
ctx->lib_dwnld_reqd = false;
}
}
void sst_post_download_ctp(struct intel_sst_drv *ctx)
{
sst_fill_config(ctx, 0);
}
void sst_post_download_byt(struct intel_sst_drv *ctx)
{
sst_dccm_config_write(ctx->dram, ctx->ddr_base);
sst_fill_config(ctx, 2 * sizeof(u32));
pr_debug("%s: lib_dwnld = %u\n", __func__, ctx->lib_dwnld_reqd);
if (ctx->lib_dwnld_reqd) {
sst_load_all_modules_elf(ctx, sst_modules_byt,
ARRAY_SIZE(sst_modules_byt));
ctx->lib_dwnld_reqd = false;
}
}
static void sst_init_lib_mem_mgr(struct intel_sst_drv *ctx)
{
struct sst_mem_mgr *mgr = &ctx->lib_mem_mgr;
const struct sst_lib_dnld_info *lib_info = ctx->pdata->lib_info;
memset(mgr, 0, sizeof(*mgr));
mgr->current_base = lib_info->mod_base + lib_info->mod_table_offset
+ lib_info->mod_table_size;
mgr->avail = lib_info->mod_end - mgr->current_base + 1;
pr_debug("current base = 0x%lx , avail = 0x%x\n",
(unsigned long)mgr->current_base, mgr->avail);
}
/**
* sst_load_fw - function to load FW into DSP
*
*
* Transfers the FW to DSP using dma/memcpy
*/
int sst_load_fw(void)
{
int ret_val = 0;
struct sst_block *block;
pr_debug("sst_load_fw\n");
if ((sst_drv_ctx->sst_state != SST_START_INIT &&
sst_drv_ctx->sst_state != SST_FW_LIB_LOAD) ||
sst_drv_ctx->sst_state == SST_SHUTDOWN)
return -EAGAIN;
if (!sst_drv_ctx->fw_in_mem) {
if (sst_drv_ctx->sst_state != SST_START_INIT) {
/* even wake*/
pr_err("sst : wait for FW to be downloaded\n");
return -EBUSY;
} else {
trace_sst_fw_download("Req FW sent in check device",
sst_drv_ctx->sst_state);
pr_debug("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, CSTATE_EXIT_LATENCY_S0i1 - 1);
ret_val = sst_drv_ctx->ops->reset();
if (ret_val)
goto restore;
trace_sst_fw_download("Start FW copy", sst_drv_ctx->sst_state);
if (sst_drv_ctx->use_dma) {
ret_val = sst_do_dma(&sst_drv_ctx->fw_sg_list);
if (ret_val) {
pr_err("sst_do_dma failed, abort\n");
goto restore;
}
} else {
sst_do_memcpy(&sst_drv_ctx->memcpy_list);
}
trace_sst_fw_download("Post download for Lib start",
sst_drv_ctx->sst_state);
/* Write the DRAM/DCCM config before enabling FW */
if (sst_drv_ctx->ops->post_download)
sst_drv_ctx->ops->post_download(sst_drv_ctx);
trace_sst_fw_download("Post download for Lib end",
sst_drv_ctx->sst_state);
sst_drv_ctx->sst_state = SST_FW_LOADED;
/* bring sst out of reset */
ret_val = sst_drv_ctx->ops->start();
if (ret_val)
goto restore;
trace_sst_fw_download("DSP reset done",
sst_drv_ctx->sst_state);
ret_val = sst_wait_timeout(sst_drv_ctx, block);
if (ret_val) {
pr_err("fw download failed %d\n" , ret_val);
/* assume FW d/l 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);
return ret_val;
}
/**
* sst_load_library - function to load FW into DSP
*
* @lib: Pointer to the lib download structure
* @ops: Contains the stream ops
* This function is called when FW requests for a particular library download
* This function prepares & downloads the library
*/
int sst_load_library(struct snd_sst_lib_download *lib, u8 ops)
{
char buf[20];
const char *type, *dir;
int len = 0, error = 0;
u32 entry_point;
const struct firmware *fw_lib;
struct snd_sst_lib_download_info dload_info = {{{0},},};
memset(buf, 0, sizeof(buf));
pr_debug("Lib Type 0x%x, Slot 0x%x, ops 0x%x\n",
lib->lib_info.lib_type, lib->slot_info.slot_num, ops);
pr_debug("Version 0x%x, name %s, caps 0x%x media type 0x%x\n",
lib->lib_info.lib_version, lib->lib_info.lib_name,
lib->lib_info.lib_caps, lib->lib_info.media_type);
pr_debug("IRAM Size 0x%x, offset 0x%x\n",
lib->slot_info.iram_size, lib->slot_info.iram_offset);
pr_debug("DRAM Size 0x%x, offset 0x%x\n",
lib->slot_info.dram_size, lib->slot_info.dram_offset);
switch (lib->lib_info.lib_type) {
case SST_CODEC_TYPE_MP3:
type = "mp3_";
break;
case SST_CODEC_TYPE_AAC:
type = "aac_";
break;
case SST_CODEC_TYPE_AACP:
type = "aac_v1_";
break;
case SST_CODEC_TYPE_eAACP:
type = "aac_v2_";
break;
case SST_CODEC_TYPE_WMA9:
type = "wma9_";
break;
default:
pr_err("Invalid codec type\n");
error = -EINVAL;
goto wake;
}
if (ops == STREAM_OPS_CAPTURE)
dir = "enc_";
else
dir = "dec_";
len = strlen(type) + strlen(dir);
strncpy(buf, type, sizeof(buf)-1);
strncpy(buf + strlen(type), dir, sizeof(buf)-strlen(type)-1);
len += snprintf(buf + len, sizeof(buf) - len, "%d",
lib->slot_info.slot_num);
len += snprintf(buf + len, sizeof(buf) - len, ".bin");
pr_debug("Requesting %s\n", buf);
error = request_firmware(&fw_lib, buf, sst_drv_ctx->dev);
if (fw_lib == NULL) {
pr_err("fw_lib pointer is returning null\n");
return -EINVAL;
}
if (error) {
pr_err("library load failed %d\n", error);
goto wake;
}
error = sst_validate_library(fw_lib, &lib->slot_info, &entry_point);
if (error)
goto wake_free;
lib->mod_entry_pt = entry_point;
memcpy(&dload_info.dload_lib, lib, sizeof(*lib));
/* Prevent C-states beyond C6 */
pm_qos_update_request(sst_drv_ctx->qos, CSTATE_EXIT_LATENCY_S0i1 - 1);
error = sst_download_library(fw_lib, &dload_info);
/* Re-enable Deeper C-states beyond C6 */
pm_qos_update_request(sst_drv_ctx->qos, PM_QOS_DEFAULT_VALUE);
if (error)
goto wake_free;
/* lib is downloaded and init send alloc again */
pr_debug("Library is downloaded now...\n");
wake_free:
/* sst_wake_up_alloc_block(sst_drv_ctx, pvt_id, error, NULL); */
release_firmware(fw_lib);
wake:
return error;
}
/* In relocatable elf file, there can be relocatable variables and functions.
* Variables are kept in Global Address Offset Table (GOT) and functions in
* Procedural Linkage Table (PLT). In current codec binaries only relocatable
* variables are seen. So we use the GOT table.
*/
static int sst_find_got_table(Elf32_Shdr *shdr, int nsec, char *in_elf,
Elf32_Rela **got, unsigned int *cnt)
{
int i = 0;
while (i < nsec) {
if (shdr[i].sh_type == SHT_RELA) {
*got = (Elf32_Rela *)(in_elf + shdr[i].sh_offset);
*cnt = shdr[i].sh_size / sizeof(Elf32_Rela);
break;
}
i++;
}
if (i == nsec)
return -EINVAL;
return 0;
}
/* For each entry in the GOT table, find the unrelocated offset. Then
* add the relocation base to the offset and write back the new address to the
* original variable location.
*/
static int sst_relocate_got_entries(Elf32_Rela *table, unsigned int size,
char *in_elf, int elf_size, u32 rel_base)
{
int i;
Elf32_Rela *entry;
Elf32_Addr *target_addr, unreloc_addr;
for (i = 0; i < size; i++) {
entry = &table[i];
if (ELF32_R_SYM(entry->r_info) != 0) {
return -EINVAL;
} else {
if (entry->r_offset > elf_size) {
pr_err("GOT table target addr out of range\n");
return -EINVAL;
}
target_addr = (Elf32_Addr *)(in_elf + entry->r_offset);
unreloc_addr = *target_addr + entry->r_addend;
if (unreloc_addr > elf_size) {
pr_err("GOT table entry invalid\n");
continue;
}
*target_addr = unreloc_addr + rel_base;
}
}
return 0;
}
static int sst_relocate_elf(char *in_elf, int elf_size, phys_addr_t rel_base,
Elf32_Addr *entry_pt)
{
int retval = 0;
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)in_elf;
Elf32_Shdr *shdr = (Elf32_Shdr *) (in_elf + ehdr->e_shoff);
Elf32_Phdr *phdr = (Elf32_Phdr *) (in_elf + ehdr->e_phoff);
int i, num_sec;
Elf32_Rela *rel_table = NULL;
unsigned int rela_cnt = 0;
u32 rbase;
BUG_ON(rel_base > (u32)(-1));
rbase = (u32) (rel_base & (u32)(~0));
/* relocate the entry_pt */
*entry_pt = (Elf32_Addr)(ehdr->e_entry + rbase);
num_sec = ehdr->e_shnum;
/* Find the relocation(GOT) table through the section header */
retval = sst_find_got_table(shdr, num_sec, in_elf,
&rel_table, &rela_cnt);
if (retval < 0)
return retval;
/* Relocate all the entries in the GOT */
retval = sst_relocate_got_entries(rel_table, rela_cnt, in_elf,
elf_size, rbase);
if (retval < 0)
return retval;
pr_debug("GOT entries relocated\n");
/* Update the program headers in the ELF */
for (i = 0; i < ehdr->e_phnum; i++) {
if (phdr[i].p_type == PT_LOAD) {
phdr[i].p_vaddr += rbase;
phdr[i].p_paddr += rbase;
}
}
pr_debug("program header entries updated\n");
return retval;
}
#define ALIGN_256 0x100
int sst_get_next_lib_mem(struct sst_mem_mgr *mgr, int size,
unsigned long *lib_base)
{
int retval = 0;
pr_debug("library orig size = 0x%x", size);
if (size % ALIGN_256)
size += (ALIGN_256 - (size % ALIGN_256));
if (size > mgr->avail)
return -ENOMEM;
*lib_base = mgr->current_base;
mgr->current_base += size;
mgr->avail -= size;
mgr->count++;
pr_debug("library base = 0x%lx", *lib_base);
pr_debug("library aligned size = 0x%x", size);
pr_debug("lib count = %d\n", mgr->count);
return retval;
}
static int sst_download_lib_elf(struct intel_sst_drv *sst, const void *lib,
int size)
{
int retval = 0;
pr_debug("In %s\n", __func__);
if (sst->use_dma) {
retval = sst_parse_elf_fw_dma(sst, lib,
&sst->library_list);
if (retval)
goto free_dma_res;
retval = sst_do_dma(&sst->library_list);
if (retval)
pr_err("sst_do_dma failed, abort\n");
free_dma_res:
kfree(sst->library_list.src);
kfree(sst->library_list.dst);
sst->library_list.list_len = 0;
} else {
retval = sst_parse_elf_fw_memcpy(sst, lib,
&sst->libmemcpy_list);
if (retval)
return retval;
sst_do_memcpy(&sst->libmemcpy_list);
sst_memcpy_free_lib_resources();
}
pr_debug("download lib complete");
return retval;
}
static void sst_fill_fw_module_table(struct sst_module_info *mod_list,
int list_size, unsigned long ddr_base)
{
int i;
u32 *write_ptr = (u32 *)ddr_base;
pr_debug("In %s\n", __func__);
for (i = 0; i < list_size; i++) {
if (mod_list[i].status == SST_LIB_DOWNLOADED) {
pr_debug("status dnwld for %d\n", i);
pr_debug("module id %d\n", mod_list[i].id);
pr_debug("entry pt 0x%x\n", mod_list[i].entry_pt);
*write_ptr++ = mod_list[i].id;
*write_ptr++ = mod_list[i].entry_pt;
}
}
}
static int sst_request_lib_elf(struct sst_module_info *mod_entry,
const struct firmware **fw_lib, int pci_id, struct device *dev)
{
char name[25];
int retval = 0;
snprintf(name, sizeof(name), "%s%s%04x%s", mod_entry->name,
"_", pci_id, ".bin");
pr_debug("Requesting %s\n", name);
retval = request_firmware(fw_lib, name, dev);
if (retval) {
pr_err("%s library load failed %d\n", name, retval);
return retval;
}
pr_debug("got lib\n");
mod_entry->status = SST_LIB_FOUND;
return 0;
}
static int sst_allocate_lib_mem(const struct firmware *lib, int size,
struct sst_mem_mgr *mem_mgr, char **out_elf, unsigned long *lib_start)
{
int retval = 0;
*out_elf = kzalloc(size, GFP_KERNEL);
if (!*out_elf) {
pr_err("cannot alloc mem for elf copy %d\n", retval);
goto mem_error;
}
memcpy(*out_elf, lib->data, size);
retval = sst_get_next_lib_mem(mem_mgr, size, lib_start);
if (retval < 0) {
pr_err("cannot alloc ddr mem for lib: %d\n", retval);
kfree(*out_elf);
goto mem_error;
}
return 0;
mem_error:
release_firmware(lib);
return -ENOMEM;
}
int sst_load_all_modules_elf(struct intel_sst_drv *ctx, struct sst_module_info *mod_table,
int num_modules)
{
int retval = 0;
int i;
const struct firmware *fw_lib;
struct sst_module_info *mod = NULL;
char *out_elf;
unsigned int lib_size = 0;
unsigned int mod_table_offset = ctx->pdata->lib_info->mod_table_offset;
unsigned long lib_base;
pr_debug("In %s", __func__);
sst_init_lib_mem_mgr(ctx);
for (i = 0; i < num_modules; i++) {
mod = &mod_table[i];
trace_sst_lib_download("Start of Request Lib", mod->name);
retval = sst_request_lib_elf(mod, &fw_lib,
ctx->pci_id, ctx->dev);
if (retval < 0)
continue;
lib_size = fw_lib->size;
trace_sst_lib_download("End of Request Lib", mod->name);
retval = sst_validate_elf(fw_lib, true);
if (retval < 0) {
pr_err("library is not valid elf %d\n", retval);
release_firmware(fw_lib);
continue;
}
pr_debug("elf validated\n");
retval = sst_allocate_lib_mem(fw_lib, lib_size,
&ctx->lib_mem_mgr, &out_elf, &lib_base);
if (retval < 0) {
pr_err("lib mem allocation failed: %d\n", retval);
continue;
}
pr_debug("lib space allocated\n");
/* relocate in place */
retval = sst_relocate_elf(out_elf, lib_size,
lib_base, &mod->entry_pt);
if (retval < 0) {
pr_err("lib elf relocation failed: %d\n", retval);
release_firmware(fw_lib);
kfree(out_elf);
continue;
}
pr_debug("relocation done\n");
release_firmware(fw_lib);
trace_sst_lib_download("Start of download Lib", mod->name);
/* write to ddr imr region,use memcpy method */
retval = sst_download_lib_elf(ctx, out_elf, lib_size);
trace_sst_lib_download("End of download Lib", mod->name);
mod->status = SST_LIB_DOWNLOADED;
kfree(out_elf);
}
/* write module table to DDR */
sst_fill_fw_module_table(mod_table, num_modules,
(unsigned long)(ctx->ddr + mod_table_offset));
return retval;
}