--- /dev/null
+From 29fdbec2dcb1ce364812778271056aa9516ff3ed Mon Sep 17 00:00:00 2001
+From: Takashi Iwai <tiwai@suse.de>
+Date: Tue, 20 Jan 2009 13:07:55 +0100
+Subject: ALSA: hda - Add extra volume offset to standard volume amp macros
+Patch-mainline:
+References: bnc#466428
+
+
+Added the volume offset to base for the standard volume controls
+to handle elements with too big volume scales like -96dB..0dB.
+For such elements, you can set the base volume to reduce the range.
+
+Signed-off-by: Takashi Iwai <tiwai@suse.de>
+
+---
+ sound/pci/hda/hda_codec.c | 45 +++++++++++++++++++++++++++++++++++++--------
+ sound/pci/hda/hda_local.h | 5 ++++-
+ 2 files changed, 41 insertions(+), 9 deletions(-)
+
+diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
+index b7bba7d..0cf2424 100644
+--- a/sound/pci/hda/hda_codec.c
++++ b/sound/pci/hda/hda_codec.c
+@@ -967,6 +967,7 @@ int snd_hda_mixer_amp_volume_info(struct
+ u16 nid = get_amp_nid(kcontrol);
+ u8 chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
++ unsigned int ofs = get_amp_offset(kcontrol);
+ u32 caps;
+
+ caps = query_amp_caps(codec, nid, dir);
+@@ -978,6 +979,8 @@ int snd_hda_mixer_amp_volume_info(struct
+ kcontrol->id.name);
+ return -EINVAL;
+ }
++ if (ofs < caps)
++ caps -= ofs;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+@@ -985,6 +988,32 @@ int snd_hda_mixer_amp_volume_info(struct
+ return 0;
+ }
+
++
++static inline unsigned int
++read_amp_value(struct hda_codec *codec, hda_nid_t nid,
++ int ch, int dir, int idx, unsigned int ofs)
++{
++ unsigned int val;
++ val = snd_hda_codec_amp_read(codec, nid, ch, dir, idx);
++ val &= HDA_AMP_VOLMASK;
++ if (val >= ofs)
++ val -= ofs;
++ else
++ val = 0;
++ return val;
++}
++
++static inline int
++update_amp_value(struct hda_codec *codec, hda_nid_t nid,
++ int ch, int dir, int idx, unsigned int ofs,
++ unsigned int val)
++{
++ if (val > 0)
++ val += ofs;
++ return snd_hda_codec_amp_update(codec, nid, ch, dir, idx,
++ HDA_AMP_VOLMASK, val);
++}
++
+ int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+ {
+@@ -993,14 +1022,13 @@ int snd_hda_mixer_amp_volume_get(struct
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
++ unsigned int ofs = get_amp_offset(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+- *valp++ = snd_hda_codec_amp_read(codec, nid, 0, dir, idx)
+- & HDA_AMP_VOLMASK;
++ *valp++ = read_amp_value(codec, nid, 0, dir, idx, ofs);
+ if (chs & 2)
+- *valp = snd_hda_codec_amp_read(codec, nid, 1, dir, idx)
+- & HDA_AMP_VOLMASK;
++ *valp = read_amp_value(codec, nid, 1, dir, idx, ofs);
+ return 0;
+ }
+
+@@ -1012,18 +1040,17 @@ int snd_hda_mixer_amp_volume_put(struct
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
++ unsigned int ofs = get_amp_offset(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+
+ snd_hda_power_up(codec);
+ if (chs & 1) {
+- change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx,
+- 0x7f, *valp);
++ change = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp);
+ valp++;
+ }
+ if (chs & 2)
+- change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx,
+- 0x7f, *valp);
++ change |= update_amp_value(codec, nid, 1, dir, idx, ofs, *valp);
+ snd_hda_power_down(codec);
+ return change;
+ }
+@@ -1034,6 +1061,7 @@ int snd_hda_mixer_amp_tlv(struct snd_kco
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int dir = get_amp_direction(kcontrol);
++ unsigned int ofs = get_amp_offset(kcontrol);
+ u32 caps, val1, val2;
+
+ if (size < 4 * sizeof(unsigned int))
+@@ -1042,6 +1070,7 @@ int snd_hda_mixer_amp_tlv(struct snd_kco
+ val2 = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+ val2 = (val2 + 1) * 25;
+ val1 = -((caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT);
++ val1 += ofs;
+ val1 = ((int)val1) * ((int)val2);
+ if (put_user(SNDRV_CTL_TLVT_DB_SCALE, _tlv))
+ return -EFAULT;
+diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
+index 1dd8716..d53ce1f 100644
+--- a/sound/pci/hda/hda_local.h
++++ b/sound/pci/hda/hda_local.h
+@@ -26,8 +26,10 @@
+ /*
+ * for mixer controls
+ */
++#define HDA_COMPOSE_AMP_VAL_OFS(nid,chs,idx,dir,ofs) \
++ ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19) | ((ofs)<<23))
+ #define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) \
+- ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19))
++ HDA_COMPOSE_AMP_VAL_OFS(nid, chs, idx, dir, 0)
+ /* mono volume with index (index=0,1,...) (channel=1,2) */
+ #define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+@@ -432,5 +434,6 @@ int snd_hda_check_amp_list_power(struct
+ #define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
+ #define get_amp_direction(kc) (((kc)->private_value >> 18) & 0x1)
+ #define get_amp_index(kc) (((kc)->private_value >> 19) & 0xf)
++#define get_amp_offset(kc) (((kc)->private_value >> 23) & 0x3f)
+
+ #endif /* __SOUND_HDA_LOCAL_H */
+--
+1.6.1
+