[alsa-devel] RFC:
Peter Ujfalusi
peter.ujfalusi at nokia.com
Tue May 18 14:39:20 CEST 2010
Hello,
I apologize for the long mail, but I feel that I need to explain this in a bit
longish way...
ASoC core recently gained support for limiting the volume control's range
(maximum), which is really useful in embedded systems.
The implementation for this is really simple:
The codec drivers are implementing the full range the HW can support. Machine
drivers can than override the maximum volume on a given control if it is
necessary.
This works well with most of the controls:
Volume controls without dB scale.
Volume controls with DECLARE_TLV_DB_SCALE()
Problems started to emerge, when the volume is limited on control, which has
SNDRV_CTL_TLVT_DB_RANGE (from the sound/soc/codecs/tpa6130a2.c):
static const unsigned int tpa6140_tlv[] = {
TLV_DB_RANGE_HEAD(3),
0, 8, TLV_DB_SCALE_ITEM(-5900, 400, 0),
9, 16, TLV_DB_SCALE_ITEM(-2500, 200, 0),
17, 31, TLV_DB_SCALE_ITEM(-1000, 100, 0),
};
static const struct snd_kcontrol_new tpa6140a2_controls[] = {
SOC_SINGLE_EXT_TLV("TPA6140A2 Headphone Playback Volume",
TPA6130A2_REG_VOL_MUTE, 1, 0x1f, 0,
tpa6130a2_get_volsw, tpa6130a2_put_volsw,
tpa6140_tlv),
};
The original HW supported range is 0 .. 31 (-59 .. +4 dB).
When the machine driver sets the limit to 21 (-6 dB), than the volume range is
reported correctly as 0 .. 21, but snd_mixer_selem_get_playback_dB_range()
reports range of -59 .. +4 dB. This is not correct, it should report: -59 .. -6
dB.
If the control has single DECLARE_TLV_DB_SCALE() after the limiting both volume
and dB range is reported correctly.
The explanation for this is in the alsa-lib:
src/control/tlv.c:
/**
* \brief Get the dB min/max values
* \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
* \param rangemin the minimum value of the raw volume
* \param rangemax the maximum value of the raw volume
* \param min the pointer to store the minimum dB value (in 0.01dB unit)
* \param max the pointer to store the maximum dB value (in 0.01dB unit)
* \return 0 if successful, or a negative error code
*/
int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
long *min, long *max)
{
int err;
switch (tlv[0]) {
case SND_CTL_TLVT_DB_RANGE: {
unsigned int pos, len;
len = int_index(tlv[1]);
if (len > MAX_TLV_RANGE_SIZE)
return -EINVAL;
pos = 2;
while (pos + 4 <= len) {
long rmin, rmax;
rangemin = (int)tlv[pos];
rangemax = (int)tlv[pos + 1];
err = snd_tlv_get_dB_range(tlv + pos + 2,
rangemin, rangemax,
&rmin, &rmax);
if (err < 0)
return err;
if (pos > 2) {
if (rmin < *min)
*min = rmin;
if (rmax > *max)
*max = rmax;
} else {
*min = rmin;
*max = rmax;
}
pos += int_index(tlv[pos + 3]) + 4;
}
return 0;
}
case SND_CTL_TLVT_DB_SCALE: {
int step;
*min = (int)tlv[2];
step = (tlv[3] & 0xffff);
*max = *min + (long)(step * (rangemax - rangemin));
return 0;
}
case SND_CTL_TLVT_DB_MINMAX:
case SND_CTL_TLVT_DB_MINMAX_MUTE:
case SND_CTL_TLVT_DB_LINEAR:
*min = (int)tlv[2];
*max = (int)tlv[3];
return 0;
}
return -EINVAL;
}
In case of DECLARE_TLV_DB_SCALE(), the SND_CTL_TLVT_DB_SCALE case is taken, and
the control's maximum, and minimum range are used for the dB scale limit
calculation.
But, when SNDRV_CTL_TLVT_DB_RANGE is used (tpa6130a2 driver), than that case
will dismisses the control's maximum and minimum range, and goes through the
whole TLV struct, and reports the HW supported range.
If it would stop at the driver reported maximum, than the dB range reported
would be correct.
So a 'simple' patch to alsa-lib would solve this. But what happens, if we want
to limit control, which has DB_MINMAX, DB_MINMAX_MUTE, or DB_LINEAR?
There is no way to actually limit those in a way, that the dB scale reported
would be correct. We just stretch the steps bigger, like what happens with the
SNDRV_CTL_TLVT_DB_RANGE in the tpa6130a2 driver.
When looking for users of DB_LINEAR I have found 8 drivers, DB_MINMAX is used by
usbmixer only, the DB_MINMAX_MUTE has no users at all.
Since both DB_LINEAR and DB_MINMAX can be presented as DB_SCALE, and the effort
to convert the drivers should not be that big.
In this way, we will only have drivers using DB_SCALE in kernel, we can mark the
other DB_ types as deprecated (but keeping the support for out of tree drivers),
and the alsa-lib's tlv code can be adjusted to support properly the volume
limiting. If there is interest, than the ALSA core could also gain support for
platform/configuration dependent volume limiting.
If this is too big change to ALSA (which I think it is not, since for the user
it makes no difference, and the volume range, dB scale not going to be changed),
than probably cleaning up the sound/soc/ to use _only_ DB_SCALE type for volume
controls might be the solution, since the volume limiting feature is only in
ASoC AFAIK.
Either way, the alsa-lib's src/control/tlv.c:snd_tlv_get_dB_range function in
case of SND_CTL_TLVT_DB_RANGE needs some fix to handle the situation, when the
volume range is _not_ at the HW supported range.
To summarize, I'd like to do (or ask help to do) one of the following
[1] Replace the use of DB_LINEAR, DB_MINMAX with DB_SCALE in all drivers under
sound/
Document, that the now unused DB_ types are deprecated
Fix alsa-lib to support the volume limiting in regards of dB scale
[2] Replace the use of DB_LINEAR with DB_SCALE in all drivers under
sound/soc/
Fix alsa-lib to support the volume limiting in regards of dB scale
What do you think?
--
Péter
More information about the Alsa-devel
mailing list