diff options
author | Takashi Iwai <tiwai@suse.de> | 2015-03-04 20:43:20 +0100 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2015-03-23 13:19:42 +0100 |
commit | d313e0a88d1b29d17198ef659af042a633a2d3de (patch) | |
tree | bee10919b779f05bda95eb8c5ac2610226e79396 /sound | |
parent | a551d91473e5e3a591f6fe86ac5a5fb460c3f96a (diff) | |
download | op-kernel-dev-d313e0a88d1b29d17198ef659af042a633a2d3de.zip op-kernel-dev-d313e0a88d1b29d17198ef659af042a633a2d3de.tar.gz |
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')
-rw-r--r-- | sound/hda/hdac_regmap.c | 71 |
1 files changed, 70 insertions, 1 deletions
diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c index 486ef72..fb4a02e 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; |