mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-16 08:44:21 +08:00
471f488583
WMDR coefficient files can specify coefficients in terms of algorithm specific data regions. Record the start addresses of these regions while parsing the algorithms and then use them to handle coefficients with these formats. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
982 lines
25 KiB
C
982 lines
25 KiB
C
/*
|
|
* wm_adsp.c -- Wolfson ADSP support
|
|
*
|
|
* Copyright 2012 Wolfson Microelectronics plc
|
|
*
|
|
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/slab.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/jack.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/tlv.h>
|
|
|
|
#include <linux/mfd/arizona/registers.h>
|
|
|
|
#include "wm_adsp.h"
|
|
|
|
#define adsp_crit(_dsp, fmt, ...) \
|
|
dev_crit(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__)
|
|
#define adsp_err(_dsp, fmt, ...) \
|
|
dev_err(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__)
|
|
#define adsp_warn(_dsp, fmt, ...) \
|
|
dev_warn(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__)
|
|
#define adsp_info(_dsp, fmt, ...) \
|
|
dev_info(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__)
|
|
#define adsp_dbg(_dsp, fmt, ...) \
|
|
dev_dbg(_dsp->dev, "DSP%d: " fmt, _dsp->num, ##__VA_ARGS__)
|
|
|
|
#define ADSP1_CONTROL_1 0x00
|
|
#define ADSP1_CONTROL_2 0x02
|
|
#define ADSP1_CONTROL_3 0x03
|
|
#define ADSP1_CONTROL_4 0x04
|
|
#define ADSP1_CONTROL_5 0x06
|
|
#define ADSP1_CONTROL_6 0x07
|
|
#define ADSP1_CONTROL_7 0x08
|
|
#define ADSP1_CONTROL_8 0x09
|
|
#define ADSP1_CONTROL_9 0x0A
|
|
#define ADSP1_CONTROL_10 0x0B
|
|
#define ADSP1_CONTROL_11 0x0C
|
|
#define ADSP1_CONTROL_12 0x0D
|
|
#define ADSP1_CONTROL_13 0x0F
|
|
#define ADSP1_CONTROL_14 0x10
|
|
#define ADSP1_CONTROL_15 0x11
|
|
#define ADSP1_CONTROL_16 0x12
|
|
#define ADSP1_CONTROL_17 0x13
|
|
#define ADSP1_CONTROL_18 0x14
|
|
#define ADSP1_CONTROL_19 0x16
|
|
#define ADSP1_CONTROL_20 0x17
|
|
#define ADSP1_CONTROL_21 0x18
|
|
#define ADSP1_CONTROL_22 0x1A
|
|
#define ADSP1_CONTROL_23 0x1B
|
|
#define ADSP1_CONTROL_24 0x1C
|
|
#define ADSP1_CONTROL_25 0x1E
|
|
#define ADSP1_CONTROL_26 0x20
|
|
#define ADSP1_CONTROL_27 0x21
|
|
#define ADSP1_CONTROL_28 0x22
|
|
#define ADSP1_CONTROL_29 0x23
|
|
#define ADSP1_CONTROL_30 0x24
|
|
#define ADSP1_CONTROL_31 0x26
|
|
|
|
/*
|
|
* ADSP1 Control 19
|
|
*/
|
|
#define ADSP1_WDMA_BUFFER_LENGTH_MASK 0x00FF /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */
|
|
#define ADSP1_WDMA_BUFFER_LENGTH_SHIFT 0 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */
|
|
#define ADSP1_WDMA_BUFFER_LENGTH_WIDTH 8 /* DSP1_WDMA_BUFFER_LENGTH - [7:0] */
|
|
|
|
|
|
/*
|
|
* ADSP1 Control 30
|
|
*/
|
|
#define ADSP1_DBG_CLK_ENA 0x0008 /* DSP1_DBG_CLK_ENA */
|
|
#define ADSP1_DBG_CLK_ENA_MASK 0x0008 /* DSP1_DBG_CLK_ENA */
|
|
#define ADSP1_DBG_CLK_ENA_SHIFT 3 /* DSP1_DBG_CLK_ENA */
|
|
#define ADSP1_DBG_CLK_ENA_WIDTH 1 /* DSP1_DBG_CLK_ENA */
|
|
#define ADSP1_SYS_ENA 0x0004 /* DSP1_SYS_ENA */
|
|
#define ADSP1_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */
|
|
#define ADSP1_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */
|
|
#define ADSP1_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */
|
|
#define ADSP1_CORE_ENA 0x0002 /* DSP1_CORE_ENA */
|
|
#define ADSP1_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */
|
|
#define ADSP1_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */
|
|
#define ADSP1_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */
|
|
#define ADSP1_START 0x0001 /* DSP1_START */
|
|
#define ADSP1_START_MASK 0x0001 /* DSP1_START */
|
|
#define ADSP1_START_SHIFT 0 /* DSP1_START */
|
|
#define ADSP1_START_WIDTH 1 /* DSP1_START */
|
|
|
|
#define ADSP2_CONTROL 0
|
|
#define ADSP2_CLOCKING 1
|
|
#define ADSP2_STATUS1 4
|
|
|
|
/*
|
|
* ADSP2 Control
|
|
*/
|
|
|
|
#define ADSP2_MEM_ENA 0x0010 /* DSP1_MEM_ENA */
|
|
#define ADSP2_MEM_ENA_MASK 0x0010 /* DSP1_MEM_ENA */
|
|
#define ADSP2_MEM_ENA_SHIFT 4 /* DSP1_MEM_ENA */
|
|
#define ADSP2_MEM_ENA_WIDTH 1 /* DSP1_MEM_ENA */
|
|
#define ADSP2_SYS_ENA 0x0004 /* DSP1_SYS_ENA */
|
|
#define ADSP2_SYS_ENA_MASK 0x0004 /* DSP1_SYS_ENA */
|
|
#define ADSP2_SYS_ENA_SHIFT 2 /* DSP1_SYS_ENA */
|
|
#define ADSP2_SYS_ENA_WIDTH 1 /* DSP1_SYS_ENA */
|
|
#define ADSP2_CORE_ENA 0x0002 /* DSP1_CORE_ENA */
|
|
#define ADSP2_CORE_ENA_MASK 0x0002 /* DSP1_CORE_ENA */
|
|
#define ADSP2_CORE_ENA_SHIFT 1 /* DSP1_CORE_ENA */
|
|
#define ADSP2_CORE_ENA_WIDTH 1 /* DSP1_CORE_ENA */
|
|
#define ADSP2_START 0x0001 /* DSP1_START */
|
|
#define ADSP2_START_MASK 0x0001 /* DSP1_START */
|
|
#define ADSP2_START_SHIFT 0 /* DSP1_START */
|
|
#define ADSP2_START_WIDTH 1 /* DSP1_START */
|
|
|
|
/*
|
|
* ADSP2 clocking
|
|
*/
|
|
#define ADSP2_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */
|
|
#define ADSP2_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */
|
|
#define ADSP2_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */
|
|
|
|
/*
|
|
* ADSP2 Status 1
|
|
*/
|
|
#define ADSP2_RAM_RDY 0x0001
|
|
#define ADSP2_RAM_RDY_MASK 0x0001
|
|
#define ADSP2_RAM_RDY_SHIFT 0
|
|
#define ADSP2_RAM_RDY_WIDTH 1
|
|
|
|
|
|
static struct wm_adsp_region const *wm_adsp_find_region(struct wm_adsp *dsp,
|
|
int type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < dsp->num_mems; i++)
|
|
if (dsp->mem[i].type == type)
|
|
return &dsp->mem[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static unsigned int wm_adsp_region_to_reg(struct wm_adsp_region const *region,
|
|
unsigned int offset)
|
|
{
|
|
switch (region->type) {
|
|
case WMFW_ADSP1_PM:
|
|
return region->base + (offset * 3);
|
|
case WMFW_ADSP1_DM:
|
|
return region->base + (offset * 2);
|
|
case WMFW_ADSP2_XM:
|
|
return region->base + (offset * 2);
|
|
case WMFW_ADSP2_YM:
|
|
return region->base + (offset * 2);
|
|
case WMFW_ADSP1_ZM:
|
|
return region->base + (offset * 2);
|
|
default:
|
|
WARN_ON(NULL != "Unknown memory region type");
|
|
return offset;
|
|
}
|
|
}
|
|
|
|
static int wm_adsp_load(struct wm_adsp *dsp)
|
|
{
|
|
const struct firmware *firmware;
|
|
struct regmap *regmap = dsp->regmap;
|
|
unsigned int pos = 0;
|
|
const struct wmfw_header *header;
|
|
const struct wmfw_adsp1_sizes *adsp1_sizes;
|
|
const struct wmfw_adsp2_sizes *adsp2_sizes;
|
|
const struct wmfw_footer *footer;
|
|
const struct wmfw_region *region;
|
|
const struct wm_adsp_region *mem;
|
|
const char *region_name;
|
|
char *file, *text;
|
|
unsigned int reg;
|
|
int regions = 0;
|
|
int ret, offset, type, sizes;
|
|
|
|
file = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (file == NULL)
|
|
return -ENOMEM;
|
|
|
|
snprintf(file, PAGE_SIZE, "%s-dsp%d.wmfw", dsp->part, dsp->num);
|
|
file[PAGE_SIZE - 1] = '\0';
|
|
|
|
ret = request_firmware(&firmware, file, dsp->dev);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to request '%s'\n", file);
|
|
goto out;
|
|
}
|
|
ret = -EINVAL;
|
|
|
|
pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer);
|
|
if (pos >= firmware->size) {
|
|
adsp_err(dsp, "%s: file too short, %zu bytes\n",
|
|
file, firmware->size);
|
|
goto out_fw;
|
|
}
|
|
|
|
header = (void*)&firmware->data[0];
|
|
|
|
if (memcmp(&header->magic[0], "WMFW", 4) != 0) {
|
|
adsp_err(dsp, "%s: invalid magic\n", file);
|
|
goto out_fw;
|
|
}
|
|
|
|
if (header->ver != 0) {
|
|
adsp_err(dsp, "%s: unknown file format %d\n",
|
|
file, header->ver);
|
|
goto out_fw;
|
|
}
|
|
|
|
if (header->core != dsp->type) {
|
|
adsp_err(dsp, "%s: invalid core %d != %d\n",
|
|
file, header->core, dsp->type);
|
|
goto out_fw;
|
|
}
|
|
|
|
switch (dsp->type) {
|
|
case WMFW_ADSP1:
|
|
pos = sizeof(*header) + sizeof(*adsp1_sizes) + sizeof(*footer);
|
|
adsp1_sizes = (void *)&(header[1]);
|
|
footer = (void *)&(adsp1_sizes[1]);
|
|
sizes = sizeof(*adsp1_sizes);
|
|
|
|
adsp_dbg(dsp, "%s: %d DM, %d PM, %d ZM\n",
|
|
file, le32_to_cpu(adsp1_sizes->dm),
|
|
le32_to_cpu(adsp1_sizes->pm),
|
|
le32_to_cpu(adsp1_sizes->zm));
|
|
break;
|
|
|
|
case WMFW_ADSP2:
|
|
pos = sizeof(*header) + sizeof(*adsp2_sizes) + sizeof(*footer);
|
|
adsp2_sizes = (void *)&(header[1]);
|
|
footer = (void *)&(adsp2_sizes[1]);
|
|
sizes = sizeof(*adsp2_sizes);
|
|
|
|
adsp_dbg(dsp, "%s: %d XM, %d YM %d PM, %d ZM\n",
|
|
file, le32_to_cpu(adsp2_sizes->xm),
|
|
le32_to_cpu(adsp2_sizes->ym),
|
|
le32_to_cpu(adsp2_sizes->pm),
|
|
le32_to_cpu(adsp2_sizes->zm));
|
|
break;
|
|
|
|
default:
|
|
BUG_ON(NULL == "Unknown DSP type");
|
|
goto out_fw;
|
|
}
|
|
|
|
if (le32_to_cpu(header->len) != sizeof(*header) +
|
|
sizes + sizeof(*footer)) {
|
|
adsp_err(dsp, "%s: unexpected header length %d\n",
|
|
file, le32_to_cpu(header->len));
|
|
goto out_fw;
|
|
}
|
|
|
|
adsp_dbg(dsp, "%s: timestamp %llu\n", file,
|
|
le64_to_cpu(footer->timestamp));
|
|
|
|
while (pos < firmware->size &&
|
|
pos - firmware->size > sizeof(*region)) {
|
|
region = (void *)&(firmware->data[pos]);
|
|
region_name = "Unknown";
|
|
reg = 0;
|
|
text = NULL;
|
|
offset = le32_to_cpu(region->offset) & 0xffffff;
|
|
type = be32_to_cpu(region->type) & 0xff;
|
|
mem = wm_adsp_find_region(dsp, type);
|
|
|
|
switch (type) {
|
|
case WMFW_NAME_TEXT:
|
|
region_name = "Firmware name";
|
|
text = kzalloc(le32_to_cpu(region->len) + 1,
|
|
GFP_KERNEL);
|
|
break;
|
|
case WMFW_INFO_TEXT:
|
|
region_name = "Information";
|
|
text = kzalloc(le32_to_cpu(region->len) + 1,
|
|
GFP_KERNEL);
|
|
break;
|
|
case WMFW_ABSOLUTE:
|
|
region_name = "Absolute";
|
|
reg = offset;
|
|
break;
|
|
case WMFW_ADSP1_PM:
|
|
BUG_ON(!mem);
|
|
region_name = "PM";
|
|
reg = wm_adsp_region_to_reg(mem, offset);
|
|
break;
|
|
case WMFW_ADSP1_DM:
|
|
BUG_ON(!mem);
|
|
region_name = "DM";
|
|
reg = wm_adsp_region_to_reg(mem, offset);
|
|
break;
|
|
case WMFW_ADSP2_XM:
|
|
BUG_ON(!mem);
|
|
region_name = "XM";
|
|
reg = wm_adsp_region_to_reg(mem, offset);
|
|
break;
|
|
case WMFW_ADSP2_YM:
|
|
BUG_ON(!mem);
|
|
region_name = "YM";
|
|
reg = wm_adsp_region_to_reg(mem, offset);
|
|
break;
|
|
case WMFW_ADSP1_ZM:
|
|
BUG_ON(!mem);
|
|
region_name = "ZM";
|
|
reg = wm_adsp_region_to_reg(mem, offset);
|
|
break;
|
|
default:
|
|
adsp_warn(dsp,
|
|
"%s.%d: Unknown region type %x at %d(%x)\n",
|
|
file, regions, type, pos, pos);
|
|
break;
|
|
}
|
|
|
|
adsp_dbg(dsp, "%s.%d: %d bytes at %d in %s\n", file,
|
|
regions, le32_to_cpu(region->len), offset,
|
|
region_name);
|
|
|
|
if (text) {
|
|
memcpy(text, region->data, le32_to_cpu(region->len));
|
|
adsp_info(dsp, "%s: %s\n", file, text);
|
|
kfree(text);
|
|
}
|
|
|
|
if (reg) {
|
|
ret = regmap_raw_write(regmap, reg, region->data,
|
|
le32_to_cpu(region->len));
|
|
if (ret != 0) {
|
|
adsp_err(dsp,
|
|
"%s.%d: Failed to write %d bytes at %d in %s: %d\n",
|
|
file, regions,
|
|
le32_to_cpu(region->len), offset,
|
|
region_name, ret);
|
|
goto out_fw;
|
|
}
|
|
}
|
|
|
|
pos += le32_to_cpu(region->len) + sizeof(*region);
|
|
regions++;
|
|
}
|
|
|
|
if (pos > firmware->size)
|
|
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
|
file, regions, pos - firmware->size);
|
|
|
|
out_fw:
|
|
release_firmware(firmware);
|
|
out:
|
|
kfree(file);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int wm_adsp_setup_algs(struct wm_adsp *dsp)
|
|
{
|
|
struct regmap *regmap = dsp->regmap;
|
|
struct wmfw_adsp1_id_hdr adsp1_id;
|
|
struct wmfw_adsp2_id_hdr adsp2_id;
|
|
struct wmfw_adsp1_alg_hdr *adsp1_alg;
|
|
struct wmfw_adsp2_alg_hdr *adsp2_alg;
|
|
void *alg, *buf;
|
|
struct wm_adsp_alg_region *region;
|
|
const struct wm_adsp_region *mem;
|
|
unsigned int pos, term;
|
|
size_t algs, buf_size;
|
|
__be32 val;
|
|
int i, ret;
|
|
|
|
switch (dsp->type) {
|
|
case WMFW_ADSP1:
|
|
mem = wm_adsp_find_region(dsp, WMFW_ADSP1_DM);
|
|
break;
|
|
case WMFW_ADSP2:
|
|
mem = wm_adsp_find_region(dsp, WMFW_ADSP2_XM);
|
|
break;
|
|
default:
|
|
mem = NULL;
|
|
break;
|
|
}
|
|
|
|
if (mem == NULL) {
|
|
BUG_ON(mem != NULL);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (dsp->type) {
|
|
case WMFW_ADSP1:
|
|
ret = regmap_raw_read(regmap, mem->base, &adsp1_id,
|
|
sizeof(adsp1_id));
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to read algorithm info: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
buf = &adsp1_id;
|
|
buf_size = sizeof(adsp1_id);
|
|
|
|
algs = be32_to_cpu(adsp1_id.algs);
|
|
adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n",
|
|
be32_to_cpu(adsp1_id.fw.id),
|
|
(be32_to_cpu(adsp1_id.fw.ver) & 0xff0000) >> 16,
|
|
(be32_to_cpu(adsp1_id.fw.ver) & 0xff00) >> 8,
|
|
be32_to_cpu(adsp1_id.fw.ver) & 0xff,
|
|
algs);
|
|
|
|
pos = sizeof(adsp1_id) / 2;
|
|
term = pos + ((sizeof(*adsp1_alg) * algs) / 2);
|
|
break;
|
|
|
|
case WMFW_ADSP2:
|
|
ret = regmap_raw_read(regmap, mem->base, &adsp2_id,
|
|
sizeof(adsp2_id));
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to read algorithm info: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
buf = &adsp2_id;
|
|
buf_size = sizeof(adsp2_id);
|
|
|
|
algs = be32_to_cpu(adsp2_id.algs);
|
|
adsp_info(dsp, "Firmware: %x v%d.%d.%d, %zu algorithms\n",
|
|
be32_to_cpu(adsp2_id.fw.id),
|
|
(be32_to_cpu(adsp2_id.fw.ver) & 0xff0000) >> 16,
|
|
(be32_to_cpu(adsp2_id.fw.ver) & 0xff00) >> 8,
|
|
be32_to_cpu(adsp2_id.fw.ver) & 0xff,
|
|
algs);
|
|
|
|
pos = sizeof(adsp2_id) / 2;
|
|
term = pos + ((sizeof(*adsp2_alg) * algs) / 2);
|
|
break;
|
|
|
|
default:
|
|
BUG_ON(NULL == "Unknown DSP type");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (algs == 0) {
|
|
adsp_err(dsp, "No algorithms\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (algs > 1024) {
|
|
adsp_err(dsp, "Algorithm count %zx excessive\n", algs);
|
|
print_hex_dump_bytes(dev_name(dsp->dev), DUMP_PREFIX_OFFSET,
|
|
buf, buf_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Read the terminator first to validate the length */
|
|
ret = regmap_raw_read(regmap, mem->base + term, &val, sizeof(val));
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to read algorithm list end: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
if (be32_to_cpu(val) != 0xbedead)
|
|
adsp_warn(dsp, "Algorithm list end %x 0x%x != 0xbeadead\n",
|
|
term, be32_to_cpu(val));
|
|
|
|
alg = kzalloc((term - pos) * 2, GFP_KERNEL);
|
|
if (!alg)
|
|
return -ENOMEM;
|
|
|
|
ret = regmap_raw_read(regmap, mem->base + pos, alg, (term - pos) * 2);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to read algorithm list: %d\n",
|
|
ret);
|
|
goto out;
|
|
}
|
|
|
|
adsp1_alg = alg;
|
|
adsp2_alg = alg;
|
|
|
|
for (i = 0; i < algs; i++) {
|
|
switch (dsp->type) {
|
|
case WMFW_ADSP1:
|
|
adsp_info(dsp, "%d: ID %x v%d.%d.%d DM@%x ZM@%x\n",
|
|
i, be32_to_cpu(adsp1_alg[i].alg.id),
|
|
(be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff0000) >> 16,
|
|
(be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff00) >> 8,
|
|
be32_to_cpu(adsp1_alg[i].alg.ver) & 0xff,
|
|
be32_to_cpu(adsp1_alg[i].dm),
|
|
be32_to_cpu(adsp1_alg[i].zm));
|
|
|
|
if (adsp1_alg[i].dm) {
|
|
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
if (!region)
|
|
return -ENOMEM;
|
|
region->type = WMFW_ADSP1_DM;
|
|
region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
|
|
region->base = be32_to_cpu(adsp1_alg[i].dm);
|
|
list_add_tail(®ion->list,
|
|
&dsp->alg_regions);
|
|
}
|
|
|
|
if (adsp1_alg[i].zm) {
|
|
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
if (!region)
|
|
return -ENOMEM;
|
|
region->type = WMFW_ADSP1_ZM;
|
|
region->alg = be32_to_cpu(adsp1_alg[i].alg.id);
|
|
region->base = be32_to_cpu(adsp1_alg[i].zm);
|
|
list_add_tail(®ion->list,
|
|
&dsp->alg_regions);
|
|
}
|
|
break;
|
|
|
|
case WMFW_ADSP2:
|
|
adsp_info(dsp,
|
|
"%d: ID %x v%d.%d.%d XM@%x YM@%x ZM@%x\n",
|
|
i, be32_to_cpu(adsp2_alg[i].alg.id),
|
|
(be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff0000) >> 16,
|
|
(be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff00) >> 8,
|
|
be32_to_cpu(adsp2_alg[i].alg.ver) & 0xff,
|
|
be32_to_cpu(adsp2_alg[i].xm),
|
|
be32_to_cpu(adsp2_alg[i].ym),
|
|
be32_to_cpu(adsp2_alg[i].zm));
|
|
|
|
if (adsp2_alg[i].xm) {
|
|
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
if (!region)
|
|
return -ENOMEM;
|
|
region->type = WMFW_ADSP2_XM;
|
|
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
|
region->base = be32_to_cpu(adsp2_alg[i].xm);
|
|
list_add_tail(®ion->list,
|
|
&dsp->alg_regions);
|
|
}
|
|
|
|
if (adsp2_alg[i].ym) {
|
|
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
if (!region)
|
|
return -ENOMEM;
|
|
region->type = WMFW_ADSP2_YM;
|
|
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
|
region->base = be32_to_cpu(adsp2_alg[i].ym);
|
|
list_add_tail(®ion->list,
|
|
&dsp->alg_regions);
|
|
}
|
|
|
|
if (adsp2_alg[i].zm) {
|
|
region = kzalloc(sizeof(*region), GFP_KERNEL);
|
|
if (!region)
|
|
return -ENOMEM;
|
|
region->type = WMFW_ADSP2_ZM;
|
|
region->alg = be32_to_cpu(adsp2_alg[i].alg.id);
|
|
region->base = be32_to_cpu(adsp2_alg[i].zm);
|
|
list_add_tail(®ion->list,
|
|
&dsp->alg_regions);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
kfree(alg);
|
|
return ret;
|
|
}
|
|
|
|
static int wm_adsp_load_coeff(struct wm_adsp *dsp)
|
|
{
|
|
struct regmap *regmap = dsp->regmap;
|
|
struct wmfw_coeff_hdr *hdr;
|
|
struct wmfw_coeff_item *blk;
|
|
const struct firmware *firmware;
|
|
const struct wm_adsp_region *mem;
|
|
struct wm_adsp_alg_region *alg_region;
|
|
const char *region_name;
|
|
int ret, pos, blocks, type, offset, reg;
|
|
char *file;
|
|
|
|
file = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (file == NULL)
|
|
return -ENOMEM;
|
|
|
|
snprintf(file, PAGE_SIZE, "%s-dsp%d.bin", dsp->part, dsp->num);
|
|
file[PAGE_SIZE - 1] = '\0';
|
|
|
|
ret = request_firmware(&firmware, file, dsp->dev);
|
|
if (ret != 0) {
|
|
adsp_warn(dsp, "Failed to request '%s'\n", file);
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
ret = -EINVAL;
|
|
|
|
if (sizeof(*hdr) >= firmware->size) {
|
|
adsp_err(dsp, "%s: file too short, %zu bytes\n",
|
|
file, firmware->size);
|
|
goto out_fw;
|
|
}
|
|
|
|
hdr = (void*)&firmware->data[0];
|
|
if (memcmp(hdr->magic, "WMDR", 4) != 0) {
|
|
adsp_err(dsp, "%s: invalid magic\n", file);
|
|
return -EINVAL;
|
|
}
|
|
|
|
adsp_dbg(dsp, "%s: v%d.%d.%d\n", file,
|
|
(le32_to_cpu(hdr->ver) >> 16) & 0xff,
|
|
(le32_to_cpu(hdr->ver) >> 8) & 0xff,
|
|
le32_to_cpu(hdr->ver) & 0xff);
|
|
|
|
pos = le32_to_cpu(hdr->len);
|
|
|
|
blocks = 0;
|
|
while (pos < firmware->size &&
|
|
pos - firmware->size > sizeof(*blk)) {
|
|
blk = (void*)(&firmware->data[pos]);
|
|
|
|
type = be32_to_cpu(blk->type) & 0xff;
|
|
offset = le32_to_cpu(blk->offset) & 0xffffff;
|
|
|
|
adsp_dbg(dsp, "%s.%d: %x v%d.%d.%d\n",
|
|
file, blocks, le32_to_cpu(blk->id),
|
|
(le32_to_cpu(blk->ver) >> 16) & 0xff,
|
|
(le32_to_cpu(blk->ver) >> 8) & 0xff,
|
|
le32_to_cpu(blk->ver) & 0xff);
|
|
adsp_dbg(dsp, "%s.%d: %d bytes at 0x%x in %x\n",
|
|
file, blocks, le32_to_cpu(blk->len), offset, type);
|
|
|
|
reg = 0;
|
|
region_name = "Unknown";
|
|
switch (type) {
|
|
case WMFW_NAME_TEXT:
|
|
case WMFW_INFO_TEXT:
|
|
break;
|
|
case WMFW_ABSOLUTE:
|
|
region_name = "register";
|
|
reg = offset;
|
|
break;
|
|
|
|
case WMFW_ADSP1_DM:
|
|
case WMFW_ADSP1_ZM:
|
|
case WMFW_ADSP2_XM:
|
|
case WMFW_ADSP2_YM:
|
|
adsp_dbg(dsp, "%s.%d: %d bytes in %x for %x\n",
|
|
file, blocks, le32_to_cpu(blk->len),
|
|
type, le32_to_cpu(blk->id));
|
|
|
|
mem = wm_adsp_find_region(dsp, type);
|
|
if (!mem) {
|
|
adsp_err(dsp, "No base for region %x\n", type);
|
|
break;
|
|
}
|
|
|
|
reg = 0;
|
|
list_for_each_entry(alg_region,
|
|
&dsp->alg_regions, list) {
|
|
if (le32_to_cpu(blk->id) == alg_region->alg &&
|
|
type == alg_region->type) {
|
|
reg = alg_region->base + offset;
|
|
reg = wm_adsp_region_to_reg(mem,
|
|
reg);
|
|
}
|
|
}
|
|
|
|
if (reg == 0)
|
|
adsp_err(dsp, "No %x for algorithm %x\n",
|
|
type, le32_to_cpu(blk->id));
|
|
break;
|
|
|
|
default:
|
|
adsp_err(dsp, "Unknown region type %x\n", type);
|
|
break;
|
|
}
|
|
|
|
if (reg) {
|
|
ret = regmap_raw_write(regmap, reg, blk->data,
|
|
le32_to_cpu(blk->len));
|
|
if (ret != 0) {
|
|
adsp_err(dsp,
|
|
"%s.%d: Failed to write to %x in %s\n",
|
|
file, blocks, reg, region_name);
|
|
}
|
|
}
|
|
|
|
pos += le32_to_cpu(blk->len) + sizeof(*blk);
|
|
blocks++;
|
|
}
|
|
|
|
if (pos > firmware->size)
|
|
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
|
file, blocks, pos - firmware->size);
|
|
|
|
out_fw:
|
|
release_firmware(firmware);
|
|
out:
|
|
kfree(file);
|
|
return 0;
|
|
}
|
|
|
|
int wm_adsp1_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol,
|
|
int event)
|
|
{
|
|
struct snd_soc_codec *codec = w->codec;
|
|
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
|
|
struct wm_adsp *dsp = &dsps[w->shift];
|
|
int ret;
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
ADSP1_SYS_ENA, ADSP1_SYS_ENA);
|
|
|
|
ret = wm_adsp_load(dsp);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
ret = wm_adsp_setup_algs(dsp);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
ret = wm_adsp_load_coeff(dsp);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
/* Start the core running */
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
ADSP1_CORE_ENA | ADSP1_START,
|
|
ADSP1_CORE_ENA | ADSP1_START);
|
|
break;
|
|
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
/* Halt the core */
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
ADSP1_CORE_ENA | ADSP1_START, 0);
|
|
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_19,
|
|
ADSP1_WDMA_BUFFER_LENGTH_MASK, 0);
|
|
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
ADSP1_SYS_ENA, 0);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
ADSP1_SYS_ENA, 0);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wm_adsp1_event);
|
|
|
|
static int wm_adsp2_ena(struct wm_adsp *dsp)
|
|
{
|
|
unsigned int val;
|
|
int ret, count;
|
|
|
|
ret = regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
|
|
ADSP2_SYS_ENA, ADSP2_SYS_ENA);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
/* Wait for the RAM to start, should be near instantaneous */
|
|
count = 0;
|
|
do {
|
|
ret = regmap_read(dsp->regmap, dsp->base + ADSP2_STATUS1,
|
|
&val);
|
|
if (ret != 0)
|
|
return ret;
|
|
} while (!(val & ADSP2_RAM_RDY) && ++count < 10);
|
|
|
|
if (!(val & ADSP2_RAM_RDY)) {
|
|
adsp_err(dsp, "Failed to start DSP RAM\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
adsp_dbg(dsp, "RAM ready after %d polls\n", count);
|
|
adsp_info(dsp, "RAM ready after %d polls\n", count);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int wm_adsp2_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_codec *codec = w->codec;
|
|
struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
|
|
struct wm_adsp *dsp = &dsps[w->shift];
|
|
struct wm_adsp_alg_region *alg_region;
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
/*
|
|
* For simplicity set the DSP clock rate to be the
|
|
* SYSCLK rate rather than making it configurable.
|
|
*/
|
|
ret = regmap_read(dsp->regmap, ARIZONA_SYSTEM_CLOCK_1, &val);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to read SYSCLK state: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
val = (val & ARIZONA_SYSCLK_FREQ_MASK)
|
|
>> ARIZONA_SYSCLK_FREQ_SHIFT;
|
|
|
|
ret = regmap_update_bits(dsp->regmap,
|
|
dsp->base + ADSP2_CLOCKING,
|
|
ADSP2_CLK_SEL_MASK, val);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to set clock rate: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
if (dsp->dvfs) {
|
|
ret = regmap_read(dsp->regmap,
|
|
dsp->base + ADSP2_CLOCKING, &val);
|
|
if (ret != 0) {
|
|
dev_err(dsp->dev,
|
|
"Failed to read clocking: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if ((val & ADSP2_CLK_SEL_MASK) >= 3) {
|
|
ret = regulator_enable(dsp->dvfs);
|
|
if (ret != 0) {
|
|
dev_err(dsp->dev,
|
|
"Failed to enable supply: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_set_voltage(dsp->dvfs,
|
|
1800000,
|
|
1800000);
|
|
if (ret != 0) {
|
|
dev_err(dsp->dev,
|
|
"Failed to raise supply: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = wm_adsp2_ena(dsp);
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
ret = wm_adsp_load(dsp);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
ret = wm_adsp_setup_algs(dsp);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
ret = wm_adsp_load_coeff(dsp);
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
ret = regmap_update_bits(dsp->regmap,
|
|
dsp->base + ADSP2_CONTROL,
|
|
ADSP2_CORE_ENA | ADSP2_START,
|
|
ADSP2_CORE_ENA | ADSP2_START);
|
|
if (ret != 0)
|
|
goto err;
|
|
break;
|
|
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
|
|
ADSP2_SYS_ENA | ADSP2_CORE_ENA |
|
|
ADSP2_START, 0);
|
|
|
|
if (dsp->dvfs) {
|
|
ret = regulator_set_voltage(dsp->dvfs, 1200000,
|
|
1800000);
|
|
if (ret != 0)
|
|
dev_warn(dsp->dev,
|
|
"Failed to lower supply: %d\n",
|
|
ret);
|
|
|
|
ret = regulator_disable(dsp->dvfs);
|
|
if (ret != 0)
|
|
dev_err(dsp->dev,
|
|
"Failed to enable supply: %d\n",
|
|
ret);
|
|
}
|
|
|
|
while (!list_empty(&dsp->alg_regions)) {
|
|
alg_region = list_first_entry(&dsp->alg_regions,
|
|
struct wm_adsp_alg_region,
|
|
list);
|
|
list_del(&alg_region->list);
|
|
kfree(alg_region);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
|
|
ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wm_adsp2_event);
|
|
|
|
int wm_adsp2_init(struct wm_adsp *adsp, bool dvfs)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* Disable the DSP memory by default when in reset for a small
|
|
* power saving.
|
|
*/
|
|
ret = regmap_update_bits(adsp->regmap, adsp->base + ADSP2_CONTROL,
|
|
ADSP2_MEM_ENA, 0);
|
|
if (ret != 0) {
|
|
adsp_err(adsp, "Failed to clear memory retention: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&adsp->alg_regions);
|
|
|
|
if (dvfs) {
|
|
adsp->dvfs = devm_regulator_get(adsp->dev, "DCVDD");
|
|
if (IS_ERR(adsp->dvfs)) {
|
|
ret = PTR_ERR(adsp->dvfs);
|
|
dev_err(adsp->dev, "Failed to get DCVDD: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_enable(adsp->dvfs);
|
|
if (ret != 0) {
|
|
dev_err(adsp->dev, "Failed to enable DCVDD: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_set_voltage(adsp->dvfs, 1200000, 1800000);
|
|
if (ret != 0) {
|
|
dev_err(adsp->dev, "Failed to initialise DVFS: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = regulator_disable(adsp->dvfs);
|
|
if (ret != 0) {
|
|
dev_err(adsp->dev, "Failed to disable DCVDD: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wm_adsp2_init);
|