[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