diff options
author | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2011-03-11 18:09:04 +0000 |
---|---|---|
committer | Mark Brown <broonie@opensource.wolfsonmicro.com> | 2011-03-22 18:41:20 +0000 |
commit | fbbf592002ee46ed14d5bd88f1150c604b34e705 (patch) | |
tree | b4fe275ab768a411c2ca85fa925c40d813d5df19 /sound/soc/codecs/wm8958-dsp2.c | |
parent | af9af866020ea341aca32123b3109b6a9408dd8c (diff) | |
download | linux-fbbf592002ee46ed14d5bd88f1150c604b34e705.tar.bz2 |
ASoC: Support download of WM8958 MBC firmware
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>
Diffstat (limited to 'sound/soc/codecs/wm8958-dsp2.c')
-rw-r--r-- | sound/soc/codecs/wm8958-dsp2.c | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c index f07775ebec04..58fe40416709 100644 --- a/sound/soc/codecs/wm8958-dsp2.c +++ b/sound/soc/codecs/wm8958-dsp2.c @@ -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; @@ -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, @@ -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); @@ -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; |