From d17bf31832d30b91225a84b53fae380dbdd07d3d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 22 Sep 2011 11:05:48 +0300 Subject: ASoC: twl6040: Introduce SW only shadow register Software only shadow register to be used by the driver. For example Earpiece path will need this shadow register. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/linux/mfd/twl6040.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index ec1ec794fa23..47470cadf969 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -68,8 +68,6 @@ #define TWL6040_REG_ACCCTL 0x2D #define TWL6040_REG_STATUS 0x2E -#define TWL6040_CACHEREGNUM (TWL6040_REG_STATUS + 1) - /* INTID (0x03) fields */ #define TWL6040_THINT 0x01 -- cgit v1.2.3 From ab6cf13943303f865320407b17b0f86095d23ce3 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 22 Sep 2011 11:05:54 +0300 Subject: ASoC/MFD: twl6040: Combine bit definitions for Headset control registers Use one set of defines for the HS bits, since they are identical in both control register. Signed-off-by: Peter Ujfalusi Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/linux/mfd/twl6040.h | 11 +++-------- sound/soc/codecs/twl6040.c | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) (limited to 'include/linux') diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index 47470cadf969..d9e05eabef33 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -120,15 +120,10 @@ #define TWL6040_LPLLFIN 0x08 #define TWL6040_HPLLSEL 0x10 -/* HSLCTL (0x10) fields */ +/* HSLCTL/R (0x10/0x11) fields */ -#define TWL6040_HSDACMODEL 0x02 -#define TWL6040_HSDRVMODEL 0x08 - -/* HSRCTL (0x11) fields */ - -#define TWL6040_HSDACMODER 0x02 -#define TWL6040_HSDRVMODER 0x08 +#define TWL6040_HSDACMODE (1 << 1) +#define TWL6040_HSDRVMODE (1 << 3) /* VIBCTLL (0x18) fields */ diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 760701e89fa7..68e52c9282a5 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -642,7 +642,7 @@ static int pga_event(struct snd_soc_dapm_widget *w, static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) { int hslctl, hsrctl; - int mask = TWL6040_HSDRVMODEL | TWL6040_HSDACMODEL; + int mask = TWL6040_HSDRVMODE | TWL6040_HSDACMODE; hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL); hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL); -- cgit v1.2.3 From b1f43bf3a52b085b786adf0b719712df574955f9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 24 May 2011 17:35:40 +0800 Subject: mfd: Add WM1811 support The WM1811 is mostly register compatible with the WM8994 and WM8958, providing a high performance audio hub CODEC in a small form factor suitable for ultra compact system designs. Signed-off-by: Mark Brown Acked-by: Samuel Ortiz --- drivers/mfd/wm8994-core.c | 27 +++++++++++++++++++++++++++ include/linux/mfd/wm8994/core.h | 1 + 2 files changed, 28 insertions(+) (limited to 'include/linux') diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index 96479c9b1728..7c13ab2c64a4 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -243,6 +243,18 @@ static struct mfd_cell wm8994_devs[] = { * and should be handled via the standard regulator API supply * management. */ +static const char *wm1811_main_supplies[] = { + "DBVDD1", + "DBVDD2", + "DBVDD3", + "DCVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD1", + "SPKVDD2", +}; + static const char *wm8994_main_supplies[] = { "DBVDD", "DCVDD", @@ -401,6 +413,9 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) } switch (wm8994->type) { + case WM1811: + wm8994->num_supplies = ARRAY_SIZE(wm1811_main_supplies); + break; case WM8994: wm8994->num_supplies = ARRAY_SIZE(wm8994_main_supplies); break; @@ -421,6 +436,10 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) } switch (wm8994->type) { + case WM1811: + for (i = 0; i < ARRAY_SIZE(wm1811_main_supplies); i++) + wm8994->supplies[i].supply = wm1811_main_supplies[i]; + break; case WM8994: for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++) wm8994->supplies[i].supply = wm8994_main_supplies[i]; @@ -454,6 +473,13 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) goto err_enable; } switch (ret) { + case 0x1811: + devname = "WM1811"; + if (wm8994->type != WM1811) + dev_warn(wm8994->dev, "Device registered as type %d\n", + wm8994->type); + wm8994->type = WM1811; + break; case 0x8994: devname = "WM8994"; if (wm8994->type != WM8994) @@ -651,6 +677,7 @@ static int wm8994_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id wm8994_i2c_id[] = { + { "wm1811", WM1811 }, { "wm8994", WM8994 }, { "wm8958", WM8958 }, { } diff --git a/include/linux/mfd/wm8994/core.h b/include/linux/mfd/wm8994/core.h index f0b69cdae41c..bfb221b3abf7 100644 --- a/include/linux/mfd/wm8994/core.h +++ b/include/linux/mfd/wm8994/core.h @@ -20,6 +20,7 @@ enum wm8994_type { WM8994 = 0, WM8958 = 1, + WM1811 = 2, }; struct regulator_dev; -- cgit v1.2.3 From 81204c84ca46604a04ab3d43ccfa1e464e6b1303 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 24 May 2011 17:35:53 +0800 Subject: ASoC: Add WM1811 support The WM1811 is mostly register compatible with the WM8994 and WM8958, providing a high performance audio hub CODEC in a small form factor suitable for ultra compact system designs. Signed-off-by: Mark Brown --- include/linux/mfd/wm8994/registers.h | 4 ++ sound/soc/codecs/wm8994.c | 83 +++++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/include/linux/mfd/wm8994/registers.h b/include/linux/mfd/wm8994/registers.h index 83ecdcd8aaf9..fae295048a8b 100644 --- a/include/linux/mfd/wm8994/registers.h +++ b/include/linux/mfd/wm8994/registers.h @@ -2069,6 +2069,10 @@ /* * R96 (0x60) - Analogue HP (1) */ +#define WM1811_HPOUT1_ATTN 0x0100 /* HPOUT1_ATTN */ +#define WM1811_HPOUT1_ATTN_MASK 0x0100 /* HPOUT1_ATTN */ +#define WM1811_HPOUT1_ATTN_SHIFT 8 /* HPOUT1_ATTN */ +#define WM1811_HPOUT1_ATTN_WIDTH 1 /* HPOUT1_ATTN */ #define WM8994_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ #define WM8994_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ #define WM8994_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index e5372675123d..5e8d66d085f5 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -283,6 +283,7 @@ static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0); static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0); static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); static const DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0); +static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0); #define WM8994_DRC_SWITCH(xname, reg, shift) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ @@ -703,6 +704,13 @@ SOC_SINGLE_TLV("AIF2DAC Noise Gate Threshold Volume", 7, 1, ng_tlv), }; +static const struct snd_kcontrol_new wm1811_snd_controls[] = { +SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0, + mixin_boost_tlv), +SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0, + mixin_boost_tlv), +}; + static int clk_sys_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -2053,6 +2061,15 @@ static int wm8994_set_bias_level(struct snd_soc_codec *codec, WM8958_CP_DISCH); } break; + + case WM1811: + if (wm8994->revision < 2) { + snd_soc_write(codec, 0x102, 0x3); + snd_soc_write(codec, 0x5d, 0x7e); + snd_soc_write(codec, 0x5e, 0x0); + snd_soc_write(codec, 0x102, 0x0); + } + break; } /* Discharge LINEOUT1 & 2 */ @@ -2168,10 +2185,18 @@ static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) /* The AIF2 format configuration needs to be mirrored to AIF3 * on WM8958 if it's in use so just do it all the time. */ - if (control->type == WM8958 && dai->id == 2) - snd_soc_update_bits(codec, WM8958_AIF3_CONTROL_1, - WM8994_AIF1_LRCLK_INV | - WM8958_AIF3_FMT_MASK, aif1); + switch (control->type) { + case WM1811: + case WM8958: + if (dai->id == 2) + snd_soc_update_bits(codec, WM8958_AIF3_CONTROL_1, + WM8994_AIF1_LRCLK_INV | + WM8958_AIF3_FMT_MASK, aif1); + break; + + default: + break; + } snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV | @@ -2258,6 +2283,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, break; case 3: switch (control->type) { + case WM1811: case WM8958: aif1_reg = WM8958_AIF3_CONTROL_1; break; @@ -2384,6 +2410,7 @@ static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream, switch (dai->id) { case 3: switch (control->type) { + case WM1811: case WM8958: aif1_reg = WM8958_AIF3_CONTROL_1; break; @@ -2614,6 +2641,7 @@ static int wm8994_suspend(struct snd_soc_codec *codec, pm_message_t state) case WM8994: snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, 0); break; + case WM1811: case WM8958: snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, WM8958_MICD_ENA, 0); @@ -2682,6 +2710,7 @@ static int wm8994_resume(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, WM8994_MICD_ENA); break; + case WM1811: case WM8958: if (wm8994->jack_cb) snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, @@ -2980,8 +3009,13 @@ int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack, struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994 *control = codec->control_data; - if (control->type != WM8958) + switch (control->type) { + case WM1811: + case WM8958: + break; + default: return -EINVAL; + } if (jack) { if (!cb) { @@ -3135,6 +3169,24 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->hubs.dcs_readback_mode = 1; break; + case WM1811: + wm8994->hubs.dcs_readback_mode = 2; + wm8994->hubs.no_series_update = 1; + + switch (wm8994->revision) { + case 0: + case 1: + wm8994->hubs.dcs_codes_l = -7; + wm8994->hubs.dcs_codes_r = -4; + break; + default: + break; + } + + snd_soc_update_bits(codec, WM8994_ANALOGUE_HP_1, + WM1811_HPOUT1_ATTN, WM1811_HPOUT1_ATTN); + break; + default: break; } @@ -3195,6 +3247,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; case WM8958: + case WM1811: if (wm8994->micdet_irq) { ret = request_threaded_irq(wm8994->micdet_irq, NULL, wm8958_mic_irq, @@ -3357,6 +3410,19 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) ARRAY_SIZE(wm8994_dac_widgets)); } break; + + case WM1811: + snd_soc_add_controls(codec, wm8958_snd_controls, + ARRAY_SIZE(wm8958_snd_controls)); + snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets, + ARRAY_SIZE(wm8958_dapm_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets, + ARRAY_SIZE(wm8994_lateclk_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets, + ARRAY_SIZE(wm8994_adc_widgets)); + snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets, + ARRAY_SIZE(wm8994_dac_widgets)); + break; } @@ -3393,6 +3459,12 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8958_dsp2_init(codec); break; + case WM1811: + snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon, + ARRAY_SIZE(wm8994_lateclk_intercon)); + snd_soc_dapm_add_routes(dapm, wm8958_intercon, + ARRAY_SIZE(wm8958_intercon)); + break; } return 0; @@ -3448,6 +3520,7 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) wm8994); break; + case WM1811: case WM8958: if (wm8994->micdet_irq) free_irq(wm8994->micdet_irq, wm8994); -- cgit v1.2.3 From 1e036f65329901a2432c92132b785654944743d9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 12 Oct 2011 11:57:53 +0300 Subject: Input: twl6040: Simplify vibra regsiter definitions The bits within the two control registers (for left and right channel) are identical. Use common names for the bits acros the two register. Also add the missing definition for the path selection bit. Signed-off-by: Peter Ujfalusi Acked-by: Dmitry Torokhov Signed-off-by: Mark Brown --- drivers/input/misc/twl6040-vibra.c | 12 ++++++------ include/linux/mfd/twl6040.h | 20 +++++++------------- 2 files changed, 13 insertions(+), 19 deletions(-) (limited to 'include/linux') diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c index 154b7a324d67..cb741858229a 100644 --- a/drivers/input/misc/twl6040-vibra.c +++ b/drivers/input/misc/twl6040-vibra.c @@ -74,12 +74,12 @@ static irqreturn_t twl6040_vib_irq_handler(int irq, void *data) if (status & TWL6040_VIBLOCDET) { dev_warn(info->dev, "Left Vibrator overcurrent detected\n"); twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL, - TWL6040_VIBENAL); + TWL6040_VIBENA); } if (status & TWL6040_VIBROCDET) { dev_warn(info->dev, "Right Vibrator overcurrent detected\n"); twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR, - TWL6040_VIBENAR); + TWL6040_VIBENA); } return IRQ_HANDLED; @@ -104,16 +104,16 @@ static void twl6040_vibra_enable(struct vibra_info *info) * overcurrent detection */ twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, - TWL6040_VIBENAL | TWL6040_VIBCTRLL); + TWL6040_VIBENA | TWL6040_VIBCTRL); twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, - TWL6040_VIBENAR | TWL6040_VIBCTRLR); + TWL6040_VIBENA | TWL6040_VIBCTRL); usleep_range(3000, 3500); } twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, - TWL6040_VIBENAL); + TWL6040_VIBENA); twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, - TWL6040_VIBENAR); + TWL6040_VIBENA); info->enabled = true; } diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index d9e05eabef33..e6c755db4560 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -125,24 +125,18 @@ #define TWL6040_HSDACMODE (1 << 1) #define TWL6040_HSDRVMODE (1 << 3) -/* VIBCTLL (0x18) fields */ +/* VIBCTLL/R (0x18/0x1A) fields */ -#define TWL6040_VIBENAL 0x01 -#define TWL6040_VIBCTRLL 0x04 -#define TWL6040_VIBCTRLLP 0x08 -#define TWL6040_VIBCTRLLN 0x10 +#define TWL6040_VIBENA (1 << 0) +#define TWL6040_VIBSEL (1 << 1) +#define TWL6040_VIBCTRL (1 << 2) +#define TWL6040_VIBCTRL_P (1 << 3) +#define TWL6040_VIBCTRL_N (1 << 4) -/* VIBDATL (0x19) fields */ +/* VIBDATL/R (0x19/0x1B) fields */ #define TWL6040_VIBDAT_MAX 0x64 -/* VIBCTLR (0x1A) fields */ - -#define TWL6040_VIBENAR 0x01 -#define TWL6040_VIBCTRLR 0x04 -#define TWL6040_VIBCTRLRP 0x08 -#define TWL6040_VIBCTRLRN 0x10 - /* GPOCTL (0x1E) fields */ #define TWL6040_GPO1 0x01 -- cgit v1.2.3 From 31b402e3c9eb839a00530511dcf7de47bbf723f6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 12 Oct 2011 11:57:54 +0300 Subject: MFD: twl6040: Cache the vibra control registers The vibra control register will be used from the ASoC codec driver as well. In order to avoid latency issues caused by I2C read access, cache the two control register within the core driver, so we do not need to reach out to the chip to read it back. Signed-off-by: Peter Ujfalusi Acked-by: Samuel Ortiz Signed-off-by: Mark Brown --- drivers/mfd/twl6040-core.c | 19 +++++++++++++++---- include/linux/mfd/twl6040.h | 1 + 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c index 7dc8c4715001..75987c8ca049 100644 --- a/drivers/mfd/twl6040-core.c +++ b/drivers/mfd/twl6040-core.c @@ -34,16 +34,24 @@ #include #include +#define VIBRACTRL_MEMBER(reg) ((reg == TWL6040_REG_VIBCTLL) ? 0 : 1) + int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg) { int ret; u8 val = 0; mutex_lock(&twl6040->io_mutex); - ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); - if (ret < 0) { - mutex_unlock(&twl6040->io_mutex); - return ret; + /* Vibra control registers from cache */ + if (unlikely(reg == TWL6040_REG_VIBCTLL || + reg == TWL6040_REG_VIBCTLR)) { + val = twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)]; + } else { + ret = twl_i2c_read_u8(TWL_MODULE_AUDIO_VOICE, &val, reg); + if (ret < 0) { + mutex_unlock(&twl6040->io_mutex); + return ret; + } } mutex_unlock(&twl6040->io_mutex); @@ -57,6 +65,9 @@ int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val) mutex_lock(&twl6040->io_mutex); ret = twl_i2c_write_u8(TWL_MODULE_AUDIO_VOICE, val, reg); + /* Cache the vibra control registers */ + if (reg == TWL6040_REG_VIBCTLL || reg == TWL6040_REG_VIBCTLR) + twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)] = val; mutex_unlock(&twl6040->io_mutex); return ret; diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index e6c755db4560..2f8585a4c74b 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -184,6 +184,7 @@ struct twl6040 { int audpwron; int power_count; int rev; + u8 vibra_ctrl_cache[2]; int pll; unsigned int sysclk; -- cgit v1.2.3 From 70601ec10a2450369d554e49d708ab26deb17b66 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 12 Oct 2011 11:57:55 +0300 Subject: MFD: twl6040: function to query the vibra status for clients If the client only interested, if any of the vibra channels enabled, or if any of the channels are set to receive audio data via PDM. This function targets mainly the vibra driver, so it can check if it is allowed to execute effects ot not. Signed-off-by: Peter Ujfalusi Acked-by: Samuel Ortiz Signed-off-by: Mark Brown --- drivers/mfd/twl6040-core.c | 12 ++++++++++++ include/linux/mfd/twl6040.h | 3 +++ 2 files changed, 15 insertions(+) (limited to 'include/linux') diff --git a/drivers/mfd/twl6040-core.c b/drivers/mfd/twl6040-core.c index 75987c8ca049..268f80fd0439 100644 --- a/drivers/mfd/twl6040-core.c +++ b/drivers/mfd/twl6040-core.c @@ -444,6 +444,18 @@ unsigned int twl6040_get_sysclk(struct twl6040 *twl6040) } EXPORT_SYMBOL(twl6040_get_sysclk); +/* Get the combined status of the vibra control register */ +int twl6040_get_vibralr_status(struct twl6040 *twl6040) +{ + u8 status; + + status = twl6040->vibra_ctrl_cache[0] | twl6040->vibra_ctrl_cache[1]; + status &= (TWL6040_VIBENA | TWL6040_VIBSEL); + + return status; +} +EXPORT_SYMBOL(twl6040_get_vibralr_status); + static struct resource twl6040_vibra_rsrc[] = { { .flags = IORESOURCE_IRQ, diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index 2f8585a4c74b..87a4778ed4b0 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -209,10 +209,13 @@ int twl6040_get_pll(struct twl6040 *twl6040); unsigned int twl6040_get_sysclk(struct twl6040 *twl6040); int twl6040_irq_init(struct twl6040 *twl6040); void twl6040_irq_exit(struct twl6040 *twl6040); +/* Get the combined status of the vibra control register */ +int twl6040_get_vibralr_status(struct twl6040 *twl6040); static inline int twl6040_get_revid(struct twl6040 *twl6040) { return twl6040->rev; } + #endif /* End of __TWL6040_CODEC_H__ */ -- cgit v1.2.3 From 33b6816ca3a4027a1b5444c83c1c24c0b1991262 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 12 Oct 2011 14:46:02 +0300 Subject: ASoC: twl6040: Workaround for headset DC offset caused pop noise Both Headset DAC need to be turned on/off at the same time before any of the output drivers are enabled (HS Left/Right, Earpiece). Move the HS DAC enable code to sequenced DAPM_SUPPLY, and attach it to the DACs. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- include/linux/mfd/twl6040.h | 1 + sound/soc/codecs/twl6040.c | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/include/linux/mfd/twl6040.h b/include/linux/mfd/twl6040.h index 87a4778ed4b0..2463c2619596 100644 --- a/include/linux/mfd/twl6040.h +++ b/include/linux/mfd/twl6040.h @@ -122,6 +122,7 @@ /* HSLCTL/R (0x10/0x11) fields */ +#define TWL6040_HSDACENA (1 << 0) #define TWL6040_HSDACMODE (1 << 1) #define TWL6040_HSDRVMODE (1 << 3) diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 864849838f4d..636923051ad3 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -654,6 +654,26 @@ static int headset_power_mode(struct snd_soc_codec *codec, int high_perf) static int twl6040_hs_dac_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { + struct snd_soc_codec *codec = w->codec; + u8 hslctl, hsrctl; + + /* + * Workaround for Headset DC offset caused pop noise: + * Both HS DAC need to be turned on (before the HS driver) and off at + * the same time. + */ + hslctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSLCTL); + hsrctl = twl6040_read_reg_cache(codec, TWL6040_REG_HSRCTL); + if (SND_SOC_DAPM_EVENT_ON(event)) { + hslctl |= TWL6040_HSDACENA; + hsrctl |= TWL6040_HSDACENA; + } else { + hslctl &= ~TWL6040_HSDACENA; + hsrctl &= ~TWL6040_HSDACENA; + } + twl6040_write(codec, TWL6040_REG_HSLCTL, hslctl); + twl6040_write(codec, TWL6040_REG_HSRCTL, hsrctl); + msleep(1); return 0; } @@ -1103,14 +1123,8 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { TWL6040_REG_DMICBCTL, 4, 0), /* DACs */ - SND_SOC_DAPM_DAC_E("HSDAC Left", "Headset Playback", - TWL6040_REG_HSLCTL, 0, 0, - twl6040_hs_dac_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_DAC_E("HSDAC Right", "Headset Playback", - TWL6040_REG_HSRCTL, 0, 0, - twl6040_hs_dac_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC("HSDAC Left", "Headset Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("HSDAC Right", "Headset Playback", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC_E("HFDAC Left", "Handsfree Playback", TWL6040_REG_HFLCTL, 0, 0, twl6040_power_mode_event, @@ -1175,6 +1189,9 @@ static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { NULL, 0), SND_SOC_DAPM_SUPPLY("Vibra Right Control", TWL6040_REG_VIBCTLR, 2, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("HSDAC Power", 1, SND_SOC_NOPM, 0, 0, + twl6040_hs_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), /* Analog playback PGAs */ SND_SOC_DAPM_PGA("HF Left PGA", @@ -1204,6 +1221,9 @@ static const struct snd_soc_dapm_route intercon[] = { {"AFMAmpL", NULL, "AFML"}, {"AFMAmpR", NULL, "AFMR"}, + {"HSDAC Left", NULL, "HSDAC Power"}, + {"HSDAC Right", NULL, "HSDAC Power"}, + {"Headset Left Playback", "HS DAC", "HSDAC Left"}, {"Headset Left Playback", "Line-In amp", "AFMAmpL"}, -- cgit v1.2.3