[alsa-devel] ASoC: TLV320AIC3x: Adding additional functionality for 3106 with [Patch] for discuss
Timur Karaldin
karaldin at mcsplus.ru
Wed Mar 23 16:21:47 CET 2016
Hi Peter!
Here is my patch v2. It works only for aic3106, but I don't have any
others. Also patch contains some debug output, I would clean it. I like
behaviour of this improvements and I didn't know any issue right now for
these add-ons except debug output and that it's correct only for aic3106
(didn't check for others even on datasheet level). Thank you for all
your help!
-----------------------------------------------------------------------
diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c
index d7349bc..2c06e21 100644
--- a/sound/soc/codecs/tlv320aic3x.c
+++ b/sound/soc/codecs/tlv320aic3x.c
@@ -89,6 +89,7 @@ struct aic3x_priv {
/* Selects the micbias voltage */
enum aic3x_micbias_voltage micbias_vg;
+ unsigned char cached_gain[LINE1L_2_RADC_CTRL-MIC3LR_2_LADC_CTRL+1];
};
static const struct reg_default aic3x_reg[] = {
@@ -134,16 +135,175 @@ static const struct regmap_config aic3x_regmap = {
#define SOC_DAPM_SINGLE_AIC3X(xname, reg, shift, mask, invert) \
SOC_SINGLE_EXT(xname, reg, shift, mask, invert, \
- snd_soc_dapm_get_volsw, snd_soc_dapm_put_volsw_aic3x)
+ snd_soc_dapm_get_volsw_aic3x, snd_soc_dapm_put_volsw_aic3x)
+
+#define SOC_DOUBLE_R_AIC3X_TLV(xname, reg, rreg, shift, mask, invert) \
+ SOC_DOUBLE_R_EXT_TLV(xname, reg, rreg, shift, mask, invert, \
+ snd_soc_get_gain_aic3x, snd_soc_put_gain_aic3x, gain_stage_tlv)
+
+#define SOC_SINGLE_AIC3X_TLV(xname, reg, shift, mask, invert) \
+ SOC_SINGLE_EXT_TLV(xname, reg, shift, mask, invert, \
+ snd_soc_get_gain_aic3x, snd_soc_put_gain_aic3x, gain_stage_tlv)
+
+#define SOC_SINGLE_EXT_VOL(xname, reg, shift, mask, invert) \
+ SOC_SINGLE_EXT(xname, reg, shift, mask, invert, \
+ snd_soc_get_volsw_uncached, snd_soc_put_volsw)
+
+/*
+ * Headset detect flag, button press detect flag and headset type flag
is read only register,
+ * but we could not declare these regs as volatile because other bits
are RW, so we use
+ * unchached get hanler for these regs.
+*/
+static int snd_soc_get_volsw_uncached( struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value * ucontrol)
+{
+ int ret;
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+
+ regcache_cache_bypass(aic3x->regmap, true);
+ ret = snd_soc_get_volsw(kcontrol, ucontrol);
+ regcache_cache_bypass(aic3x->regmap, false);
+ return ret;
+}
+
+/*
+ * All input lines have additional gain controls which can be controled
by volume control.
+ * 0x0 - means 0dB and 0x8 - means -12 dB. It's a litlle bit tricky
because value 0xF means mute.
+ * value 0x9-0xE reserverd, so in case of switch muted we should store
value in cache and should
+ * not set up it to register
+*/
+static int snd_soc_put_gain_aic3x(struct snd_kcontrol* kcontrol,
+ struct snd_ctl_elem_value* ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc = (struct soc_mixer_control
*)kcontrol->private_value;
+ unsigned int reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1<<fls(max))-1;
+ unsigned int invert = mc->invert;
+
+ u8 regdata, newval, regval;
+ u8 loop = 0;
+
+ //First of all we need to cache value. Then we need to test each
value on mute in register
+ //If not muted, we should setup new value
+ reg = mc->reg;
+ newval = ( ucontrol->value.integer.value[0] & mask );
+ printk(KERN_INFO "set gain control reg:%d val:%0X mask:%0X
shift:%0X invert %d\n", reg,newval, mask, shift,invert);
+ do
+ {
+ if(invert)
+ {
+ newval = ( 8 - newval ) & mask;
+ printk(KERN_INFO "inverted val %0X\n", newval);
+ }
+ //read, test and update if first reg if needs
+ regdata = snd_soc_read(codec, reg);
+ regval = ( regdata >> shift) & mask;
+
+ if( reg >= MIC3LR_2_LADC_CTRL && reg <= LINE1L_2_RADC_CTRL )
+ {
+ printk(KERN_INFO "cached_gain before
%0X\n",aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]);
+ aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]&=~(mask<<shift);
+ aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]|=(newval<<shift);
+ printk(KERN_INFO "cached_gain after
%0X\n",aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]);
+ }
+
+ if ( regval!=0xf && regval!=newval )
+ snd_soc_update_bits( codec, reg, mask<<shift,
newval<<shift );
+
+ if(snd_soc_volsw_is_stereo(mc))
+ {
+ loop=!loop; //switch loop, so for first
time loop will be true after and for second time it will be false, so we
will finish loop
+ if(loop) //if first time
+ {
+ reg=mc->rreg;
+ newval =
(ucontrol->value.integer.value[1] & mask);
+ printk(KERN_INFO "control is stereo
second reg:%d val:%0X\n", reg,newval);
+ continue;
+ }
+ }
+
+ }while(loop);
+ return 0;
+}
+
+/*
+ * All input lines have additional gain controls which can be controled
by volume control.
+ * 0x0 - means 0dB and 0x8 - means -12 dB. It's a litlle bit tricky
because value 0xF means mute.
+ * value 0x9-0xE reserverd, so in case of switch muted we should read
value from cache in aic3x_priv
+ * and should not read it from regcache or register
+*/
+static int snd_soc_get_gain_aic3x( struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+ struct soc_mixer_control *mc = (struct soc_mixer_control
*)kcontrol->private_value;
+ unsigned int reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1<<fls(max))-1;
+ unsigned int invert = mc->invert;
+
+ u8 regdata, regval;
+ u8 loop = 0;
+
+ //First of all we need to cache value. Then we need to test each
value on mute in register
+ //If not muted, we should set up new value
+ reg = mc->reg;
+
+ printk(KERN_INFO "get gain control reg:%d mask:%0X shift:%0X
invert %d\n", reg, mask, shift,invert);
+ do
+ {
+ regdata = snd_soc_read(codec, reg);
+ regval = ( regdata >> shift) & mask;
+ printk(KERN_INFO "regval:%0X\n", regval);
+
+ //check if register is muted then return cached value
+ if( regval == 0xf )
+ {
+ if( reg >=MIC3LR_2_LADC_CTRL && reg <=
LINE1L_2_RADC_CTRL )
+ regval =
(aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]>>shift) & mask;
+ printk(KERN_INFO "regval from cache:%0X\n", regval);
+
+ }
+ if( invert )
+ {
+ regval = (8 - regval) & mask;
+ printk(KERN_INFO "regval after invert:%0X\n",
regval);
+ }
+ ucontrol->value.integer.value[loop] = regval;
+
+ if(snd_soc_volsw_is_stereo(mc))
+ {
+ loop=loop?0:1; //switch loop, so for first time
loop will be 1 after and for second time it will be 0, so we will exit
from while-loop
+ if(loop) //if first time
+ {
+ reg=mc->rreg;
+ printk(KERN_INFO "control is stereo
second reg:%d\n", reg);
+ continue;
+ }
+ }
+
+ }while(loop);
+ return 0;
+}
/*
* All input lines are connected when !0xf and disconnected with 0xf
bit field,
- * so we have to use specific dapm_put call for input mixer
+ * so we have to use specific dapm_put call for input mixer. In case of
unmute
+ * we should set up register value from cached values from aic3x_priv
struct.
*/
static int snd_soc_dapm_put_volsw_aic3x(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value
*ucontrol)
{
struct snd_soc_codec *codec =
snd_soc_dapm_kcontrol_codec(kcontrol);
+ struct aic3x_priv *aic3x = snd_soc_codec_get_drvdata(codec);
+
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
unsigned int reg = mc->reg;
@@ -151,20 +311,32 @@ static int snd_soc_dapm_put_volsw_aic3x(struct
snd_kcontrol *kcontrol,
int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1;
unsigned int invert = mc->invert;
- unsigned short val;
+ unsigned int val;
struct snd_soc_dapm_update update;
int connect, change;
val = (ucontrol->value.integer.value[0] & mask);
+ printk(KERN_INFO "dapm put volsw val:%X reg:%X mask:%X shift:%X
invert:%X\n", val, reg, mask, shift, invert);
mask = 0xf;
if (val)
+ {
val = mask;
-
+ printk(KERN_INFO "new val:%X\n", val);
+ }
connect = !!val;
if (invert)
+ {
val = mask - val;
+ printk(KERN_INFO "val after invert:%X\n", val);
+ }
+ if (!val)
+ {
+ if( reg>=MIC3LR_2_LADC_CTRL && reg<=LINE1L_2_RADC_CTRL )
+ val =
(aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]>>shift) & mask;
+ printk(KERN_INFO "cached val:%X\n", val);
+ }
mask <<= shift;
val <<= shift;
@@ -183,6 +355,36 @@ static int snd_soc_dapm_put_volsw_aic3x(struct
snd_kcontrol *kcontrol,
return change;
}
+/*
+ * Based on standart handler snd_soc_dapm_get_volsw, but changing mask
to 0xF
+ *
+ */
+static int snd_soc_dapm_get_volsw_aic3x(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ unsigned int reg = mc->reg;
+ unsigned int shift = mc->shift;
+ int max = mc->max;
+ unsigned int mask = (1 << fls(max)) - 1;
+ int ret;
+
+ //register contains 4 bits, so we change max temoprally to read
register by original handler, then return it back
+ mc->max = 15;
+ ret = snd_soc_dapm_get_volsw( kcontrol, ucontrol);
+ printk(KERN_INFO "soc_dapm_get_volsw_aic3x:%ld reg:%X shift:%X
max:%X newmax:%X\n", ucontrol->value.integer.value[0], reg, shift, max,
mc->max );
+ mc->max = max;
+
+ //all gains except mute (0xf) after invertion is not equl to 0,
so we need to set 1(on)
+ //for all values except 0. 0 is mute, so we do not need to change it
+ if(ucontrol->value.integer.value[0]!=0)
+ ucontrol->value.integer.value[0]=1;
+ printk(KERN_INFO "new value:%ld\n",
ucontrol->value.integer.value[0]);
+
+ return ret;
+}
+
/*
* mic bias power on/off share the same register bits with
* output voltage of mic bias. when power on mic bias, we
@@ -270,6 +472,14 @@ static const struct soc_enum aic3x_agc_decay_enum[] = {
SOC_ENUM_SINGLE(RAGC_CTRL_A, 0, 4, aic3x_agc_decay),
};
+static const char *aic3x_headset_debounce[] = { "16ms", "32ms", "64ms",
"128ms", "256ms", "512ms" };
+static const struct soc_enum aic3x_headset_debounce_enum =
+SOC_ENUM_SINGLE( AIC3X_HEADSET_DETECT_CTRL_A, 2, 6,
aic3x_headset_debounce);
+
+static const char *aic3x_button_debounce[] = { "0ms", "8ms", "16ms",
"32ms" };
+static const struct soc_enum aic3x_button_debounce_enum =
+SOC_ENUM_SINGLE( AIC3X_HEADSET_DETECT_CTRL_A, 0, 4, aic3x_button_debounce);
+
/*
* DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps
*/
@@ -287,6 +497,9 @@ static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 50, 0);
*/
static DECLARE_TLV_DB_SCALE(output_stage_tlv, -5900, 50, 1);
+static DECLARE_TLV_DB_SCALE(gain_stage_tlv, -1200, 150, 0);
+
+
static const struct snd_kcontrol_new aic3x_snd_controls[] = {
/* Output */
SOC_DOUBLE_R_TLV("PCM Playback Volume",
@@ -399,6 +612,24 @@ static const struct snd_kcontrol_new
aic3x_snd_controls[] = {
SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1),
SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]),
+ /* Additional controls */
+ SOC_DOUBLE_R_AIC3X_TLV("Mic3L Volume", MIC3LR_2_LADC_CTRL,
MIC3LR_2_RADC_CTRL, 4, 8, 1),
+ SOC_DOUBLE_R_AIC3X_TLV("Mic3R Volume", MIC3LR_2_LADC_CTRL,
MIC3LR_2_RADC_CTRL, 0, 8, 1),
+ SOC_DOUBLE_R_AIC3X_TLV("Line1L Volume", LINE1L_2_LADC_CTRL,
LINE1L_2_RADC_CTRL, 3, 8, 1),
+ SOC_DOUBLE_R_AIC3X_TLV("Line1R Volume", LINE1R_2_LADC_CTRL,
LINE1R_2_RADC_CTRL, 3, 8, 1),
+ SOC_SINGLE_AIC3X_TLV("Line2L Volume", LINE2L_2_LADC_CTRL, 3, 8, 1),
+ SOC_SINGLE_AIC3X_TLV("Line2R Volume", LINE2R_2_RADC_CTRL, 3, 8, 1),
+ SOC_ENUM("Headset Jack Debounce", aic3x_headset_debounce_enum ),
+ SOC_ENUM("Button Press Debounce", aic3x_button_debounce_enum ),
+ SOC_SINGLE("Headset Detect Enable", AIC3X_HEADSET_DETECT_CTRL_A,
7, 1, 0),
+ SOC_SINGLE_EXT_VOL("Headset Detect Type",
AIC3X_HEADSET_DETECT_CTRL_A,
+ AIC3X_HEADSET_DETECT_A_SHIFT,
AIC3X_HEADSET_DETECT_A_MASK, 0),
+ SOC_SINGLE_EXT_VOL("Button Detect Flag",
AIC3X_HEADSET_DETECT_CTRL_B,
+ AIC3X_BUTTON_DETECT_SHIFT, AIC3X_BUTTON_DETECT_MASK, 0),
+ SOC_SINGLE_EXT_VOL("Headset Detect Flag",
AIC3X_HEADSET_DETECT_CTRL_B,
+ AIC3X_HEADSET_DETECT_B_SHIFT,
AIC3X_HEADSET_DETECT_B_MASK, 0),
+ SOC_SINGLE("High power output Ac-coupled",
AIC3X_HEADSET_DETECT_CTRL_B, 7, 1, 0),
+ SOC_SINGLE("Stereo pseudodifferential output",
AIC3X_HEADSET_DETECT_CTRL_B, 3, 1, 0),
};
static const struct snd_kcontrol_new aic3x_mono_controls[] = {
--------------------------------------
diff --git a/sound/soc/codecs/tlv320aic3x.h b/sound/soc/codecs/tlv320aic3x.h
index e521ac3..bcac87f 100644
--- a/sound/soc/codecs/tlv320aic3x.h
+++ b/sound/soc/codecs/tlv320aic3x.h
@@ -271,9 +271,20 @@ enum {
AIC3X_BUTTON_DEBOUNCE_32MS = 3
};
+typedef struct {
+ struct platform_device* pdev;
+ struct proc_dir_entry *proc_value;
+ struct snd_soc_codec *codec;
+} aic3106_detect_t;
+
#define AIC3X_HEADSET_DETECT_ENABLED 0x80
-#define AIC3X_HEADSET_DETECT_SHIFT 5
-#define AIC3X_HEADSET_DETECT_MASK 3
+#define AIC3X_HEADSET_DETECT_A_SHIFT 5
+#define AIC3X_HEADSET_DETECT_A_MASK 3
+
+#define AIC3X_BUTTON_DETECT_SHIFT 5
+#define AIC3X_BUTTON_DETECT_MASK 1
+#define AIC3X_HEADSET_DETECT_B_SHIFT 4
+#define AIC3X_HEADSET_DETECT_B_MASK 1
#define AIC3X_HEADSET_DEBOUNCE_SHIFT 2
#define AIC3X_HEADSET_DEBOUNCE_MASK 7
#define AIC3X_BUTTON_DEBOUNCE_SHIFT 0
--------------------------------------
17.03.2016 12:26, Peter Ujfalusi пишет:
> On 03/16/16 18:53, Timur Karaldin wrote:
>>> your mail client does not seem to wrap the lines correctly, can you check that.
>> I have no idea how these lines should looks, so it's very hard for me to see
>> what's wrong. Could you point me how it looks in original?
> They are looong.
>
> see: https://wiki.openstack.org/wiki/MailingListEtiquette
>
>> Ok, now it's much more clear for me.
>> Another question is register behaviour during soft reset. There is
>> "aic3x_set_power" handle. In this handle kernel makes SOFT_RESET, markes cache
>> as dirty, then power down the codec for handle power down request.
>> But as I could see main volumes like "HP DAC" and "PCM" stores values between
>> close and open in mixer and I could not see any code to handle it. On the
>> other hand my controls do not save states, as you mentioned because of
>> SOFT_RESET, could you explain such different behaviour?
> All cached registers are going to be restored after power on with exception of
> volatile registers. You need to restore the bits in a volatile registers in
> the driver.
>
--
С уважением,
Руководитель проектов
Морские комплексы и системы плюс
Каралдин Т.М.
----------------------------------------------------------------------------------
Best regards,
Projects director
Marine complexes and systems plus
Timur Karaldin
More information about the Alsa-devel
mailing list