From 1beb91f004e0efe83b933ca6c84a8b9935f4cf53 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 22 Mar 2010 19:30:54 +0000 Subject: [PATCH 01/14] ASoC: pandora - update DAPM pins Remove bogus TWL4030 pins. Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/omap3pandora.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index de10f76baded..87ce842fa2e8 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -188,8 +188,6 @@ static int omap3pandora_out_init(struct snd_soc_codec *codec) int ret; /* All TWL4030 output pins are floating */ - snd_soc_dapm_nc_pin(codec, "OUTL"); - snd_soc_dapm_nc_pin(codec, "OUTR"); snd_soc_dapm_nc_pin(codec, "EARPIECE"); snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); snd_soc_dapm_nc_pin(codec, "PREDRIVER"); From 1849235876b046e26a07e33972906bd23fbb8705 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Mon, 22 Mar 2010 19:35:06 +0000 Subject: [PATCH 02/14] ASoC: zoom2 - update DAPM pins Remove bogus twl4030 pins Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/zoom2.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c index f90a2ac888cf..50a94ee76ecc 100644 --- a/sound/soc/omap/zoom2.c +++ b/sound/soc/omap/zoom2.c @@ -181,9 +181,6 @@ static int zoom2_twl4030_init(struct snd_soc_codec *codec) snd_soc_dapm_nc_pin(codec, "CARKITMIC"); snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); - - snd_soc_dapm_nc_pin(codec, "OUTL"); - snd_soc_dapm_nc_pin(codec, "OUTR"); snd_soc_dapm_nc_pin(codec, "EARPIECE"); snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); snd_soc_dapm_nc_pin(codec, "PREDRIVER"); From cf134d5bfb19cdee922b95738ce3cfe86c0e8f7a Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 26 Mar 2010 20:05:54 +0000 Subject: [PATCH 03/14] ASoC: tlv320dac33 - disable regulators at i2c remove() Acked-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 3eddaec728c1..54b2a0508a11 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -1584,6 +1584,7 @@ static int __devexit dac33_i2c_remove(struct i2c_client *client) if (dac33->irq >= 0) free_irq(dac33->irq, &dac33->codec); + regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); destroy_workqueue(dac33->dac33_wq); From 7b4c734eead5ef0b1c95ec336ddd28e58e648676 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 29 Apr 2010 10:58:08 +0300 Subject: [PATCH 04/14] ASoC: TWL4030: AIF/APLL fix in DAPM domain This patch orders the APLL and AIF power sequence in case of HiFi (audio in TWL4030 terms) playback/capture. We also need to make sure that the AIF is running during playback/capture, when there is no valid DAPM route available. For this purpose I introduce these virtual widgets: /* To have complete playback route all the time */ DAPM_OUTPUT("Virtual HiFi OUT") /* Will keep AIF/APLL enabled */ /* To have complete capture route all the time */ DAPM_INPUT("Virtual HiFi IN") /* Will keep AIF/APLL enabled */ /* To have complete playback route for the voice module */ DAPM_OUTPUT("Virtual Voice OUT") /* Will keep APLL enabled */ The DAPM_SUPPLY widgets for APLL and AIF are placed in a way, that during any audio activity the needed configuration of AIF and APLL will be enabled (playback, capture, analog loopback, digital loopback, and voice activity). The apll reference counting code has been lifted, and modified from Liam Girdwood's earlier patch. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl4030.c | 82 ++++++++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 2e025a3a2618..12931f6d445b 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -123,6 +123,8 @@ struct twl4030_priv { struct snd_soc_codec codec; unsigned int codec_powered; + + /* reference counts of AIF/APLL users */ unsigned int apll_enabled; struct snd_pcm_substream *master_substream; @@ -259,22 +261,22 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable) { struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec); - int status; + int status = -1; - if (enable == twl4030->apll_enabled) - return; - - if (enable) - /* Enable PLL */ - status = twl4030_codec_enable_resource(TWL4030_CODEC_RES_APLL); - else - /* Disable PLL */ - status = twl4030_codec_disable_resource(TWL4030_CODEC_RES_APLL); + if (enable) { + twl4030->apll_enabled++; + if (twl4030->apll_enabled == 1) + status = twl4030_codec_enable_resource( + TWL4030_CODEC_RES_APLL); + } else { + twl4030->apll_enabled--; + if (!twl4030->apll_enabled) + status = twl4030_codec_disable_resource( + TWL4030_CODEC_RES_APLL); + } if (status >= 0) twl4030_write_reg_cache(codec, TWL4030_REG_APLL_CTL, status); - - twl4030->apll_enabled = enable; } static void twl4030_power_up(struct snd_soc_codec *codec) @@ -672,6 +674,31 @@ static int apll_event(struct snd_soc_dapm_widget *w, return 0; } +static int aif_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u8 audio_if; + + audio_if = twl4030_read_reg_cache(w->codec, TWL4030_REG_AUDIO_IF); + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable AIF */ + /* enable the PLL before we use it to clock the DAI */ + twl4030_apll_enable(w->codec, 1); + + twl4030_write(w->codec, TWL4030_REG_AUDIO_IF, + audio_if | TWL4030_AIF_EN); + break; + case SND_SOC_DAPM_POST_PMD: + /* disable the DAI before we stop it's source PLL */ + twl4030_write(w->codec, TWL4030_REG_AUDIO_IF, + audio_if & ~TWL4030_AIF_EN); + twl4030_apll_enable(w->codec, 0); + break; + } + return 0; +} + static void headset_ramp(struct snd_soc_codec *codec, int ramp) { struct snd_soc_device *socdev = codec->socdev; @@ -1180,6 +1207,11 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HFR"), SND_SOC_DAPM_OUTPUT("VIBRA"), + /* AIF and APLL clocks for running DAIs (including loopback) */ + SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"), + SND_SOC_DAPM_INPUT("Virtual HiFi IN"), + SND_SOC_DAPM_OUTPUT("Virtual Voice OUT"), + /* DACs */ SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback", SND_SOC_NOPM, 0, 0), @@ -1243,7 +1275,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SUPPLY("APLL Enable", SND_SOC_NOPM, 0, 0, apll_event, SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY("AIF Enable", TWL4030_REG_AUDIO_IF, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("AIF Enable", SND_SOC_NOPM, 0, 0, aif_event, + SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), /* Output MIXER controls */ /* Earpiece */ @@ -1373,10 +1406,6 @@ static const struct snd_soc_dapm_route intercon[] = { {"Digital Voice Playback Mixer", NULL, "DAC Voice"}, /* Supply for the digital part (APLL) */ - {"Digital R1 Playback Mixer", NULL, "APLL Enable"}, - {"Digital L1 Playback Mixer", NULL, "APLL Enable"}, - {"Digital R2 Playback Mixer", NULL, "APLL Enable"}, - {"Digital L2 Playback Mixer", NULL, "APLL Enable"}, {"Digital Voice Playback Mixer", NULL, "APLL Enable"}, {"Digital R1 Playback Mixer", NULL, "AIF Enable"}, @@ -1450,6 +1479,14 @@ static const struct snd_soc_dapm_route intercon[] = { {"Vibra Mux", "AudioR2", "DAC Right2"}, /* outputs */ + /* Must be always connected (for AIF and APLL) */ + {"Virtual HiFi OUT", NULL, "Digital L1 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "Digital R1 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "Digital L2 Playback Mixer"}, + {"Virtual HiFi OUT", NULL, "Digital R2 Playback Mixer"}, + /* Must be always connected (for APLL) */ + {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, + /* Physical outputs */ {"OUTL", NULL, "Analog L2 Playback Mixer"}, {"OUTR", NULL, "Analog R2 Playback Mixer"}, {"EARPIECE", NULL, "Earpiece PGA"}, @@ -1465,6 +1502,12 @@ static const struct snd_soc_dapm_route intercon[] = { {"VIBRA", NULL, "Vibra Route"}, /* Capture path */ + /* Must be always connected (for AIF and APLL) */ + {"ADC Virtual Left1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right1", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Left2", NULL, "Virtual HiFi IN"}, + {"ADC Virtual Right2", NULL, "Virtual HiFi IN"}, + /* Physical inputs */ {"Analog Left", "Main Mic Capture Switch", "MAINMIC"}, {"Analog Left", "Headset Mic Capture Switch", "HSMIC"}, {"Analog Left", "AUXL Capture Switch", "AUXL"}, @@ -1497,11 +1540,6 @@ static const struct snd_soc_dapm_route intercon[] = { {"ADC Virtual Left2", NULL, "TX2 Capture Route"}, {"ADC Virtual Right2", NULL, "TX2 Capture Route"}, - {"ADC Virtual Left1", NULL, "APLL Enable"}, - {"ADC Virtual Right1", NULL, "APLL Enable"}, - {"ADC Virtual Left2", NULL, "APLL Enable"}, - {"ADC Virtual Right2", NULL, "APLL Enable"}, - {"ADC Virtual Left1", NULL, "AIF Enable"}, {"ADC Virtual Right1", NULL, "AIF Enable"}, {"ADC Virtual Left2", NULL, "AIF Enable"}, From 1b7c9afbfbfde93d4da89dcebfd2314f7d79c064 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 29 Apr 2010 10:58:09 +0300 Subject: [PATCH 05/14] ASoC: TWL4030: Remove OUTL/R outputs OUTL/R are leftovers from the original driver, and they are no longer needed. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/twl4030.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 12931f6d445b..b717a03dfacf 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1194,8 +1194,6 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_INPUT("DIGIMIC1"), /* Outputs */ - SND_SOC_DAPM_OUTPUT("OUTL"), - SND_SOC_DAPM_OUTPUT("OUTR"), SND_SOC_DAPM_OUTPUT("EARPIECE"), SND_SOC_DAPM_OUTPUT("PREDRIVEL"), SND_SOC_DAPM_OUTPUT("PREDRIVER"), @@ -1487,8 +1485,6 @@ static const struct snd_soc_dapm_route intercon[] = { /* Must be always connected (for APLL) */ {"Virtual Voice OUT", NULL, "Digital Voice Playback Mixer"}, /* Physical outputs */ - {"OUTL", NULL, "Analog L2 Playback Mixer"}, - {"OUTR", NULL, "Analog R2 Playback Mixer"}, {"EARPIECE", NULL, "Earpiece PGA"}, {"PREDRIVEL", NULL, "PredriveL PGA"}, {"PREDRIVER", NULL, "PredriveR PGA"}, From ef909d67299498010f07889bd0980c829ae78990 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:33 +0300 Subject: [PATCH 06/14] ASoC: tlv320dac33: Optimize power up, and restore On power up we only need to initialize the codec, and restore only registers, which are not in either in DAPM nor in the playback start sequence. These are mostly gain related registers. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 106 ++++++++++++--------------------- 1 file changed, 39 insertions(+), 67 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 54b2a0508a11..329a97f6e0f8 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -284,45 +284,49 @@ static int dac33_write16(struct snd_soc_codec *codec, unsigned int reg, return ret; } -static void dac33_restore_regs(struct snd_soc_codec *codec) +static void dac33_init_chip(struct snd_soc_codec *codec) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - u8 *cache = codec->reg_cache; - u8 data[2]; - int i, ret; - if (!dac33->chip_power) + if (unlikely(!dac33->chip_power)) return; - for (i = DAC33_PWR_CTRL; i <= DAC33_INTP_CTRL_B; i++) { - data[0] = i; - data[1] = cache[i]; - /* Skip the read only registers */ - if ((i >= DAC33_INT_OSC_STATUS && - i <= DAC33_INT_OSC_FREQ_RAT_READ_B) || - (i >= DAC33_FIFO_WPTR_MSB && i <= DAC33_FIFO_IRQ_FLAG) || - i == DAC33_DAC_STATUS_FLAGS || - i == DAC33_SRC_EST_REF_CLK_RATIO_A || - i == DAC33_SRC_EST_REF_CLK_RATIO_B) - continue; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } - for (i = DAC33_LDAC_PWR_CTRL; i <= DAC33_LINEL_TO_LLO_VOL; i++) { - data[0] = i; - data[1] = cache[i]; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } - for (i = DAC33_LINER_TO_RLO_VOL; i <= DAC33_OSC_TRIM; i++) { - data[0] = i; - data[1] = cache[i]; - ret = codec->hw_write(codec->control_data, data, 2); - if (ret != 2) - dev_err(codec->dev, "Write failed (%d)\n", ret); - } + /* 44-46: DAC Control Registers */ + /* A : DAC sample rate Fsref/1.5 */ + dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); + /* B : DAC src=normal, not muted */ + dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | + DAC33_DACSRCL_LEFT); + /* C : (defaults) */ + dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); + + /* 64-65 : L&R DAC power control + Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ + dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); + dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); + + /* 73 : volume soft stepping control, + clock source = internal osc (?) */ + dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); + + /* 66 : LOP/LOM Modes */ + dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); + + /* 68 : LOM inverted from LOP */ + dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); + + 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)); + dac33_write(codec, DAC33_RDAC_DIG_VOL_CTRL, + dac33_read_reg_cache(codec, DAC33_RDAC_DIG_VOL_CTRL)); + + dac33_write(codec, DAC33_LINEL_TO_LLO_VOL, + dac33_read_reg_cache(codec, DAC33_LINEL_TO_LLO_VOL)); + dac33_write(codec, DAC33_LINER_TO_RLO_VOL, + dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL)); } static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) @@ -358,8 +362,7 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) dac33->chip_power = 1; - /* Restore registers */ - dac33_restore_regs(codec); + dac33_init_chip(codec); dac33_soft_power(codec, 1); } else { @@ -1269,35 +1272,6 @@ static int dac33_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } -static void dac33_init_chip(struct snd_soc_codec *codec) -{ - /* 44-46: DAC Control Registers */ - /* A : DAC sample rate Fsref/1.5 */ - dac33_write(codec, DAC33_DAC_CTRL_A, DAC33_DACRATE(0)); - /* B : DAC src=normal, not muted */ - dac33_write(codec, DAC33_DAC_CTRL_B, DAC33_DACSRCR_RIGHT | - DAC33_DACSRCL_LEFT); - /* C : (defaults) */ - dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); - - /* 64-65 : L&R DAC power control - Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ - dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - - /* 73 : volume soft stepping control, - clock source = internal osc (?) */ - dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); - - /* 66 : LOP/LOM Modes */ - dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); - - /* 68 : LOM inverted from LOP */ - dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); - - dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); -} - static int dac33_soc_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -1313,8 +1287,6 @@ static int dac33_soc_probe(struct platform_device *pdev) /* Power up the codec */ dac33_hard_power(codec, 1); - /* Set default configuration */ - dac33_init_chip(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); From 239fe55c7fe17d67403cb1e9222fcaea84248974 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:34 +0300 Subject: [PATCH 07/14] ASoC: tlv320dac33: Revised module loading, and DAC33 ID read Optimize the way how tlv320dac33 is powered uppon module and soc initialization. Also read the DAC33 ID registers, and update the reg_cache to reflect it. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 37 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 329a97f6e0f8..9944721a055c 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -329,6 +329,15 @@ static void dac33_init_chip(struct snd_soc_codec *codec) dac33_read_reg_cache(codec, DAC33_LINER_TO_RLO_VOL)); } +static inline void dac33_read_id(struct snd_soc_codec *codec) +{ + u8 reg; + + dac33_read(codec, DAC33_DEVICE_ID_MSB, ®); + dac33_read(codec, DAC33_DEVICE_ID_LSB, ®); + dac33_read(codec, DAC33_DEVICE_REV_ID, ®); +} + static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) { u8 reg; @@ -1285,9 +1294,6 @@ static int dac33_soc_probe(struct platform_device *pdev) socdev->card->codec = codec; dac33 = snd_soc_codec_get_drvdata(codec); - /* Power up the codec */ - dac33_hard_power(codec, 1); - /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -1307,9 +1313,6 @@ static int dac33_soc_probe(struct platform_device *pdev) /* power on device */ dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - /* Bias level configuration has enabled regulator an extra time */ - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); - return 0; pcm_err: @@ -1459,8 +1462,6 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto error_gpio; } gpio_direction_output(dac33->power_gpio, 0); - } else { - dac33->chip_power = 1; } /* Check if the IRQ number is valid and request it */ @@ -1498,12 +1499,14 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto err_get; } - ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), - dac33->supplies); + /* Read the tlv320dac33 ID registers */ + ret = dac33_hard_power(codec, 1); if (ret != 0) { - dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); - goto err_enable; + dev_err(codec->dev, "Failed to power up codec: %d\n", ret); + goto error_codec; } + dac33_read_id(codec); + dac33_hard_power(codec, 0); ret = snd_soc_register_codec(codec); if (ret != 0) { @@ -1518,14 +1521,9 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, goto error_codec; } - /* Shut down the codec for now */ - dac33_hard_power(codec, 0); - return ret; error_codec: - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); -err_enable: regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); err_get: if (dac33->irq >= 0) { @@ -1549,14 +1547,15 @@ static int __devexit dac33_i2c_remove(struct i2c_client *client) struct tlv320dac33_priv *dac33; dac33 = i2c_get_clientdata(client); - dac33_hard_power(&dac33->codec, 0); + + if (unlikely(dac33->chip_power)) + dac33_hard_power(&dac33->codec, 0); if (dac33->power_gpio >= 0) gpio_free(dac33->power_gpio); if (dac33->irq >= 0) free_irq(dac33->irq, &dac33->codec); - regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies); regulator_bulk_free(ARRAY_SIZE(dac33->supplies), dac33->supplies); destroy_workqueue(dac33->dac33_wq); From 0b61d2b9f2f78fc55faaedcc37f622ffd4103d14 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:35 +0300 Subject: [PATCH 08/14] ASoC: tlv320dac33: Manage a pointer for snd_pcm_substream in private structure As a preparation for supporting codec to be turned off, when we are in BIAS_STANDBY. The substream must be easily available in other places than pcm_* callbacks. Manage a pointer in _startup, and _shutdown for this. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 9944721a055c..50d152215abd 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -91,6 +91,7 @@ struct tlv320dac33_priv { struct work_struct work; struct snd_soc_codec codec; struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES]; + struct snd_pcm_substream *substream; int power_gpio; int chip_power; int irq; @@ -720,6 +721,31 @@ static void dac33_oscwait(struct snd_soc_codec *codec) "internal oscillator calibration failed\n"); } +static int dac33_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + /* Stream started, save the substream pointer */ + dac33->substream = substream; + + return 0; +} + +static void dac33_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); + + dac33->substream = NULL; +} + static int dac33_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -1367,6 +1393,8 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_tlv320dac33); #define DAC33_FORMATS SNDRV_PCM_FMTBIT_S16_LE static struct snd_soc_dai_ops dac33_dai_ops = { + .startup = dac33_startup, + .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, From ad05c03b1c4c1fb4db066a7bd502b674148ccd89 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 30 Apr 2010 14:59:36 +0300 Subject: [PATCH 09/14] ASoC: tlv320dac33: Support for turning off the codec Let the codec to hit OFF instead of STANDBY, when there is no activity. When the codec is off, than the associated regulator can be also turned off (if the number of users on the regulator is 0). After initialization, the codec remains in power off, it is only turned on for reading the ID registers (also testing the regulators). The codec power is enabled, when the codec is moving from BIAS_OFF to BIAS_STANDBY. The codec is turned off, when it hits BIAS_OFF. There are few scenarios, which has to be taken care:: 1. Analog bypass caused BIAS_OFF -> BIAS_ON We need to power on the codec, and do the chip init, but we does not need to execute the playback related configuration 2. Playback caused BIAS_OFF -> BIAS_ON We need to power on the codec, and do the chip init, and also we need to execute the playback related configuration. 3. Playback start, while Analog bypass is on (BIAS_ON -> BIAS_ON) We need to execute the playback related configuration. The codec is already on. 4. Analog bypass enable, while playback (BIAS_ON -> BIAS_ON) Nothing need to be done. 5. Playback start withing soc power down timeout (BIAS_ON -> BIAS_ON) We need to execute the playback related configuration. The codec is still on. Since the power up, and the codec init is optimized, the added overhead in stream start is minimal. Withing this patch, the hard_power function is now only doing what it supposed to: only handle the powers, and GPIO reset line. The codec initialization and state restore has been moved out. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 66 +++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 50d152215abd..68b7ccbf2e7c 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -61,6 +61,8 @@ #define US_TO_SAMPLES(rate, us) \ (rate / (1000000 / us)) +static void dac33_calculate_times(struct snd_pcm_substream *substream); +static int dac33_prepare_chip(struct snd_pcm_substream *substream); static struct snd_soc_codec *tlv320dac33_codec; @@ -355,9 +357,17 @@ static inline void dac33_soft_power(struct snd_soc_codec *codec, int power) static int dac33_hard_power(struct snd_soc_codec *codec, int power) { struct tlv320dac33_priv *dac33 = snd_soc_codec_get_drvdata(codec); - int ret; + int ret = 0; mutex_lock(&dac33->mutex); + + /* Safety check */ + if (unlikely(power == dac33->chip_power)) { + dev_warn(codec->dev, "Trying to set the same power state: %s\n", + power ? "ON" : "OFF"); + goto exit; + } + if (power) { ret = regulator_bulk_enable(ARRAY_SIZE(dac33->supplies), dac33->supplies); @@ -371,10 +381,6 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) gpio_set_value(dac33->power_gpio, 1); dac33->chip_power = 1; - - dac33_init_chip(codec); - - dac33_soft_power(codec, 1); } else { dac33_soft_power(codec, 0); if (dac33->power_gpio >= 0) @@ -396,6 +402,22 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power) return ret; } +static int 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); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (likely(dac33->substream)) { + dac33_calculate_times(dac33->substream); + dac33_prepare_chip(dac33->substream); + } + break; + } + return 0; +} + static int dac33_get_nsample(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -525,6 +547,8 @@ static const struct snd_soc_dapm_widget dac33_dapm_widgets[] = { DAC33_OUT_AMP_PWR_CTRL, 6, 3, 3, 0), SND_SOC_DAPM_REG(snd_soc_dapm_mixer, "Output Right Amp Power", DAC33_OUT_AMP_PWR_CTRL, 4, 3, 3, 0), + + SND_SOC_DAPM_PRE("Prepare Playback", playback_event), }; static const struct snd_soc_dapm_route audio_map[] = { @@ -567,18 +591,18 @@ static int dac33_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Coming from OFF, switch on the codec */ ret = dac33_hard_power(codec, 1); if (ret != 0) return ret; - } - dac33_soft_power(codec, 0); + dac33_init_chip(codec); + } break; case SND_SOC_BIAS_OFF: ret = dac33_hard_power(codec, 0); if (ret != 0) return ret; - break; } codec->bias_level = level; @@ -829,6 +853,16 @@ static int dac33_prepare_chip(struct snd_pcm_substream *substream) } mutex_lock(&dac33->mutex); + + if (!dac33->chip_power) { + /* + * Chip is not powered yet. + * Do the init in the dac33_set_bias_level later. + */ + mutex_unlock(&dac33->mutex); + return 0; + } + dac33_soft_power(codec, 0); dac33_soft_power(codec, 1); @@ -1035,15 +1069,6 @@ static void dac33_calculate_times(struct snd_pcm_substream *substream) } -static int dac33_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - dac33_calculate_times(substream); - dac33_prepare_chip(substream); - - return 0; -} - static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { @@ -1336,9 +1361,6 @@ static int dac33_soc_probe(struct platform_device *pdev) dac33_add_widgets(codec); - /* power on device */ - dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; pcm_err: @@ -1375,6 +1397,8 @@ static int dac33_soc_resume(struct platform_device *pdev) struct snd_soc_codec *codec = socdev->card->codec; dac33_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + dac33_set_bias_level(codec, SND_SOC_BIAS_PREPARE); dac33_set_bias_level(codec, codec->suspend_bias_level); return 0; @@ -1396,7 +1420,6 @@ static struct snd_soc_dai_ops dac33_dai_ops = { .startup = dac33_startup, .shutdown = dac33_shutdown, .hw_params = dac33_hw_params, - .prepare = dac33_pcm_prepare, .trigger = dac33_pcm_trigger, .delay = dac33_dai_delay, .set_sysclk = dac33_set_dai_sysclk, @@ -1450,6 +1473,7 @@ static int __devinit dac33_i2c_probe(struct i2c_client *client, codec->hw_write = (hw_write_t) i2c_master_send; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = dac33_set_bias_level; + codec->idle_bias_off = 1; codec->dai = &dac33_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(dac33_reg); From e5e5b31e8c729b6bae569bec0790c655ee0121a1 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 4 May 2010 11:08:18 +0300 Subject: [PATCH 10/14] ASoC: tpa6130a2: TLV mapping for tpa6140a2 Both tpa6130a2, and tpa6140a2 is supported by the same driver, but the gain dB scaling is different on the amplifiers. Provide different mixer control for the chips with correct TLV mapping. User space will see: "TPA6130A2 Headphone Playback Volume" in case of 6130 "TPA6140A2 Headphone Playback Volume" in case of 6140 The way machine drivers are using this amplifier remained the same. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tpa6130a2.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 958d49c969ac..0cf3e3862e7b 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -53,6 +53,7 @@ struct tpa6130a2_data { struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES]; int power_gpio; unsigned char power_state; + enum tpa_model id; }; static int tpa6130a2_i2c_read(int reg) @@ -263,6 +264,20 @@ static const struct snd_kcontrol_new tpa6130a2_controls[] = { tpa6130_tlv), }; +static const unsigned int tpa6140_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0), + 9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0), + 17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0), +}; + +static const struct snd_kcontrol_new tpa6140a2_controls[] = { + SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, + tpa6130a2_get_reg, tpa6130a2_set_reg, + tpa6140_tlv), +}; + /* * Enable or disable channel (left or right) * The bit number for mute and amplifier are the same per channel: @@ -368,13 +383,22 @@ static const struct snd_soc_dapm_route audio_map[] = { int tpa6130a2_add_controls(struct snd_soc_codec *codec) { + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + data = i2c_get_clientdata(tpa6130a2_client); + snd_soc_dapm_new_controls(codec, tpa6130a2_dapm_widgets, ARRAY_SIZE(tpa6130a2_dapm_widgets)); snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); - return snd_soc_add_controls(codec, tpa6130a2_controls, - ARRAY_SIZE(tpa6130a2_controls)); + 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); @@ -407,6 +431,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, pdata = client->dev.platform_data; data->power_gpio = pdata->power_gpio; + data->id = pdata->id; mutex_init(&data->mutex); @@ -425,7 +450,7 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, gpio_direction_output(data->power_gpio, 0); } - switch (pdata->id) { + switch (data->id) { case TPA6130A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; From 49100c98359a56ea4e8c9a76e3d625cdb25f25f5 Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 5 May 2010 11:14:22 +0300 Subject: [PATCH 11/14] ASoC: omap: Add basic audio support for Nokia RX-51/N900 This patch adds support for integrated stereo speakers and digital microphone found on Nokia RX-51 hardware. This is a cut down version based on Maemo kernel sources and earlier patchset by Eduardo Valentin et al. http://mailman.alsa-project.org/pipermail/alsa-devel/2009-October/022033.html Signed-off-by: Jarkko Nikula Cc: Eduardo Valentin Cc: Peter Ujfalusi Acked-by: Eduardo Valentin Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/omap/Kconfig | 10 ++ sound/soc/omap/Makefile | 2 + sound/soc/omap/rx51.c | 294 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 sound/soc/omap/rx51.c diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index f11963c21873..83be4a76d2bb 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -18,6 +18,16 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810. +config SND_OMAP_SOC_RX51 + tristate "SoC Audio support for Nokia RX-51" + depends on SND_OMAP_SOC && MACH_NOKIA_RX51 + select OMAP_MCBSP + select SND_OMAP_SOC_MCBSP + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on Nokia RX-51 + hardware. This is also known as Nokia N900 product. + config SND_OMAP_SOC_AMS_DELTA tristate "SoC Audio support for Amstrad E3 (Delta) videophone" depends on SND_OMAP_SOC && MACH_AMS_DELTA diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 0bc00ca14b37..3a75755f25e4 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SND_OMAP_SOC_MCPDM) += snd-soc-omap-mcpdm.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-rx51-objs := rx51.o snd-soc-ams-delta-objs := ams-delta.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o @@ -22,6 +23,7 @@ snd-soc-zoom2-objs := zoom2.o snd-soc-igep0020-objs := igep0020.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_RX51) += snd-soc-rx51.o obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c new file mode 100644 index 000000000000..47d831ef2dbb --- /dev/null +++ b/sound/soc/omap/rx51.c @@ -0,0 +1,294 @@ +/* + * rx51.c -- SoC audio for Nokia RX-51 + * + * Copyright (C) 2008 - 2009 Nokia Corporation + * + * Contact: Peter Ujfalusi + * Eduardo Valentin + * Jarkko Nikula + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/tlv320aic3x.h" + +/* + * REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This + * gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c + */ +#define RX51_SPEAKER_AMP_TWL_GPIO (192 + 7) + +static int rx51_spk_func; +static int rx51_dmic_func; + +static void rx51_ext_control(struct snd_soc_codec *codec) +{ + if (rx51_spk_func) + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + else + snd_soc_dapm_disable_pin(codec, "Ext Spk"); + if (rx51_dmic_func) + snd_soc_dapm_enable_pin(codec, "DMic"); + else + snd_soc_dapm_disable_pin(codec, "DMic"); + + snd_soc_dapm_sync(codec); +} + +static int rx51_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->socdev->card->codec; + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + rx51_ext_control(codec); + + return 0; +} + +static int rx51_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) + return err; + + /* Set the codec system clock for DAC and ADC */ + return snd_soc_dai_set_sysclk(codec_dai, 0, 19200000, + SND_SOC_CLOCK_IN); +} + +static struct snd_soc_ops rx51_ops = { + .startup = rx51_startup, + .hw_params = rx51_hw_params, +}; + +static int rx51_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_spk_func; + + return 0; +} + +static int rx51_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_spk_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_spk_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static int rx51_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 1); + else + gpio_set_value(RX51_SPEAKER_AMP_TWL_GPIO, 0); + + return 0; +} + +static int rx51_get_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = rx51_dmic_func; + + return 0; +} + +static int rx51_set_input(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (rx51_dmic_func == ucontrol->value.integer.value[0]) + return 0; + + rx51_dmic_func = ucontrol->value.integer.value[0]; + rx51_ext_control(codec); + + return 1; +} + +static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event), + SND_SOC_DAPM_MIC("DMic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Ext Spk", NULL, "HPLOUT"}, + {"Ext Spk", NULL, "HPROUT"}, + + {"DMic Rate 64", NULL, "Mic Bias 2V"}, + {"Mic Bias 2V", NULL, "DMic"}, +}; + +static const char *spk_function[] = {"Off", "On"}; +static const char *input_function[] = {"ADC", "Digital Mic"}; + +static const struct soc_enum rx51_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), +}; + +static const struct snd_kcontrol_new aic34_rx51_controls[] = { + SOC_ENUM_EXT("Speaker Function", rx51_enum[0], + rx51_get_spk, rx51_set_spk), + SOC_ENUM_EXT("Input Select", rx51_enum[1], + rx51_get_input, rx51_set_input), +}; + +static int rx51_aic34_init(struct snd_soc_codec *codec) +{ + int err; + + /* Set up NC codec pins */ + snd_soc_dapm_nc_pin(codec, "MIC3L"); + snd_soc_dapm_nc_pin(codec, "MIC3R"); + snd_soc_dapm_nc_pin(codec, "LINE1R"); + + /* Add RX-51 specific controls */ + err = snd_soc_add_controls(codec, aic34_rx51_controls, + ARRAY_SIZE(aic34_rx51_controls)); + if (err < 0) + return err; + + /* Add RX-51 specific widgets */ + snd_soc_dapm_new_controls(codec, aic34_dapm_widgets, + ARRAY_SIZE(aic34_dapm_widgets)); + + /* Set up RX-51 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link rx51_dai[] = { + { + .name = "TLV320AIC34", + .stream_name = "AIC34", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &aic3x_dai, + .init = rx51_aic34_init, + .ops = &rx51_ops, + }, +}; + +/* Audio private data */ +static struct aic3x_setup_data rx51_aic34_setup = { + .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, + .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, +}; + +/* Audio card */ +static struct snd_soc_card rx51_sound_card = { + .name = "RX-51", + .dai_link = rx51_dai, + .num_links = ARRAY_SIZE(rx51_dai), + .platform = &omap_soc_platform, +}; + +/* Audio subsystem */ +static struct snd_soc_device rx51_snd_devdata = { + .card = &rx51_sound_card, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &rx51_aic34_setup, +}; + +static struct platform_device *rx51_snd_device; + +static int __init rx51_soc_init(void) +{ + int err; + + if (!machine_is_nokia_rx51()) + return -ENODEV; + + rx51_snd_device = platform_device_alloc("soc-audio", -1); + if (!rx51_snd_device) { + err = -ENOMEM; + goto err1; + } + + platform_set_drvdata(rx51_snd_device, &rx51_snd_devdata); + rx51_snd_devdata.dev = &rx51_snd_device->dev; + *(unsigned int *)rx51_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + + err = platform_device_add(rx51_snd_device); + if (err) + goto err2; + + return 0; +err2: + platform_device_put(rx51_snd_device); +err1: + + return err; +} + +static void __exit rx51_soc_exit(void) +{ + platform_device_unregister(rx51_snd_device); +} + +module_init(rx51_soc_init); +module_exit(rx51_soc_exit); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("ALSA SoC Nokia RX-51"); +MODULE_LICENSE("GPL"); From 5193d62f1824cdfd72b5523be2b1cdb8049225ad Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Wed, 5 May 2010 13:02:03 +0300 Subject: [PATCH 12/14] ASoC: tlv320aic3x: Add platform data and reset gpio handling Handle the reset GPIO within the codec driver in order to follow the startup protocol for the tlv320aic3x codecs. Signed-off-by: Jarkko Nikula Acked-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- include/sound/tlv320aic3x.h | 17 +++++++++++++++++ sound/soc/codecs/tlv320aic3x.c | 25 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 include/sound/tlv320aic3x.h diff --git a/include/sound/tlv320aic3x.h b/include/sound/tlv320aic3x.h new file mode 100644 index 000000000000..b1a5f34e5cfa --- /dev/null +++ b/include/sound/tlv320aic3x.h @@ -0,0 +1,17 @@ +/* + * Platform data for Texas Instruments TLV320AIC3x codec + * + * Author: Jarkko Nikula + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __TLV320AIC3x_H__ +#define __TLV320AIC3x_H__ + +struct aic3x_pdata { + int gpio_reset; /* < 0 if not used */ +}; + +#endif \ No newline at end of file diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 584bc1e67f76..d57372be7a96 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include "tlv320aic3x.h" @@ -64,6 +66,7 @@ struct aic3x_priv { struct regulator_bulk_data supplies[AIC3X_NUM_SUPPLIES]; unsigned int sysclk; int master; + int gpio_reset; }; /* @@ -1278,6 +1281,10 @@ static int aic3x_unregister(struct aic3x_priv *aic3x) snd_soc_unregister_dai(&aic3x_dai); snd_soc_unregister_codec(&aic3x->codec); + if (aic3x->gpio_reset >= 0) { + gpio_set_value(aic3x->gpio_reset, 0); + gpio_free(aic3x->gpio_reset); + } regulator_bulk_disable(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); @@ -1302,6 +1309,7 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, { struct snd_soc_codec *codec; struct aic3x_priv *aic3x; + struct aic3x_pdata *pdata = i2c->dev.platform_data; int ret, i; aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL); @@ -1318,6 +1326,15 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, aic3x); + aic3x->gpio_reset = -1; + if (pdata && pdata->gpio_reset >= 0) { + ret = gpio_request(pdata->gpio_reset, "tlv320aic3x reset"); + if (ret != 0) + goto err_gpio; + aic3x->gpio_reset = pdata->gpio_reset; + gpio_direction_output(aic3x->gpio_reset, 0); + } + for (i = 0; i < ARRAY_SIZE(aic3x->supplies); i++) aic3x->supplies[i].supply = aic3x_supply_names[i]; @@ -1335,11 +1352,19 @@ static int aic3x_i2c_probe(struct i2c_client *i2c, goto err_enable; } + if (aic3x->gpio_reset >= 0) { + udelay(1); + gpio_set_value(aic3x->gpio_reset, 1); + } + return aic3x_register(codec); err_enable: regulator_bulk_free(ARRAY_SIZE(aic3x->supplies), aic3x->supplies); err_get: + if (aic3x->gpio_reset >= 0) + gpio_free(aic3x->gpio_reset); +err_gpio: kfree(aic3x); return ret; } From 6f3991152f20933b77eff30413e893bf1a15e578 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 6 May 2010 10:37:18 +0300 Subject: [PATCH 13/14] ASoC: tpa6130a2: Support for limiting gain Add support for platform dependent gain limiting on the tpa6130a2 (and tpa6140a2) Headset amplifier. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- include/sound/tpa6130a2-plat.h | 1 + sound/soc/codecs/tpa6130a2.c | 76 ++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/include/sound/tpa6130a2-plat.h b/include/sound/tpa6130a2-plat.h index e29fde6b5cbe..426f62767dab 100644 --- a/include/sound/tpa6130a2-plat.h +++ b/include/sound/tpa6130a2-plat.h @@ -31,6 +31,7 @@ enum tpa_model { struct tpa6130a2_platform_data { enum tpa_model id; int power_gpio; + int limit_gain; }; #endif diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c index 0cf3e3862e7b..31f67b527ca1 100644 --- a/sound/soc/codecs/tpa6130a2.c +++ b/sound/soc/codecs/tpa6130a2.c @@ -46,6 +46,9 @@ static const char *tpa6140a2_supply_names[TPA6130A2_NUM_SUPPLIES] = { "AVdd", }; +#define TPA6130A2_GAIN_MAX 0x3f +#define TPA6140A2_GAIN_MAX 0x1f + /* This struct is used to save the context */ struct tpa6130a2_data { struct mutex mutex; @@ -54,6 +57,7 @@ struct tpa6130a2_data { int power_gpio; unsigned char power_state; enum tpa_model id; + int gain_limit; }; static int tpa6130a2_i2c_read(int reg) @@ -176,6 +180,40 @@ static int tpa6130a2_power(int power) return ret; } +static int tpa6130a2_info_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct tpa6130a2_data *data; + + BUG_ON(tpa6130a2_client == NULL); + data = i2c_get_clientdata(tpa6130a2_client); + + mutex_lock(&data->mutex); + switch (mc->reg) { + case TPA6130A2_REG_VOL_MUTE: + if (data->gain_limit != mc->max) + mc->max = data->gain_limit; + break; + default: + dev_err(&tpa6130a2_client->dev, + "Invalid register: 0x02%x\n", mc->reg); + goto out; + } + if (unlikely(mc->max == 1)) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mc->max; +out: + mutex_unlock(&data->mutex); + return 0; +} + static int tpa6130a2_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -239,6 +277,15 @@ static int tpa6130a2_set_reg(struct snd_kcontrol *kcontrol, return 1; } +#define SOC_SINGLE_EXT_TLV_TPA(xname, xreg, 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,\ + .tlv.p = (tlv_array), \ + .info = tpa6130a2_info_volsw, \ + .get = tpa6130a2_get_reg, .put = tpa6130a2_set_reg, \ + .private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) } + /* * TPA6130 volume. From -59.5 to 4 dB with increasing step size when going * down in gain. @@ -258,10 +305,9 @@ static const unsigned int tpa6130_tlv[] = { }; static const struct snd_kcontrol_new tpa6130a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6130A2 Headphone Playback Volume", - TPA6130A2_REG_VOL_MUTE, 0, 0x3f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, - tpa6130_tlv), + SOC_SINGLE_EXT_TLV_TPA("TPA6130A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 0, TPA6130A2_GAIN_MAX, 0, + tpa6130_tlv), }; static const unsigned int tpa6140_tlv[] = { @@ -272,10 +318,9 @@ static const unsigned int tpa6140_tlv[] = { }; static const struct snd_kcontrol_new tpa6140a2_controls[] = { - SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume", - TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0, - tpa6130a2_get_reg, tpa6130a2_set_reg, - tpa6140_tlv), + SOC_SINGLE_EXT_TLV_TPA("TPA6140A2 Headphone Playback Volume", + TPA6130A2_REG_VOL_MUTE, 1, TPA6140A2_GAIN_MAX, 0, + tpa6140_tlv), }; /* @@ -454,16 +499,31 @@ static int __devinit tpa6130a2_probe(struct i2c_client *client, case TPA6130A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; + if (pdata->limit_gain > 0 && + pdata->limit_gain < TPA6130A2_GAIN_MAX) + data->gain_limit = pdata->limit_gain; + else + data->gain_limit = TPA6130A2_GAIN_MAX; break; case TPA6140A2: for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6140a2_supply_names[i];; + if (pdata->limit_gain > 0 && + pdata->limit_gain < TPA6140A2_GAIN_MAX) + data->gain_limit = pdata->limit_gain; + else + data->gain_limit = TPA6140A2_GAIN_MAX; break; default: dev_warn(dev, "Unknown TPA model (%d). Assuming 6130A2\n", pdata->id); for (i = 0; i < ARRAY_SIZE(data->supplies); i++) data->supplies[i].supply = tpa6130a2_supply_names[i]; + if (pdata->limit_gain > 0 && + pdata->limit_gain < TPA6130A2_GAIN_MAX) + data->gain_limit = pdata->limit_gain; + else + data->gain_limit = TPA6130A2_GAIN_MAX; } ret = regulator_bulk_get(dev, ARRAY_SIZE(data->supplies), From 2f005471e2e2f2c7fa5898153387d421f7d24ad6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 6 May 2010 12:04:25 +0300 Subject: [PATCH 14/14] ASoC: tlv320dac33: Use codec defaults for LOM/LOP and DAC power Do not change the codec defaults for the following registers: 0x40, 0x41: Line output gains, do not use amplification 0x42: LOM/LOP Voltage hold, and selection 0x44: LOM inversion control It has been found, that the values configured to these registers can cause amplification, which can make the output of DAC33 distorted. The codec reset values are considered safe in all environmnts. Signed-off-by: Peter Ujfalusi Acked-by: Mark Brown Signed-off-by: Liam Girdwood --- sound/soc/codecs/tlv320dac33.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index 68b7ccbf2e7c..ad5e2636c944 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -303,21 +303,10 @@ static void dac33_init_chip(struct snd_soc_codec *codec) /* C : (defaults) */ dac33_write(codec, DAC33_DAC_CTRL_C, 0x00); - /* 64-65 : L&R DAC power control - Line In -> OUT 1V/V Gain, DAC -> OUT 4V/V Gain*/ - dac33_write(codec, DAC33_LDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - dac33_write(codec, DAC33_RDAC_PWR_CTRL, DAC33_LROUT_GAIN(2)); - /* 73 : volume soft stepping control, clock source = internal osc (?) */ dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN); - /* 66 : LOP/LOM Modes */ - dac33_write(codec, DAC33_OUT_AMP_CM_CTRL, 0xff); - - /* 68 : LOM inverted from LOP */ - dac33_write(codec, DAC33_OUT_AMP_CTRL, (3<<2)); - dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB); /* Restore only selected registers (gains mostly) */