diff options
Diffstat (limited to 'sound/soc/codecs/wm8960.c')
-rw-r--r-- | sound/soc/codecs/wm8960.c | 99 |
1 files changed, 91 insertions, 8 deletions
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 7233cc68435a..3c6ee61f6c95 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -79,12 +79,13 @@ struct wm8960_priv { struct snd_soc_dapm_widget *lout1; struct snd_soc_dapm_widget *rout1; struct snd_soc_dapm_widget *out3; + bool deemph; + int playback_fs; }; #define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) /* enumerated controls */ -static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", "Right Inverted", "Stereo Inversion"}; static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; @@ -93,7 +94,6 @@ static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; static const struct soc_enum wm8960_enum[] = { - SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), @@ -102,6 +102,59 @@ static const struct soc_enum wm8960_enum[] = { SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), }; +static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; + +static int wm8960_set_deemph(struct snd_soc_codec *codec) +{ + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int val, i, best; + + /* If we're using deemphasis select the nearest available sample + * rate. + */ + if (wm8960->deemph) { + best = 1; + for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { + if (abs(deemph_settings[i] - wm8960->playback_fs) < + abs(deemph_settings[best] - wm8960->playback_fs)) + best = i; + } + + val = best << 1; + } else { + val = 0; + } + + dev_dbg(codec->dev, "Set deemphasis %d\n", val); + + return snd_soc_update_bits(codec, WM8960_DACCTL1, + 0x6, val); +} + +static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + + return wm8960->deemph; +} + +static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + int deemph = ucontrol->value.enumerated.item[0]; + + if (deemph > 1) + return -EINVAL; + + wm8960->deemph = deemph; + + return wm8960_set_deemph(codec); +} + static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); @@ -131,23 +184,24 @@ SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), -SOC_ENUM("ADC Polarity", wm8960_enum[1]), -SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), +SOC_ENUM("ADC Polarity", wm8960_enum[0]), SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), SOC_ENUM("DAC Polarity", wm8960_enum[2]), +SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, + wm8960_get_deemph, wm8960_put_deemph), -SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), -SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]), SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), -SOC_ENUM("ALC Function", wm8960_enum[5]), +SOC_ENUM("ALC Function", wm8960_enum[4]), SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), -SOC_ENUM("ALC Mode", wm8960_enum[6]), +SOC_ENUM("ALC Mode", wm8960_enum[5]), SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), @@ -433,6 +487,21 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static struct { + int rate; + unsigned int val; +} alc_rates[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11250, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -440,7 +509,9 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; + int i; /* bit size */ switch (params_format(params)) { @@ -454,6 +525,18 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, break; } + /* Update filters for the new rate */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + wm8960->playback_fs = params_rate(params); + wm8960_set_deemph(codec); + } else { + for (i = 0; i < ARRAY_SIZE(alc_rates); i++) + if (alc_rates[i].rate == params_rate(params)) + snd_soc_update_bits(codec, + WM8960_ADDCTL3, 0x7, + alc_rates[i].val); + } + /* set iface */ snd_soc_write(codec, WM8960_IFACE1, iface); return 0; |