summaryrefslogtreecommitdiffstats
path: root/sound/soc/codecs
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2010-05-06 17:06:27 +0200
committerTakashi Iwai <tiwai@suse.de>2010-05-06 17:06:27 +0200
commitaeb29a82de7c80d4d0253b042f17eb1f725b08f1 (patch)
treef15ba557a22b010cd790ead5a3c4a4a9872fe58f /sound/soc/codecs
parent39b8eab7e7fe429d8d57f18c0ebdb7c25df55f5c (diff)
parent2f005471e2e2f2c7fa5898153387d421f7d24ad6 (diff)
downloadlinux-aeb29a82de7c80d4d0253b042f17eb1f725b08f1.tar.bz2
Merge branch 'for-2.6.35' of git://git.kernel.org/pub/scm/linux/kernel/git/lrg/asoc-2.6 into topic/asoc
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/tlv320aic3x.c25
-rw-r--r--sound/soc/codecs/tlv320dac33.c223
-rw-r--r--sound/soc/codecs/tpa6130a2.c99
-rw-r--r--sound/soc/codecs/twl4030.c86
4 files changed, 295 insertions, 138 deletions
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 <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
+#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_device.h>
#include <sound/core.h>
@@ -47,6 +48,7 @@
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>
+#include <sound/tlv320aic3x.h>
#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;
}
diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c
index 3eddaec728c1..ad5e2636c944 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;
@@ -91,6 +93,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;
@@ -284,45 +287,47 @@ 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);
+
+ /* 73 : volume soft stepping control,
+ clock source = internal osc (?) */
+ dac33_write(codec, DAC33_ANA_VOL_SOFT_STEP_CTRL, DAC33_VOLCLKEN);
+
+ dac33_write(codec, DAC33_PWR_CTRL, DAC33_PDNALLB);
+
+ /* Restore only selected registers (gains mostly) */
+ dac33_write(codec, DAC33_LDAC_DIG_VOL_CTRL,
+ dac33_read_reg_cache(codec, DAC33_LDAC_DIG_VOL_CTRL));
+ 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_read_id(struct snd_soc_codec *codec)
+{
+ u8 reg;
+
+ dac33_read(codec, DAC33_DEVICE_ID_MSB, &reg);
+ dac33_read(codec, DAC33_DEVICE_ID_LSB, &reg);
+ dac33_read(codec, DAC33_DEVICE_REV_ID, &reg);
}
static inline void dac33_soft_power(struct snd_soc_codec *codec, int power)
@@ -341,9 +346,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);
@@ -357,11 +370,6 @@ static int dac33_hard_power(struct snd_soc_codec *codec, int power)
gpio_set_value(dac33->power_gpio, 1);
dac33->chip_power = 1;
-
- /* Restore registers */
- dac33_restore_regs(codec);
-
- dac33_soft_power(codec, 1);
} else {
dac33_soft_power(codec, 0);
if (dac33->power_gpio >= 0)
@@ -383,6 +391,22 @@ exit:
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)
{
@@ -512,6 +536,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[] = {
@@ -554,18 +580,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;
@@ -708,6 +734,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)
@@ -791,6 +842,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);
@@ -997,15 +1058,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)
{
@@ -1269,35 +1321,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);
@@ -1311,11 +1334,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);
- /* Set default configuration */
- dac33_init_chip(codec);
-
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
@@ -1332,12 +1350,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);
-
- /* Bias level configuration has enabled regulator an extra time */
- regulator_bulk_disable(ARRAY_SIZE(dac33->supplies), dac33->supplies);
-
return 0;
pcm_err:
@@ -1374,6 +1386,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;
@@ -1392,8 +1406,9 @@ 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,
.delay = dac33_dai_delay,
.set_sysclk = dac33_set_dai_sysclk,
@@ -1447,6 +1462,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);
@@ -1487,8 +1503,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 */
@@ -1526,12 +1540,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) {
@@ -1546,14 +1562,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) {
@@ -1577,7 +1588,9 @@ 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);
diff --git a/sound/soc/codecs/tpa6130a2.c b/sound/soc/codecs/tpa6130a2.c
index 958d49c969ac..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;
@@ -53,6 +56,8 @@ struct tpa6130a2_data {
struct regulator_bulk_data supplies[TPA6130A2_NUM_SUPPLIES];
int power_gpio;
unsigned char power_state;
+ enum tpa_model id;
+ int gain_limit;
};
static int tpa6130a2_i2c_read(int reg)
@@ -175,6 +180,40 @@ exit:
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)
{
@@ -238,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.
@@ -257,10 +305,22 @@ 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[] = {
+ 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_TPA("TPA6140A2 Headphone Playback Volume",
+ TPA6130A2_REG_VOL_MUTE, 1, TPA6140A2_GAIN_MAX, 0,
+ tpa6140_tlv),
};
/*
@@ -368,13 +428,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 +476,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,20 +495,35 @@ 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];
+ 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),
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index 2e025a3a2618..b717a03dfacf 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;
-
- if (enable == twl4030->apll_enabled)
- return;
+ int status = -1;
- 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;
@@ -1167,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"),
@@ -1180,6 +1205,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 +1273,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 +1404,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,8 +1477,14 @@ static const struct snd_soc_dapm_route intercon[] = {
{"Vibra Mux", "AudioR2", "DAC Right2"},
/* outputs */
- {"OUTL", NULL, "Analog L2 Playback Mixer"},
- {"OUTR", NULL, "Analog R2 Playback Mixer"},
+ /* 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 */
{"EARPIECE", NULL, "Earpiece PGA"},
{"PREDRIVEL", NULL, "PredriveL PGA"},
{"PREDRIVER", NULL, "PredriveR PGA"},
@@ -1465,6 +1498,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 +1536,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"},