@@ -89,6 +89,9 @@ 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];
+ int bypass;
};
static const struct reg_default aic3x_reg[] = {
@@ -134,16 +137,166 @@ 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)
+
+/*
+ * The headset detect flag, the button press detect flag and the headset
+ * type flag are stored in read only registers, 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);
+ if (aic3x->power)
+ regcache_cache_bypass(aic3x->regmap, true);
+ ret = snd_soc_get_volsw(kcontrol, ucontrol);
+ if (aic3x->power)
+ regcache_cache_bypass(aic3x->regmap, false);
+ return ret;
+}
+
+/*
+ * All input lines have additional volume gain controls. Value 0x0 is 0dB gain
+ * and value 0x8 is -12 dB gain. It's a little bit tricky because value 0xF
+ * means mute. values 0x9-0xE are reserved. If switch is muted we should store
+ * value in cache and should not set up 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;
+
+ /* We need to cache value. Then we need to test each value on
+ * mute in hw register. If not muted, we should set up new value */
+ reg = mc->reg;
+ newval = (ucontrol->value.integer.value[0] & mask);
+ do {
+ if (invert)
+ newval = (8 - newval) & mask;
+ /* 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) {
+ aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]
+ &= ~(mask<<shift);
+ aic3x->cached_gain[reg-MIC3LR_2_LADC_CTRL]
+ |= (newval<<shift);
+ }
+
+ if (regval != 0xf && regval != newval)
+ snd_soc_update_bits(codec, reg, mask<<shift,
+ newval<<shift);
+
+ if (snd_soc_volsw_is_stereo(mc)) {
+ /* first time loop will be switched to true and
+ * second time loop will be switched to false, so
+ * we could finish loop */
+ loop = !loop;
+ if (loop) { /* reinit vars for second loop */
+ reg = mc->rreg;
+ newval = (ucontrol->value.integer.value[1]
+ & mask);
+ continue;
+ }
+ }
+
+ } while (loop);
+ return 0;
+}
+
+/*
+ * All input lines have additional volume gain controls. Value 0x0 is 0dB gain
+ * and value 0x8 is -12 dB gain. It's a little bit tricky because value 0xF
+ * means mute. Values 0x9-0xE are reserved. If switch is muted we should read
+ * value from cache but not from hw 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;
+
+ reg = mc->reg;
+
+ do {
+ regdata = snd_soc_read(codec, reg);
+ regval = (regdata >> shift) & mask;
+
+ /* 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;
+ }
+ if (invert)
+ regval = (8 - regval) & mask;
+
+ ucontrol->value.integer.value[loop] = regval;
+
+ if (snd_soc_volsw_is_stereo(mc)) {
+ /* first time loop will be switched to 1 and second time
+ * loop will be switched to 0, so we could finish loop */
+ loop = loop ? 0 : 1;
+ if (loop) { /* reinit vars for second loop */
+ reg = mc->rreg;
+ 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
+ * All input lines are connected when gain is not equal to 0xf and disconnected
+ * in other cases. We have to use the specific dapm_put call for input mixer.
+ * In case of unmute we should set up register's value by cached value.
*/
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,7 +304,7 @@ 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;