Skip to content

Commit

Permalink
ASoC: Support download of WM8958 MBC firmware
Browse files Browse the repository at this point in the history
Allow userspace to supply an update to the ROM firmware. The firmware
request is non-blocking so userspace can load the firmware at its
leisure without delaying startup, the driver will begin using the
firmware the next time MBC is started after it has been supplied.

Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Liam Girdwood <lrg@ti.com>
  • Loading branch information
Mark Brown committed Mar 22, 2011
1 parent af9af86 commit fbbf592
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 0 deletions.
223 changes: 223 additions & 0 deletions sound/soc/codecs/wm8958-dsp2.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,212 @@

#include "wm8994.h"

#define WM_FW_BLOCK_INFO 0xff
#define WM_FW_BLOCK_PM 0x00
#define WM_FW_BLOCK_X 0x01
#define WM_FW_BLOCK_Y 0x02
#define WM_FW_BLOCK_Z 0x03
#define WM_FW_BLOCK_I 0x06
#define WM_FW_BLOCK_A 0x08
#define WM_FW_BLOCK_C 0x0c

static int wm8958_dsp2_fw(struct snd_soc_codec *codec, const char *name,
const struct firmware *fw, bool check)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
u64 data64;
u32 data32;
const u8 *data;
char *str;
size_t block_len, len;
int ret = 0;

/* Suppress unneeded downloads */
if (wm8994->cur_fw == fw)
return 0;

if (fw->size < 32) {
dev_err(codec->dev, "%s: firmware too short\n", name);
goto err;
}

if (memcmp(fw->data, "WMFW", 4) != 0) {
dev_err(codec->dev, "%s: firmware has bad file magic %08x\n",
name, data32);
goto err;
}

memcpy(&data32, fw->data + 4, sizeof(data32));
len = be32_to_cpu(data32);

memcpy(&data32, fw->data + 8, sizeof(data32));
data32 = be32_to_cpu(data32);
if ((data32 >> 24) & 0xff) {
dev_err(codec->dev, "%s: unsupported firmware version %d\n",
name, (data32 >> 24) & 0xff);
goto err;
}
if ((data32 & 0xffff) != 8958) {
dev_err(codec->dev, "%s: unsupported target device %d\n",
name, data32 & 0xffff);
goto err;
}
if (((data32 >> 16) & 0xff) != 0xc) {
dev_err(codec->dev, "%s: unsupported target core %d\n",
name, (data32 >> 16) & 0xff);
goto err;
}

if (check) {
memcpy(&data64, fw->data + 24, sizeof(u64));
dev_info(codec->dev, "%s timestamp %llx\n",
name, be64_to_cpu(data64));
} else {
snd_soc_write(codec, 0x102, 0x2);
snd_soc_write(codec, 0x900, 0x2);
}

data = fw->data + len;
len = fw->size - len;
while (len) {
if (len < 12) {
dev_err(codec->dev, "%s short data block of %d\n",
name, len);
goto err;
}

memcpy(&data32, data + 4, sizeof(data32));
block_len = be32_to_cpu(data32);
if (block_len + 8 > len) {
dev_err(codec->dev, "%d byte block longer than file\n",
block_len);
goto err;
}
if (block_len == 0) {
dev_err(codec->dev, "Zero length block\n");
goto err;
}

memcpy(&data32, data, sizeof(data32));
data32 = be32_to_cpu(data32);

switch ((data32 >> 24) & 0xff) {
case WM_FW_BLOCK_INFO:
/* Informational text */
if (!check)
break;

str = kzalloc(block_len + 1, GFP_KERNEL);
if (str) {
memcpy(str, data + 8, block_len);
dev_info(codec->dev, "%s: %s\n", name, str);
kfree(str);
} else {
dev_err(codec->dev, "Out of memory\n");
}
break;
case WM_FW_BLOCK_PM:
case WM_FW_BLOCK_X:
case WM_FW_BLOCK_Y:
case WM_FW_BLOCK_Z:
case WM_FW_BLOCK_I:
case WM_FW_BLOCK_A:
case WM_FW_BLOCK_C:
dev_dbg(codec->dev, "%s: %d bytes of %x@%x\n", name,
block_len, (data32 >> 24) & 0xff,
data32 & 0xffffff);

if (check)
break;

data32 &= 0xffffff;

wm8994_bulk_write(codec->control_data,
data32 & 0xffffff,
block_len / 2,
(void *)(data + 8));

break;
default:
dev_warn(codec->dev, "%s: unknown block type %d\n",
name, (data32 >> 24) & 0xff);
break;
}

/* Round up to the next 32 bit word */
block_len += block_len % 4;

data += block_len + 8;
len -= block_len + 8;
}

if (!check) {
dev_dbg(codec->dev, "%s: download done\n", name);
wm8994->cur_fw = fw;
} else {
dev_info(codec->dev, "%s: got firmware\n", name);
}

goto ok;

err:
ret = -EINVAL;
ok:
if (!check) {
snd_soc_write(codec, 0x900, 0x0);
snd_soc_write(codec, 0x102, 0x0);
}

