summaryrefslogtreecommitdiffstats
path: root/sound/hda/hdac_regmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/hda/hdac_regmap.c')
-rw-r--r--sound/hda/hdac_regmap.c71
1 files changed, 70 insertions, 1 deletions
diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c
index 486ef720cbff..fb4a02e0319f 100644
--- a/sound/hda/hdac_regmap.c
+++ b/sound/hda/hdac_regmap.c
@@ -124,6 +124,70 @@ static bool hda_readable_reg(struct device *dev, unsigned int reg)
return hda_writeable_reg(dev, reg);
}
+/*
+ * Stereo amp pseudo register:
+ * for making easier to handle the stereo volume control, we provide a
+ * fake register to deal both left and right channels by a single
+ * (pseudo) register access. A verb consisting of SET_AMP_GAIN with
+ * *both* SET_LEFT and SET_RIGHT bits takes a 16bit value, the lower 8bit
+ * for the left and the upper 8bit for the right channel.
+ */
+static bool is_stereo_amp_verb(unsigned int reg)
+{
+ if (((reg >> 8) & 0x700) != AC_VERB_SET_AMP_GAIN_MUTE)
+ return false;
+ return (reg & (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT)) ==
+ (AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
+}
+
+/* read a pseudo stereo amp register (16bit left+right) */
+static int hda_reg_read_stereo_amp(struct hdac_device *codec,
+ unsigned int reg, unsigned int *val)
+{
+ unsigned int left, right;
+ int err;
+
+ reg &= ~(AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT);
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_LEFT, 0, &left);
+ if (err < 0)
+ return err;
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_GET_RIGHT, 0, &right);
+ if (err < 0)
+ return err;
+ *val = left | (right << 8);
+ return 0;
+}
+
+/* write a pseudo stereo amp register (16bit left+right) */
+static int hda_reg_write_stereo_amp(struct hdac_device *codec,
+ unsigned int reg, unsigned int val)
+{
+ int err;
+ unsigned int verb, left, right;
+
+ verb = AC_VERB_SET_AMP_GAIN_MUTE << 8;
+ if (reg & AC_AMP_GET_OUTPUT)
+ verb |= AC_AMP_SET_OUTPUT;
+ else
+ verb |= AC_AMP_SET_INPUT | ((reg & 0xf) << 8);
+ reg = (reg & ~0xfffff) | verb;
+
+ left = val & 0xff;
+ right = (val >> 8) & 0xff;
+ if (left == right) {
+ reg |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
+ return snd_hdac_exec_verb(codec, reg | left, 0, NULL);
+ }
+
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_LEFT | left, 0, NULL);
+ if (err < 0)
+ return err;
+ err = snd_hdac_exec_verb(codec, reg | AC_AMP_SET_RIGHT | right, 0, NULL);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct hdac_device *codec = context;
@@ -131,6 +195,8 @@ static int hda_reg_read(void *context, unsigned int reg, unsigned int *val)
if (!codec_is_running(codec))
return -EAGAIN;
reg |= (codec->addr << 28);
+ if (is_stereo_amp_verb(reg))
+ return hda_reg_read_stereo_amp(codec, reg, val);
return snd_hdac_exec_verb(codec, reg, 0, val);
}
@@ -145,8 +211,11 @@ static int hda_reg_write(void *context, unsigned int reg, unsigned int val)
reg &= ~0x00080000U; /* drop GET bit */
reg |= (codec->addr << 28);
- verb = get_verb(reg);
+ if (is_stereo_amp_verb(reg))
+ return hda_reg_write_stereo_amp(codec, reg, val);
+
+ verb = get_verb(reg);
switch (verb & 0xf00) {
case AC_VERB_SET_AMP_GAIN_MUTE:
verb = AC_VERB_SET_AMP_GAIN_MUTE;