[alsa-devel] [PATCH] ASoC: TWL4030: Add capture source selection and mic gain control
The TWL4030 codec device has two ADCs. Both of them can have several inputs routed to them, but TRM says that only one source can be selected for every ADC, even though every source has a dedicated bit in the registers.
This patch adds controls for capture source controls. When a microphone is selected, handlers automatically enable bias power (disable when deselected). The patch also adds microphone gain control. Microphone and line input recording tested on OMAP3 pandora board.
Signed-off-by: Grazvydas Ignotas notasas@gmail.com --- sound/soc/codecs/twl4030.c | 139 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/twl4030.h | 7 ++ 2 files changed, 146 insertions(+), 0 deletions(-)
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index ffd5120..755a872 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -347,6 +347,130 @@ static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, return err; }
+static int twl4030_get_left_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + int result = 0; + + /* one bit must be set a time */ + reg &= TWL4030_CKMIC_EN | TWL4030_AUXL_EN | TWL4030_HSMIC_EN + | TWL4030_MAINMIC_EN; + if (reg != 0) { + while ((reg & 1) == 0) { + result++; + reg >>= 1; + } + } + + ucontrol->value.integer.value[0] = result; + return 0; +} + +static int twl4030_put_left_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + u8 anamicl, micbias; + + anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL); + anamicl &= ~(TWL4030_CKMIC_EN | TWL4030_AUXL_EN | TWL4030_HSMIC_EN + | TWL4030_MAINMIC_EN); + micbias = twl4030_read_reg_cache(codec, TWL4030_REG_MICBIAS_CTL); + micbias &= ~(TWL4030_HSMICBIAS_EN | TWL4030_MICBIAS1_EN); + + switch (value) { + case 0: + anamicl |= TWL4030_MAINMIC_EN; + micbias |= TWL4030_MICBIAS1_EN; + break; + case 1: + anamicl |= TWL4030_HSMIC_EN; + micbias |= TWL4030_HSMICBIAS_EN; + break; + case 2: + anamicl |= TWL4030_AUXL_EN; + break; + case 3: + anamicl |= TWL4030_CKMIC_EN; + break; + default: + return -EINVAL; + } + + twl4030_write(codec, TWL4030_REG_ANAMICL, anamicl); + twl4030_write(codec, TWL4030_REG_MICBIAS_CTL, micbias); + + return 1; +} + +static int twl4030_get_right_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR); + + reg &= TWL4030_SUBMIC_EN|TWL4030_AUXR_EN; + if (reg == TWL4030_SUBMIC_EN) + ucontrol->value.integer.value[0] = 0; + else if (reg == TWL4030_AUXR_EN) + ucontrol->value.integer.value[0] = 1; + + return 0; +} + +static int twl4030_put_right_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + int value = ucontrol->value.integer.value[0]; + u8 anamicr, micbias; + + anamicr = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR); + anamicr &= ~(TWL4030_SUBMIC_EN|TWL4030_AUXR_EN); + micbias = twl4030_read_reg_cache(codec, TWL4030_REG_MICBIAS_CTL); + micbias &= ~TWL4030_MICBIAS2_EN; + + switch (value) { + case 0: + anamicr |= TWL4030_SUBMIC_EN; + micbias |= TWL4030_MICBIAS2_EN; + break; + case 1: + anamicr |= TWL4030_AUXR_EN; + break; + default: + return -EINVAL; + } + + twl4030_write(codec, TWL4030_REG_ANAMICR, anamicr); + twl4030_write(codec, TWL4030_REG_MICBIAS_CTL, micbias); + + return 1; +} + +static const char *twl4030_left_in_sel[] = { + "Main Mic", + "Headset Mic", + "Line In", + "Carkit Mic", +}; + +static const char *twl4030_right_in_sel[] = { + "Sub Mic", + "Line In", +}; + +static const struct soc_enum twl4030_left_input_mux = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl4030_left_in_sel), + twl4030_left_in_sel); + +static const struct soc_enum twl4030_right_input_mux = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(twl4030_right_in_sel), + twl4030_right_in_sel); + /* * FGAIN volume control: * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) @@ -378,6 +502,12 @@ static DECLARE_TLV_DB_SCALE(output_tvl, -1200, 600, 1); */ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
+/* + * Gain control for microphone amplifier + * 0 dB to 30 dB in 6 dB steps + */ +static DECLARE_TLV_DB_SCALE(microphone_gain_tlv, 0, 600, 0); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { /* Common playback gain controls */ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", @@ -420,6 +550,15 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_DOUBLE_R_TLV("Capture Volume", TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, 0, 0x1f, 0, digital_capture_tlv), + + SOC_DOUBLE_TLV("Microphone Boost", TWL4030_REG_ANAMIC_GAIN, + 0, 3, 5, 0, microphone_gain_tlv), + + /* Capture source controls */ + SOC_ENUM_EXT("Left Capture Source", twl4030_left_input_mux, + twl4030_get_left_input, twl4030_put_left_input), + SOC_ENUM_EXT("Right Capture Source", twl4030_right_input_mux, + twl4030_get_right_input, twl4030_put_right_input), };
/* add non dapm controls */ diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 09865d9..8dae844 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -113,6 +113,13 @@ #define TWL4030_CODECPDZ 0x02 #define TWL4030_OPT_MODE 0x01
+/* TWL4030_REG_MICBIAS_CTL (0x04) Fields */ +#define TWL4030_MICBIAS2_CTL 0x40 +#define TWL4030_MICBIAS1_CTL 0x20 +#define TWL4030_HSMICBIAS_EN 0x04 +#define TWL4030_MICBIAS2_EN 0x02 +#define TWL4030_MICBIAS1_EN 0x01 + /* ANAMICL (0x05) Fields */ #define TWL4030_CNCL_OFFSET_START 0x80 #define TWL4030_OFFSET_CNCL_SEL 0x60
On Tue, Dec 02, 2008 at 01:07:28AM +0200, Grazvydas Ignotas wrote:
This patch adds controls for capture source controls. When a microphone is selected, handlers automatically enable bias power (disable when deselected). The patch also adds microphone gain control. Microphone and line input recording tested on OMAP3 pandora board.
Hrm. Disabling the gain like this is likely to cause trouble for jack detect. However, that's not implemented yet so not a problem currently.
+static int twl4030_get_right_input(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_codec *codec = kcontrol->private_data;
- u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR);
- reg &= TWL4030_SUBMIC_EN|TWL4030_AUXR_EN;
- if (reg == TWL4030_SUBMIC_EN)
ucontrol->value.integer.value[0] = 0;
- else if (reg == TWL4030_AUXR_EN)
ucontrol->value.integer.value[0] = 1;
- return 0;
This will report something undefined as selected when neither option is selected. Why not include a "None" option in the mux (the same applies to the other path)? A switch statement might make for clearer code here.
- SOC_DOUBLE_TLV("Microphone Boost", TWL4030_REG_ANAMIC_GAIN,
0, 3, 5, 0, microphone_gain_tlv),
Should be "Microphone Boost Volume" or possibly just "Microphone Volume".
On Tue, Dec 2, 2008 at 1:26 AM, Mark Brown broonie@sirena.org.uk wrote:
On Tue, Dec 02, 2008 at 01:07:28AM +0200, Grazvydas Ignotas wrote:
This patch adds controls for capture source controls. When a microphone is selected, handlers automatically enable bias power (disable when deselected). The patch also adds microphone gain control. Microphone and line input recording tested on OMAP3 pandora board.
Hrm. Disabling the gain like this is likely to cause trouble for jack detect. However, that's not implemented yet so not a problem currently.
Well the TRM doesn't mention jack detect at all (except headset detect using separate switch connected to GPIO). Or is that some software feature?
+static int twl4030_get_right_input(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = kcontrol->private_data;
u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICR);
reg &= TWL4030_SUBMIC_EN|TWL4030_AUXR_EN;
if (reg == TWL4030_SUBMIC_EN)
ucontrol->value.integer.value[0] = 0;
else if (reg == TWL4030_AUXR_EN)
ucontrol->value.integer.value[0] = 1;
return 0;
This will report something undefined as selected when neither option is selected. Why not include a "None" option in the mux (the same applies to the other path)? A switch statement might make for clearer code here.
Sounds good, will update.
SOC_DOUBLE_TLV("Microphone Boost", TWL4030_REG_ANAMIC_GAIN,
0, 3, 5, 0, microphone_gain_tlv),
Should be "Microphone Boost Volume" or possibly just "Microphone Volume".
Setting this to 0 still records from MICs, but the signal is rather weak, so it looks more like boost to me. This control also affects aux/line inputs if they are selected, and causes rather bad distortions for them if control is not set to 0. Perhaps this control could be disabled (or at least automatically set to 0) while aux/line inputs are selected? Can that be done? There are actually MICAMP_EN bits to enable microphone amplifiers, but if they are not set, aux/line recording won't work too (records some strange noise).
On Tue, Dec 02, 2008 at 12:15:51PM +0200, Grazvydas Ignotas wrote:
On Tue, Dec 2, 2008 at 1:26 AM, Mark Brown broonie@sirena.org.uk wrote:
Hrm. Disabling the gain like this is likely to cause trouble for jack detect. However, that's not implemented yet so not a problem currently.
Well the TRM doesn't mention jack detect at all (except headset detect using separate switch connected to GPIO). Or is that some software feature?
Hardware normally.
SOC_DOUBLE_TLV("Microphone Boost", TWL4030_REG_ANAMIC_GAIN,
0, 3, 5, 0, microphone_gain_tlv),
Should be "Microphone Boost Volume" or possibly just "Microphone Volume".
Setting this to 0 still records from MICs, but the signal is rather weak, so it looks more like boost to me. This control also affects aux/line inputs if they are selected, and causes rather bad
So Microphone Boost Volume then (the addition of Volume is the important bit here).
distortions for them if control is not set to 0. Perhaps this control could be disabled (or at least automatically set to 0) while aux/line inputs are selected? Can that be done?
Yes, have your custom control update it and use snd_ctl_notify() to kick applications if you update the user visible value. Even better would be to do something more fancy and use a custom control to present a shadowed control to user space so they don't see the adjustment.
Hello,
On Tuesday 02 December 2008 01:07:28 ext Grazvydas Ignotas wrote:
The TWL4030 codec device has two ADCs. Both of them can have several inputs routed to them, but TRM says that only one source can be selected for every ADC, even though every source has a dedicated bit in the registers.
This patch adds controls for capture source controls. When a microphone is selected, handlers automatically enable bias power (disable when deselected). The patch also adds microphone gain control. Microphone and line input recording tested on OMAP3 pandora board.
Do you have plans for the Digital microphones (DIGIMIC0, DIGIMIC1)? Since they are not connected to any of the ADCs, but have separate digital path towards the main CPU... One can use either Analog mic or the Digital mic for TX1 and TX2 (ADCMICSEL register).
Do you have plans for the Digital microphones (DIGIMIC0, DIGIMIC1)? Since they are not connected to any of the ADCs, but have separate digital path towards the main CPU...
Unfortunately I have no way to test that, so not at the moment.
participants (3)
-
Grazvydas Ignotas
-
Mark Brown
-
Peter Ujfalusi