forked from Minki/linux
b6ed61cfa2
Now that we have regular register mapped controls we should be splitting the control sets for ADSP1 and ADSP2 as the register maps are not identical. Do that. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
1216 lines
32 KiB
C
1216 lines
32 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/list.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 "arizona.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 */
|
|
|
|
/*
|
|
* ADSP1 Control 31
|
|
*/
|
|
#define ADSP1_CLK_SEL_MASK 0x0007 /* CLK_SEL_ENA */
|
|
#define ADSP1_CLK_SEL_SHIFT 0 /* CLK_SEL_ENA */
|
|
#define ADSP1_CLK_SEL_WIDTH 3 /* CLK_SEL_ENA */
|
|
|
|
#define ADSP2_CONTROL 0x0
|
|
#define ADSP2_CLOCKING 0x1
|
|
#define ADSP2_STATUS1 0x4
|
|
#define ADSP2_WDMA_CONFIG_1 0x30
|
|
#define ADSP2_WDMA_CONFIG_2 0x31
|
|
#define ADSP2_RDMA_CONFIG_1 0x34
|
|
|
|
/*
|
|
* 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
|
|
|
|
struct wm_adsp_buf {
|
|
struct list_head list;
|
|
void *buf;
|
|
};
|
|
|
|
static struct wm_adsp_buf *wm_adsp_buf_alloc(const void *src, size_t len,
|
|
struct list_head *list)
|
|
{
|
|
struct wm_adsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL);
|
|
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
buf->buf = kmemdup(src, len, GFP_KERNEL | GFP_DMA);
|
|
if (!buf->buf) {
|
|
kfree(buf);
|
|
return NULL;
|
|
}
|
|
|
|
if (list)
|
|
list_add_tail(&buf->list, list);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void wm_adsp_buf_free(struct list_head *list)
|
|
{
|
|
while (!list_empty(list)) {
|
|
struct wm_adsp_buf *buf = list_first_entry(list,
|
|
struct wm_adsp_buf,
|
|
list);
|
|
list_del(&buf->list);
|
|
kfree(buf->buf);
|
|
kfree(buf);
|
|
}
|
|
}
|
|
|
|
#define WM_ADSP_NUM_FW 4
|
|
|
|
static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = {
|
|
"MBC/VSS", "Tx", "Tx Speaker", "Rx ANC"
|
|
};
|
|
|
|
static struct {
|
|
const char *file;
|
|
} wm_adsp_fw[WM_ADSP_NUM_FW] = {
|
|
{ .file = "mbc-vss" },
|
|
{ .file = "tx" },
|
|
{ .file = "tx-spk" },
|
|
{ .file = "rx-anc" },
|
|
};
|
|
|
|
static int wm_adsp_fw_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct wm_adsp *adsp = snd_soc_codec_get_drvdata(codec);
|
|
|
|
ucontrol->value.integer.value[0] = adsp[e->shift_l].fw;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wm_adsp_fw_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
|
|
struct wm_adsp *adsp = snd_soc_codec_get_drvdata(codec);
|
|
|
|
if (ucontrol->value.integer.value[0] == adsp[e->shift_l].fw)
|
|
return 0;
|
|
|
|
if (ucontrol->value.integer.value[0] >= WM_ADSP_NUM_FW)
|
|
return -EINVAL;
|
|
|
|
if (adsp[e->shift_l].running)
|
|
return -EBUSY;
|
|
|
|
adsp[e->shift_l].fw = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct soc_enum wm_adsp_fw_enum[] = {
|
|
SOC_ENUM_SINGLE(0, 0, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
SOC_ENUM_SINGLE(0, 1, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
SOC_ENUM_SINGLE(0, 2, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
SOC_ENUM_SINGLE(0, 3, ARRAY_SIZE(wm_adsp_fw_text), wm_adsp_fw_text),
|
|
};
|
|
|
|
const struct snd_kcontrol_new wm_adsp1_fw_controls[] = {
|
|
SOC_ENUM_EXT("DSP1 Firmware", wm_adsp_fw_enum[0],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
SOC_ENUM_EXT("DSP2 Firmware", wm_adsp_fw_enum[1],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
SOC_ENUM_EXT("DSP3 Firmware", wm_adsp_fw_enum[2],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
};
|
|
EXPORT_SYMBOL_GPL(wm_adsp1_fw_controls);
|
|
|
|
#if IS_ENABLED(CONFIG_SND_SOC_ARIZONA)
|
|
static const struct soc_enum wm_adsp2_rate_enum[] = {
|
|
SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP1_CONTROL_1,
|
|
ARIZONA_DSP1_RATE_SHIFT, 0xf,
|
|
ARIZONA_RATE_ENUM_SIZE,
|
|
arizona_rate_text, arizona_rate_val),
|
|
SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP2_CONTROL_1,
|
|
ARIZONA_DSP1_RATE_SHIFT, 0xf,
|
|
ARIZONA_RATE_ENUM_SIZE,
|
|
arizona_rate_text, arizona_rate_val),
|
|
SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP3_CONTROL_1,
|
|
ARIZONA_DSP1_RATE_SHIFT, 0xf,
|
|
ARIZONA_RATE_ENUM_SIZE,
|
|
arizona_rate_text, arizona_rate_val),
|
|
SOC_VALUE_ENUM_SINGLE(ARIZONA_DSP3_CONTROL_1,
|
|
ARIZONA_DSP1_RATE_SHIFT, 0xf,
|
|
ARIZONA_RATE_ENUM_SIZE,
|
|
arizona_rate_text, arizona_rate_val),
|
|
};
|
|
|
|
const struct snd_kcontrol_new wm_adsp2_fw_controls[] = {
|
|
SOC_ENUM_EXT("DSP1 Firmware", wm_adsp_fw_enum[0],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
SOC_ENUM("DSP1 Rate", wm_adsp2_rate_enum[0]),
|
|
SOC_ENUM_EXT("DSP2 Firmware", wm_adsp_fw_enum[1],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
SOC_ENUM("DSP2 Rate", wm_adsp2_rate_enum[1]),
|
|
SOC_ENUM_EXT("DSP3 Firmware", wm_adsp_fw_enum[2],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
SOC_ENUM("DSP3 Rate", wm_adsp2_rate_enum[2]),
|
|
SOC_ENUM_EXT("DSP4 Firmware", wm_adsp_fw_enum[3],
|
|
wm_adsp_fw_get, wm_adsp_fw_put),
|
|
SOC_ENUM("DSP4 Rate", wm_adsp2_rate_enum[3]),
|
|
};
|
|
EXPORT_SYMBOL_GPL(wm_adsp2_fw_controls);
|
|
#endif
|
|
|
|
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)
|
|
{
|
|
LIST_HEAD(buf_list);
|
|
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;
|
|
struct wm_adsp_buf *buf;
|
|
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-%s.wmfw", dsp->part, dsp->num,
|
|
wm_adsp_fw[dsp->fw].file);
|
|
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) {
|
|
buf = wm_adsp_buf_alloc(region->data,
|
|
le32_to_cpu(region->len),
|
|
&buf_list);
|
|
if (!buf) {
|
|
adsp_err(dsp, "Out of memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = regmap_raw_write_async(regmap, reg, buf->buf,
|
|
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++;
|
|
}
|
|
|
|
ret = regmap_async_complete(regmap);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to complete async write: %d\n", ret);
|
|
goto out_fw;
|
|
}
|
|
|
|
if (pos > firmware->size)
|
|
adsp_warn(dsp, "%s.%d: %zu bytes at end of file\n",
|
|
file, regions, pos - firmware->size);
|
|
|
|
out_fw:
|
|
regmap_async_complete(regmap);
|
|
wm_adsp_buf_free(&buf_list);
|
|
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 | GFP_DMA);
|
|
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));
|
|
|
|
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);
|
|
|
|
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));
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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)
|
|
{
|
|
LIST_HEAD(buf_list);
|
|
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;
|
|
struct wm_adsp_buf *buf;
|
|
int tmp;
|
|
|
|
file = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
if (file == NULL)
|
|
return -ENOMEM;
|
|
|
|
snprintf(file, PAGE_SIZE, "%s-dsp%d-%s.bin", dsp->part, dsp->num,
|
|
wm_adsp_fw[dsp->fw].file);
|
|
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);
|
|
goto out_fw;
|
|
}
|
|
|
|
switch (be32_to_cpu(hdr->rev) & 0xff) {
|
|
case 1:
|
|
break;
|
|
default:
|
|
adsp_err(dsp, "%s: Unsupported coefficient file format %d\n",
|
|
file, be32_to_cpu(hdr->rev) & 0xff);
|
|
ret = -EINVAL;
|
|
goto out_fw;
|
|
}
|
|
|
|
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 = le16_to_cpu(blk->type);
|
|
offset = le16_to_cpu(blk->offset);
|
|
|
|
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 << 8):
|
|
case (WMFW_INFO_TEXT << 8):
|
|
break;
|
|
case (WMFW_ABSOLUTE << 8):
|
|
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;
|
|
reg = wm_adsp_region_to_reg(mem,
|
|
reg);
|
|
reg += offset;
|
|
}
|
|
}
|
|
|
|
if (reg == 0)
|
|
adsp_err(dsp, "No %x for algorithm %x\n",
|
|
type, le32_to_cpu(blk->id));
|
|
break;
|
|
|
|
default:
|
|
adsp_err(dsp, "%s.%d: Unknown region type %x at %d\n",
|
|
file, blocks, type, pos);
|
|
break;
|
|
}
|
|
|
|
if (reg) {
|
|
buf = wm_adsp_buf_alloc(blk->data,
|
|
le32_to_cpu(blk->len),
|
|
&buf_list);
|
|
if (!buf) {
|
|
adsp_err(dsp, "Out of memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
adsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n",
|
|
file, blocks, le32_to_cpu(blk->len),
|
|
reg);
|
|
ret = regmap_raw_write_async(regmap, reg, buf->buf,
|
|
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);
|
|
}
|
|
}
|
|
|
|
tmp = le32_to_cpu(blk->len) % 4;
|
|
if (tmp)
|
|
pos += le32_to_cpu(blk->len) + (4 - tmp) + sizeof(*blk);
|
|
else
|
|
pos += le32_to_cpu(blk->len) + sizeof(*blk);
|
|
|
|
blocks++;
|
|
}
|
|
|
|
ret = regmap_async_complete(regmap);
|
|
if (ret != 0)
|
|
adsp_err(dsp, "Failed to complete async write: %d\n", ret);
|
|
|
|
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);
|
|
wm_adsp_buf_free(&buf_list);
|
|
out:
|
|
kfree(file);
|
|
return 0;
|
|
}
|
|
|
|
int wm_adsp1_init(struct wm_adsp *adsp)
|
|
{
|
|
INIT_LIST_HEAD(&adsp->alg_regions);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(wm_adsp1_init);
|
|
|
|
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;
|
|
int val;
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP1_CONTROL_30,
|
|
ADSP1_SYS_ENA, ADSP1_SYS_ENA);
|
|
|
|
/*
|
|
* For simplicity set the DSP clock rate to be the
|
|
* SYSCLK rate rather than making it configurable.
|
|
*/
|
|
if(dsp->sysclk_reg) {
|
|
ret = regmap_read(dsp->regmap, dsp->sysclk_reg, &val);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to read SYSCLK state: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
val = (val & dsp->sysclk_mask)
|
|
>> dsp->sysclk_shift;
|
|
|
|
ret = regmap_update_bits(dsp->regmap,
|
|
dsp->base + ADSP1_CONTROL_31,
|
|
ADSP1_CLK_SEL_MASK, val);
|
|
if (ret != 0) {
|
|
adsp_err(dsp, "Failed to set clock rate: %d\n",
|
|
ret);
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
dsp->running = true;
|
|
break;
|
|
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
dsp->running = false;
|
|
|
|
regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
|
|
ADSP2_SYS_ENA | ADSP2_CORE_ENA |
|
|
ADSP2_START, 0);
|
|
|
|
/* Make sure DMAs are quiesced */
|
|
regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_1, 0);
|
|
regmap_write(dsp->regmap, dsp->base + ADSP2_WDMA_CONFIG_2, 0);
|
|
regmap_write(dsp->regmap, dsp->base + ADSP2_RDMA_CONFIG_1, 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);
|