return ret;
}

static void wm8958_mbc_apply(struct snd_soc_codec *codec, int mbc, int start)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
struct wm8994_pdata *pdata = wm8994->pdata;
int i;

/* If the DSP is already running then noop */
if (snd_soc_read(codec, WM8958_DSP2_PROGRAM) & WM8958_DSP2_ENA)
return;

/* If we have MBC firmware download it */
if (wm8994->mbc)
wm8958_dsp2_fw(codec, "MBC", wm8994->mbc, false);

snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM,
WM8958_DSP2_ENA, WM8958_DSP2_ENA);

/* If we've got user supplied MBC settings use them */
if (pdata && pdata->num_mbc_cfgs) {
struct wm8958_mbc_cfg *cfg
= &pdata->mbc_cfgs[wm8994->mbc_cfg];

for (i = 0; i < ARRAY_SIZE(cfg->coeff_regs); i++)
snd_soc_write(codec, i + WM8958_MBC_BAND_1_K_1,
cfg->coeff_regs[i]);

for (i = 0; i < ARRAY_SIZE(cfg->cutoff_regs); i++)
snd_soc_write(codec,
i + WM8958_MBC_BAND_2_LOWER_CUTOFF_C1_1,
cfg->cutoff_regs[i]);
}

/* Run the DSP */
snd_soc_write(codec, WM8958_DSP2_EXECCONTROL,
WM8958_DSP2_RUNR);

/* And we're off! */
snd_soc_update_bits(codec, WM8958_DSP2_CONFIG,
WM8958_MBC_ENA |
WM8958_MBC_SEL_MASK,
path << WM8958_MBC_SEL_SHIFT |
WM8958_MBC_ENA);
}

static void wm8958_dsp_apply(struct snd_soc_codec *codec, int path, int start)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
int pwr_reg = snd_soc_read(codec, WM8994_POWER_MANAGEMENT_5);
int ena, reg, aif, i;

Expand Down Expand Up @@ -76,6 +278,10 @@ static void wm8958_mbc_apply(struct snd_soc_codec *codec, int mbc, int start)
& WM8994_AIF2CLK_ENA_MASK))
return;

/* If we have MBC firmware download it */
if (wm8994->mbc && wm8994->mbc_ena[mbc])
wm8958_dsp2_fw(codec, "MBC", wm8994->mbc, false);

/* Switch the clock over to the appropriate AIF */
snd_soc_update_bits(codec, WM8994_CLOCKING_1,
WM8958_DSP2CLK_SRC | WM8958_DSP2CLK_ENA,
Expand Down Expand Up @@ -238,6 +444,18 @@ WM8958_MBC_SWITCH("AIF1DAC2 MBC Switch", 1),
WM8958_MBC_SWITCH("AIF2DAC MBC Switch", 2),
};

static void wm8958_mbc_loaded(const struct firmware *fw, void *context)
{
struct snd_soc_codec *codec = context;
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);

if (fw && wm8958_dsp2_fw(codec, "MBC", fw, true) != 0) {
mutex_lock(&codec->mutex);
wm8994->mbc = fw;
mutex_unlock(&codec->mutex);
}
}

void wm8958_dsp2_init(struct snd_soc_codec *codec)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
Expand All @@ -247,6 +465,11 @@ void wm8958_dsp2_init(struct snd_soc_codec *codec)
snd_soc_add_controls(codec, wm8958_mbc_snd_controls,
ARRAY_SIZE(wm8958_mbc_snd_controls));

/* We don't require firmware and don't want to delay boot */
request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
"wm8958_mbc.wfw", codec->dev, GFP_KERNEL,
codec, wm8958_mbc_loaded);

if (!pdata)
return;

Expand Down
4 changes: 4 additions & 0 deletions sound/soc/codecs/wm8994.c
Original file line number Diff line number Diff line change
Expand Up @@ -1922,6 +1922,8 @@ static int wm8994_set_bias_level(struct snd_soc_codec *codec,
WM8994_VMID_BUF_ENA |
WM8994_VMID_RAMP_MASK, 0);

wm8994->cur_fw = NULL;

pm_runtime_put(codec->dev);
}
break;
Expand Down Expand Up @@ -3136,6 +3138,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec)
free_irq(wm8994->micdet_irq, wm8994);
break;
}
if (wm8994->mbc)
release_firmware(wm8994->mbc);
kfree(wm8994->retune_mobile_texts);
kfree(wm8994->drc_texts);
kfree(wm8994);
Expand Down
4 changes: 4 additions & 0 deletions sound/soc/codecs/wm8994.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define _WM8994_H

#include <sound/soc.h>
#include <linux/firmware.h>

#include "wm_hubs.h"

Expand Down Expand Up @@ -114,6 +115,9 @@ struct wm8994_priv {

unsigned int aif1clk_disable:1;
unsigned int aif2clk_disable:1;

const struct firmware *cur_fw;
const struct firmware *mbc;
};

#endif

0 comments on commit fbbf592

Please sign in to comment.