From 18f454047b3d009e347dc3aeacb1aec91a4c493f Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 30 Nov 2010 14:31:45 +0200 Subject: ASoC: tlv320dac33: Do not enable the codec in init_chip No need to enable the codec at this time. The codec will be enabled later by other events Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index ccb267f..080ec91 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -315,8 +315,6 @@ static void dac33_init_chip(struct snd_soc_codec *codec) clock source = internal osc (?) */ dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); - dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); - /* Restore only selected registers (gains mostly) */ dac33_write(codec, DAC33_LDAC_DIG_VOL_CTRL, dac33_read_reg_cache(codec, DAC33_LDAC_DIG_VOL_CTRL)); -- cgit v1.1 From 3e202345abc2cea09a3601df527629102f37e563 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 30 Nov 2010 14:31:46 +0200 Subject: ASoC: tlv320dac33: Avoid multiple soft power up During playback start the codec has been already powered at BIAS_ON event time, so there's no need to enable the codec again. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 080ec91..a0ba5d1 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -642,7 +642,8 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: - dac33_soft_power(codec, 1); + if (!dac33->substream) + dac33_soft_power(codec, 1); break; case SND_SOC_BIAS_PREPARE: break; -- cgit v1.1 From d5876ce1242b78987e6243ba3cb23bb61d44d4a9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 30 Nov 2010 16:00:00 +0200 Subject: ASoC: tpa6130a2: Simplify power state management Use simpler way to avoid setting the same power state for the amplifier. Simplifies the check introduced by patch: ASoC: tpa6130a2: Fix unbalanced regulator disables Signed-off-by: Peter Ujfalusi Cc: Jarkko Nikula Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 9d61a1d..199edf0 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -41,7 +41,7 @@ struct tpa6130a2_data { unsigned char regs[TPA6130A2_CACHEREGNUM]; struct regulator *supply; int power_gpio; - unsigned char power_state; + u8 power_state:1; enum tpa_model id; }; @@ -116,7 +116,7 @@ static int tpa6130a2_initialize(void) return ret; } -static int tpa6130a2_power(int power) +static int tpa6130a2_power(u8 power) { struct tpa6130a2_data *data; u8 val; @@ -126,8 +126,10 @@ static int tpa6130a2_power(int power) data = i2c_get_clientdata(tpa6130a2_client); mutex_lock(&data->mutex); - if (power && !data->power_state) { + if (power == data->power_state) + goto exit; + if (power) { ret = regulator_enable(data->supply); if (ret != 0) { dev_err(&tpa6130a2_client->dev, @@ -154,7 +156,7 @@ static int tpa6130a2_power(int power) val = tpa6130a2_read(TPA6130A2_REG_CONTROL); val &= ~TPA6130A2_SWS; tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); - } else if (!power && data->power_state) { + } else { /* set SWS */ val = tpa6130a2_read(TPA6130A2_REG_CONTROL); val |= TPA6130A2_SWS; -- cgit v1.1 From d534bacd918fcf0909039b95db7c090702e739d5 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 30 Nov 2010 16:00:01 +0200 Subject: ASoC: tpa6130a2: Defer SW enable from power enable Do not enable the amplifier right after the power has been restored to the amplifier. The DAPM_SUPPLY widget turns on the amp early in the DAPM power walk, and the unmuting of channel happens quite late. Keeping the amp in SW reset state ensures better muting. In this way the pop noise coming from other components (codec) can be filtered out. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 199edf0..42887ae 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -151,11 +151,6 @@ static int tpa6130a2_power(u8 power) data->power_state = 0; goto exit; } - - /* Clear SWS */ - val = tpa6130a2_read(TPA6130A2_REG_CONTROL); - val &= ~TPA6130A2_SWS; - tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); } else { /* set SWS */ val = tpa6130a2_read(TPA6130A2_REG_CONTROL); @@ -301,6 +296,7 @@ static void tpa6130a2_channel_enable(u8 channel, int enable) /* Enable amplifier */ val = tpa6130a2_read(TPA6130A2_REG_CONTROL); val |= channel; + val &= ~TPA6130A2_SWS; tpa6130a2_i2c_write(TPA6130A2_REG_CONTROL, val); /* Unmute channel */ -- cgit v1.1 From 8cc14e13d15ec558c880ce6eaaddf99c08f85ab6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 30 Nov 2010 16:00:02 +0200 Subject: ASoC: tpa6130a2: Use one event handler for PGA_E Reduce the amount of duplicated code by using single event handler for PGA_E to enable the needed channel. Use the w->shift to pass the channel information to the handler function. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 42887ae..4c77a82 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -317,29 +317,15 @@ static void tpa6130a2_channel_enable(u8 channel, int enable) } } -static int tpa6130a2_left_event(struct snd_soc_dapm_widget *w, +static int tpa6130a2_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { switch (event) { case SND_SOC_DAPM_POST_PMU: - tpa6130a2_channel_enable(TPA6130A2_HP_EN_L, 1); + tpa6130a2_channel_enable(w->shift, 1); break; case SND_SOC_DAPM_POST_PMD: - tpa6130a2_channel_enable(TPA6130A2_HP_EN_L, 0); - break; - } - return 0; -} - -static int tpa6130a2_right_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - switch (event) { - case SND_SOC_DAPM_POST_PMU: - tpa6130a2_channel_enable(TPA6130A2_HP_EN_R, 1); - break; - case SND_SOC_DAPM_POST_PMD: - tpa6130a2_channel_enable(TPA6130A2_HP_EN_R, 0); + tpa6130a2_channel_enable(w->shift, 0); break; } return 0; @@ -363,10 +349,10 @@ static int tpa6130a2_supply_event(struct snd_soc_dapm_widget *w, static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { SND_SOC_DAPM_PGA_E("TPA6130A2 Left", SND_SOC_NOPM, - 0, 0, NULL, 0, tpa6130a2_left_event, + TPA6130A2_HP_EN_L, 0, NULL, 0, tpa6130a2_pga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("TPA6130A2 Right", SND_SOC_NOPM, - 0, 0, NULL, 0, tpa6130a2_right_event, + TPA6130A2_HP_EN_R, 0, NULL, 0, tpa6130a2_pga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("TPA6130A2 Enable", SND_SOC_NOPM, 0, 0, tpa6130a2_supply_event, -- cgit v1.1 From 1bb5ec6a6a0e094c84cc4fa2ba4a6d7cf8e9e8c6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 30 Nov 2010 16:00:03 +0200 Subject: ASoC: tpa6130a2: Add stereo DAPM path New DAPM widgets, and paths to enable both channels at the same time (for stereo output). With this path the switch time difference can be avoided between left and right channels. The original DAPM paths can be still used, if only one of TPA's output has been connected to a speaker, but for most of the cases, switching to the stereo path is better. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 4c77a82..c97badf 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -354,20 +354,27 @@ static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { SND_SOC_DAPM_PGA_E("TPA6130A2 Right", SND_SOC_NOPM, TPA6130A2_HP_EN_R, 0, NULL, 0, tpa6130a2_pga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PGA_E("TPA6130A2 Stereo", SND_SOC_NOPM, + TPA6130A2_HP_EN_L | TPA6130A2_HP_EN_R, 0, NULL, 0, + tpa6130a2_pga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("TPA6130A2 Enable", SND_SOC_NOPM, 0, 0, tpa6130a2_supply_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* Outputs */ SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Left"), SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Right"), + SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Stereo"), }; static const struct snd_soc_dapm_route audio_map[] = { {"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Left"}, {"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Right"}, + {"TPA6130A2 Headphone Stereo", NULL, "TPA6130A2 Stereo"}, {"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Enable"}, {"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Enable"}, + {"TPA6130A2 Headphone Stereo", NULL, "TPA6130A2 Enable"}, }; int tpa6130a2_add_controls(struct snd_soc_codec *codec) -- cgit v1.1 From 39646871a47fd8808c08de0ce7d7ca8393af2805 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 2 Dec 2010 09:29:56 +0200 Subject: ASoC: tpa6130a2: Replace DAPM code with direct interface The use of DAPM widgets, and extra routing can cause ordering problems in the system. Machine drivers should use the exported direct interface with SND_SOC_DAPM_HP's event callback to manage the state of the amplifier. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 69 +++++++------------------------------------- sound/soc/codecs/tpa6130a2.h | 1 + 2 files changed, 12 insertions(+), 58 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index c97badf..0a99f31 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -317,65 +317,24 @@ static void tpa6130a2_channel_enable(u8 channel, int enable) } } -static int tpa6130a2_pga_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - switch (event) { - case SND_SOC_DAPM_POST_PMU: - tpa6130a2_channel_enable(w->shift, 1); - break; - case SND_SOC_DAPM_POST_PMD: - tpa6130a2_channel_enable(w->shift, 0); - break; - } - return 0; -} - -static int tpa6130a2_supply_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable) { int ret = 0; - - switch (event) { - case SND_SOC_DAPM_POST_PMU: + if (enable) { ret = tpa6130a2_power(1); - break; - case SND_SOC_DAPM_POST_PMD: + if (ret < 0) + return ret; + tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L, + 1); + } else { + tpa6130a2_channel_enable(TPA6130A2_HP_EN_R | TPA6130A2_HP_EN_L, + 0); ret = tpa6130a2_power(0); - break; } + return ret; } - -static const struct snd_soc_dapm_widget tpa6130a2_dapm_widgets[] = { - SND_SOC_DAPM_PGA_E("TPA6130A2 Left", SND_SOC_NOPM, - TPA6130A2_HP_EN_L, 0, NULL, 0, tpa6130a2_pga_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_PGA_E("TPA6130A2 Right", SND_SOC_NOPM, - TPA6130A2_HP_EN_R, 0, NULL, 0, tpa6130a2_pga_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_PGA_E("TPA6130A2 Stereo", SND_SOC_NOPM, - TPA6130A2_HP_EN_L | TPA6130A2_HP_EN_R, 0, NULL, 0, - tpa6130a2_pga_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("TPA6130A2 Enable", SND_SOC_NOPM, - 0, 0, tpa6130a2_supply_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - /* Outputs */ - SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Left"), - SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Right"), - SND_SOC_DAPM_OUTPUT("TPA6130A2 Headphone Stereo"), -}; - -static const struct snd_soc_dapm_route audio_map[] = { - {"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Left"}, - {"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Right"}, - {"TPA6130A2 Headphone Stereo", NULL, "TPA6130A2 Stereo"}, - - {"TPA6130A2 Headphone Left", NULL, "TPA6130A2 Enable"}, - {"TPA6130A2 Headphone Right", NULL, "TPA6130A2 Enable"}, - {"TPA6130A2 Headphone Stereo", NULL, "TPA6130A2 Enable"}, -}; +EXPORT_SYMBOL_GPL(tpa6130a2_stereo_enable); int tpa6130a2_add_controls(struct snd_soc_codec *codec) { @@ -387,18 +346,12 @@ int tpa6130a2_add_controls(struct snd_soc_codec *codec) data = i2c_get_clientdata(tpa6130a2_client); - snd_soc_dapm_new_controls(dapm, tpa6130a2_dapm_widgets, - ARRAY_SIZE(tpa6130a2_dapm_widgets)); - - snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); - if (data->id == TPA6140A2) return snd_soc_add_controls(codec, tpa6140a2_controls, ARRAY_SIZE(tpa6140a2_controls)); else return snd_soc_add_controls(codec, tpa6130a2_controls, ARRAY_SIZE(tpa6130a2_controls)); - } EXPORT_SYMBOL_GPL(tpa6130a2_add_controls); diff --git a/sound/soc/codecs/tpa6130a2.h b/sound/soc/codecs/tpa6130a2.h index 57e867f..5df49c8 100644 --- a/sound/soc/codecs/tpa6130a2.h +++ b/sound/soc/codecs/tpa6130a2.h @@ -57,5 +57,6 @@ #define TPA6130A2_VERSION_MASK (0x0f) extern int tpa6130a2_add_controls(struct snd_soc_codec *codec); +extern int tpa6130a2_stereo_enable(struct snd_soc_codec *codec, int enable); #endif /* __TPA6130A2_H__ */ -- cgit v1.1 From fbe609e41b48f2e7da7c053ca835ba1277d3bed2 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Wed, 1 Dec 2010 14:38:24 +0800 Subject: ASoC: Remove unused aic3x_i2c_init and aic3x_i2c_exit functions Signed-off-by: Axel Lin Acked-by: Mark Brown Acked-by: Jarkko Nikula Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320aic3x.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index fc5abdf..8cd4cf5 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -1545,21 +1545,6 @@ static struct i2c_driver aic3x_i2c_driver = { .remove = aic3x_i2c_remove, .id_table = aic3x_i2c_id, }; - -static inline void aic3x_i2c_init(void) -{ - int ret; - - ret = i2c_add_driver(&aic3x_i2c_driver); - if (ret) - printk(KERN_ERR "%s: error regsitering i2c driver, %d\n", - __func__, ret); -} - -static inline void aic3x_i2c_exit(void) -{ - i2c_del_driver(&aic3x_i2c_driver); -} #endif static int __init aic3x_modinit(void) -- cgit v1.1 From 9e87186fff939924da58b8f562ec275757e29776 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 8 Dec 2010 16:04:32 +0200 Subject: ASoC: tlv320dac33: Rename outpup amplifier widget Use better name for the widget, and remove the 'Power' from it's name. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index a0ba5d1..e2e873e 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -599,9 +599,9 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { SND_SOC_DAPM_SWITCH("Analog Right Bypass", SND_SOC_NOPM, 0, 0, &dac33_dapm_abypassr_control), - SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Left Amp Power", + SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Left Amplifier", DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0), - SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amp Power", + SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amplifier", DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0), SND_SOC_DAPM_PRE("Prepare Playback", playback_event), @@ -612,15 +612,15 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Analog Left Bypass", "Switch", "LINEL"}, {"Analog Right Bypass", "Switch", "LINER"}, - {"Output Left Amp Power", NULL, "DACL"}, - {"Output Right Amp Power", NULL, "DACR"}, + {"Output Left Amplifier", NULL, "DACL"}, + {"Output Right Amplifier", NULL, "DACR"}, - {"Output Left Amp Power", NULL, "Analog Left Bypass"}, - {"Output Right Amp Power", NULL, "Analog Right Bypass"}, + {"Output Left Amplifier", NULL, "Analog Left Bypass"}, + {"Output Right Amplifier", NULL, "Analog Right Bypass"}, /* output */ - {"LEFT_LO", NULL, "Output Left Amp Power"}, - {"RIGHT_LO", NULL, "Output Right Amp Power"}, + {"LEFT_LO", NULL, "Output Left Amplifier"}, + {"RIGHT_LO", NULL, "Output Right Amplifier"}, }; static int dac33_add_widgets(struct snd_soc_codec *codec) -- cgit v1.1 From 76eac39ce5f64b95931a6026812e902cb8863a6c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 8 Dec 2010 16:04:33 +0200 Subject: ASoC: tlv320dac33: Move DAC LR power on to a supply widget The power for the DACs need to be enabled, even when only the analog bypass is in use with the codec, otherwise the audio is going to be distorted. Make sure that the DACs are powered all the time, when there is audio activity. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index e2e873e..cee0f99 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -590,8 +590,8 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINEL"), SND_SOC_DAPM_INPUT("LINER"), - SND_SOC_DAPM_DAC("DACL", "Left Playback", DAC33_LDAC_PWR_CTRL, 2, 0), - SND_SOC_DAPM_DAC("DACR", "Right Playback", DAC33_RDAC_PWR_CTRL, 2, 0), + SND_SOC_DAPM_DAC("DACL", "Left Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DACR", "Right Playback", SND_SOC_NOPM, 0, 0), /* Analog bypass */ SND_SOC_DAPM_SWITCH("Analog Left Bypass", SND_SOC_NOPM, 0, 0, @@ -604,6 +604,11 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amplifier", DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0), + SND_SOC_DAPM_SUPPLY("Left DAC Power", + DAC33_LDAC_PWR_CTRL, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Right DAC Power", + DAC33_RDAC_PWR_CTRL, 2, 0, NULL, 0), + SND_SOC_DAPM_PRE("Prepare Playback", playback_event), }; @@ -618,6 +623,9 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Output Left Amplifier", NULL, "Analog Left Bypass"}, {"Output Right Amplifier", NULL, "Analog Right Bypass"}, + {"Output Left Amplifier", NULL, "Left DAC Power"}, + {"Output Right Amplifier", NULL, "Right DAC Power"}, + /* output */ {"LEFT_LO", NULL, "Output Left Amplifier"}, {"RIGHT_LO", NULL, "Output Right Amplifier"}, -- cgit v1.1 From 3ee4fe15aba7531f75be4dcc331caa8f0c6369ec Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 8 Dec 2010 15:12:56 +0200 Subject: ASoC: tlv320dac33: Fix compillation error Fix the compilation error introduced by patch: ASoC: tlv320dac33: Avoid multiple soft power up Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index cee0f99..b3445b3 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -646,6 +646,7 @@ static int dac33_add_widgets(struct snd_soc_codec *codec) static int dac33_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); int ret; switch (level) { -- cgit v1.1 From 23ac3b61331137355064d8b22a3624fe1cd8527a Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Wed, 8 Dec 2010 10:55:05 -0600 Subject: ASoC: sdp4430: Enable FM stereo pins Add FM stereo pins to the machine driver and add them as a dapm widget. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/sdp4430.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index ebbd62f..0c37c51 100644 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -101,6 +101,7 @@ static const struct snd_soc_dapm_widget sdp4430_twl6040_dapm_widgets[] = { SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_HP("Headset Stereophone", NULL), SND_SOC_DAPM_SPK("Earphone Spk", NULL), + SND_SOC_DAPM_INPUT("Aux/FM Stereo In"), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -123,6 +124,10 @@ static const struct snd_soc_dapm_route audio_map[] = { /* Earphone speaker */ {"Earphone Spk", NULL, "EP"}, + + /* Aux/FM Stereo In: AFML, AFMR */ + {"AFML", NULL, "Aux/FM Stereo In"}, + {"AFMR", NULL, "Aux/FM Stereo In"}, }; static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd) @@ -149,13 +154,11 @@ static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd) /* SDP4430 connected pins */ snd_soc_dapm_enable_pin(dapm, "Ext Mic"); snd_soc_dapm_enable_pin(dapm, "Ext Spk"); + snd_soc_dapm_enable_pin(dapm, "AFML"); + snd_soc_dapm_enable_pin(dapm, "AFMR"); snd_soc_dapm_enable_pin(dapm, "Headset Mic"); snd_soc_dapm_enable_pin(dapm, "Headset Stereophone"); - /* TWL6040 not connected pins */ - snd_soc_dapm_nc_pin(dapm, "AFML"); - snd_soc_dapm_nc_pin(dapm, "AFMR"); - ret = snd_soc_dapm_sync(dapm); return ret; -- cgit v1.1 From a6cea9655bfa821dbf53c6fffb9b2b99fe77367c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 10 Dec 2010 13:26:31 +0200 Subject: ASoC: tlv320dac33: Power down digital parts, when not needed If the following scenario has been followed: 1. Enable analog bypass amixer sset 'Analog Left Bypass' on amixer sset 'Analog Right Bypass' on 2. Start playback aplay -fdat -d3 /dev/zero After the playback stopped (3 sec), and the soc timeout (5 sec), the digital parts of the codec will remain powered up. This means that the DAI clocks are continue to run, the oscillator remain operational, etc. Use the SND_SOC_DAPM_POST_PMD widget to get notification about the stopped stream, and power down the digital part of the codec. If the analog bypass is enabled, than the codec will remain in BIAS_ON level, and things will work correctly. In case, if the bypass is disabled, than the codec will fall to BIAS_STANDBY than to BIAS_OFF level, as it used to. The digital part of DAC33 is initialized at every stream start (DAPM_PRE:PRE_PMU event), so subsequent streams (within 5 sec) will have working DAI. When the codec is coming out from BIAS_OFF, the full power-up sequence followed by the same DAPM_PRE widget event will power up the digital part. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index b3445b3..776ac80 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -354,6 +354,21 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) dac33_write(codec, DAC33_PWR_CTRL, reg); } +static inline void dac33_disable_digital(struct snd_soc_codec *codec) +{ + u8 reg; + + /* Stop the DAI clock */ + reg = dac33_read_reg_cache(codec, DAC33_SER_AUDIOIF_CTRL_B); + reg &= ~DAC33_BCLKON; + dac33_write(codec, DAC33_SER_AUDIOIF_CTRL_B, reg); + + /* Power down the Oscillator, and DACs */ + reg = dac33_read_reg_cache(codec, DAC33_PWR_CTRL); + reg &= ~(DAC33_OSCPDNB | DAC33_DACRPDNB | DAC33_DACLPDNB); + dac33_write(codec, DAC33_PWR_CTRL, reg); +} + static int dac33_hard_power(struct snd_soc_codec *codec, int power) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); @@ -402,7 +417,7 @@ exit: return ret; } -static int playback_event(struct snd_soc_dapm_widget *w, +static int dac33_playback_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(w->codec); @@ -414,6 +429,9 @@ static int playback_event(struct snd_soc_dapm_widget *w, dac33_prepare_chip(dac33->substream); } break; + case SND_SOC_DAPM_POST_PMD: + dac33_disable_digital(w->codec); + break; } return 0; } @@ -609,7 +627,8 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("Right DAC Power", DAC33_RDAC_PWR_CTRL, 2, 0, NULL, 0), - SND_SOC_DAPM_PRE("Prepare Playback", playback_event), + SND_SOC_DAPM_PRE("Pre Playback", dac33_playback_event), + SND_SOC_DAPM_POST("Post Playback", dac33_playback_event), }; static const struct snd_soc_dapm_route audio_map[] = { -- cgit v1.1 From dcdeda4a60b2046dd18d3dd20cbd888f25d8916b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 14 Dec 2010 13:45:29 +0200 Subject: ASoC: TWL4030: Fix 24bit support twl4030 series of codecs supports S32_LE with msbits=24. Replace the S24_LE with S32_LE format, and add constraint for 24msbit in case of 32 S32_LE format. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl4030.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index c173cf0..e4d464b 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1724,6 +1724,7 @@ static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = rtd->codec; struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); + snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); if (twl4030->master_substream) { twl4030->slave_substream = substream; /* The DAI has one configuration for playback and capture, so @@ -1848,7 +1849,7 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, case SNDRV_PCM_FORMAT_S16_LE: format |= TWL4030_DATA_WIDTH_16S_16W; break; - case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: format |= TWL4030_DATA_WIDTH_32S_24W; break; default: @@ -2181,7 +2182,7 @@ static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate) } #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) -#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) +#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) static struct snd_soc_dai_ops twl4030_dai_hifi_ops = { .startup = twl4030_startup, -- cgit v1.1 From a2d2362edf9f068bdee7d0411e0603b322f8415d Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Fri, 10 Dec 2010 20:45:17 -0600 Subject: ASoC: twl6040: Add jack support for headset and handset This patch adds support for reporting twl6040 headset and handset jack events. The machine driver retrieves and report the status through twl6040_hs_jack_detect. A workq is used to debounce of the irq. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Misael Lopez Cruz Signed-off-by: Margarita Olaya Cabrera Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/twl6040.h | 7 +++++ 2 files changed, 74 insertions(+) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index b92f2b7..5d7e2f7 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -42,6 +42,11 @@ #define TWL6040_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +struct twl6040_jack_data { + struct snd_soc_jack *jack; + int report; +}; + /* codec private data */ struct twl6040_data { int audpwron; @@ -52,6 +57,11 @@ struct twl6040_data { unsigned int sysclk; struct snd_pcm_hw_constraint_list *sysclk_constraints; struct completion ready; + struct twl6040_jack_data hs_jack; + struct snd_soc_codec *codec; + struct workqueue_struct *workqueue; + struct delayed_work delayed_work; + struct mutex mutex; }; /* @@ -381,6 +391,47 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w, return 0; } +void twl6040_hs_jack_report(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int report) +{ + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + int status; + + mutex_lock(&priv->mutex); + + /* Sync status */ + status = twl6040_read_reg_volatile(codec, TWL6040_REG_STATUS); + if (status & TWL6040_PLUGCOMP) + snd_soc_jack_report(jack, report, report); + else + snd_soc_jack_report(jack, 0, report); + + mutex_unlock(&priv->mutex); +} + +void twl6040_hs_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int report) +{ + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_jack_data *hs_jack = &priv->hs_jack; + + hs_jack->jack = jack; + hs_jack->report = report; + + twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report); +} +EXPORT_SYMBOL_GPL(twl6040_hs_jack_detect); + +static void twl6040_accessory_work(struct work_struct *work) +{ + struct twl6040_data *priv = container_of(work, + struct twl6040_data, delayed_work.work); + struct snd_soc_codec *codec = priv->codec; + struct twl6040_jack_data *hs_jack = &priv->hs_jack; + + twl6040_hs_jack_report(codec, hs_jack->jack, hs_jack->report); +} + /* audio interrupt handler */ static irqreturn_t twl6040_naudint_handler(int irq, void *data) { @@ -396,6 +447,9 @@ static irqreturn_t twl6040_naudint_handler(int irq, void *data) break; case TWL6040_PLUGINT: case TWL6040_UNPLUGINT: + queue_delayed_work(priv->workqueue, &priv->delayed_work, + msecs_to_jiffies(200)); + break; case TWL6040_HOOKINT: break; case TWL6040_HFINT: @@ -1023,6 +1077,8 @@ static int twl6040_probe(struct snd_soc_codec *codec) return -ENOMEM; snd_soc_codec_set_drvdata(codec, priv); + priv->codec = codec; + if (twl_codec) { audpwron = twl_codec->audpwron_gpio; naudint = twl_codec->naudint_irq; @@ -1033,6 +1089,14 @@ static int twl6040_probe(struct snd_soc_codec *codec) priv->audpwron = audpwron; priv->naudint = naudint; + priv->workqueue = create_singlethread_workqueue("twl6040-codec"); + + if (!priv->workqueue) + goto work_err; + + INIT_DELAYED_WORK(&priv->delayed_work, twl6040_accessory_work); + + mutex_init(&priv->mutex); init_completion(&priv->ready); @@ -1089,6 +1153,8 @@ gpio2_err: if (gpio_is_valid(audpwron)) gpio_free(audpwron); gpio1_err: + destroy_workqueue(priv->workqueue); +work_err: kfree(priv); return ret; } @@ -1107,6 +1173,7 @@ static int twl6040_remove(struct snd_soc_codec *codec) if (naudint) free_irq(naudint, codec); + destroy_workqueue(priv->workqueue); kfree(priv); return 0; diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h index f7c77fa..67396f6 100644 --- a/sound/soc/codecs/twl6040.h +++ b/sound/soc/codecs/twl6040.h @@ -135,4 +135,11 @@ #define TWL6040_HPPLL_ID 1 #define TWL6040_LPPLL_ID 2 +/* STATUS (0x2E) fields */ + +#define TWL6040_PLUGCOMP 0x02 + +void twl6040_hs_jack_detect(struct snd_soc_codec *codec, + struct snd_soc_jack *jack, int report); + #endif /* End of __TWL6040_H__ */ -- cgit v1.1 From 96dc227c9086bc84ca23af70741b2a76e3dd08eb Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Fri, 10 Dec 2010 20:45:19 -0600 Subject: ASoC: sdp4430: Add Jack support Use jack framework to enable detection for the headset microphone and stereo output in the sdp4430. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: David Anders Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/sdp4430.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sound/soc/omap/sdp4430.c b/sound/soc/omap/sdp4430.c index 0c37c51..189e039 100644 --- a/sound/soc/omap/sdp4430.c +++ b/sound/soc/omap/sdp4430.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,21 @@ static struct snd_soc_ops sdp4430_ops = { .hw_params = sdp4430_hw_params, }; +/* Headset jack */ +static struct snd_soc_jack hs_jack; + +/*Headset jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Headset Stereophone", + .mask = SND_JACK_HEADPHONE, + }, +}; + static int sdp4430_get_power_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -160,6 +176,22 @@ static int sdp4430_twl6040_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_enable_pin(dapm, "Headset Stereophone"); ret = snd_soc_dapm_sync(dapm); + if (ret) + return ret; + + /* Headset jack detection */ + ret = snd_soc_jack_new(codec, "Headset Jack", + SND_JACK_HEADSET, &hs_jack); + if (ret) + return ret; + + ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + + if (machine_is_omap_4430sdp()) + twl6040_hs_jack_detect(codec, &hs_jack, SND_JACK_HEADSET); + else + snd_soc_jack_report(&hs_jack, SND_JACK_HEADSET, SND_JACK_HEADSET); return ret; } -- cgit v1.1 From 0dec1ec72317caa64f0174f8190c714ae4d51040 Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:05:24 -0600 Subject: ASoC: twl6040: Update twl IO macro Update the codec to use the new twl core register macros Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 5d7e2f7..d33d2b4 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -210,7 +210,7 @@ static int twl6040_read_reg_volatile(struct snd_soc_codec *codec, if (reg >= TWL6040_CACHEREGNUM) return -EIO; - twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &value, reg); + twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &value, reg); twl6040_write_reg_cache(codec, reg, value); return value; @@ -226,7 +226,7 @@ static int twl6040_write(struct snd_soc_codec *codec, return -EIO; twl6040_write_reg_cache(codec, reg, value); - return twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); + return twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, value, reg); } static void twl6040_init_vio_regs(struct snd_soc_codec *codec) @@ -439,7 +439,7 @@ static irqreturn_t twl6040_naudint_handler(int irq, void *data) struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); u8 intid; - twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); + twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); switch (intid) { case TWL6040_THINT: @@ -715,7 +715,7 @@ static int twl6040_power_up_completion(struct snd_soc_codec *codec, msecs_to_jiffies(48)); if (!time_left) { - twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &intid, + twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); if (!(intid & TWL6040_READYINT)) { dev_err(codec->dev, "timeout waiting for READYINT\n"); -- cgit v1.1 From cf370a5a0e9d3b111f93216a55f275d66daed952 Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:05:30 -0600 Subject: ASoC: twl6040: Modify the IRQ handler Multiples interrupts can be received. The irq handler is modified to attend all of them. Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index d33d2b4..8a6c623 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -441,30 +441,24 @@ static irqreturn_t twl6040_naudint_handler(int irq, void *data) twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, TWL6040_REG_INTID); - switch (intid) { - case TWL6040_THINT: + if (intid & TWL6040_THINT) dev_alert(codec->dev, "die temp over-limit detection\n"); - break; - case TWL6040_PLUGINT: - case TWL6040_UNPLUGINT: + + if ((intid & TWL6040_PLUGINT) || (intid & TWL6040_UNPLUGINT)) queue_delayed_work(priv->workqueue, &priv->delayed_work, msecs_to_jiffies(200)); - break; - case TWL6040_HOOKINT: - break; - case TWL6040_HFINT: + + if (intid & TWL6040_HOOKINT) + dev_info(codec->dev, "hook detection\n"); + + if (intid & TWL6040_HFINT) dev_alert(codec->dev, "hf drivers over current detection\n"); - break; - case TWL6040_VIBINT: + + if (intid & TWL6040_VIBINT) dev_alert(codec->dev, "vib drivers over current detection\n"); - break; - case TWL6040_READYINT: + + if (intid & TWL6040_READYINT) complete(&priv->ready); - break; - default: - dev_err(codec->dev, "unknown audio interrupt %d\n", intid); - break; - } return IRQ_HANDLED; } -- cgit v1.1 From 370a0314ff3e1315e7fdec32a88a7ae49ccd22c8 Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Fri, 10 Dec 2010 21:05:32 -0600 Subject: ASoC: twl6040: Add headset and handset mux controls This patch adds support for the twl6040 headset and handset MUX controls. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 100 +++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 8a6c623..b575fd3 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -476,6 +476,12 @@ static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0); static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0); /* + * AFMGAIN volume control: + * from 18 to 24 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(afm_amp_tlv, 1800, 600, 0); + +/* * HSGAIN volume control: * from -30 to 0 dB in 2 dB steps */ @@ -506,6 +512,28 @@ static const struct soc_enum twl6040_enum[] = { SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 3, twl6040_amicr_texts), }; +static const char *twl6040_hs_texts[] = { + "Off", "HS DAC", "Line-In amp" +}; + +static const struct soc_enum twl6040_hs_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_HSLCTL, 5, ARRAY_SIZE(twl6040_hs_texts), + twl6040_hs_texts), + SOC_ENUM_SINGLE(TWL6040_REG_HSRCTL, 5, ARRAY_SIZE(twl6040_hs_texts), + twl6040_hs_texts), +}; + +static const char *twl6040_hf_texts[] = { + "Off", "HF DAC", "Line-In amp" +}; + +static const struct soc_enum twl6040_hf_enum[] = { + SOC_ENUM_SINGLE(TWL6040_REG_HFLCTL, 2, ARRAY_SIZE(twl6040_hf_texts), + twl6040_hf_texts), + SOC_ENUM_SINGLE(TWL6040_REG_HFRCTL, 2, ARRAY_SIZE(twl6040_hf_texts), + twl6040_hf_texts), +}; + static const struct snd_kcontrol_new amicl_control = SOC_DAPM_ENUM("Route", twl6040_enum[0]); @@ -513,18 +541,18 @@ static const struct snd_kcontrol_new amicr_control = SOC_DAPM_ENUM("Route", twl6040_enum[1]); /* Headset DAC playback switches */ -static const struct snd_kcontrol_new hsdacl_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSLCTL, 5, 1, 0); +static const struct snd_kcontrol_new hsl_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hs_enum[0]); -static const struct snd_kcontrol_new hsdacr_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HSRCTL, 5, 1, 0); +static const struct snd_kcontrol_new hsr_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hs_enum[1]); /* Handsfree DAC playback switches */ -static const struct snd_kcontrol_new hfdacl_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFLCTL, 2, 1, 0); +static const struct snd_kcontrol_new hfl_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hf_enum[0]); -static const struct snd_kcontrol_new hfdacr_switch_controls = - SOC_DAPM_SINGLE("Switch", TWL6040_REG_HFRCTL, 2, 1, 0); +static const struct snd_kcontrol_new hfr_mux_controls = + SOC_DAPM_ENUM("Route", twl6040_hf_enum[1]); static const struct snd_kcontrol_new ep_driver_switch_controls = SOC_DAPM_SINGLE("Switch", TWL6040_REG_EARCTL, 0, 1, 0); @@ -536,6 +564,10 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { SOC_DOUBLE_TLV("Capture Volume", TWL6040_REG_MICGAIN, 0, 3, 4, 0, mic_amp_tlv), + /* AFM gains */ + SOC_DOUBLE_TLV("Aux FM Volume", + TWL6040_REG_LINEGAIN, 0, 5, 0xF, 0, afm_amp_tlv), + /* Playback gains */ SOC_DOUBLE_TLV("Headset Playback Volume", TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), @@ -572,6 +604,12 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { SND_SOC_DAPM_PGA("MicAmpR", TWL6040_REG_MICRCTL, 0, 0, NULL, 0), + /* Auxiliary FM PGAs */ + SND_SOC_DAPM_PGA("AFMAmpL", + TWL6040_REG_MICLCTL, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("AFMAmpR", + TWL6040_REG_MICRCTL, 1, 0, NULL, 0), + /* ADCs */ SND_SOC_DAPM_ADC("ADC Left", "Left Front Capture", TWL6040_REG_MICLCTL, 2, 0), @@ -606,15 +644,15 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { twl6040_power_mode_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - /* Analog playback switches */ - SND_SOC_DAPM_SWITCH("HSDAC Left Playback", - SND_SOC_NOPM, 0, 0, &hsdacl_switch_controls), - SND_SOC_DAPM_SWITCH("HSDAC Right Playback", - SND_SOC_NOPM, 0, 0, &hsdacr_switch_controls), - SND_SOC_DAPM_SWITCH("HFDAC Left Playback", - SND_SOC_NOPM, 0, 0, &hfdacl_switch_controls), - SND_SOC_DAPM_SWITCH("HFDAC Right Playback", - SND_SOC_NOPM, 0, 0, &hfdacr_switch_controls), + SND_SOC_DAPM_MUX("HF Left Playback", + SND_SOC_NOPM, 0, 0, &hfl_mux_controls), + SND_SOC_DAPM_MUX("HF Right Playback", + SND_SOC_NOPM, 0, 0, &hfr_mux_controls), + /* Analog playback Muxes */ + SND_SOC_DAPM_MUX("HS Left Playback", + SND_SOC_NOPM, 0, 0, &hsl_mux_controls), + SND_SOC_DAPM_MUX("HS Right Playback", + SND_SOC_NOPM, 0, 0, &hsr_mux_controls), /* Analog playback drivers */ SND_SOC_DAPM_PGA_E("Handsfree Left Driver", @@ -658,12 +696,18 @@ static const struct snd_soc_dapm_route intercon[] = { {"ADC Left", NULL, "MicAmpL"}, {"ADC Right", NULL, "MicAmpR"}, - /* Headset playback path */ - {"HSDAC Left Playback", "Switch", "HSDAC Left"}, - {"HSDAC Right Playback", "Switch", "HSDAC Right"}, + /* AFM path */ + {"AFMAmpL", "NULL", "AFML"}, + {"AFMAmpR", "NULL", "AFMR"}, + + {"HS Left Playback", "HS DAC", "HSDAC Left"}, + {"HS Left Playback", "Line-In amp", "AFMAmpL"}, - {"Headset Left Driver", NULL, "HSDAC Left Playback"}, - {"Headset Right Driver", NULL, "HSDAC Right Playback"}, + {"HS Right Playback", "HS DAC", "HSDAC Right"}, + {"HS Right Playback", "Line-In amp", "AFMAmpR"}, + + {"Headset Left Driver", "NULL", "HS Left Playback"}, + {"Headset Right Driver", "NULL", "HS Right Playback"}, {"HSOL", NULL, "Headset Left Driver"}, {"HSOR", NULL, "Headset Right Driver"}, @@ -672,12 +716,14 @@ static const struct snd_soc_dapm_route intercon[] = { {"Earphone Driver", "Switch", "HSDAC Left"}, {"EP", NULL, "Earphone Driver"}, - /* Handsfree playback path */ - {"HFDAC Left Playback", "Switch", "HFDAC Left"}, - {"HFDAC Right Playback", "Switch", "HFDAC Right"}, + {"HF Left Playback", "HF DAC", "HFDAC Left"}, + {"HF Left Playback", "Line-In amp", "AFMAmpL"}, + + {"HF Right Playback", "HF DAC", "HFDAC Right"}, + {"HF Right Playback", "Line-In amp", "AFMAmpR"}, - {"HFDAC Left PGA", NULL, "HFDAC Left Playback"}, - {"HFDAC Right PGA", NULL, "HFDAC Right Playback"}, + {"HFDAC Left PGA", NULL, "HF Left Playback"}, + {"HFDAC Right PGA", NULL, "HF Right Playback"}, {"Handsfree Left Driver", "Switch", "HFDAC Left PGA"}, {"Handsfree Right Driver", "Switch", "HFDAC Right PGA"}, -- cgit v1.1 From 6c311041c1d3d0b9d1fc6ddacd49e50d83ba158a Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:05:46 -0600 Subject: ASoC: twl6040: Restore bias level at resume This patch restores the CODEC bias level at resume(). Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index b575fd3..3973bf6 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1097,6 +1097,7 @@ static int twl6040_suspend(struct snd_soc_codec *codec, pm_message_t state) static int twl6040_resume(struct snd_soc_codec *codec) { twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + twl6040_set_bias_level(codec, codec->dapm.suspend_bias_level); return 0; } -- cgit v1.1 From 4e624d0609081e4394695fba3d7c3b7ebb6171ce Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:05:54 -0600 Subject: ASoC: twl6040: Fix PCM error handling ops This patch moves all the PCM error handling for clock config out of trigger() and startup() and into prepare(). Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 55 +++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 3973bf6..fd9a3ab 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -865,23 +865,6 @@ static int twl6040_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = rtd->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - if (!priv->sysclk) { - dev_err(codec->dev, - "no mclk configured, call set_sysclk() on init\n"); - return -EINVAL; - } - - /* - * capture is not supported at 17.64 MHz, - * it's reserved for headset low-power playback scenario - */ - if ((priv->sysclk == 17640000) && substream->stream) { - dev_err(codec->dev, - "capture mode is not supported at %dHz\n", - priv->sysclk); - return -EINVAL; - } - snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, priv->sysclk_constraints); @@ -925,31 +908,37 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream, return 0; } -static int twl6040_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) +static int twl6040_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - /* - * low-power playback mode is restricted - * for headset path only - */ - if ((priv->sysclk == 17640000) && priv->non_lp) { + if (!priv->sysclk) { + dev_err(codec->dev, + "no mclk configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + /* + * capture is not supported at 17.64 MHz, + * it's reserved for headset low-power playback scenario + */ + if ((priv->sysclk == 17640000) && + substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + dev_err(codec->dev, + "capture mode is not supported at %dHz\n", + priv->sysclk); + return -EINVAL; + } + + if ((priv->sysclk == 17640000) && priv->non_lp) { dev_err(codec->dev, "some enabled paths aren't supported at %dHz\n", priv->sysclk); return -EPERM; - } - break; - default: - break; } - return 0; } @@ -1063,7 +1052,7 @@ static int twl6040_set_dai_sysclk(struct snd_soc_dai *codec_dai, static struct snd_soc_dai_ops twl6040_dai_ops = { .startup = twl6040_startup, .hw_params = twl6040_hw_params, - .trigger = twl6040_trigger, + .prepare = twl6040_prepare, .set_sysclk = twl6040_set_dai_sysclk, }; -- cgit v1.1 From 60ea4cecddd03ed86b91bc8c057d3d305dc678be Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:05:58 -0600 Subject: ASoC: twl6040: Support other sample rates. The twl6040 can support more sample rates other than 88.2 and 96k. Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index fd9a3ab..b59d947 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -39,7 +39,7 @@ #include "twl6040.h" -#define TWL6040_RATES (SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) +#define TWL6040_RATES SNDRV_PCM_RATE_8000_96000 #define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) struct twl6040_jack_data { @@ -890,10 +890,17 @@ static int twl6040_hw_params(struct snd_pcm_substream *substream, rate = params_rate(params); switch (rate) { + case 11250: + case 22500: + case 44100: case 88200: lppllctl |= TWL6040_LPLLFIN; priv->sysclk = 17640000; break; + case 8000: + case 16000: + case 32000: + case 48000: case 96000: lppllctl &= ~TWL6040_LPLLFIN; priv->sysclk = 19200000; -- cgit v1.1 From cb973d78f82f038c7d8d78d469fb89842d246871 Mon Sep 17 00:00:00 2001 From: Francois Mazard Date: Fri, 10 Dec 2010 21:06:03 -0600 Subject: ASoC: twl6040: Fix analog Mic L & R mux controls The mux control has 4 elements not 3 Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index b59d947..5081e81 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -508,8 +508,8 @@ static const char *twl6040_amicr_texts[] = {"Headset Mic", "Sub Mic", "Aux/FM Right", "Off"}; static const struct soc_enum twl6040_enum[] = { - SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 3, twl6040_amicl_texts), - SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 3, twl6040_amicr_texts), + SOC_ENUM_SINGLE(TWL6040_REG_MICLCTL, 3, 4, twl6040_amicl_texts), + SOC_ENUM_SINGLE(TWL6040_REG_MICRCTL, 3, 4, twl6040_amicr_texts), }; static const char *twl6040_hs_texts[] = { -- cgit v1.1 From 99903ea23655a43ce4f75b64fef69e341fd0b7df Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:06:07 -0600 Subject: ASoC: twl6040: Enable automatic power for phoenix 1.1 Phoenix 1.1 supports automatic power on sequence, a verification is added to use it with new revision of the chip. Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 5081e81..c543504 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1108,6 +1108,7 @@ static int twl6040_probe(struct snd_soc_codec *codec) struct twl6040_data *priv; int audpwron, naudint; int ret = 0; + u8 icrev; priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL); if (priv == NULL) @@ -1116,13 +1117,17 @@ static int twl6040_probe(struct snd_soc_codec *codec) priv->codec = codec; - if (twl_codec) { + twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &icrev, TWL6040_REG_ASICREV); + + if (twl_codec && (icrev > 0)) audpwron = twl_codec->audpwron_gpio; - naudint = twl_codec->naudint_irq; - } else { + else audpwron = -EINVAL; + + if (twl_codec) + naudint = twl_codec->naudint_irq; + else naudint = 0; - } priv->audpwron = audpwron; priv->naudint = naudint; -- cgit v1.1 From f1f489a6aa89993892cd7b4d08f67e7e110492cb Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Fri, 10 Dec 2010 21:06:13 -0600 Subject: ASoC: twl6040: Clear interrupt status at boot time On Phoenix 1.1, the INTID register default value is an invalid one, causing the interrupt handler to think the phoenix power on sequence is ready before it actually finishes. This causes some i2c errors when trying to configure twl. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index c543504..f5d5f89 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1152,6 +1152,17 @@ static int twl6040_probe(struct snd_soc_codec *codec) goto gpio2_err; priv->codec_powered = 0; + + /* enable only codec ready interrupt */ + twl6040_write(codec, TWL6040_REG_INTMR, + ~TWL6040_READYMSK & TWL6040_ALLINT_MSK); + + /* reset interrupt status to allow correct power up sequence */ + twl6040_read_reg_volatile(codec, TWL6040_REG_INTID); + } else { + /* no interrupts at all */ + twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, + TWL6040_ALLINT_MSK); } if (naudint) { @@ -1162,16 +1173,6 @@ static int twl6040_probe(struct snd_soc_codec *codec) "twl6040_codec", codec); if (ret) goto gpio2_err; - } else { - if (gpio_is_valid(audpwron)) { - /* enable only codec ready interrupt */ - twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, - ~TWL6040_READYMSK & TWL6040_ALLINT_MSK); - } else { - /* no interrupts at all */ - twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, - TWL6040_ALLINT_MSK); - } } /* init vio registers */ -- cgit v1.1 From 4f44ee1f494edef1fea3db20565b2e209bef6280 Mon Sep 17 00:00:00 2001 From: Misael Lopez Cruz Date: Fri, 10 Dec 2010 21:06:24 -0600 Subject: ASoC: twl6040: Enable plug detection interrupts Enable plug detection interrupt mask in order to get headset PLUGINT/UNPLUGINT interrupts. Signed-off-by: Misael Lopez Cruz Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 10 +++------- sound/soc/codecs/twl6040.h | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index f5d5f89..a8ec911 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1108,7 +1108,7 @@ static int twl6040_probe(struct snd_soc_codec *codec) struct twl6040_data *priv; int audpwron, naudint; int ret = 0; - u8 icrev; + u8 icrev, intmr = TWL6040_ALLINT_MSK; priv = kzalloc(sizeof(struct twl6040_data), GFP_KERNEL); if (priv == NULL) @@ -1154,16 +1154,12 @@ static int twl6040_probe(struct snd_soc_codec *codec) priv->codec_powered = 0; /* enable only codec ready interrupt */ - twl6040_write(codec, TWL6040_REG_INTMR, - ~TWL6040_READYMSK & TWL6040_ALLINT_MSK); + intmr &= ~(TWL6040_READYMSK | TWL6040_PLUGMSK); /* reset interrupt status to allow correct power up sequence */ twl6040_read_reg_volatile(codec, TWL6040_REG_INTID); - } else { - /* no interrupts at all */ - twl6040_write_reg_cache(codec, TWL6040_REG_INTMR, - TWL6040_ALLINT_MSK); } + twl6040_write(codec, TWL6040_REG_INTMR, intmr); if (naudint) { /* audio interrupt */ diff --git a/sound/soc/codecs/twl6040.h b/sound/soc/codecs/twl6040.h index 67396f6..23aeed0 100644 --- a/sound/soc/codecs/twl6040.h +++ b/sound/soc/codecs/twl6040.h @@ -79,6 +79,7 @@ /* INTMR (0x04) fields */ +#define TWL6040_PLUGMSK 0x02 #define TWL6040_READYMSK 0x40 #define TWL6040_ALLINT_MSK 0x7B -- cgit v1.1 From cbd9cb5de3182d688544611c019b06bf04e2ad06 Mon Sep 17 00:00:00 2001 From: Jorge Eduardo Candelaria Date: Fri, 10 Dec 2010 21:06:30 -0600 Subject: ASoC: twl6040: Increase timeout for power up After coming back from suspend, the timeout waiting for Phoenix chip to complete its power up sequence is not enough, which leaves the codec cache value for some registers in an outdated state. Increase the timeout value to wait for the power up sequence to correclty complete. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index a8ec911..39f0bc5 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -752,7 +752,7 @@ static int twl6040_power_up_completion(struct snd_soc_codec *codec, u8 intid; time_left = wait_for_completion_timeout(&priv->ready, - msecs_to_jiffies(48)); + msecs_to_jiffies(144)); if (!time_left) { twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &intid, -- cgit v1.1 From 9020808b4d9ff6b7eebb026492dba6a805309df8 Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Fri, 10 Dec 2010 21:06:39 -0600 Subject: ASoC: twl6040: Fix TLV dB step values for gains Some gains were incorrectly configured for dB values. Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 39f0bc5..b0ca9f9 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -471,9 +471,9 @@ static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0); /* * MICGAIN volume control: - * from 6 to 30 dB in 6 dB steps + * from -6 to 30 dB in 6 dB steps */ -static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0); +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -600, 600, 0); /* * AFMGAIN volume control: -- cgit v1.1 From 53a9ef15df8c0dc688fae33277a252f0dd2faf2d Mon Sep 17 00:00:00 2001 From: Misael Lopez Cruz Date: Fri, 10 Dec 2010 21:06:34 -0600 Subject: ASoC: twl6040: Use correct offset for LineInAmp Right Gain for LineInAmp Right uses LINEGAIN[5:3], which means that offset for right channel should be 4. Signed-off-by: Misael Lopez Cruz Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index b0ca9f9..9dfe208 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -566,7 +566,7 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { /* AFM gains */ SOC_DOUBLE_TLV("Aux FM Volume", - TWL6040_REG_LINEGAIN, 0, 5, 0xF, 0, afm_amp_tlv), + TWL6040_REG_LINEGAIN, 0, 4, 0xF, 0, afm_amp_tlv), /* Playback gains */ SOC_DOUBLE_TLV("Headset Playback Volume", -- cgit v1.1 From 65b7cecc85b9bb7bb8ce74c6a3b280464b00c86c Mon Sep 17 00:00:00 2001 From: "Olaya, Margarita" Date: Tue, 14 Dec 2010 19:18:36 -0600 Subject: ASoC: twl6040: Set default gains to minimun value Updated default values to improve power consumption. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Margarita Olaya Cabrera Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 9dfe208..b252cf8 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -806,6 +806,15 @@ static int twl6040_set_bias_level(struct snd_soc_codec *codec, /* initialize vdd/vss registers with reg_cache */ twl6040_init_vdd_regs(codec); + + /* Set external boost GPO */ + twl6040_write(codec, TWL6040_REG_GPOCTL, 0x02); + + /* Set initial minimal gain values */ + twl6040_write(codec, TWL6040_REG_HSGAIN, 0xFF); + twl6040_write(codec, TWL6040_REG_EARCTL, 0x1E); + twl6040_write(codec, TWL6040_REG_HFLGAIN, 0x1D); + twl6040_write(codec, TWL6040_REG_HFRGAIN, 0x1D); break; case SND_SOC_BIAS_OFF: if (!priv->codec_powered) -- cgit v1.1 From 1bf84759bdcc08933b22ee70722f1575ad84f9b9 Mon Sep 17 00:00:00 2001 From: Margarita Olaya Cabrera Date: Tue, 14 Dec 2010 19:00:21 -0600 Subject: ASoC: twl6040: Add ramp up/down volume for HS and HF Add ramp functions for the headset and handsfree outputs in order to reduce the pops during power on/off sequences. In order to give more control to volume ramp, step size and delay between steps can be specified. The patches are based on wm8350 implementation from Liam Girdwood. Signed-off-by: Margarita Olaya Cabrera Signed-off-by: Misael Lopez Cruz Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl6040.c | 533 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 521 insertions(+), 12 deletions(-) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index b252cf8..2f68f59 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -40,7 +40,35 @@ #include "twl6040.h" #define TWL6040_RATES SNDRV_PCM_RATE_8000_96000 -#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) +#define TWL6040_FORMATS (SNDRV_PCM_FMTBIT_S32_LE) + +#define TWL6040_OUTHS_0dB 0x00 +#define TWL6040_OUTHS_M30dB 0x0F +#define TWL6040_OUTHF_0dB 0x03 +#define TWL6040_OUTHF_M52dB 0x1D + +#define TWL6040_RAMP_NONE 0 +#define TWL6040_RAMP_UP 1 +#define TWL6040_RAMP_DOWN 2 + +#define TWL6040_HSL_VOL_MASK 0x0F +#define TWL6040_HSL_VOL_SHIFT 0 +#define TWL6040_HSR_VOL_MASK 0xF0 +#define TWL6040_HSR_VOL_SHIFT 4 +#define TWL6040_HF_VOL_MASK 0x1F +#define TWL6040_HF_VOL_SHIFT 0 + +struct twl6040_output { + u16 active; + u16 left_vol; + u16 right_vol; + u16 left_step; + u16 right_step; + unsigned int step_delay; + u16 ramp; + u16 mute; + struct completion ramp_done; +}; struct twl6040_jack_data { struct snd_soc_jack *jack; @@ -62,6 +90,12 @@ struct twl6040_data { struct workqueue_struct *workqueue; struct delayed_work delayed_work; struct mutex mutex; + struct twl6040_output headset; + struct twl6040_output handsfree; + struct workqueue_struct *hf_workqueue; + struct workqueue_struct *hs_workqueue; + struct delayed_work hs_delayed_work; + struct delayed_work hf_delayed_work; }; /* @@ -263,6 +297,305 @@ static void twl6040_init_vdd_regs(struct snd_soc_codec *codec) } } +/* + * Ramp HS PGA volume to minimise pops at stream startup and shutdown. + */ +static inline int twl6040_hs_ramp_step(struct snd_soc_codec *codec, + unsigned int left_step, unsigned int right_step) +{ + + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *headset = &priv->headset; + int left_complete = 0, right_complete = 0; + u8 reg, val; + + /* left channel */ + left_step = (left_step > 0xF) ? 0xF : left_step; + reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN); + val = (~reg & TWL6040_HSL_VOL_MASK); + + if (headset->ramp == TWL6040_RAMP_UP) { + /* ramp step up */ + if (val < headset->left_vol) { + val += left_step; + reg &= ~TWL6040_HSL_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HSGAIN, + (reg | (~val & TWL6040_HSL_VOL_MASK))); + } else { + left_complete = 1; + } + } else if (headset->ramp == TWL6040_RAMP_DOWN) { + /* ramp step down */ + if (val > 0x0) { + val -= left_step; + reg &= ~TWL6040_HSL_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HSGAIN, reg | + (~val & TWL6040_HSL_VOL_MASK)); + } else { + left_complete = 1; + } + } + + /* right channel */ + right_step = (right_step > 0xF) ? 0xF : right_step; + reg = twl6040_read_reg_cache(codec, TWL6040_REG_HSGAIN); + val = (~reg & TWL6040_HSR_VOL_MASK) >> TWL6040_HSR_VOL_SHIFT; + + if (headset->ramp == TWL6040_RAMP_UP) { + /* ramp step up */ + if (val < headset->right_vol) { + val += right_step; + reg &= ~TWL6040_HSR_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HSGAIN, + (reg | (~val << TWL6040_HSR_VOL_SHIFT))); + } else { + right_complete = 1; + } + } else if (headset->ramp == TWL6040_RAMP_DOWN) { + /* ramp step down */ + if (val > 0x0) { + val -= right_step; + reg &= ~TWL6040_HSR_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HSGAIN, + reg | (~val << TWL6040_HSR_VOL_SHIFT)); + } else { + right_complete = 1; + } + } + + return left_complete & right_complete; +} + +/* + * Ramp HF PGA volume to minimise pops at stream startup and shutdown. + */ +static inline int twl6040_hf_ramp_step(struct snd_soc_codec *codec, + unsigned int left_step, unsigned int right_step) +{ + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *handsfree = &priv->handsfree; + int left_complete = 0, right_complete = 0; + u16 reg, val; + + /* left channel */ + left_step = (left_step > 0x1D) ? 0x1D : left_step; + reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFLGAIN); + reg = 0x1D - reg; + val = (reg & TWL6040_HF_VOL_MASK); + if (handsfree->ramp == TWL6040_RAMP_UP) { + /* ramp step up */ + if (val < handsfree->left_vol) { + val += left_step; + reg &= ~TWL6040_HF_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HFLGAIN, + reg | (0x1D - val)); + } else { + left_complete = 1; + } + } else if (handsfree->ramp == TWL6040_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val -= left_step; + reg &= ~TWL6040_HF_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HFLGAIN, + reg | (0x1D - val)); + } else { + left_complete = 1; + } + } + + /* right channel */ + right_step = (right_step > 0x1D) ? 0x1D : right_step; + reg = twl6040_read_reg_cache(codec, TWL6040_REG_HFRGAIN); + reg = 0x1D - reg; + val = (reg & TWL6040_HF_VOL_MASK); + if (handsfree->ramp == TWL6040_RAMP_UP) { + /* ramp step up */ + if (val < handsfree->right_vol) { + val += right_step; + reg &= ~TWL6040_HF_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HFRGAIN, + reg | (0x1D - val)); + } else { + right_complete = 1; + } + } else if (handsfree->ramp == TWL6040_RAMP_DOWN) { + /* ramp step down */ + if (val > 0) { + val -= right_step; + reg &= ~TWL6040_HF_VOL_MASK; + twl6040_write(codec, TWL6040_REG_HFRGAIN, + reg | (0x1D - val)); + } + } + + return left_complete & right_complete; +} + +/* + * This work ramps both output PGAs at stream start/stop time to + * minimise pop associated with DAPM power switching. + */ +static void twl6040_pga_hs_work(struct work_struct *work) +{ + struct twl6040_data *priv = + container_of(work, struct twl6040_data, hs_delayed_work.work); + struct snd_soc_codec *codec = priv->codec; + struct twl6040_output *headset = &priv->headset; + unsigned int delay = headset->step_delay; + int i, headset_complete; + + /* do we need to ramp at all ? */ + if (headset->ramp == TWL6040_RAMP_NONE) + return; + + /* HS PGA volumes have 4 bits of resolution to ramp */ + for (i = 0; i <= 16; i++) { + headset_complete = 1; + if (headset->ramp != TWL6040_RAMP_NONE) + headset_complete = twl6040_hs_ramp_step(codec, + headset->left_step, + headset->right_step); + + /* ramp finished ? */ + if (headset_complete) + break; + + /* + * TODO: tune: delay is longer over 0dB + * as increases are larger. + */ + if (i >= 8) + schedule_timeout_interruptible(msecs_to_jiffies(delay + + (delay >> 1))); + else + schedule_timeout_interruptible(msecs_to_jiffies(delay)); + } + + if (headset->ramp == TWL6040_RAMP_DOWN) { + headset->active = 0; + complete(&headset->ramp_done); + } else { + headset->active = 1; + } + headset->ramp = TWL6040_RAMP_NONE; +} + +static void twl6040_pga_hf_work(struct work_struct *work) +{ + struct twl6040_data *priv = + container_of(work, struct twl6040_data, hf_delayed_work.work); + struct snd_soc_codec *codec = priv->codec; + struct twl6040_output *handsfree = &priv->handsfree; + unsigned int delay = handsfree->step_delay; + int i, handsfree_complete; + + /* do we need to ramp at all ? */ + if (handsfree->ramp == TWL6040_RAMP_NONE) + return; + + /* HF PGA volumes have 5 bits of resolution to ramp */ + for (i = 0; i <= 32; i++) { + handsfree_complete = 1; + if (handsfree->ramp != TWL6040_RAMP_NONE) + handsfree_complete = twl6040_hf_ramp_step(codec, + handsfree->left_step, + handsfree->right_step); + + /* ramp finished ? */ + if (handsfree_complete) + break; + + /* + * TODO: tune: delay is longer over 0dB + * as increases are larger. + */ + if (i >= 16) + schedule_timeout_interruptible(msecs_to_jiffies(delay + + (delay >> 1))); + else + schedule_timeout_interruptible(msecs_to_jiffies(delay)); + } + + + if (handsfree->ramp == TWL6040_RAMP_DOWN) { + handsfree->active = 0; + complete(&handsfree->ramp_done); + } else + handsfree->active = 1; + handsfree->ramp = TWL6040_RAMP_NONE; +} + +static int pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *out; + struct delayed_work *work; + struct workqueue_struct *queue; + + switch (w->shift) { + case 2: + case 3: + out = &priv->headset; + work = &priv->hs_delayed_work; + queue = priv->hs_workqueue; + out->step_delay = 5; /* 5 ms between volume ramp steps */ + break; + case 4: + out = &priv->handsfree; + work = &priv->hf_delayed_work; + queue = priv->hf_workqueue; + out->step_delay = 5; /* 5 ms between volume ramp steps */ + if (SND_SOC_DAPM_EVENT_ON(event)) + priv->non_lp++; + else + priv->non_lp--; + break; + default: + return -1; + } + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (out->active) + break; + + /* don't use volume ramp for power-up */ + out->left_step = out->left_vol; + out->right_step = out->right_vol; + + if (!delayed_work_pending(work)) { + out->ramp = TWL6040_RAMP_UP; + queue_delayed_work(queue, work, + msecs_to_jiffies(1)); + } + break; + + case SND_SOC_DAPM_PRE_PMD: + if (!out->active) + break; + + if (!delayed_work_pending(work)) { + /* use volume ramp for power-down */ + out->left_step = 1; + out->right_step = 1; + out->ramp = TWL6040_RAMP_DOWN; + INIT_COMPLETION(out->ramp_done); + + queue_delayed_work(queue, work, + msecs_to_jiffies(1)); + + wait_for_completion_timeout(&out->ramp_done, + msecs_to_jiffies(2000)); + } + break; + } + + return 0; +} + /* twl6040 codec manual power-up sequence */ static void twl6040_power_up(struct snd_soc_codec *codec) { @@ -463,6 +796,156 @@ static irqreturn_t twl6040_naudint_handler(int irq, void *data) return IRQ_HANDLED; } +static int twl6040_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *out = NULL; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int ret; + unsigned int reg = mc->reg; + + /* For HS and HF we shadow the values and only actually write + * them out when active in order to ensure the amplifier comes on + * as quietly as possible. */ + switch (reg) { + case TWL6040_REG_HSGAIN: + out = &twl6040_priv->headset; + break; + default: + break; + } + + if (out) { + out->left_vol = ucontrol->value.integer.value[0]; + out->right_vol = ucontrol->value.integer.value[1]; + if (!out->active) + return 1; + } + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + return 1; +} + +static int twl6040_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *out = &twl6040_priv->headset; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + + switch (reg) { + case TWL6040_REG_HSGAIN: + out = &twl6040_priv->headset; + ucontrol->value.integer.value[0] = out->left_vol; + ucontrol->value.integer.value[1] = out->right_vol; + return 0; + + default: + break; + } + + return snd_soc_get_volsw(kcontrol, ucontrol); +} + +static int twl6040_put_volsw_2r_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *out = NULL; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int ret; + unsigned int reg = mc->reg; + + /* For HS and HF we shadow the values and only actually write + * them out when active in order to ensure the amplifier comes on + * as quietly as possible. */ + switch (reg) { + case TWL6040_REG_HFLGAIN: + case TWL6040_REG_HFRGAIN: + out = &twl6040_priv->handsfree; + break; + default: + break; + } + + if (out) { + out->left_vol = ucontrol->value.integer.value[0]; + out->right_vol = ucontrol->value.integer.value[1]; + if (!out->active) + return 1; + } + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + if (ret < 0) + return ret; + + return 1; +} + +static int twl6040_get_volsw_2r(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct twl6040_data *twl6040_priv = snd_soc_codec_get_drvdata(codec); + struct twl6040_output *out = &twl6040_priv->handsfree; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + + /* If these are cached registers use the cache */ + switch (reg) { + case TWL6040_REG_HFLGAIN: + case TWL6040_REG_HFRGAIN: + out = &twl6040_priv->handsfree; + ucontrol->value.integer.value[0] = out->left_vol; + ucontrol->value.integer.value[1] = out->right_vol; + return 0; + + default: + break; + } + + return snd_soc_get_volsw_2r(kcontrol, ucontrol); +} + +/* double control with volume update */ +#define SOC_TWL6040_DOUBLE_TLV(xname, xreg, shift_left, shift_right, xmax,\ + xinvert, tlv_array)\ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, .get = twl6040_get_volsw, \ + .put = twl6040_put_volsw, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = xreg, .shift = shift_left, .rshift = shift_right,\ + .max = xmax, .platform_max = xmax, .invert = xinvert} } + +/* double control with volume update */ +#define SOC_TWL6040_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax,\ + xinvert, tlv_array)\ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_VOLATILE, \ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw_2r, \ + .get = twl6040_get_volsw_2r, .put = twl6040_put_volsw_2r_vu, \ + .private_value = (unsigned long)&(struct soc_mixer_control) \ + {.reg = reg_left, .rreg = reg_right, .shift = xshift, \ + .rshift = xshift, .max = xmax, .invert = xinvert}, } + /* * MICATT volume control: * from -6 to 0 dB in 6 dB steps @@ -569,9 +1052,9 @@ static const struct snd_kcontrol_new twl6040_snd_controls[] = { TWL6040_REG_LINEGAIN, 0, 4, 0xF, 0, afm_amp_tlv), /* Playback gains */ - SOC_DOUBLE_TLV("Headset Playback Volume", + SOC_TWL6040_DOUBLE_TLV("Headset Playback Volume", TWL6040_REG_HSGAIN, 0, 4, 0xF, 1, hs_tlv), - SOC_DOUBLE_R_TLV("Handsfree Playback Volume", + SOC_TWL6040_DOUBLE_R_TLV("Handsfree Playback Volume", TWL6040_REG_HFLGAIN, TWL6040_REG_HFRGAIN, 0, 0x1D, 1, hf_tlv), SOC_SINGLE_TLV("Earphone Playback Volume", TWL6040_REG_EARCTL, 1, 0xF, 1, ep_tlv), @@ -657,16 +1140,20 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { /* Analog playback drivers */ SND_SOC_DAPM_PGA_E("Handsfree Left Driver", TWL6040_REG_HFLCTL, 4, 0, NULL, 0, - twl6040_power_mode_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Handsfree Right Driver", TWL6040_REG_HFRCTL, 4, 0, NULL, 0, - twl6040_power_mode_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_PGA("Headset Left Driver", - TWL6040_REG_HSLCTL, 2, 0, NULL, 0), - SND_SOC_DAPM_PGA("Headset Right Driver", - TWL6040_REG_HSRCTL, 2, 0, NULL, 0), + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Headset Left Driver", + TWL6040_REG_HSLCTL, 2, 0, NULL, 0, + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("Headset Right Driver", + TWL6040_REG_HSRCTL, 2, 0, NULL, 0, + pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_SWITCH_E("Earphone Driver", SND_SOC_NOPM, 0, 0, &ep_driver_switch_controls, twl6040_power_mode_event, @@ -1150,6 +1637,8 @@ static int twl6040_probe(struct snd_soc_codec *codec) mutex_init(&priv->mutex); init_completion(&priv->ready); + init_completion(&priv->headset.ramp_done); + init_completion(&priv->handsfree.ramp_done); if (gpio_is_valid(audpwron)) { ret = gpio_request(audpwron, "audpwron"); @@ -1183,10 +1672,24 @@ static int twl6040_probe(struct snd_soc_codec *codec) /* init vio registers */ twl6040_init_vio_regs(codec); + priv->hf_workqueue = create_singlethread_workqueue("twl6040-hf"); + if (priv->hf_workqueue == NULL) { + ret = -ENOMEM; + goto irq_err; + } + priv->hs_workqueue = create_singlethread_workqueue("twl6040-hs"); + if (priv->hs_workqueue == NULL) { + ret = -ENOMEM; + goto wq_err; + } + + INIT_DELAYED_WORK(&priv->hs_delayed_work, twl6040_pga_hs_work); + INIT_DELAYED_WORK(&priv->hf_delayed_work, twl6040_pga_hf_work); + /* power on device */ ret = twl6040_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret) - goto irq_err; + goto bias_err; snd_soc_add_controls(codec, twl6040_snd_controls, ARRAY_SIZE(twl6040_snd_controls)); @@ -1194,6 +1697,10 @@ static int twl6040_probe(struct snd_soc_codec *codec) return 0; +bias_err: + destroy_workqueue(priv->hs_workqueue); +wq_err: + destroy_workqueue(priv->hf_workqueue); irq_err: if (naudint) free_irq(naudint, codec); @@ -1222,6 +1729,8 @@ static int twl6040_remove(struct snd_soc_codec *codec) free_irq(naudint, codec); destroy_workqueue(priv->workqueue); + destroy_workqueue(priv->hf_workqueue); + destroy_workqueue(priv->hs_workqueue); kfree(priv); return 0; -- cgit v1.1