aboutsummaryrefslogtreecommitdiff
path: root/sound/hda
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2015-03-04 20:43:20 +0100
committerTakashi Iwai <tiwai@suse.de>2015-03-23 13:19:42 +0100
commitd313e0a88d1b29d17198ef659af042a633a2d3de (patch)
treebee10919b779f05bda95eb8c5ac2610226e79396 /sound/hda
parenta551d91473e5e3a591f6fe86ac5a5fb460c3f96a (diff)
ALSA: hda - Add a fake stereo amp register support
HD-audio spec is inconvenient regarding the handling of stereo volume controls. It can set and get only single channel at once (although there is a special option to set the same value to both channels). This patch provides a fake pseudo-register via the regmap access so that the stereo channels can be read and written by a single call. It'd be useful, for example, for implementing DAPM widgets. A stereo amp pseudo register consists of the encoding like the normal amp verbs but it has both SET_LEFT (bit 13) and SET_RIGHT (bit 12) bits set. The regmap reads and writes a 16bit value for this pseudo register where the upper 8bit is for the right chanel and the lower 8bit for the left channel. Note that the driver doesn't recognize conflicts when both stereo and mono channel registers are mixed. Mixing them would certainly confuse the operation. So, use carefully. Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/hda')
-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;