[alsa-devel] [PATCH 1/4] ASoC: Initial WM8958 audio configuration
The WM8958 is a WM8994 derivative. This patch merely ensures that some revision specific configuration for WM8994 is not enabled on WM8994, additional patches will add support for the new features introduced on the WM8958.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- sound/soc/codecs/wm8994.c | 110 +++++++++++++++++++++++++++++++-------------- 1 files changed, 76 insertions(+), 34 deletions(-)
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 2833994..8232d5e 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -1535,6 +1535,7 @@ static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai, static int wm8994_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct wm8994 *control = codec->control_data; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
switch (level) { @@ -1551,7 +1552,7 @@ static int wm8994_set_bias_level(struct snd_soc_codec *codec, if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { /* Tweak DC servo and DSP configuration for * improved performance. */ - if (wm8994->revision < 4) { + if (control->type == WM8994 && wm8994->revision < 4) { /* Tweak DC servo and DSP configuration for * improved performance. */ snd_soc_write(codec, 0x102, 0x3); @@ -2264,8 +2265,12 @@ int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994_micdet *micdet; + struct wm8994 *control = codec->control_data; int reg;
+ if (control->type != WM8994) + return -EINVAL; + switch (micbias) { case 1: micdet = &wm8994->micdet[0]; @@ -2334,11 +2339,13 @@ static irqreturn_t wm8994_mic_irq(int irq, void *data)
static int wm8994_codec_probe(struct snd_soc_codec *codec) { + struct wm8994 *control; struct wm8994_priv *wm8994; struct snd_soc_dapm_context *dapm = &codec->dapm; int ret, i;
codec->control_data = dev_get_drvdata(codec->dev->parent); + control = codec->control_data;
wm8994 = kzalloc(sizeof(struct wm8994_priv), GFP_KERNEL); if (wm8994 == NULL) @@ -2369,41 +2376,67 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
/* Set revision-specific configuration */ wm8994->revision = snd_soc_read(codec, WM8994_CHIP_REVISION); - switch (wm8994->revision) { - case 2: - case 3: - wm8994->hubs.dcs_codes = -5; - wm8994->hubs.hp_startup_mode = 1; + switch (control->type) { + case WM8994: + switch (wm8994->revision) { + case 2: + case 3: + wm8994->hubs.dcs_codes = -5; + wm8994->hubs.hp_startup_mode = 1; + wm8994->hubs.dcs_readback_mode = 1; + break; + default: + wm8994->hubs.dcs_readback_mode = 1; + break; + } + + case WM8958: wm8994->hubs.dcs_readback_mode = 1; break; + default: - wm8994->hubs.dcs_readback_mode = 1; break; }
- ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_DET, - wm8994_mic_irq, "Mic 1 detect", wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic1 detect IRQ: %d\n", ret); - - ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, - wm8994_mic_irq, "Mic 1 short", wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic1 short IRQ: %d\n", ret); - - ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_DET, - wm8994_mic_irq, "Mic 2 detect", wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic2 detect IRQ: %d\n", ret); - - ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, - wm8994_mic_irq, "Mic 2 short", wm8994); - if (ret != 0) - dev_warn(codec->dev, - "Failed to request Mic2 short IRQ: %d\n", ret); + switch (control->type) { + case WM8994: + ret = wm8994_request_irq(codec->control_data, + WM8994_IRQ_MIC1_DET, + wm8994_mic_irq, "Mic 1 detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic1 detect IRQ: %d\n", + ret); + + ret = wm8994_request_irq(codec->control_data, + WM8994_IRQ_MIC1_SHRT, + wm8994_mic_irq, "Mic 1 short", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic1 short IRQ: %d\n", + ret); + + ret = wm8994_request_irq(codec->control_data, + WM8994_IRQ_MIC2_DET, + wm8994_mic_irq, "Mic 2 detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic2 detect IRQ: %d\n", + ret); + + ret = wm8994_request_irq(codec->control_data, + WM8994_IRQ_MIC2_SHRT, + wm8994_mic_irq, "Mic 2 short", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic2 short IRQ: %d\n", + ret); + break; + }
/* Remember if AIFnLRCLK is configured as a GPIO. This should be * configured on init - if a system wants to do this dynamically @@ -2496,13 +2529,22 @@ err: static int wm8994_codec_remove(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994 *control = codec->control_data;
wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
- wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994); - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994); - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); - wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); + switch (control->type) { + case WM8994: + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, + wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, + wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, + wm8994); + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, + wm8994); + break; + } kfree(wm8994->retune_mobile_texts); kfree(wm8994->drc_texts); kfree(wm8994);
Additional audio routing options are available on the WM8958 audio interface 3. Add support for these.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/linux/mfd/wm8994/registers.h | 67 ++++++++++++++ sound/soc/codecs/wm8994.c | 166 ++++++++++++++++++++++++++++++++-- 2 files changed, 224 insertions(+), 9 deletions(-)
diff --git a/include/linux/mfd/wm8994/registers.h b/include/linux/mfd/wm8994/registers.h index 967f62f..3eb70a4 100644 --- a/include/linux/mfd/wm8994/registers.h +++ b/include/linux/mfd/wm8994/registers.h @@ -109,6 +109,10 @@ #define WM8994_AIF2DAC_LRCLK 0x315 #define WM8994_AIF2DAC_DATA 0x316 #define WM8994_AIF2ADC_DATA 0x317 +#define WM8958_AIF3_CONTROL_1 0x320 +#define WM8958_AIF3_CONTROL_2 0x321 +#define WM8958_AIF3DAC_DATA 0x322 +#define WM8958_AIF3ADC_DATA 0x323 #define WM8994_AIF1_ADC1_LEFT_VOLUME 0x400 #define WM8994_AIF1_ADC1_RIGHT_VOLUME 0x401 #define WM8994_AIF1_DAC1_LEFT_VOLUME 0x402 @@ -992,6 +996,12 @@ /* * R6 (0x06) - Power Management (6) */ +#define WM8958_AIF3ADC_SRC_MASK 0x0600 /* AIF3ADC_SRC - [10:9] */ +#define WM8958_AIF3ADC_SRC_SHIFT 9 /* AIF3ADC_SRC - [10:9] */ +#define WM8958_AIF3ADC_SRC_WIDTH 2 /* AIF3ADC_SRC - [10:9] */ +#define WM8958_AIF2DAC_SRC_MASK 0x0180 /* AIF2DAC_SRC - [8:7] */ +#define WM8958_AIF2DAC_SRC_SHIFT 7 /* AIF2DAC_SRC - [8:7] */ +#define WM8958_AIF2DAC_SRC_WIDTH 2 /* AIF2DAC_SRC - [8:7] */ #define WM8994_AIF3_TRI 0x0020 /* AIF3_TRI */ #define WM8994_AIF3_TRI_MASK 0x0020 /* AIF3_TRI */ #define WM8994_AIF3_TRI_SHIFT 5 /* AIF3_TRI */ @@ -2553,6 +2563,63 @@ #define WM8994_AIF2ADCR_DAT_INV_WIDTH 1 /* AIF2ADCR_DAT_INV */
/* + * R800 (0x320) - AIF3 Control (1) + */ +#define WM8958_AIF3_LRCLK_INV 0x0080 /* AIF3_LRCLK_INV */ +#define WM8958_AIF3_LRCLK_INV_MASK 0x0080 /* AIF3_LRCLK_INV */ +#define WM8958_AIF3_LRCLK_INV_SHIFT 7 /* AIF3_LRCLK_INV */ +#define WM8958_AIF3_LRCLK_INV_WIDTH 1 /* AIF3_LRCLK_INV */ +#define WM8958_AIF3_WL_MASK 0x0060 /* AIF3_WL - [6:5] */ +#define WM8958_AIF3_WL_SHIFT 5 /* AIF3_WL - [6:5] */ +#define WM8958_AIF3_WL_WIDTH 2 /* AIF3_WL - [6:5] */ +#define WM8958_AIF3_FMT_MASK 0x0018 /* AIF3_FMT - [4:3] */ +#define WM8958_AIF3_FMT_SHIFT 3 /* AIF3_FMT - [4:3] */ +#define WM8958_AIF3_FMT_WIDTH 2 /* AIF3_FMT - [4:3] */ + +/* + * R801 (0x321) - AIF3 Control (2) + */ +#define WM8958_AIF3DAC_BOOST_MASK 0x0C00 /* AIF3DAC_BOOST - [11:10] */ +#define WM8958_AIF3DAC_BOOST_SHIFT 10 /* AIF3DAC_BOOST - [11:10] */ +#define WM8958_AIF3DAC_BOOST_WIDTH 2 /* AIF3DAC_BOOST - [11:10] */ +#define WM8958_AIF3DAC_COMP 0x0010 /* AIF3DAC_COMP */ +#define WM8958_AIF3DAC_COMP_MASK 0x0010 /* AIF3DAC_COMP */ +#define WM8958_AIF3DAC_COMP_SHIFT 4 /* AIF3DAC_COMP */ +#define WM8958_AIF3DAC_COMP_WIDTH 1 /* AIF3DAC_COMP */ +#define WM8958_AIF3DAC_COMPMODE 0x0008 /* AIF3DAC_COMPMODE */ +#define WM8958_AIF3DAC_COMPMODE_MASK 0x0008 /* AIF3DAC_COMPMODE */ +#define WM8958_AIF3DAC_COMPMODE_SHIFT 3 /* AIF3DAC_COMPMODE */ +#define WM8958_AIF3DAC_COMPMODE_WIDTH 1 /* AIF3DAC_COMPMODE */ +#define WM8958_AIF3ADC_COMP 0x0004 /* AIF3ADC_COMP */ +#define WM8958_AIF3ADC_COMP_MASK 0x0004 /* AIF3ADC_COMP */ +#define WM8958_AIF3ADC_COMP_SHIFT 2 /* AIF3ADC_COMP */ +#define WM8958_AIF3ADC_COMP_WIDTH 1 /* AIF3ADC_COMP */ +#define WM8958_AIF3ADC_COMPMODE 0x0002 /* AIF3ADC_COMPMODE */ +#define WM8958_AIF3ADC_COMPMODE_MASK 0x0002 /* AIF3ADC_COMPMODE */ +#define WM8958_AIF3ADC_COMPMODE_SHIFT 1 /* AIF3ADC_COMPMODE */ +#define WM8958_AIF3ADC_COMPMODE_WIDTH 1 /* AIF3ADC_COMPMODE */ +#define WM8958_AIF3_LOOPBACK 0x0001 /* AIF3_LOOPBACK */ +#define WM8958_AIF3_LOOPBACK_MASK 0x0001 /* AIF3_LOOPBACK */ +#define WM8958_AIF3_LOOPBACK_SHIFT 0 /* AIF3_LOOPBACK */ +#define WM8958_AIF3_LOOPBACK_WIDTH 1 /* AIF3_LOOPBACK */ + +/* + * R802 (0x322) - AIF3DAC Data + */ +#define WM8958_AIF3DAC_DAT_INV 0x0001 /* AIF3DAC_DAT_INV */ +#define WM8958_AIF3DAC_DAT_INV_MASK 0x0001 /* AIF3DAC_DAT_INV */ +#define WM8958_AIF3DAC_DAT_INV_SHIFT 0 /* AIF3DAC_DAT_INV */ +#define WM8958_AIF3DAC_DAT_INV_WIDTH 1 /* AIF3DAC_DAT_INV */ + +/* + * R803 (0x323) - AIF3ADC Data + */ +#define WM8958_AIF3ADC_DAT_INV 0x0001 /* AIF3ADC_DAT_INV */ +#define WM8958_AIF3ADC_DAT_INV_MASK 0x0001 /* AIF3ADC_DAT_INV */ +#define WM8958_AIF3ADC_DAT_INV_SHIFT 0 /* AIF3ADC_DAT_INV */ +#define WM8958_AIF3ADC_DAT_INV_WIDTH 1 /* AIF3ADC_DAT_INV */ + +/* * R1024 (0x400) - AIF1 ADC1 Left Volume */ #define WM8994_AIF1ADC1_VU 0x0100 /* AIF1ADC1_VU */ diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 8232d5e..fb06093 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -647,6 +647,10 @@ SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0, eq_tlv), };
+static const struct snd_kcontrol_new wm8958_snd_controls[] = { +SOC_SINGLE_TLV("AIF3 Boost Volume", WM8958_AIF3_CONTROL_2, 10, 3, 0, aif_tlv), +}; + static int clk_sys_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -953,14 +957,47 @@ static const struct snd_kcontrol_new aif2adc_mux = SOC_DAPM_ENUM("AIF2ADC Mux", aif2adc_enum);
static const char *aif3adc_text[] = { - "AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT", + "AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT", "Mono PCM", };
-static const struct soc_enum aif3adc_enum = +static const struct soc_enum wm8994_aif3adc_enum = SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 3, aif3adc_text);
-static const struct snd_kcontrol_new aif3adc_mux = - SOC_DAPM_ENUM("AIF3ADC Mux", aif3adc_enum); +static const struct snd_kcontrol_new wm8994_aif3adc_mux = + SOC_DAPM_ENUM("AIF3ADC Mux", wm8994_aif3adc_enum); + +static const struct soc_enum wm8958_aif3adc_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 4, aif3adc_text); + +static const struct snd_kcontrol_new wm8958_aif3adc_mux = + SOC_DAPM_ENUM("AIF3ADC Mux", wm8958_aif3adc_enum); + +static const char *mono_pcm_out_text[] = { + "None", "AIF2ADCL", "AIF2ADCR", +}; + +static const struct soc_enum mono_pcm_out_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 9, 3, mono_pcm_out_text); + +static const struct snd_kcontrol_new mono_pcm_out_mux = + SOC_DAPM_ENUM("Mono PCM Out Mux", mono_pcm_out_enum); + +static const char *aif2dac_src_text[] = { + "AIF2", "AIF3", +}; + +/* Note that these two control shouldn't be simultaneously switched to AIF3 */ +static const struct soc_enum aif2dacl_src_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 7, 2, aif2dac_src_text); + +static const struct snd_kcontrol_new aif2dacl_src_mux = + SOC_DAPM_ENUM("AIF2DACL Mux", aif2dacl_src_enum); + +static const struct soc_enum aif2dacr_src_enum = + SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 8, 2, aif2dac_src_text); + +static const struct snd_kcontrol_new aif2dacr_src_mux = + SOC_DAPM_ENUM("AIF2DACR Mux", aif2dacr_src_enum);
static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = { SND_SOC_DAPM_INPUT("DMIC1DAT"), @@ -1034,7 +1071,6 @@ SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux), SND_SOC_DAPM_MUX("AIF2DAC Mux", SND_SOC_NOPM, 0, 0, &aif2dac_mux), SND_SOC_DAPM_MUX("AIF2ADC Mux", SND_SOC_NOPM, 0, 0, &aif2adc_mux), -SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &aif3adc_mux),
SND_SOC_DAPM_AIF_IN("AIF3DACDAT", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("AIF3ADCDAT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0), @@ -1072,8 +1108,18 @@ SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, SND_SOC_DAPM_POST("Debug log", post_ev), };
-static const struct snd_soc_dapm_route intercon[] = { +static const struct snd_soc_dapm_widget wm8994_specific_dapm_widgets[] = { +SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux), +};
+static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = { +SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux), +SND_SOC_DAPM_MUX("AIF2DACL Mux", SND_SOC_NOPM, 0, 0, &aif2dacl_src_mux), +SND_SOC_DAPM_MUX("AIF2DACR Mux", SND_SOC_NOPM, 0, 0, &aif2dacr_src_mux), +SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8958_aif3adc_mux), +}; + +static const struct snd_soc_dapm_route intercon[] = { { "CLK_SYS", NULL, "AIF1CLK", check_clk_sys }, { "CLK_SYS", NULL, "AIF2CLK", check_clk_sys },
@@ -1181,9 +1227,6 @@ static const struct snd_soc_dapm_route intercon[] = { { "AIF1DAC2L", NULL, "AIF1DAC Mux" }, { "AIF1DAC2R", NULL, "AIF1DAC Mux" },
- { "AIF2DACL", NULL, "AIF2DAC Mux" }, - { "AIF2DACR", NULL, "AIF2DAC Mux" }, - { "AIF1DAC Mux", "AIF1DACDAT", "AIF1DACDAT" }, { "AIF1DAC Mux", "AIF3DACDAT", "AIF3DACDAT" }, { "AIF2DAC Mux", "AIF2DACDAT", "AIF2DACDAT" }, @@ -1256,6 +1299,26 @@ static const struct snd_soc_dapm_route intercon[] = { { "Right Headphone Mux", "DAC", "DAC1R" }, };
+static const struct snd_soc_dapm_route wm8994_intercon[] = { + { "AIF2DACL", NULL, "AIF2DAC Mux" }, + { "AIF2DACR", NULL, "AIF2DAC Mux" }, +}; + +static const struct snd_soc_dapm_route wm8958_intercon[] = { + { "AIF2DACL", NULL, "AIF2DACL Mux" }, + { "AIF2DACR", NULL, "AIF2DACR Mux" }, + + { "AIF2DACL Mux", "AIF2", "AIF2DAC Mux" }, + { "AIF2DACL Mux", "AIF3", "AIF3DACDAT" }, + { "AIF2DACR Mux", "AIF2", "AIF2DAC Mux" }, + { "AIF2DACR Mux", "AIF3", "AIF3DACDAT" }, + + { "Mono PCM Out Mux", "AIF2ADCL", "AIF2ADCL" }, + { "Mono PCM Out Mux", "AIF2ADCR", "AIF2ADCR" }, + + { "AIF3ADC Mux", "Mono PCM", "Mono PCM Out Mux" }, +}; + /* The size in bits of the FLL divide multiplied by 10 * to allow rounding later */ #define FIXED_FLL_SIZE ((1 << 16) * 10) @@ -1635,6 +1698,7 @@ static int wm8994_set_bias_level(struct snd_soc_codec *codec, static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; + struct wm8994 *control = codec->control_data; int ms_reg; int aif1_reg; int ms = 0; @@ -1719,6 +1783,13 @@ static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; }
+ /* The AIF2 format configuration needs to be mirrored to AIF3 + * on WM8958 if it's in use so just do it all the time. */ + if (control->type == WM8958 && dai->id == 2) + snd_soc_update_bits(codec, WM8958_AIF3_CONTROL_1, + WM8994_AIF1_LRCLK_INV | + WM8958_AIF3_FMT_MASK, aif1); + snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV | WM8994_AIF1_FMT_MASK, @@ -1759,6 +1830,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; + struct wm8994 *control = codec->control_data; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); int aif1_reg; int bclk_reg; @@ -1797,6 +1869,14 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, dev_dbg(codec->dev, "AIF2 using split LRCLK\n"); } break; + case 3: + switch (control->type) { + case WM8958: + aif1_reg = WM8958_AIF3_CONTROL_1; + break; + default: + return 0; + } default: return -EINVAL; } @@ -1900,6 +1980,47 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, return 0; }
+static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8994 *control = codec->control_data; + int aif1_reg; + int aif1 = 0; + + switch (dai->id) { + case 3: + switch (control->type) { + case WM8958: + aif1_reg = WM8958_AIF3_CONTROL_1; + break; + default: + return 0; + } + default: + return 0; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + aif1 |= 0x20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + aif1 |= 0x40; + break; + case SNDRV_PCM_FORMAT_S32_LE: + aif1 |= 0x60; + break; + default: + return -EINVAL; + } + + return snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1); +} + static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_codec *codec = codec_dai->codec; @@ -1981,6 +2102,7 @@ static struct snd_soc_dai_ops wm8994_aif2_dai_ops = { };
static struct snd_soc_dai_ops wm8994_aif3_dai_ops = { + .hw_params = wm8994_aif3_hw_params, .set_tristate = wm8994_set_tristate, };
@@ -2511,9 +2633,35 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) ARRAY_SIZE(wm8994_snd_controls)); snd_soc_dapm_new_controls(dapm, wm8994_dapm_widgets, ARRAY_SIZE(wm8994_dapm_widgets)); + + switch (control->type) { + case WM8994: + snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets, + ARRAY_SIZE(wm8994_specific_dapm_widgets)); + break; + case WM8958: + snd_soc_add_controls(codec, wm8958_snd_controls, + ARRAY_SIZE(wm8958_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets, + ARRAY_SIZE(wm8958_dapm_widgets)); + break; + } + + wm_hubs_add_analogue_routes(codec, 0, 0); snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+ switch (control->type) { + case WM8994: + snd_soc_dapm_add_routes(dapm, wm8994_intercon, + ARRAY_SIZE(wm8994_intercon)); + break; + case WM8958: + snd_soc_dapm_add_routes(dapm, wm8958_intercon, + ARRAY_SIZE(wm8958_intercon)); + break; + } + return 0;
err_irq:
The WM8958 features a multi-band compressor which can be enabled on any of the AIF inputs. The MBC allows different gains to be applied to differnt audio bands, providing an improvement in perceived loudness of the signal by avoiding overdriving the output transducers. This patch enables support for the MBC.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/linux/mfd/wm8994/registers.h | 115 ++++++++++++++++++++ sound/soc/codecs/wm8994.c | 198 +++++++++++++++++++++++++++++++-- 2 files changed, 301 insertions(+), 12 deletions(-)
diff --git a/include/linux/mfd/wm8994/registers.h b/include/linux/mfd/wm8994/registers.h index 3eb70a4..423b2b5 100644 --- a/include/linux/mfd/wm8994/registers.h +++ b/include/linux/mfd/wm8994/registers.h @@ -246,6 +246,15 @@ #define WM8994_INTERRUPT_STATUS_2_MASK 0x739 #define WM8994_INTERRUPT_CONTROL 0x740 #define WM8994_IRQ_DEBOUNCE 0x748 +#define WM8958_DSP2_PROGRAM 0x900 +#define WM8958_DSP2_CONFIG 0x901 +#define WM8958_DSP2_MAGICNUM 0xA00 +#define WM8958_DSP2_RELEASEYEAR 0xA01 +#define WM8958_DSP2_RELEASEMONTHDAY 0xA02 +#define WM8958_DSP2_RELEASETIME 0xA03 +#define WM8958_DSP2_VERMAJMIN 0xA04 +#define WM8958_DSP2_VERBUILD 0xA05 +#define WM8958_DSP2_EXECCONTROL 0xA0D #define WM8994_WRITE_SEQUENCER_0 0x3000 #define WM8994_WRITE_SEQUENCER_1 0x3001 #define WM8994_WRITE_SEQUENCER_2 0x3002 @@ -2079,6 +2088,14 @@ /* * R520 (0x208) - Clocking (1) */ +#define WM8958_DSP2CLK_ENA 0x4000 /* DSP2CLK_ENA */ +#define WM8958_DSP2CLK_ENA_MASK 0x4000 /* DSP2CLK_ENA */ +#define WM8958_DSP2CLK_ENA_SHIFT 14 /* DSP2CLK_ENA */ +#define WM8958_DSP2CLK_ENA_WIDTH 1 /* DSP2CLK_ENA */ +#define WM8958_DSP2CLK_SRC 0x1000 /* DSP2CLK_SRC */ +#define WM8958_DSP2CLK_SRC_MASK 0x1000 /* DSP2CLK_SRC */ +#define WM8958_DSP2CLK_SRC_SHIFT 12 /* DSP2CLK_SRC */ +#define WM8958_DSP2CLK_SRC_WIDTH 1 /* DSP2CLK_SRC */ #define WM8994_TOCLK_ENA 0x0010 /* TOCLK_ENA */ #define WM8994_TOCLK_ENA_MASK 0x0010 /* TOCLK_ENA */ #define WM8994_TOCLK_ENA_SHIFT 4 /* TOCLK_ENA */ @@ -4356,4 +4373,102 @@ #define WM8994_TEMP_SHUT_DB_SHIFT 0 /* TEMP_SHUT_DB */ #define WM8994_TEMP_SHUT_DB_WIDTH 1 /* TEMP_SHUT_DB */
+/* + * R2304 (0x900) - DSP2_Program + */ +#define WM8958_DSP2_ENA 0x0001 /* DSP2_ENA */ +#define WM8958_DSP2_ENA_MASK 0x0001 /* DSP2_ENA */ +#define WM8958_DSP2_ENA_SHIFT 0 /* DSP2_ENA */ +#define WM8958_DSP2_ENA_WIDTH 1 /* DSP2_ENA */ + +/* + * R2305 (0x901) - DSP2_Config + */ +#define WM8958_MBC_SEL_MASK 0x0030 /* MBC_SEL - [5:4] */ +#define WM8958_MBC_SEL_SHIFT 4 /* MBC_SEL - [5:4] */ +#define WM8958_MBC_SEL_WIDTH 2 /* MBC_SEL - [5:4] */ +#define WM8958_MBC_ENA 0x0001 /* MBC_ENA */ +#define WM8958_MBC_ENA_MASK 0x0001 /* MBC_ENA */ +#define WM8958_MBC_ENA_SHIFT 0 /* MBC_ENA */ +#define WM8958_MBC_ENA_WIDTH 1 /* MBC_ENA */ + +/* + * R2560 (0xA00) - DSP2_MagicNum + */ +#define WM8958_DSP2_MAGIC_NUM_MASK 0xFFFF /* DSP2_MAGIC_NUM - [15:0] */ +#define WM8958_DSP2_MAGIC_NUM_SHIFT 0 /* DSP2_MAGIC_NUM - [15:0] */ +#define WM8958_DSP2_MAGIC_NUM_WIDTH 16 /* DSP2_MAGIC_NUM - [15:0] */ + +/* + * R2561 (0xA01) - DSP2_ReleaseYear + */ +#define WM8958_DSP2_RELEASE_YEAR_MASK 0xFFFF /* DSP2_RELEASE_YEAR - [15:0] */ +#define WM8958_DSP2_RELEASE_YEAR_SHIFT 0 /* DSP2_RELEASE_YEAR - [15:0] */ +#define WM8958_DSP2_RELEASE_YEAR_WIDTH 16 /* DSP2_RELEASE_YEAR - [15:0] */ + +/* + * R2562 (0xA02) - DSP2_ReleaseMonthDay + */ +#define WM8958_DSP2_RELEASE_MONTH_MASK 0xFF00 /* DSP2_RELEASE_MONTH - [15:8] */ +#define WM8958_DSP2_RELEASE_MONTH_SHIFT 8 /* DSP2_RELEASE_MONTH - [15:8] */ +#define WM8958_DSP2_RELEASE_MONTH_WIDTH 8 /* DSP2_RELEASE_MONTH - [15:8] */ +#define WM8958_DSP2_RELEASE_DAY_MASK 0x00FF /* DSP2_RELEASE_DAY - [7:0] */ +#define WM8958_DSP2_RELEASE_DAY_SHIFT 0 /* DSP2_RELEASE_DAY - [7:0] */ +#define WM8958_DSP2_RELEASE_DAY_WIDTH 8 /* DSP2_RELEASE_DAY - [7:0] */ + +/* + * R2563 (0xA03) - DSP2_ReleaseTime + */ +#define WM8958_DSP2_RELEASE_HOURS_MASK 0xFF00 /* DSP2_RELEASE_HOURS - [15:8] */ +#define WM8958_DSP2_RELEASE_HOURS_SHIFT 8 /* DSP2_RELEASE_HOURS - [15:8] */ +#define WM8958_DSP2_RELEASE_HOURS_WIDTH 8 /* DSP2_RELEASE_HOURS - [15:8] */ +#define WM8958_DSP2_RELEASE_MINS_MASK 0x00FF /* DSP2_RELEASE_MINS - [7:0] */ +#define WM8958_DSP2_RELEASE_MINS_SHIFT 0 /* DSP2_RELEASE_MINS - [7:0] */ +#define WM8958_DSP2_RELEASE_MINS_WIDTH 8 /* DSP2_RELEASE_MINS - [7:0] */ + +/* + * R2564 (0xA04) - DSP2_VerMajMin + */ +#define WM8958_DSP2_MAJOR_VER_MASK 0xFF00 /* DSP2_MAJOR_VER - [15:8] */ +#define WM8958_DSP2_MAJOR_VER_SHIFT 8 /* DSP2_MAJOR_VER - [15:8] */ +#define WM8958_DSP2_MAJOR_VER_WIDTH 8 /* DSP2_MAJOR_VER - [15:8] */ +#define WM8958_DSP2_MINOR_VER_MASK 0x00FF /* DSP2_MINOR_VER - [7:0] */ +#define WM8958_DSP2_MINOR_VER_SHIFT 0 /* DSP2_MINOR_VER - [7:0] */ +#define WM8958_DSP2_MINOR_VER_WIDTH 8 /* DSP2_MINOR_VER - [7:0] */ + +/* + * R2565 (0xA05) - DSP2_VerBuild + */ +#define WM8958_DSP2_BUILD_VER_MASK 0xFFFF /* DSP2_BUILD_VER - [15:0] */ +#define WM8958_DSP2_BUILD_VER_SHIFT 0 /* DSP2_BUILD_VER - [15:0] */ +#define WM8958_DSP2_BUILD_VER_WIDTH 16 /* DSP2_BUILD_VER - [15:0] */ + +/* + * R2573 (0xA0D) - DSP2_ExecControl + */ +#define WM8958_DSP2_STOPC 0x0020 /* DSP2_STOPC */ +#define WM8958_DSP2_STOPC_MASK 0x0020 /* DSP2_STOPC */ +#define WM8958_DSP2_STOPC_SHIFT 5 /* DSP2_STOPC */ +#define WM8958_DSP2_STOPC_WIDTH 1 /* DSP2_STOPC */ +#define WM8958_DSP2_STOPS 0x0010 /* DSP2_STOPS */ +#define WM8958_DSP2_STOPS_MASK 0x0010 /* DSP2_STOPS */ +#define WM8958_DSP2_STOPS_SHIFT 4 /* DSP2_STOPS */ +#define WM8958_DSP2_STOPS_WIDTH 1 /* DSP2_STOPS */ +#define WM8958_DSP2_STOPI 0x0008 /* DSP2_STOPI */ +#define WM8958_DSP2_STOPI_MASK 0x0008 /* DSP2_STOPI */ +#define WM8958_DSP2_STOPI_SHIFT 3 /* DSP2_STOPI */ +#define WM8958_DSP2_STOPI_WIDTH 1 /* DSP2_STOPI */ +#define WM8958_DSP2_STOP 0x0004 /* DSP2_STOP */ +#define WM8958_DSP2_STOP_MASK 0x0004 /* DSP2_STOP */ +#define WM8958_DSP2_STOP_SHIFT 2 /* DSP2_STOP */ +#define WM8958_DSP2_STOP_WIDTH 1 /* DSP2_STOP */ +#define WM8958_DSP2_RUNR 0x0002 /* DSP2_RUNR */ +#define WM8958_DSP2_RUNR_MASK 0x0002 /* DSP2_RUNR */ +#define WM8958_DSP2_RUNR_SHIFT 1 /* DSP2_RUNR */ +#define WM8958_DSP2_RUNR_WIDTH 1 /* DSP2_RUNR */ +#define WM8958_DSP2_RUN 0x0001 /* DSP2_RUN */ +#define WM8958_DSP2_RUN_MASK 0x0001 /* DSP2_RUN */ +#define WM8958_DSP2_RUN_SHIFT 0 /* DSP2_RUN */ +#define WM8958_DSP2_RUN_WIDTH 1 /* DSP2_RUN */ + #endif diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index fb06093..b30b2dd 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -80,6 +80,8 @@ struct wm8994_priv { int dac_rates[2]; int lrclk_shared[2];
+ int mbc_ena[3]; + /* Platform dependant DRC configuration */ const char **drc_texts; int drc_cfg[WM8994_NUM_DRC]; @@ -137,6 +139,7 @@ static int wm8994_volatile(unsigned int reg) case WM8994_RATE_STATUS: case WM8994_LDO_1: case WM8994_LDO_2: + case WM8958_DSP2_EXECCONTROL: return 1; default: return 0; @@ -520,6 +523,168 @@ static const struct soc_enum aif2dacl_src = static const struct soc_enum aif2dacr_src = SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 14, 2, aif_chan_src_text);
+static void wm8958_mbc_apply(struct snd_soc_codec *codec, int mbc, 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; + + switch (mbc) { + case 0: + pwr_reg &= (WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA); + aif = 0; + break; + case 1: + pwr_reg &= (WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA); + aif = 0; + break; + case 2: + pwr_reg &= (WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA); + aif = 1; + break; + default: + BUG(); + return; + } + + /* We can only enable the MBC if the AIF is enabled and we + * want it to be enabled. */ + ena = pwr_reg && wm8994->mbc_ena[mbc]; + + reg = snd_soc_read(codec, WM8958_DSP2_PROGRAM); + + dev_dbg(codec->dev, "MBC %d startup: %d, power: %x, DSP: %x\n", + mbc, start, pwr_reg, reg); + + if (start && ena) { + /* If the DSP is already running then noop */ + if (reg & WM8958_DSP2_ENA) + return; + + /* Switch the clock over to the appropriate AIF */ + snd_soc_update_bits(codec, WM8994_CLOCKING_1, + WM8958_DSP2CLK_SRC | WM8958_DSP2CLK_ENA, + aif << WM8958_DSP2CLK_SRC_SHIFT | + WM8958_DSP2CLK_ENA); + + snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM, + WM8958_DSP2_ENA, WM8958_DSP2_ENA); + + /* TODO: Apply any user specified MBC settings */ + + /* 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, + mbc << WM8958_MBC_SEL_SHIFT | + WM8958_MBC_ENA); + } else { + /* If the DSP is already stopped then noop */ + if (!(reg & WM8958_DSP2_ENA)) + return; + + snd_soc_update_bits(codec, WM8958_DSP2_CONFIG, + WM8958_MBC_ENA, 0); + snd_soc_update_bits(codec, WM8958_DSP2_PROGRAM, + WM8958_DSP2_ENA, 0); + snd_soc_update_bits(codec, WM8994_CLOCKING_1, + WM8958_DSP2CLK_ENA, 0); + } +} + +static int wm8958_aif_ev(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + int mbc; + + switch (w->shift) { + case 13: + case 12: + mbc = 2; + break; + case 11: + case 10: + mbc = 1; + break; + case 9: + case 8: + mbc = 0; + break; + default: + BUG(); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wm8958_mbc_apply(codec, mbc, 1); + break; + case SND_SOC_DAPM_POST_PMD: + wm8958_mbc_apply(codec, mbc, 0); + break; + } + + return 0; +} + +static int wm8958_mbc_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int wm8958_mbc_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mbc = kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = wm8994->mbc_ena[mbc]; + + return 0; +} + +static int wm8958_mbc_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int mbc = kcontrol->private_value; + int i; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + if (ucontrol->value.integer.value[0] > 1) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(wm8994->mbc_ena); i++) { + if (mbc != i && wm8994->mbc_ena[i]) { + dev_dbg(codec->dev, "MBC %d active already\n", mbc); + return -EBUSY; + } + } + + wm8994->mbc_ena[mbc] = ucontrol->value.integer.value[0]; + + wm8958_mbc_apply(codec, mbc, wm8994->mbc_ena[mbc]); + + return 0; +} + +#define WM8958_MBC_SWITCH(xname, xval) {\ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .info = wm8958_mbc_info, \ + .get = wm8958_mbc_get, .put = wm8958_mbc_put, \ + .private_value = xval } + static const struct snd_kcontrol_new wm8994_snd_controls[] = { SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME, WM8994_AIF1_ADC1_RIGHT_VOLUME, @@ -649,6 +814,9 @@ SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0,
static const struct snd_kcontrol_new wm8958_snd_controls[] = { SOC_SINGLE_TLV("AIF3 Boost Volume", WM8958_AIF3_CONTROL_2, 10, 3, 0, aif_tlv), +WM8958_MBC_SWITCH("AIF1DAC1 MBC Switch", 0), +WM8958_MBC_SWITCH("AIF1DAC2 MBC Switch", 1), +WM8958_MBC_SWITCH("AIF2DAC MBC Switch", 2), };
static int clk_sys_event(struct snd_soc_dapm_widget *w, @@ -1018,19 +1186,23 @@ SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", "AIF1 Capture", 0, WM8994_POWER_MANAGEMENT_4, 9, 0), SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", "AIF1 Capture", 0, WM8994_POWER_MANAGEMENT_4, 8, 0), -SND_SOC_DAPM_AIF_IN("AIF1DAC1L", NULL, 0, - WM8994_POWER_MANAGEMENT_5, 9, 0), -SND_SOC_DAPM_AIF_IN("AIF1DAC1R", NULL, 0, - WM8994_POWER_MANAGEMENT_5, 8, 0), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC1L", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 9, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC1R", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 8, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", "AIF1 Capture", 0, WM8994_POWER_MANAGEMENT_4, 11, 0), SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", "AIF1 Capture", 0, WM8994_POWER_MANAGEMENT_4, 10, 0), -SND_SOC_DAPM_AIF_IN("AIF1DAC2L", NULL, 0, - WM8994_POWER_MANAGEMENT_5, 11, 0), -SND_SOC_DAPM_AIF_IN("AIF1DAC2R", NULL, 0, - WM8994_POWER_MANAGEMENT_5, 10, 0), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC2L", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 11, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_AIF_IN_E("AIF1DAC2R", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 10, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0, aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)), @@ -1059,10 +1231,12 @@ SND_SOC_DAPM_AIF_OUT("AIF2ADCL", NULL, 0, WM8994_POWER_MANAGEMENT_4, 13, 0), SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0, WM8994_POWER_MANAGEMENT_4, 12, 0), -SND_SOC_DAPM_AIF_IN("AIF2DACL", NULL, 0, - WM8994_POWER_MANAGEMENT_5, 13, 0), -SND_SOC_DAPM_AIF_IN("AIF2DACR", NULL, 0, - WM8994_POWER_MANAGEMENT_5, 12, 0), +SND_SOC_DAPM_AIF_IN_E("AIF2DACL", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 13, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_AIF_IN_E("AIF2DACR", NULL, 0, + WM8994_POWER_MANAGEMENT_5, 12, 0, wm8958_aif_ev, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
The WM8958 contains an advanced accessory detection feature which allows detection of up to seven different impedence levels on the microphone bias output, including detection of video outputs. Since some of the more involved accessory interfaces may involve noticable interactions with external components a simple detection scheme is provided by default with the option to provide custom handling of accessory detect.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com --- include/linux/mfd/wm8994/registers.h | 43 ++++++++++ sound/soc/codecs/wm8994.c | 150 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8994.h | 4 + 3 files changed, 197 insertions(+), 0 deletions(-)
diff --git a/include/linux/mfd/wm8994/registers.h b/include/linux/mfd/wm8994/registers.h index 423b2b5..a610c87 100644 --- a/include/linux/mfd/wm8994/registers.h +++ b/include/linux/mfd/wm8994/registers.h @@ -70,6 +70,9 @@ #define WM8994_DC_SERVO_4 0x57 #define WM8994_DC_SERVO_READBACK 0x58 #define WM8994_ANALOGUE_HP_1 0x60 +#define WM8958_MIC_DETECT_1 0xD0 +#define WM8958_MIC_DETECT_2 0xD1 +#define WM8958_MIC_DETECT_3 0xD2 #define WM8994_CHIP_REVISION 0x100 #define WM8994_CONTROL_INTERFACE 0x101 #define WM8994_WRITE_SEQUENCER_CTRL_1 0x110 @@ -1971,6 +1974,46 @@ #define WM8994_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */
/* + * R208 (0xD0) - Mic Detect 1 + */ +#define WM8958_MICD_BIAS_STARTTIME_MASK 0xF000 /* MICD_BIAS_STARTTIME - [15:12] */ +#define WM8958_MICD_BIAS_STARTTIME_SHIFT 12 /* MICD_BIAS_STARTTIME - [15:12] */ +#define WM8958_MICD_BIAS_STARTTIME_WIDTH 4 /* MICD_BIAS_STARTTIME - [15:12] */ +#define WM8958_MICD_RATE_MASK 0x0F00 /* MICD_RATE - [11:8] */ +#define WM8958_MICD_RATE_SHIFT 8 /* MICD_RATE - [11:8] */ +#define WM8958_MICD_RATE_WIDTH 4 /* MICD_RATE - [11:8] */ +#define WM8958_MICD_DBTIME 0x0002 /* MICD_DBTIME */ +#define WM8958_MICD_DBTIME_MASK 0x0002 /* MICD_DBTIME */ +#define WM8958_MICD_DBTIME_SHIFT 1 /* MICD_DBTIME */ +#define WM8958_MICD_DBTIME_WIDTH 1 /* MICD_DBTIME */ +#define WM8958_MICD_ENA 0x0001 /* MICD_ENA */ +#define WM8958_MICD_ENA_MASK 0x0001 /* MICD_ENA */ +#define WM8958_MICD_ENA_SHIFT 0 /* MICD_ENA */ +#define WM8958_MICD_ENA_WIDTH 1 /* MICD_ENA */ + +/* + * R209 (0xD1) - Mic Detect 2 + */ +#define WM8958_MICD_LVL_SEL_MASK 0x00FF /* MICD_LVL_SEL - [7:0] */ +#define WM8958_MICD_LVL_SEL_SHIFT 0 /* MICD_LVL_SEL - [7:0] */ +#define WM8958_MICD_LVL_SEL_WIDTH 8 /* MICD_LVL_SEL - [7:0] */ + +/* + * R210 (0xD2) - Mic Detect 3 + */ +#define WM8958_MICD_LVL_MASK 0x07FC /* MICD_LVL - [10:2] */ +#define WM8958_MICD_LVL_SHIFT 2 /* MICD_LVL - [10:2] */ +#define WM8958_MICD_LVL_WIDTH 9 /* MICD_LVL - [10:2] */ +#define WM8958_MICD_VALID 0x0002 /* MICD_VALID */ +#define WM8958_MICD_VALID_MASK 0x0002 /* MICD_VALID */ +#define WM8958_MICD_VALID_SHIFT 1 /* MICD_VALID */ +#define WM8958_MICD_VALID_WIDTH 1 /* MICD_VALID */ +#define WM8958_MICD_STS 0x0001 /* MICD_STS */ +#define WM8958_MICD_STS_MASK 0x0001 /* MICD_STS */ +#define WM8958_MICD_STS_SHIFT 0 /* MICD_STS */ +#define WM8958_MICD_STS_WIDTH 1 /* MICD_STS */ + +/* * R256 (0x100) - Chip Revision */ #define WM8994_CHIP_REV_MASK 0x000F /* CHIP_REV - [3:0] */ diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index b30b2dd..948677b 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -21,6 +21,7 @@ #include <linux/regulator/consumer.h> #include <linux/slab.h> #include <sound/core.h> +#include <sound/jack.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -95,6 +96,11 @@ struct wm8994_priv {
struct wm8994_micdet micdet[2];
+ wm8958_micdet_cb jack_cb; + void *jack_cb_data; + bool jack_is_mic; + bool jack_is_video; + int revision; struct wm8994_pdata *pdata; }; @@ -140,6 +146,7 @@ static int wm8994_volatile(unsigned int reg) case WM8994_LDO_1: case WM8994_LDO_2: case WM8958_DSP2_EXECCONTROL: + case WM8958_MIC_DETECT_3: return 1; default: return 0; @@ -2633,6 +2640,133 @@ static irqreturn_t wm8994_mic_irq(int irq, void *data) return IRQ_HANDLED; }
+/* Default microphone detection handler for WM8958 - the user can + * override this if they wish. + */ +static void wm8958_default_micdet(u16 status, void *data) +{ + struct snd_soc_codec *codec = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int report = 0; + + /* If nothing present then clear our statuses */ + if (!(status & WM8958_MICD_STS)) { + wm8994->jack_is_video = false; + wm8994->jack_is_mic = false; + goto done; + } + + /* Assume anything over 475 ohms is a microphone and remember + * that we've seen one (since buttons override it) */ + if (status & 0x600) + wm8994->jack_is_mic = true; + if (wm8994->jack_is_mic) + report |= SND_JACK_MICROPHONE; + + /* Video has an impedence of approximately 75 ohms; assume + * this isn't used as a button and remember it since buttons + * override it. */ + if (status & 0x40) + wm8994->jack_is_video = true; + if (wm8994->jack_is_video) + report |= SND_JACK_VIDEOOUT; + + /* Everything else is buttons; just assign slots */ + if (status & 0x4) + report |= SND_JACK_BTN_0; + if (status & 0x8) + report |= SND_JACK_BTN_1; + if (status & 0x10) + report |= SND_JACK_BTN_2; + if (status & 0x20) + report |= SND_JACK_BTN_3; + if (status & 0x80) + report |= SND_JACK_BTN_4; + if (status & 0x100) + report |= SND_JACK_BTN_5; + +done: + snd_soc_jack_report(wm8994->micdet[0].jack, + SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5 | + SND_JACK_MICROPHONE | SND_JACK_VIDEOOUT, + report); +} + +/** + * wm8958_mic_detect - Enable microphone detection via the WM8958 IRQ + * + * @codec: WM8958 codec + * @jack: jack to report detection events on + * + * Enable microphone detection functionality for the WM8958. By + * default simple detection which supports the detection of up to 6 + * buttons plus video and microphone functionality is supported. + * + * The WM8958 has an advanced jack detection facility which is able to + * support complex accessory detection, especially when used in + * conjunction with external circuitry. In order to provide maximum + * flexiblity a callback is provided which allows a completely custom + * detection algorithm. + */ +int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + wm8958_micdet_cb cb, void *cb_data) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994 *control = codec->control_data; + + if (control->type != WM8958) + return -EINVAL; + + if (jack) { + if (!cb) { + dev_dbg(codec->dev, "Using default micdet callback\n"); + cb = wm8958_default_micdet; + cb_data = codec; + } + + wm8994->micdet[0].jack = jack; + wm8994->jack_cb = cb; + wm8994->jack_cb_data = cb_data; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, WM8958_MICD_ENA); + } else { + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm8958_mic_detect); + +static irqreturn_t wm8958_mic_irq(int irq, void *data) +{ + struct wm8994_priv *wm8994 = data; + struct snd_soc_codec *codec = wm8994->codec; + int reg; + + reg = snd_soc_read(codec, WM8958_MIC_DETECT_3); + if (reg < 0) { + dev_err(codec->dev, "Failed to read mic detect status: %d\n", + reg); + return IRQ_NONE; + } + + if (!(reg & WM8958_MICD_VALID)) { + dev_dbg(codec->dev, "Mic detect data not valid\n"); + goto out; + } + + if (wm8994->jack_cb) + wm8994->jack_cb(reg, wm8994->jack_cb_data); + else + dev_warn(codec->dev, "Accessory detection with no callback\n"); + +out: + return IRQ_HANDLED; +} + static int wm8994_codec_probe(struct snd_soc_codec *codec) { struct wm8994 *control; @@ -2732,6 +2866,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) "Failed to request Mic2 short IRQ: %d\n", ret); break; + + case WM8958: + ret = wm8994_request_irq(codec->control_data, + WM8994_IRQ_MIC1_DET, + wm8958_mic_irq, "Mic detect", + wm8994); + if (ret != 0) + dev_warn(codec->dev, + "Failed to request Mic detect IRQ: %d\n", + ret); + break; }
/* Remember if AIFnLRCLK is configured as a GPIO. This should be @@ -2866,6 +3011,11 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994); break; + + case WM8958: + wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, + wm8994); + break; } kfree(wm8994->retune_mobile_texts); kfree(wm8994->drc_texts); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index b8b3166..455cae6 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -28,8 +28,12 @@ #define WM8994_FLL_SRC_LRCLK 3 #define WM8994_FLL_SRC_BCLK 4
+typedef void (*wm8958_micdet_cb)(u16 status, void *data); + int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, int micbias, int det, int shrt); +int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, + wm8958_micdet_cb cb, void *cb_data);
#define WM8994_CACHE_SIZE 1570
On Fri, 2010-11-26 at 15:21 +0000, Mark Brown wrote:
The WM8958 is a WM8994 derivative. This patch merely ensures that some revision specific configuration for WM8994 is not enabled on WM8994, additional patches will add support for the new features introduced on the WM8958.
Signed-off-by: Mark Brown broonie@opensource.wolfsonmicro.com
All
Acked-by: Liam Girdwood lrg@slimlogic.co.uk
participants (2)
-
Liam Girdwood
-
Mark Brown