diff options
Diffstat (limited to 'sound/soc/codecs/wm2000.c')
-rw-r--r-- | sound/soc/codecs/wm2000.c | 66 |
1 files changed, 59 insertions, 7 deletions
diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c index 12bcae63a7f0..f2ac38b61a1b 100644 --- a/sound/soc/codecs/wm2000.c +++ b/sound/soc/codecs/wm2000.c @@ -26,6 +26,7 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/firmware.h> +#include <linux/clk.h> #include <linux/delay.h> #include <linux/pm.h> #include <linux/i2c.h> @@ -62,6 +63,7 @@ enum wm2000_anc_mode { struct wm2000_priv { struct i2c_client *i2c; struct regmap *regmap; + struct clk *mclk; struct regulator_bulk_data supplies[WM2000_NUM_SUPPLIES]; @@ -71,11 +73,12 @@ struct wm2000_priv { unsigned int anc_eng_ena:1; unsigned int spk_ena:1; - unsigned int mclk_div:1; unsigned int speech_clarity:1; int anc_download_size; char *anc_download; + + struct mutex lock; }; static int wm2000_write(struct i2c_client *i2c, unsigned int reg, @@ -131,6 +134,7 @@ static int wm2000_poll_bit(struct i2c_client *i2c, static int wm2000_power_up(struct i2c_client *i2c, int analogue) { struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev); + unsigned long rate; int ret; BUG_ON(wm2000->anc_mode != ANC_OFF); @@ -143,7 +147,8 @@ static int wm2000_power_up(struct i2c_client *i2c, int analogue) return ret; } - if (!wm2000->mclk_div) { + rate = clk_get_rate(wm2000->mclk); + if (rate <= 13500000) { dev_dbg(&i2c->dev, "Disabling MCLK divider\n"); wm2000_write(i2c, WM2000_REG_SYS_CTL2, WM2000_MCLK_DIV2_ENA_CLR); @@ -550,6 +555,15 @@ static int wm2000_anc_transition(struct wm2000_priv *wm2000, return -EINVAL; } + /* Maintain clock while active */ + if (anc_transitions[i].source == ANC_OFF) { + ret = clk_prepare_enable(wm2000->mclk); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + } + for (j = 0; j < ARRAY_SIZE(anc_transitions[j].step); j++) { if (!anc_transitions[i].step[j]) break; @@ -559,7 +573,10 @@ static int wm2000_anc_transition(struct wm2000_priv *wm2000, return ret; } - return 0; + if (anc_transitions[i].dest == ANC_OFF) + clk_disable_unprepare(wm2000->mclk); + + return ret; } static int wm2000_anc_set_mode(struct wm2000_priv *wm2000) @@ -599,13 +616,20 @@ static int wm2000_anc_mode_put(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct wm2000_priv *wm2000 = dev_get_drvdata(codec->dev); int anc_active = ucontrol->value.enumerated.item[0]; + int ret; if (anc_active > 1) return -EINVAL; + mutex_lock(&wm2000->lock); + wm2000->anc_active = anc_active; - return wm2000_anc_set_mode(wm2000); + ret = wm2000_anc_set_mode(wm2000); + + mutex_unlock(&wm2000->lock); + + return ret; } static int wm2000_speaker_get(struct snd_kcontrol *kcontrol, @@ -625,16 +649,24 @@ static int wm2000_speaker_put(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct wm2000_priv *wm2000 = dev_get_drvdata(codec->dev); int val = ucontrol->value.enumerated.item[0]; + int ret; if (val > 1) return -EINVAL; + mutex_lock(&wm2000->lock); + wm2000->spk_ena = val; - return wm2000_anc_set_mode(wm2000); + ret = wm2000_anc_set_mode(wm2000); + + mutex_unlock(&wm2000->lock); + + return ret; } static const struct snd_kcontrol_new wm2000_controls[] = { + SOC_SINGLE("ANC Volume", WM2000_REG_ANC_GAIN_CTRL, 0, 255, 0), SOC_SINGLE_BOOL_EXT("WM2000 ANC Switch", 0, wm2000_anc_mode_get, wm2000_anc_mode_put), @@ -648,6 +680,9 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, { struct snd_soc_codec *codec = w->codec; struct wm2000_priv *wm2000 = dev_get_drvdata(codec->dev); + int ret; + + mutex_lock(&wm2000->lock); if (SND_SOC_DAPM_EVENT_ON(event)) wm2000->anc_eng_ena = 1; @@ -655,7 +690,11 @@ static int wm2000_anc_power_event(struct snd_soc_dapm_widget *w, if (SND_SOC_DAPM_EVENT_OFF(event)) wm2000->anc_eng_ena = 0; - return wm2000_anc_set_mode(wm2000); + ret = wm2000_anc_set_mode(wm2000); + + mutex_unlock(&wm2000->lock); + + return ret; } static const struct snd_soc_dapm_widget wm2000_dapm_widgets[] = { @@ -702,6 +741,9 @@ static bool wm2000_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { case WM2000_REG_SYS_START: + case WM2000_REG_ANC_GAIN_CTRL: + case WM2000_REG_MSE_TH1: + case WM2000_REG_MSE_TH2: case WM2000_REG_SPEECH_CLARITY: case WM2000_REG_SYS_WATCHDOG: case WM2000_REG_ANA_VMID_PD_TIME: @@ -737,6 +779,8 @@ static int wm2000_probe(struct snd_soc_codec *codec) { struct wm2000_priv *wm2000 = dev_get_drvdata(codec->dev); + snd_soc_codec_set_cache_io(codec, 16, 8, SND_SOC_REGMAP); + /* This will trigger a transition to standby mode by default */ wm2000_anc_set_mode(wm2000); @@ -782,6 +826,8 @@ static int wm2000_i2c_probe(struct i2c_client *i2c, return -ENOMEM; } + mutex_init(&wm2000->lock); + dev_set_drvdata(&i2c->dev, wm2000); wm2000->regmap = devm_regmap_init_i2c(i2c, &wm2000_regmap); @@ -823,10 +869,16 @@ static int wm2000_i2c_probe(struct i2c_client *i2c, reg = wm2000_read(i2c, WM2000_REG_REVISON); dev_info(&i2c->dev, "revision %c\n", reg + 'A'); + wm2000->mclk = devm_clk_get(&i2c->dev, "MCLK"); + if (IS_ERR(wm2000->mclk)) { + ret = PTR_ERR(wm2000->mclk); + dev_err(&i2c->dev, "Failed to get MCLK: %d\n", ret); + goto err_supplies; + } + filename = "wm2000_anc.bin"; pdata = dev_get_platdata(&i2c->dev); if (pdata) { - wm2000->mclk_div = pdata->mclkdiv2; wm2000->speech_clarity = !pdata->speech_enh_disable; if (pdata->download_file) |