diff options
Diffstat (limited to 'sound/soc/codecs/wm8903.c')
-rw-r--r-- | sound/soc/codecs/wm8903.c | 641 |
1 files changed, 459 insertions, 182 deletions
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 017d99ceb42..ae1cadfae84 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -2,6 +2,7 @@ * wm8903.c -- WM8903 ALSA SoC Audio driver * * Copyright 2008 Wolfson Microelectronics + * Copyright 2011 NVIDIA, Inc. * * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> * @@ -19,6 +20,7 @@ #include <linux/init.h> #include <linux/completion.h> #include <linux/delay.h> +#include <linux/gpio.h> #include <linux/pm.h> #include <linux/i2c.h> #include <linux/platform_device.h> @@ -213,6 +215,7 @@ static u16 wm8903_reg_defaults[] = { }; struct wm8903_priv { + struct snd_soc_codec *codec; int sysclk; int irq; @@ -220,25 +223,36 @@ struct wm8903_priv { int fs; int deemph; + int dcs_pending; + int dcs_cache[4]; + /* Reference count */ int class_w_users; - struct completion wseq; - struct snd_soc_jack *mic_jack; int mic_det; int mic_short; int mic_last_report; int mic_delay; + +#ifdef CONFIG_GPIOLIB + struct gpio_chip gpio_chip; +#endif }; -static int wm8903_volatile_register(unsigned int reg) +static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8903_SW_RESET_AND_ID: case WM8903_REVISION_NUMBER: case WM8903_INTERRUPT_STATUS_1: case WM8903_WRITE_SEQUENCER_4: + case WM8903_POWER_MANAGEMENT_3: + case WM8903_POWER_MANAGEMENT_2: + case WM8903_DC_SERVO_READBACK_1: + case WM8903_DC_SERVO_READBACK_2: + case WM8903_DC_SERVO_READBACK_3: + case WM8903_DC_SERVO_READBACK_4: return 1; default: @@ -246,50 +260,6 @@ static int wm8903_volatile_register(unsigned int reg) } } -static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) -{ - u16 reg[5]; - struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - - BUG_ON(start > 48); - - /* Enable the sequencer if it's not already on */ - reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, - reg[0] | WM8903_WSEQ_ENA); - - dev_dbg(codec->dev, "Starting sequence at %d\n", start); - - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3, - start | WM8903_WSEQ_START); - - /* Wait for it to complete. If we have the interrupt wired up then - * that will break us out of the poll early. - */ - do { - wait_for_completion_timeout(&wm8903->wseq, - msecs_to_jiffies(10)); - - reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4); - } while (reg[4] & WM8903_WSEQ_BUSY); - - dev_dbg(codec->dev, "Sequence complete\n"); - - /* Disable the sequencer again if we enabled it */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); - - return 0; -} - -static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) -{ - int i; - - /* There really ought to be something better we can do here :/ */ - for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) - cache[i] = codec->hw_read(codec, i); -} - static void wm8903_reset(struct snd_soc_codec *codec) { snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0); @@ -297,11 +267,6 @@ static void wm8903_reset(struct snd_soc_codec *codec) sizeof(wm8903_reg_defaults)); } -#define WM8903_OUTPUT_SHORT 0x8 -#define WM8903_OUTPUT_OUT 0x4 -#define WM8903_OUTPUT_INT 0x2 -#define WM8903_OUTPUT_IN 0x1 - static int wm8903_cp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -311,97 +276,101 @@ static int wm8903_cp_event(struct snd_soc_dapm_widget *w, return 0; } -/* - * Event for headphone and line out amplifier power changes. Special - * power up/down sequences are required in order to maximise pop/click - * performance. - */ -static int wm8903_output_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static int wm8903_dcs_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 val; - u16 reg; - u16 dcs_reg; - u16 dcs_bit; - int shift; + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); - switch (w->reg) { - case WM8903_POWER_MANAGEMENT_2: - reg = WM8903_ANALOGUE_HP_0; - dcs_bit = 0 + w->shift; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wm8903->dcs_pending |= 1 << w->shift; break; - case WM8903_POWER_MANAGEMENT_3: - reg = WM8903_ANALOGUE_LINEOUT_0; - dcs_bit = 2 + w->shift; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, WM8903_DC_SERVO_0, + 1 << w->shift, 0); break; - default: - BUG(); - return -EINVAL; /* Spurious warning from some compilers */ } - switch (w->shift) { - case 0: - shift = 0; - break; - case 1: - shift = 4; - break; - default: - BUG(); - return -EINVAL; /* Spurious warning from some compilers */ - } + return 0; +} - if (event & SND_SOC_DAPM_PRE_PMU) { - val = snd_soc_read(codec, reg); +#define WM8903_DCS_MODE_WRITE_STOP 0 +#define WM8903_DCS_MODE_START_STOP 2 - /* Short the output */ - val &= ~(WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); - } +static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm, + enum snd_soc_dapm_type event, int subseq) +{ + struct snd_soc_codec *codec = container_of(dapm, + struct snd_soc_codec, dapm); + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + int dcs_mode = WM8903_DCS_MODE_WRITE_STOP; + int i, val; - if (event & SND_SOC_DAPM_POST_PMU) { - val = snd_soc_read(codec, reg); + /* Complete any pending DC servo starts */ + if (wm8903->dcs_pending) { + dev_dbg(codec->dev, "Starting DC servo for %x\n", + wm8903->dcs_pending); - val |= (WM8903_OUTPUT_IN << shift); - snd_soc_write(codec, reg, val); + /* If we've no cached values then we need to do startup */ + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; - val |= (WM8903_OUTPUT_INT << shift); - snd_soc_write(codec, reg, val); + if (wm8903->dcs_cache[i]) { + dev_dbg(codec->dev, + "Restore DC servo %d value %x\n", + 3 - i, wm8903->dcs_cache[i]); + + snd_soc_write(codec, WM8903_DC_SERVO_4 + i, + wm8903->dcs_cache[i] & 0xff); + } else { + dev_dbg(codec->dev, + "Calibrate DC servo %d\n", 3 - i); + dcs_mode = WM8903_DCS_MODE_START_STOP; + } + } - /* Turn on the output ENA_OUTP */ - val |= (WM8903_OUTPUT_OUT << shift); - snd_soc_write(codec, reg, val); + /* Don't trust the cache for analogue */ + if (wm8903->class_w_users) + dcs_mode = WM8903_DCS_MODE_START_STOP; - /* Enable the DC servo */ - dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); - dcs_reg |= dcs_bit; - snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); + snd_soc_update_bits(codec, WM8903_DC_SERVO_2, + WM8903_DCS_MODE_MASK, dcs_mode); - /* Remove the short */ - val |= (WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); - } + snd_soc_update_bits(codec, WM8903_DC_SERVO_0, + WM8903_DCS_ENA_MASK, wm8903->dcs_pending); - if (event & SND_SOC_DAPM_PRE_PMD) { - val = snd_soc_read(codec, reg); + switch (dcs_mode) { + case WM8903_DCS_MODE_WRITE_STOP: + break; - /* Short the output */ - val &= ~(WM8903_OUTPUT_SHORT << shift); - snd_soc_write(codec, reg, val); + case WM8903_DCS_MODE_START_STOP: + msleep(270); - /* Disable the DC servo */ - dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); - dcs_reg &= ~dcs_bit; - snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Cache the measured offsets for digital */ + if (wm8903->class_w_users) + break; - /* Then disable the intermediate and output stages */ - val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | - WM8903_OUTPUT_IN) << shift); - snd_soc_write(codec, reg, val); - } + for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) { + if (!(wm8903->dcs_pending & (1 << i))) + continue; - return 0; + val = snd_soc_read(codec, + WM8903_DC_SERVO_READBACK_1 + i); + dev_dbg(codec->dev, "DC servo %d: %x\n", + 3 - i, val); + wm8903->dcs_cache[i] = val; + } + break; + + default: + pr_warn("DCS mode %d delay not set\n", dcs_mode); + break; + } + + wm8903->dcs_pending = 0; + } } /* @@ -667,6 +636,22 @@ static const struct soc_enum lsidetone_enum = static const struct soc_enum rsidetone_enum = SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text); +static const char *aif_text[] = { + "Left", "Right" +}; + +static const struct soc_enum lcapture_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 7, 2, aif_text); + +static const struct soc_enum rcapture_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 6, 2, aif_text); + +static const struct soc_enum lplay_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 5, 2, aif_text); + +static const struct soc_enum rplay_enum = + SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 4, 2, aif_text); + static const struct snd_kcontrol_new wm8903_snd_controls[] = { /* Input PGAs - No TLV since the scale depends on PGA mode */ @@ -784,6 +769,18 @@ static const struct snd_kcontrol_new lsidetone_mux = static const struct snd_kcontrol_new rsidetone_mux = SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum); +static const struct snd_kcontrol_new lcapture_mux = + SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum); + +static const struct snd_kcontrol_new rcapture_mux = + SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum); + +static const struct snd_kcontrol_new lplay_mux = + SOC_DAPM_ENUM("Left Playback Mux", lplay_enum); + +static const struct snd_kcontrol_new rplay_mux = + SOC_DAPM_ENUM("Right Playback Mux", rplay_enum); + static const struct snd_kcontrol_new left_output_mixer[] = { SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), @@ -847,14 +844,26 @@ SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux), SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), -SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), -SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), +SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0), +SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0), + +SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux), +SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux), + +SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux), SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux), -SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), -SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), +SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0), + +SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux), +SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux), + +SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0), +SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0), SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0, left_output_mixer, ARRAY_SIZE(left_output_mixer)), @@ -866,23 +875,45 @@ SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0, SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), -SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, - 1, 0, NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), -SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, - 0, 0, NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), - -SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, - NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), -SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, - NULL, 0, wm8903_output_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0, + 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0, + 0, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 4, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 0, 0, + NULL, 0), + +SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0), + +SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 5, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0, + NULL, 0), +SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 1, 0, + NULL, 0), + +SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, NULL, 0), @@ -892,10 +923,18 @@ SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, wm8903_cp_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0), }; static const struct snd_soc_dapm_route intercon[] = { + { "CLK_DSP", NULL, "CLK_SYS" }, + { "Mic Bias", NULL, "CLK_SYS" }, + { "HPL_DCS", NULL, "CLK_SYS" }, + { "HPR_DCS", NULL, "CLK_SYS" }, + { "LINEOUTL_DCS", NULL, "CLK_SYS" }, + { "LINEOUTR_DCS", NULL, "CLK_SYS" }, + { "Left Input Mux", "IN1L", "IN1L" }, { "Left Input Mux", "IN2L", "IN2L" }, { "Left Input Mux", "IN3L", "IN3L" }, @@ -936,18 +975,36 @@ static const struct snd_soc_dapm_route intercon[] = { { "Left Input PGA", NULL, "Left Input Mode Mux" }, { "Right Input PGA", NULL, "Right Input Mode Mux" }, + { "Left Capture Mux", "Left", "ADCL" }, + { "Left Capture Mux", "Right", "ADCR" }, + + { "Right Capture Mux", "Left", "ADCL" }, + { "Right Capture Mux", "Right", "ADCR" }, + + { "AIFTXL", NULL, "Left Capture Mux" }, + { "AIFTXR", NULL, "Right Capture Mux" }, + { "ADCL", NULL, "Left Input PGA" }, { "ADCL", NULL, "CLK_DSP" }, { "ADCR", NULL, "Right Input PGA" }, { "ADCR", NULL, "CLK_DSP" }, + { "Left Playback Mux", "Left", "AIFRXL" }, + { "Left Playback Mux", "Right", "AIFRXR" }, + + { "Right Playback Mux", "Left", "AIFRXL" }, + { "Right Playback Mux", "Right", "AIFRXR" }, + { "DACL Sidetone", "Left", "ADCL" }, { "DACL Sidetone", "Right", "ADCR" }, { "DACR Sidetone", "Left", "ADCL" }, { "DACR Sidetone", "Right", "ADCR" }, + { "DACL", NULL, "Left Playback Mux" }, { "DACL", NULL, "DACL Sidetone" }, { "DACL", NULL, "CLK_DSP" }, + + { "DACR", NULL, "Right Playback Mux" }, { "DACR", NULL, "DACR Sidetone" }, { "DACR", NULL, "CLK_DSP" }, @@ -980,11 +1037,35 @@ static const struct snd_soc_dapm_route intercon[] = { { "Left Speaker PGA", NULL, "Left Speaker Mixer" }, { "Right Speaker PGA", NULL, "Right Speaker Mixer" }, - { "HPOUTL", NULL, "Left Headphone Output PGA" }, - { "HPOUTR", NULL, "Right Headphone Output PGA" }, + { "HPL_ENA_DLY", NULL, "Left Headphone Output PGA" }, + { "HPR_ENA_DLY", NULL, "Right Headphone Output PGA" }, + { "LINEOUTL_ENA_DLY", NULL, "Left Line Output PGA" }, + { "LINEOUTR_ENA_DLY", NULL, "Right Line Output PGA" }, + + { "HPL_DCS", NULL, "DCS Master" }, + { "HPR_DCS", NULL, "DCS Master" }, + { "LINEOUTL_DCS", NULL, "DCS Master" }, + { "LINEOUTR_DCS", NULL, "DCS Master" }, + + { "HPL_DCS", NULL, "HPL_ENA_DLY" }, + { "HPR_DCS", NULL, "HPR_ENA_DLY" }, + { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" }, + { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" }, - { "LINEOUTL", NULL, "Left Line Output PGA" }, - { "LINEOUTR", NULL, "Right Line Output PGA" }, + { "HPL_ENA_OUTP", NULL, "HPL_DCS" }, + { "HPR_ENA_OUTP", NULL, "HPR_DCS" }, + { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" }, + { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" }, + + { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" }, + { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" }, + { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" }, + { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" }, + + { "HPOUTL", NULL, "HPL_RMV_SHORT" }, + { "HPOUTR", NULL, "HPR_RMV_SHORT" }, + { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" }, + { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" }, { "LOP", NULL, "Left Speaker PGA" }, { "LON", NULL, "Left Speaker PGA" }, @@ -1012,29 +1093,71 @@ static int wm8903_add_widgets(struct snd_soc_codec *codec) static int wm8903_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 reg; - switch (level) { case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: - reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); - reg &= ~(WM8903_VMID_RES_MASK); - reg |= WM8903_VMID_RES_50K; - snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); break; case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { - snd_soc_write(codec, WM8903_CLOCK_RATES_2, - WM8903_CLK_SYS_ENA); - - /* Change DC servo dither level in startup sequence */ - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); - snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); - - wm8903_run_sequence(codec, 0); - wm8903_sync_reg_cache(codec, codec->reg_cache); + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_POBCTRL | WM8903_ISEL_MASK | + WM8903_STARTUP_BIAS_ENA | + WM8903_BIAS_ENA, + WM8903_POBCTRL | + (2 << WM8903_ISEL_SHIFT) | + WM8903_STARTUP_BIAS_ENA); + + snd_soc_update_bits(codec, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, + WM8903_SPK_DISCHARGE); + + msleep(33); + + snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + WM8903_SPKL_ENA | WM8903_SPKR_ENA); + + snd_soc_update_bits(codec, + WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0, + WM8903_SPK_DISCHARGE, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_RES_MASK | + WM8903_VMID_BUF_ENA, + WM8903_VMID_TIE_ENA | + WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | + (2 << WM8903_VMID_SOFT_SHIFT) | + WM8903_VMID_RES_250K | + WM8903_VMID_BUF_ENA); + + msleep(129); + + snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5, + WM8903_SPKL_ENA | WM8903_SPKR_ENA, + 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_50K); + + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA | WM8903_POBCTRL, + WM8903_BIAS_ENA); /* By default no bypass paths are enabled so * enable Class W support. @@ -1047,17 +1170,32 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, WM8903_CP_DYN_V); } - reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); - reg &= ~(WM8903_VMID_RES_MASK); - reg |= WM8903_VMID_RES_250K; - snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_RES_MASK, + WM8903_VMID_RES_250K); break; case SND_SOC_BIAS_OFF: - wm8903_run_sequence(codec, 32); - reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2); - reg &= ~WM8903_CLK_SYS_ENA; - snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg); + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_BIAS_ENA, 0); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_SOFT_MASK, + 2 << WM8903_VMID_SOFT_SHIFT); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_BUF_ENA, 0); + + msleep(290); + + snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0, + WM8903_VMID_TIE_ENA | WM8903_BUFIO_ENA | + WM8903_VMID_IO_ENA | WM8903_VMID_RES_MASK | + WM8903_VMID_SOFT_MASK | + WM8903_VMID_BUF_ENA, 0); + + snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0, + WM8903_STARTUP_BIAS_ENA, 0); break; } @@ -1510,8 +1648,7 @@ static irqreturn_t wm8903_irq(int irq, void *data) int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask; if (int_val & WM8903_WSEQ_BUSY_EINT) { - dev_dbg(codec->dev, "Write sequencer done\n"); - complete(&wm8903->wseq); + dev_warn(codec->dev, "Write sequencer done\n"); } /* @@ -1635,6 +1772,120 @@ static int wm8903_resume(struct snd_soc_codec *codec) return 0; } +#ifdef CONFIG_GPIOLIB +static inline struct wm8903_priv *gpio_to_wm8903(struct gpio_chip *chip) +{ + return container_of(chip, struct wm8903_priv, gpio_chip); +} + +static int wm8903_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + if (offset >= WM8903_NUM_GPIO) + return -EINVAL; + + return 0; +} + +static int wm8903_gpio_direction_in(struct gpio_chip *chip, unsigned offset) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + unsigned int mask, val; + + mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK; + val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) | + WM8903_GP1_DIR; + + return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset, + mask, val); +} + +static int wm8903_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + int reg; + + reg = snd_soc_read(codec, WM8903_GPIO_CONTROL_1 + offset); + + return (reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT; +} + +static int wm8903_gpio_direction_out(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + unsigned int mask, val; + + mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK; + val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) | + (value << WM8903_GP2_LVL_SHIFT); + + return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset, + mask, val); +} + +static void wm8903_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct wm8903_priv *wm8903 = gpio_to_wm8903(chip); + struct snd_soc_codec *codec = wm8903->codec; + + snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset, + WM8903_GP1_LVL_MASK, + !!value << WM8903_GP1_LVL_SHIFT); +} + +static struct gpio_chip wm8903_template_chip = { + .label = "wm8903", + .owner = THIS_MODULE, + .request = wm8903_gpio_request, + .direction_input = wm8903_gpio_direction_in, + .get = wm8903_gpio_get, + .direction_output = wm8903_gpio_direction_out, + .set = wm8903_gpio_set, + .can_sleep = 1, +}; + +static void wm8903_init_gpio(struct snd_soc_codec *codec) +{ + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev); + int ret; + + wm8903->gpio_chip = wm8903_template_chip; + wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO; + wm8903->gpio_chip.dev = codec->dev; + + if (pdata && pdata->gpio_base) + wm8903->gpio_chip.base = pdata->gpio_base; + else + wm8903->gpio_chip.base = -1; + + ret = gpiochip_add(&wm8903->gpio_chip); + if (ret != 0) + dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret); +} + +static void wm8903_free_gpio(struct snd_soc_codec *codec) +{ + struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec); + int ret; + + ret = gpiochip_remove(&wm8903->gpio_chip); + if (ret != 0) + dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret); +} +#else +static void wm8903_init_gpio(struct snd_soc_codec *codec) +{ +} + +static void wm8903_free_gpio(struct snd_soc_codec *codec) +{ +} +#endif + static int wm8903_probe(struct snd_soc_codec *codec) { struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev); @@ -1643,7 +1894,7 @@ static int wm8903_probe(struct snd_soc_codec *codec) int trigger, irq_pol; u16 val; - init_completion(&wm8903->wseq); + wm8903->codec = codec; ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); if (ret != 0) { @@ -1659,19 +1910,33 @@ static int wm8903_probe(struct snd_soc_codec *codec) } val = snd_soc_read(codec, WM8903_REVISION_NUMBER); - dev_info(codec->dev, "WM8903 revision %d\n", - val & WM8903_CHIP_REV_MASK); + dev_info(codec->dev, "WM8903 revision %c\n", + (val & WM8903_CHIP_REV_MASK) + 'A'); wm8903_reset(codec); /* Set up GPIOs and microphone detection */ if (pdata) { + bool mic_gpio = false; + for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) { - if (!pdata->gpio_cfg[i]) + if (pdata->gpio_cfg[i] == WM8903_GPIO_NO_CONFIG) continue; snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i, pdata->gpio_cfg[i] & 0xffff); + + val = (pdata->gpio_cfg[i] & WM8903_GP1_FN_MASK) + >> WM8903_GP1_FN_SHIFT; + + switch (val) { + case WM8903_GPn_FN_MICBIAS_CURRENT_DETECT: + case WM8903_GPn_FN_MICBIAS_SHORT_DETECT: + mic_gpio = true; + break; + default: + break; + } } snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0, @@ -1682,6 +1947,14 @@ static int wm8903_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0, WM8903_WSEQ_ENA, WM8903_WSEQ_ENA); + /* If microphone detection is enabled by pdata but + * detected via IRQ then interrupts can be lost before + * the machine driver has set up microphone detection + * IRQs as the IRQs are clear on read. The detection + * will be enabled when the machine driver configures. + */ + WARN_ON(!mic_gpio && (pdata->micdet_cfg & WM8903_MICDET_ENA)); + wm8903->mic_delay = pdata->micdet_delay; } @@ -1741,20 +2014,23 @@ static int wm8903_probe(struct snd_soc_codec *codec) snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); /* Enable DAC soft mute by default */ - val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1); - val |= WM8903_DAC_MUTEMODE; - snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val); + snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE, + WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE); snd_soc_add_controls(codec, wm8903_snd_controls, ARRAY_SIZE(wm8903_snd_controls)); wm8903_add_widgets(codec); + wm8903_init_gpio(codec); + return ret; } /* power down chip */ static int wm8903_remove(struct snd_soc_codec *codec) { + wm8903_free_gpio(codec); wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -1769,6 +2045,7 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8903 = { .reg_word_size = sizeof(u16), .reg_cache_default = wm8903_reg_defaults, .volatile_register = wm8903_volatile_register, + .seq_notifier = wm8903_seq_notifier, }; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) @@ -1807,7 +2084,7 @@ MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id); static struct i2c_driver wm8903_i2c_driver = { .driver = { - .name = "wm8903-codec", + .name = "wm8903", .owner = THIS_MODULE, }, .probe = wm8903_i2c_probe, |