mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-31 16:38:12 +00:00
ASoC: wm2200: Provide initial coefficient loading
Allow a coefficient set provided using the Wolfson callibration tools to be provided along with the firmware files. Currently only coefficient files which configure absolute register addresses are supported. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
This commit is contained in:
parent
e10f871190
commit
6e87badd3f
2 changed files with 237 additions and 0 deletions
|
@ -1150,6 +1150,192 @@ static int wm2200_dsp_load(struct snd_soc_codec *codec, int base)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int wm2200_setup_algs(struct snd_soc_codec *codec, int base)
|
||||
{
|
||||
struct regmap *regmap = codec->control_data;
|
||||
struct wmfw_adsp1_id_hdr id;
|
||||
struct wmfw_adsp1_alg_hdr *alg;
|
||||
size_t algs;
|
||||
int zm, dm, pm, ret, i;
|
||||
__be32 val;
|
||||
|
||||
switch (base) {
|
||||
case WM2200_DSP1_CONTROL_1:
|
||||
dm = WM2200_DSP1_DM_BASE;
|
||||
pm = WM2200_DSP1_PM_BASE;
|
||||
zm = WM2200_DSP1_ZM_BASE;
|
||||
break;
|
||||
case WM2200_DSP2_CONTROL_1:
|
||||
dm = WM2200_DSP2_DM_BASE;
|
||||
pm = WM2200_DSP2_PM_BASE;
|
||||
zm = WM2200_DSP2_ZM_BASE;
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "BASE %x\n", base);
|
||||
BUG_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regmap_raw_read(regmap, dm, &id, sizeof(id));
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to read algorithm info: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
algs = be32_to_cpu(id.algs);
|
||||
dev_info(codec->dev, "Firmware: %x v%d.%d.%d, %d algorithms\n",
|
||||
be32_to_cpu(id.fw.id),
|
||||
(be32_to_cpu(id.fw.ver) & 0xff000) >> 16,
|
||||
(be32_to_cpu(id.fw.ver) & 0xff00) >> 8,
|
||||
be32_to_cpu(id.fw.ver) & 0xff,
|
||||
algs);
|
||||
|
||||
/* Read the terminator first to validate the length */
|
||||
ret = regmap_raw_read(regmap, dm +
|
||||
(sizeof(id) + (algs * sizeof(*alg))) / 2,
|
||||
&val, sizeof(val));
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to read algorithm list end: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (be32_to_cpu(val) != 0xbedead)
|
||||
dev_warn(codec->dev, "Algorithm list end %x 0x%x != 0xbeadead\n",
|
||||
(sizeof(id) + (algs * sizeof(*alg))) / 2,
|
||||
be32_to_cpu(val));
|
||||
|
||||
alg = kzalloc(sizeof(*alg) * algs, GFP_KERNEL);
|
||||
if (!alg)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = regmap_raw_read(regmap, dm + (sizeof(id) / 2),
|
||||
alg, algs * sizeof(*alg));
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to read algorithm list: %d\n",
|
||||
ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < algs; i++) {
|
||||
dev_info(codec->dev, "%d: ID %x v%d.%d.%d\n",
|
||||
i, be32_to_cpu(alg[i].alg.id),
|
||||
(be32_to_cpu(alg[i].alg.ver) & 0xff000) >> 16,
|
||||
(be32_to_cpu(alg[i].alg.ver) & 0xff00) >> 8,
|
||||
be32_to_cpu(alg[i].alg.ver) & 0xff);
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(alg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm2200_load_coeff(struct snd_soc_codec *codec, int base)
|
||||
{
|
||||
struct regmap *regmap = codec->control_data;
|
||||
struct wmfw_coeff_hdr *hdr;
|
||||
struct wmfw_coeff_item *blk;
|
||||
const struct firmware *firmware;
|
||||
const char *file, *region_name;
|
||||
int ret, dm, pm, zm, pos, blocks, type, offset, reg;
|
||||
|
||||
switch (base) {
|
||||
case WM2200_DSP1_CONTROL_1:
|
||||
file = "wm2200-dsp1.bin";
|
||||
dm = WM2200_DSP1_DM_BASE;
|
||||
pm = WM2200_DSP1_PM_BASE;
|
||||
zm = WM2200_DSP1_ZM_BASE;
|
||||
break;
|
||||
case WM2200_DSP2_CONTROL_1:
|
||||
file = "wm2200-dsp2.bin";
|
||||
dm = WM2200_DSP2_DM_BASE;
|
||||
pm = WM2200_DSP2_PM_BASE;
|
||||
zm = WM2200_DSP2_ZM_BASE;
|
||||
break;
|
||||
default:
|
||||
dev_err(codec->dev, "BASE %x\n", base);
|
||||
BUG_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = request_firmware(&firmware, file, codec->dev);
|
||||
if (ret != 0) {
|
||||
dev_err(codec->dev, "Failed to request '%s'\n", file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (sizeof(*hdr) >= firmware->size) {
|
||||
dev_err(codec->dev, "%s: file too short, %d bytes\n",
|
||||
file, firmware->size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
hdr = (void*)&firmware->data[0];
|
||||
if (memcmp(hdr->magic, "WMDR", 4) != 0) {
|
||||
dev_err(codec->dev, "%s: invalid magic\n", file);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(codec->dev, "%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;
|
||||
|
||||
dev_dbg(codec->dev, "%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);
|
||||
dev_dbg(codec->dev, "%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;
|
||||
default:
|
||||
dev_err(codec->dev, "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) {
|
||||
dev_err(codec->dev,
|
||||
"%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)
|
||||
dev_warn(codec->dev, "%s.%d: %d bytes at end of file\n",
|
||||
file, blocks, pos - firmware->size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol,
|
||||
int event)
|
||||
|
@ -1164,6 +1350,14 @@ static int wm2200_dsp_ev(struct snd_soc_dapm_widget *w,
|
|||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = wm2200_setup_algs(codec, base);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
ret = wm2200_load_coeff(codec, base);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* Start the core running */
|
||||
snd_soc_update_bits(codec, w->reg,
|
||||
WM2200_DSP1_CORE_ENA | WM2200_DSP1_START,
|
||||
|
|
|
@ -43,6 +43,49 @@ struct wmfw_region {
|
|||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct wmfw_id_hdr {
|
||||
__be32 core_id;
|
||||
__be32 core_rev;
|
||||
__be32 id;
|
||||
__be32 ver;
|
||||
} __packed;
|
||||
|
||||
struct wmfw_adsp1_id_hdr {
|
||||
struct wmfw_id_hdr fw;
|
||||
__be32 zm;
|
||||
__be32 dm;
|
||||
__be32 algs;
|
||||
} __packed;
|
||||
|
||||
struct wmfw_alg_hdr {
|
||||
__be32 id;
|
||||
__be32 ver;
|
||||
} __packed;
|
||||
|
||||
struct wmfw_adsp1_alg_hdr {
|
||||
struct wmfw_alg_hdr alg;
|
||||
__be32 zm;
|
||||
__be32 dm;
|
||||
} __packed;
|
||||
|
||||
struct wmfw_coeff_hdr {
|
||||
u8 magic[4];
|
||||
__le32 len;
|
||||
__le32 ver;
|
||||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct wmfw_coeff_item {
|
||||
union {
|
||||
__be32 type;
|
||||
__le32 offset;
|
||||
};
|
||||
__le32 id;
|
||||
__le32 ver;
|
||||
__le32 sr;
|
||||
__le32 len;
|
||||
u8 data[];
|
||||
} __packed;
|
||||
#define WMFW_ADSP1 1
|
||||
|
||||
#define WMFW_ABSOLUTE 0xf0
|
||||
|
|
Loading…
Reference in a new issue