From d313e0a88d1b29d17198ef659af042a633a2d3de Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 4 Mar 2015 20:43:20 +0100 Subject: [PATCH] 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 --- include/sound/hda_regmap.h | 59 +++++++++++++++++++++++++++++++ sound/hda/hdac_regmap.c | 71 +++++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) 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 @@ -45,6 +45,20 @@ int snd_hdac_regmap_update_raw(struct hdac_device *codec, unsigned int reg, ((dir) == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT) | \ (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 @@ -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; -- 2.39.5