Hi Alsa devel,
I have been working on TWL4030 codec driver for ALSA SOC. I have taken sound/soc/codec/twl4030.c as reference from main line
This Patch adds some kcontrols, widgets and interconnection map for some of the TWL4030 ASOC codec I was building it for a custom board as it was for OVERO
Suggestions on the DAPM part of the driver would be helpful
Thanks in advance.
The patch is as follows.
--- twl4030.c 2008-11-19 12:04:32.000000000 +0530 +++ /home/chnaveen/Desktop/twl4030.c 2008-11-21 15:08:06.000000000 +0530 @@ -16,7 +16,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA - * + * Modified by : Naveen Krishna Ch + * Added Kcontrols for OMAP3 WaterlooBoard + * Dated : 28th october 2008 */
#include <linux/module.h> @@ -29,24 +31,27 @@ #include <linux/i2c/twl4030.h> #include <sound/core.h> #include <sound/pcm.h> +#include <sound/jack.h> #include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> - +#include <asm/arch/twl4030.h> #include "twl4030.h"
+static int twl4030_jack_func; + /* * twl4030 register cache & default register settings */ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* this register not used */ - 0x93, /* REG_CODEC_MODE (0x1) */ + 0x03, /* REG_CODEC_MODE (0x1) */ 0xc3, /* REG_OPTION (0x2) */ 0x00, /* REG_UNKNOWN (0x3) */ 0x00, /* REG_MICBIAS_CTL (0x4) */ - 0x24, /* REG_ANAMICL (0x5) */ - 0x04, /* REG_ANAMICR (0x6) */ + 0xb0, /* REG_ANAMICL (0x5) */ + 0x10, /* REG_ANAMICR (0x6) */ 0x0a, /* REG_AVADC_CTL (0x7) */ 0x00, /* REG_ADCMICSEL (0x8) */ 0x00, /* REG_DIGMIXING (0x9) */ @@ -67,22 +72,22 @@ 0x00, /* REG_ARX2VTXPGA (0x18) */ 0x00, /* REG_ARXL1_APGA_CTL (0x19) */ 0x00, /* REG_ARXR1_APGA_CTL (0x1A) */ - 0x4b, /* REG_ARXL2_APGA_CTL (0x1B) */ - 0x4b, /* REG_ARXR2_APGA_CTL (0x1C) */ + 0x2b, /* REG_ARXL2_APGA_CTL (0x1B) */ + 0x2b, /* REG_ARXR2_APGA_CTL (0x1C) */ 0x00, /* REG_ATX2ARXPGA (0x1D) */ 0x00, /* REG_BT_IF (0x1E) */ 0x00, /* REG_BTPGA (0x1F) */ 0x00, /* REG_BTSTPGA (0x20) */ - 0x00, /* REG_EAR_CTL (0x21) */ - 0x24, /* REG_HS_SEL (0x22) */ - 0x0a, /* REG_HS_GAIN_SET (0x23) */ - 0x00, /* REG_HS_POPN_SET (0x24) */ - 0x00, /* REG_PREDL_CTL (0x25) */ - 0x00, /* REG_PREDR_CTL (0x26) */ - 0x00, /* REG_PRECKL_CTL (0x27) */ - 0x00, /* REG_PRECKR_CTL (0x28) */ - 0x00, /* REG_HFL_CTL (0x29) */ - 0x00, /* REG_HFR_CTL (0x2A) */ + 0x20, /* REG_EAR_CTL (0x21) */ + 0x00, /* REG_HS_SEL (0x22) */ + 0x05, /* REG_HS_GAIN_SET (0x23) */ + 0x42, /* REG_HS_POPN_SET (0x24) */ + 0x20, /* REG_PREDL_CTL (0x25) */ + 0x20, /* REG_PREDR_CTL (0x26) */ + 0x20, /* REG_PRECKL_CTL (0x27) */ + 0x20, /* REG_PRECKR_CTL (0x28) */ + 0x1f, /* REG_HFL_CTL (0x29) */ + 0x1f, /* REG_HFR_CTL (0x2A) */ 0x00, /* REG_ALC_CTL (0x2B) */ 0x00, /* REG_ALC_SET1 (0x2C) */ 0x00, /* REG_ALC_SET2 (0x2D) */ @@ -112,9 +117,40 @@ 0x00, /* REG_VIBRA_CTL (0x45) */ 0x00, /* REG_VIBRA_SET (0x46) */ 0x00, /* REG_VIBRA_PWM_SET (0x47) */ - 0x00, /* REG_ANAMIC_GAIN (0x48) */ + 0x24, /* REG_ANAMIC_GAIN (0x48) */ 0x00, /* REG_MISC_SET_2 (0x49) */ }; +static void twl4030_ext_control(struct snd_soc_codec *codec) +{ + if (twl4030_jack_func) + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(codec, "Headphone Jack"); + + snd_soc_dapm_sync(codec); +} + +static int twl4030_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = twl4030_jack_func; + + return 0; +} + +static int twl4030_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (twl4030_jack_func == ucontrol->value.integer.value[0]) + return 0; + + twl4030_jack_func = ucontrol->value.integer.value[0]; + twl4030_ext_control(codec); + + return 1; +}
/* * read twl4030 register cache @@ -188,14 +224,83 @@ twl4030_write(codec, i, twl4030_reg[i]);
} +static const char *jack_function[] = {"Off", "Headphone"}; + +static const struct soc_enum twl4030_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), +}; +
static const struct snd_kcontrol_new twl4030_snd_controls[] = { - SOC_DOUBLE_R("Master Playback Volume", - TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, - 0, 127, 0), - SOC_DOUBLE_R("Capture Volume", - TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, - 0, 127, 0), + + /* Master Playback Volume Controls */ + SOC_DOUBLE_R("Master PLayback Course Gain ctrl", + TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, + 6, 3, 0), + SOC_DOUBLE_R("Master Playback Fine Gain ctrl", + TWL4030_REG_ARXL2PGA, TWL4030_REG_ARXR2PGA, + 0, 63, 0), + + /* Playback Speaker volume controls */ + SOC_DOUBLE_R("Speaker R2+L2 Volume ctrls", + TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, + 3, 17, 0), + + /* Capture Gain controls */ + SOC_DOUBLE_R("Master Capture Gain ctrl", + TWL4030_REG_ATXL1PGA, TWL4030_REG_ATXR1PGA, + 0, 31, 0), + /* Loop gain controls*/ + SOC_DOUBLE("Loop Gain ctrl", TWL4030_REG_ATX2ARXPGA, + 3 , 0, 7, 0), + + SOC_DOUBLE("Main +Sub mic capture gain ctrl", + TWL4030_REG_ANAMIC_GAIN, 3 , 0, 5, 0), + + SOC_DOUBLE_R("External Speaker Volume control", + TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL, + 4, 3, 0), + + SOC_DOUBLE_R("Pre Car kit Volume control", + TWL4030_REG_PRECKL_CTL, TWL4030_REG_PRECKR_CTL, + 4, 3, 0), + + SOC_SINGLE("DACL2", TWL4030_REG_AVDAC_CTL, 3, 1, 0), + SOC_SINGLE("DACR2", TWL4030_REG_AVDAC_CTL, 2, 1, 0), + + SOC_ENUM_EXT("Jack Function", twl4030_enum[0], + twl4030_get_jack, twl4030_set_jack), +}; + +/* Right PGA Mixer control switches */ +static const struct snd_kcontrol_new twl4030_right_pga_mixer_controls[] = { + SOC_DAPM_SINGLE("HS Mic switch", TWL4030_REG_ANAMICL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux/FM right switch", TWL4030_REG_ANAMICR, 2, 1, 0), + SOC_DAPM_SINGLE("Sub Mic switch", TWL4030_REG_ANAMICR, 0, 1, 0), +}; + +/* Left PGA Mixer control switches */ +static const struct snd_kcontrol_new twl4030_left_pga_mixer_controls[] = { + SOC_DAPM_SINGLE("HS Mic switch", TWL4030_REG_ANAMICL, 1, 1, 0), + SOC_DAPM_SINGLE("Aux/FM left switch", TWL4030_REG_ANAMICL, 2, 1, 0), + SOC_DAPM_SINGLE("Main Mic switch", TWL4030_REG_ANAMICL, 0, 1, 0), +}; + +/* Right DACR2 Mixer */ +static const struct snd_kcontrol_new twl4030_dacr2_mixer_controls[] = { + SOC_DAPM_SINGLE("Headset-R switch", TWL4030_REG_HS_SEL, 5, 1, 0), + SOC_DAPM_SINGLE("Handsfree-R switch", TWL4030_REG_HFR_CTL, 5, 1, 0), + SOC_DAPM_SINGLE("PRECK-R switch", TWL4030_REG_PRECKR_CTL, 2, 1, 0), + SOC_DAPM_DOUBLE("E class-D amp-R switch", TWL4030_REG_PREDR_CTL, 3, 2, 1, 0, 0), +}; + +/* Left DACL2 Mixer */ +static const struct snd_kcontrol_new twl4030_dacl2_mixer_controls[] = { + SOC_DAPM_SINGLE("Headset-L switch", TWL4030_REG_HS_SEL, 2, 1, 0), + SOC_DAPM_SINGLE("Handsfree-L switch", TWL4030_REG_HFL_CTL, 5, 1, 0), + SOC_DAPM_SINGLE("Ear peice switch", TWL4030_REG_EAR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("PRECK-L switch", TWL4030_REG_PRECKL_CTL, 2, 1, 0), + SOC_DAPM_DOUBLE("E class-D amp-L switch", TWL4031_REG_PREDL_CTL, 3, 2, 1, 0, 0), };
/* add non dapm controls */ @@ -215,27 +320,96 @@ }
static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { - SND_SOC_DAPM_INPUT("INL"), - SND_SOC_DAPM_INPUT("INR"), - - SND_SOC_DAPM_OUTPUT("OUTL"), - SND_SOC_DAPM_OUTPUT("OUTR"), - - SND_SOC_DAPM_DAC("DACL", "Left Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DACR", "Right Playback", SND_SOC_NOPM, 0, 0), - - SND_SOC_DAPM_ADC("ADCL", "Left Capture", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_ADC("ADCR", "Right Capture", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_HP("Headphone Jack", NULL), + + /* Left ADC for capture */ + SND_SOC_DAPM_ADC("ADCL", "Left Capture ADC", TWL4030_REG_AVADC_CTL, 3, 0), + + /* dapm widget (path domain) for left PGA mixer */ + SND_SOC_DAPM_MIXER("LINEIN-L", SND_SOC_NOPM, 0, 0, + &twl4030_left_pga_mixer_controls[0], + ARRAY_SIZE(twl4030_left_pga_mixer_controls)), + + /* Right ADC for capture*/ + SND_SOC_DAPM_ADC("ADCR", "Right Capture ADC", TWL4030_REG_AVADC_CTL, 1, 0), + + /* dapm widget (path domain) for right PGA mixer */ + SND_SOC_DAPM_MIXER("LINEIN-R", SND_SOC_NOPM, 0, 0, + &twl4030_right_pga_mixer_controls[0], + ARRAY_SIZE(twl4030_right_pga_mixer_controls)), + + /* dapm widget (path domain) for left DACL2 Mixer */ + + SND_SOC_DAPM_MIXER("DACL2 Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dacl2_mixer_controls[0], + ARRAY_SIZE(twl4030_dacl2_mixer_controls)), + + /* dapm widget (path domain) for DACR2 Mixer */ + SND_SOC_DAPM_MIXER("DACR2 Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dacr2_mixer_controls[0], + ARRAY_SIZE(twl4030_dacr2_mixer_controls)), + + /* Inputs Left*/ + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("Aux/fm left"), + SND_SOC_DAPM_INPUT("Main mic"), + + /* Inputs Right*/ + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("Aux/fm right"), + SND_SOC_DAPM_INPUT("Sub mic"), + + /* Outputs Left*/ + SND_SOC_DAPM_OUTPUT("HSOL"), + SND_SOC_DAPM_OUTPUT("IHF_LEFT"), + SND_SOC_DAPM_OUTPUT("EAR"), + SND_SOC_DAPM_OUTPUT("E class-D L"), + SND_SOC_DAPM_OUTPUT("PRECK-L"); + + /* Outputs Right */ + SND_SOC_DAPM_OUTPUT("HSOR"), + SND_SOC_DAPM_OUTPUT("IHF_RIGHT"), + SND_SOC_DAPM_OUTPUT("E class-D R"), + SND_SOC_DAPM_OUTPUT("PRECK-R"); };
static const struct snd_soc_dapm_route intercon[] = { - /* outputs */ - {"OUTL", NULL, "DACL"}, - {"OUTR", NULL, "DACR"}, - - /* inputs */ - {"ADCL", NULL, "INL"}, - {"ADCR", NULL, "INR"}, + + /* ******** Left input ******** */ + {"LINEIN-L", "HS Mic switch", "HSMIC"}, + {"LINEIN-L", "Aux/FM left switch", "Aux/fm left"}, + {"LINEIN-L", "Main Mic switch", "Main mic"}, + + {"ADCL", NULL, "LINEIN-L"}, + + + /* ******** Right Input ******** */ + {"LINEIN-R", "HS Mic switch", "HSMIC"}, + {"LINEIN-R", "Aux/FM right switch", "Aux/fm right"}, + {"LINEIN-R", "Sub Mic switch", "Sub mic"}, + + {"ADCR", NULL, "LINEIN-R"}, + + /* ******** Left Output ******** */ + //{"DACL2", NULL, "DACL2 Mixer"}, + + {"DACL2 Mixer", "Headset-L switch", "HSOL"}, + {"DACL2 Mixer", "Handsfree-L switch", "IHF_LEFT"}, + {"DACL2 Mixer", "Ear peice switch", "EAR"}, + {"DACL2 Mixer", "PRECK-L switch", "PRECK-L"}, + {"DACL2 Mixer","E class-D amp-L switch","E class-D L"}, + + {"Headphone Jack",NULL, "HSOL"}, + /* ******** Right Output ******** */ + //{"DACR2", NULL, "DACR2 Mixer"}, + + {"DACR2 Mixer", "Headset-R switch","HSOR"}, + {"DACR2 Mixer", "Handsfree-R switch","IHF_RIGHT"}, + {"DACR2 Mixer", "PRECK-R switch", "PRECK-R"}, + {"DACR2 Mixer","E class-D amp-R switch","E class-D R"}, + + {"Headphone Jack",NULL, "HSOR"}, };
static int twl4030_add_widgets(struct snd_soc_codec *codec) @@ -280,7 +454,7 @@ /* toggle CODECPDZ as per TRM */ twl4030_clear_codecpdz(codec); twl4030_set_codecpdz(codec); - + /* program anti-pop with bias ramp delay */ popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); popn &= TWL4030_RAMP_DELAY; @@ -384,6 +558,9 @@ case 48000: mode |= TWL4030_APLL_RATE_48000; break; + case 96000: + mode |= TWL4030_APLL_RATE_96000; + break; default: printk(KERN_ERR "TWL4030 hw params: unknown rate %d\n", params_rate(params)); @@ -434,6 +611,7 @@ u8 infreq;
switch (freq) { + case 19200000: infreq = TWL4030_APLL_INFREQ_19200KHZ; break; @@ -504,20 +682,24 @@ return 0; }
-#define TWL4030_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define TWL4030_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
struct snd_soc_dai twl4030_dai = { .name = "twl4030", .playback = { .stream_name = "Playback", - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .rates = TWL4030_RATES, .formats = TWL4030_FORMATS,}, .capture = { .stream_name = "Capture", - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .rates = TWL4030_RATES, .formats = TWL4030_FORMATS,}, @@ -616,13 +798,14 @@
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); if (codec == NULL) - return -ENOMEM; - + return -ENOMEM; + socdev->codec = codec; mutex_init(&codec->mutex); INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - + + twl4030_socdev = socdev; twl4030_init(socdev);
Patch ends..
I need suggestions on the DAPM part of the driver.
Thanks and Regards,
(: Naveen Krishna Ch :)