aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/sound/hda_regmap.h59
-rw-r--r--sound/hda/hdac_regmap.c71
2 files changed, 129 insertions, 1 deletions
diff --git a/include/sound/hda_regmap.h b/include/sound/hda_regmap.h
index a6a4f3ddb469..76648ccfbbf8 100644
--- a/include/sound/hda_regmap.h
+++ b/include/sound/hda_regmap.h
@@ -46,6 +46,20 @@ int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg,
(idx))
/**
+ * snd_hdac_regmap_encode_amp_stereo - encode a pseudo register for stereo AMPs
+ * @nid: widget NID
+ * @dir: direction (#HDA_INPUT, #HDA_OUTPUT)
+ * @idx: input index value
+ *
+ * Returns an encoded pseudo register.
+ */
+#define snd_hdac_regmap_encode_amp_stereo(nid, dir, idx) \
+ (snd_hdac_regmap_encode_verb(nid, AC_VERB_GET_AMP_GAIN_MUTE) | \
+ AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT | /* both bits set! */ \
+ ((dir) == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT) | \
+ (idx))
+
+/**
* snd_hdac_regmap_write - Write a verb with caching
* @nid: codec NID
* @reg: verb to write
@@ -143,4 +157,49 @@ snd_hdac_regmap_update_amp(struct hdac_device *codec, hda_nid_t nid,
return snd_hdac_regmap_update_raw(codec, cmd, mask, val);
}
+/**
+ * snd_hdac_regmap_get_amp_stereo - Read stereo AMP values
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @ch: channel (left=0 or right=1)
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @index: the index value (only for input direction)
+ * @val: the pointer to store the value
+ *
+ * Read stereo AMP values. The lower byte is left, the upper byte is right.
+ * Returns the value or a negative error.
+ */
+static inline int
+snd_hdac_regmap_get_amp_stereo(struct hdac_device *codec, hda_nid_t nid,
+ int dir, int idx)
+{
+ unsigned int cmd = snd_hdac_regmap_encode_amp_stereo(nid, dir, idx);
+ int err, val;
+
+ err = snd_hdac_regmap_read_raw(codec, cmd, &val);
+ return err < 0 ? err : val;
+}
+
+/**
+ * snd_hdac_regmap_update_amp_stereo - update the stereo AMP value
+ * @codec: HD-audio codec
+ * @nid: NID to read the AMP value
+ * @direction: #HDA_INPUT or #HDA_OUTPUT
+ * @idx: the index value (only for input direction)
+ * @mask: bit mask to set
+ * @val: the bits value to set
+ *
+ * Update the stereo AMP value with a bit mask.
+ * The lower byte is left, the upper byte is right.
+ * Returns 0 if the value is unchanged, 1 if changed, or a negative error.
+ */
+static inline int
+snd_hdac_regmap_update_amp_stereo(struct hdac_device *codec, hda_nid_t nid,
+ int dir, int idx, int mask, int val)
+{
+ unsigned int cmd = snd_hdac_regmap_encode_amp_stereo(nid, dir, idx);
+
+ return snd_hdac_regmap_update_raw(codec, cmd, mask, val);
+}
+
#endif /* __SOUND_HDA_REGMAP_H */
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;