Blame src/control/tlv.c

Packit Service db8eaa
/**
Packit Service db8eaa
 * \file control/tlv.c
Packit Service db8eaa
 * \brief dB conversion functions from control TLV information
Packit Service db8eaa
 * \author Takashi Iwai <tiwai@suse.de>
Packit Service db8eaa
 * \date 2007
Packit Service db8eaa
 */
Packit Service db8eaa
/*
Packit Service db8eaa
 *  Control Interface - dB conversion functions from control TLV information
Packit Service db8eaa
 *
Packit Service db8eaa
 *  Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
Packit Service db8eaa
 *
Packit Service db8eaa
 *
Packit Service db8eaa
 *   This library is free software; you can redistribute it and/or modify
Packit Service db8eaa
 *   it under the terms of the GNU Lesser General Public License as
Packit Service db8eaa
 *   published by the Free Software Foundation; either version 2.1 of
Packit Service db8eaa
 *   the License, or (at your option) any later version.
Packit Service db8eaa
 *
Packit Service db8eaa
 *   This program is distributed in the hope that it will be useful,
Packit Service db8eaa
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service db8eaa
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service db8eaa
 *   GNU Lesser General Public License for more details.
Packit Service db8eaa
 *
Packit Service db8eaa
 *   You should have received a copy of the GNU Lesser General Public
Packit Service db8eaa
 *   License along with this library; if not, write to the Free Software
Packit Service db8eaa
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Packit Service db8eaa
 *
Packit Service db8eaa
 */
Packit Service db8eaa
Packit Service db8eaa
#include <stdio.h>
Packit Service db8eaa
#include <stdlib.h>
Packit Service db8eaa
#include <unistd.h>
Packit Service db8eaa
#include <string.h>
Packit Service db8eaa
#ifndef HAVE_SOFT_FLOAT
Packit Service db8eaa
#include <math.h>
Packit Service db8eaa
#endif
Packit Service db8eaa
#include "control_local.h"
Packit Service db8eaa
Packit Service db8eaa
#ifndef DOC_HIDDEN
Packit Service db8eaa
/* convert to index of integer array */
Packit Service db8eaa
#define int_index(size)	(((size) + sizeof(int) - 1) / sizeof(int))
Packit Service db8eaa
/* max size of a TLV entry for dB information (including compound one) */
Packit Service db8eaa
#define MAX_TLV_RANGE_SIZE	256
Packit Service db8eaa
#endif
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Parse TLV stream and retrieve dB information
Packit Service db8eaa
 * \param tlv the TLV source
Packit Service db8eaa
 * \param tlv_size the byte size of TLV source
Packit Service db8eaa
 * \param db_tlvp the pointer stored the dB TLV information
Packit Service db8eaa
 * \return the byte size of dB TLV information if found in the given
Packit Service db8eaa
 *   TLV source, or a negative error code.
Packit Service db8eaa
 *
Packit Service db8eaa
 * This function parses the given TLV source and stores the TLV start
Packit Service db8eaa
 * point if the TLV information regarding dB conversion is found.
Packit Service db8eaa
 * The stored TLV pointer can be passed to the convesion functions
Packit Service db8eaa
 * #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
Packit Service db8eaa
 * #snd_tlv_get_dB_range().
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_tlv_parse_dB_info(unsigned int *tlv,
Packit Service db8eaa
			  unsigned int tlv_size,
Packit Service db8eaa
			  unsigned int **db_tlvp)
Packit Service db8eaa
{
Packit Service db8eaa
	unsigned int type;
Packit Service db8eaa
	unsigned int size;
Packit Service db8eaa
	int err;
Packit Service db8eaa
Packit Service db8eaa
	*db_tlvp = NULL;
Packit Service db8eaa
	type = tlv[SNDRV_CTL_TLVO_TYPE];
Packit Service db8eaa
	size = tlv[SNDRV_CTL_TLVO_LEN];
Packit Service db8eaa
	tlv_size -= 2 * sizeof(int);
Packit Service db8eaa
	if (size > tlv_size) {
Packit Service db8eaa
		SNDERR("TLV size error");
Packit Service db8eaa
		return -EINVAL;
Packit Service db8eaa
	}
Packit Service db8eaa
	switch (type) {
Packit Service db8eaa
	case SND_CTL_TLVT_CONTAINER:
Packit Service db8eaa
		size = int_index(size) * sizeof(int);
Packit Service db8eaa
		tlv += 2;
Packit Service db8eaa
		while (size > 0) {
Packit Service db8eaa
			unsigned int len;
Packit Service db8eaa
			err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
Packit Service db8eaa
			if (err < 0)
Packit Service db8eaa
				return err; /* error */
Packit Service db8eaa
			if (err > 0)
Packit Service db8eaa
				return err; /* found */
Packit Service db8eaa
			len = int_index(tlv[SNDRV_CTL_TLVO_LEN]) + 2;
Packit Service db8eaa
			size -= len * sizeof(int);
Packit Service db8eaa
			tlv += len;
Packit Service db8eaa
		}
Packit Service db8eaa
		break;
Packit Service db8eaa
	case SND_CTL_TLVT_DB_SCALE:
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX:
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX_MUTE:
Packit Service db8eaa
#ifndef HAVE_SOFT_FLOAT
Packit Service db8eaa
	case SND_CTL_TLVT_DB_LINEAR:
Packit Service db8eaa
#endif
Packit Service db8eaa
	case SND_CTL_TLVT_DB_RANGE: {
Packit Service db8eaa
		unsigned int minsize;
Packit Service db8eaa
		if (type == SND_CTL_TLVT_DB_RANGE)
Packit Service db8eaa
			minsize = 4 * sizeof(int);
Packit Service db8eaa
		else
Packit Service db8eaa
			minsize = 2 * sizeof(int);
Packit Service db8eaa
		if (size < minsize) {
Packit Service db8eaa
			SNDERR("Invalid dB_scale TLV size");
Packit Service db8eaa
			return -EINVAL;
Packit Service db8eaa
		}
Packit Service db8eaa
		if (size > MAX_TLV_RANGE_SIZE) {
Packit Service db8eaa
			SNDERR("Too big dB_scale TLV size: %d", size);
Packit Service db8eaa
			return -EINVAL;
Packit Service db8eaa
		}
Packit Service db8eaa
		*db_tlvp = tlv;
Packit Service db8eaa
		return size + sizeof(int) * 2;
Packit Service db8eaa
	}
Packit Service db8eaa
	default:
Packit Service db8eaa
		break;
Packit Service db8eaa
	}
Packit Service db8eaa
	return -EINVAL; /* not found */
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Get the dB min/max values
Packit Service db8eaa
 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
Packit Service db8eaa
 * \param rangemin the minimum value of the raw volume
Packit Service db8eaa
 * \param rangemax the maximum value of the raw volume
Packit Service db8eaa
 * \param min the pointer to store the minimum dB value (in 0.01dB unit)
Packit Service db8eaa
 * \param max the pointer to store the maximum dB value (in 0.01dB unit)
Packit Service db8eaa
 * \return 0 if successful, or a negative error code
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
Packit Service db8eaa
			 long *min, long *max)
Packit Service db8eaa
{
Packit Service db8eaa
	int err;
Packit Service db8eaa
Packit Service db8eaa
	switch (tlv[SNDRV_CTL_TLVO_TYPE]) {
Packit Service db8eaa
	case SND_CTL_TLVT_DB_RANGE: {
Packit Service db8eaa
		unsigned int pos, len;
Packit Service db8eaa
		len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
Packit Service db8eaa
		if (len > MAX_TLV_RANGE_SIZE)
Packit Service db8eaa
			return -EINVAL;
Packit Service db8eaa
		pos = 2;
Packit Service db8eaa
		while (pos + 4 <= len) {
Packit Service db8eaa
			long rmin, rmax;
Packit Service db8eaa
			long submin, submax;
Packit Service db8eaa
			submin = (int)tlv[pos];
Packit Service db8eaa
			submax = (int)tlv[pos + 1];
Packit Service db8eaa
			if (rangemax < submax)
Packit Service db8eaa
				submax = rangemax;
Packit Service db8eaa
			err = snd_tlv_get_dB_range(tlv + pos + 2,
Packit Service db8eaa
						   submin, submax,
Packit Service db8eaa
						   &rmin, &rmax);
Packit Service db8eaa
			if (err < 0)
Packit Service db8eaa
				return err;
Packit Service db8eaa
			if (pos > 2) {
Packit Service db8eaa
				if (rmin < *min)
Packit Service db8eaa
					*min = rmin;
Packit Service db8eaa
				if (rmax > *max)
Packit Service db8eaa
					*max = rmax;
Packit Service db8eaa
			} else {
Packit Service db8eaa
				*min = rmin;
Packit Service db8eaa
				*max = rmax;
Packit Service db8eaa
			}
Packit Service db8eaa
			if (rangemax == submax)
Packit Service db8eaa
				return 0;
Packit Service db8eaa
			pos += int_index(tlv[pos + 3]) + 4;
Packit Service db8eaa
		}
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
	case SND_CTL_TLVT_DB_SCALE: {
Packit Service db8eaa
		int step;
Packit Service db8eaa
		if (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0x10000)
Packit Service db8eaa
			*min = SND_CTL_TLV_DB_GAIN_MUTE;
Packit Service db8eaa
		else
Packit Service db8eaa
			*min = (int)tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
Packit Service db8eaa
		step = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff);
Packit Service db8eaa
		*max = (int)tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] +
Packit Service db8eaa
						step * (rangemax - rangemin);
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX:
Packit Service db8eaa
	case SND_CTL_TLVT_DB_LINEAR:
Packit Service db8eaa
		*min = (int)tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
Packit Service db8eaa
		*max = (int)tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX_MUTE:
Packit Service db8eaa
		*min = SND_CTL_TLV_DB_GAIN_MUTE;
Packit Service db8eaa
		*max = (int)tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
	return -EINVAL;
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Convert the given raw volume value to a dB gain
Packit Service db8eaa
 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
Packit Service db8eaa
 * \param rangemin the minimum value of the raw volume
Packit Service db8eaa
 * \param rangemax the maximum value of the raw volume
Packit Service db8eaa
 * \param volume the raw volume value to convert
Packit Service db8eaa
 * \param db_gain the dB gain (in 0.01dB unit)
Packit Service db8eaa
 * \return 0 if successful, or a negative error code
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
Packit Service db8eaa
			  long volume, long *db_gain)
Packit Service db8eaa
{
Packit Service db8eaa
	unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE];
Packit Service db8eaa
Packit Service db8eaa
	switch (type) {
Packit Service db8eaa
	case SND_CTL_TLVT_DB_RANGE: {
Packit Service db8eaa
		unsigned int pos, len;
Packit Service db8eaa
		len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
Packit Service db8eaa
		if (len > MAX_TLV_RANGE_SIZE)
Packit Service db8eaa
			return -EINVAL;
Packit Service db8eaa
		pos = 2;
Packit Service db8eaa
		while (pos + 4 <= len) {
Packit Service db8eaa
			rangemin = (int)tlv[pos];
Packit Service db8eaa
			rangemax = (int)tlv[pos + 1];
Packit Service db8eaa
			if (volume >= rangemin && volume <= rangemax)
Packit Service db8eaa
				return snd_tlv_convert_to_dB(tlv + pos + 2,
Packit Service db8eaa
							     rangemin, rangemax,
Packit Service db8eaa
							     volume, db_gain);
Packit Service db8eaa
			pos += int_index(tlv[pos + 3]) + 4;
Packit Service db8eaa
		}
Packit Service db8eaa
		return -EINVAL;
Packit Service db8eaa
	}
Packit Service db8eaa
	case SND_CTL_TLVT_DB_SCALE: {
Packit Service db8eaa
		int min, step, mute;
Packit Service db8eaa
		min = tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
Packit Service db8eaa
		step = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff);
Packit Service db8eaa
		mute = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] >> 16) & 1;
Packit Service db8eaa
		if (mute && volume <= rangemin)
Packit Service db8eaa
			*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
Packit Service db8eaa
		else
Packit Service db8eaa
			*db_gain = (volume - rangemin) * step + min;
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX:
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX_MUTE: {
Packit Service db8eaa
		int mindb, maxdb;
Packit Service db8eaa
		mindb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN];
Packit Service db8eaa
		maxdb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
Packit Service db8eaa
		if (volume <= rangemin || rangemax <= rangemin) {
Packit Service db8eaa
			if (type == SND_CTL_TLVT_DB_MINMAX_MUTE)
Packit Service db8eaa
				*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
Packit Service db8eaa
			else
Packit Service db8eaa
				*db_gain = mindb;
Packit Service db8eaa
		} else if (volume >= rangemax)
Packit Service db8eaa
			*db_gain = maxdb;
Packit Service db8eaa
		else
Packit Service db8eaa
			*db_gain = (maxdb - mindb) * (volume - rangemin) /
Packit Service db8eaa
				(rangemax - rangemin) + mindb;
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
#ifndef HAVE_SOFT_FLOAT
Packit Service db8eaa
	case SND_CTL_TLVT_DB_LINEAR: {
Packit Service db8eaa
		int mindb = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
Packit Service db8eaa
		int maxdb = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
Packit Service db8eaa
		if (volume <= rangemin || rangemax <= rangemin)
Packit Service db8eaa
			*db_gain = mindb;
Packit Service db8eaa
		else if (volume >= rangemax)
Packit Service db8eaa
			*db_gain = maxdb;
Packit Service db8eaa
		else {
Packit Service db8eaa
			double val = (double)(volume - rangemin) /
Packit Service db8eaa
				(double)(rangemax - rangemin);
Packit Service db8eaa
			if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
Packit Service db8eaa
				*db_gain = (long)(100.0 * 20.0 * log10(val)) +
Packit Service db8eaa
					maxdb;
Packit Service db8eaa
			else {
Packit Service db8eaa
				/* FIXME: precalculate and cache these values */
Packit Service db8eaa
				double lmin = pow(10.0, mindb/2000.0);
Packit Service db8eaa
				double lmax = pow(10.0, maxdb/2000.0);
Packit Service db8eaa
				val = (lmax - lmin) * val + lmin;
Packit Service db8eaa
				*db_gain = (long)(100.0 * 20.0 * log10(val));
Packit Service db8eaa
			}
Packit Service db8eaa
		}
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
#endif
Packit Service db8eaa
	}
Packit Service db8eaa
	return -EINVAL;
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Convert from dB gain to the corresponding raw value
Packit Service db8eaa
 * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
Packit Service db8eaa
 * \param rangemin the minimum value of the raw volume
Packit Service db8eaa
 * \param rangemax the maximum value of the raw volume
Packit Service db8eaa
 * \param db_gain the dB gain to convert (in 0.01dB unit)
Packit Service db8eaa
 * \param value the pointer to store the converted raw volume value
Packit Service db8eaa
 * \param xdir the direction for round-up. The value is round up
Packit Service db8eaa
 *        when this is positive. A negative value means round down.
Packit Service db8eaa
 *        Zero means round-up to nearest.
Packit Service db8eaa
 * \return 0 if successful, or a negative error code
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
Packit Service db8eaa
			    long db_gain, long *value, int xdir)
Packit Service db8eaa
{
Packit Service db8eaa
	unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE];
Packit Service db8eaa
Packit Service db8eaa
	switch (type) {
Packit Service db8eaa
	case SND_CTL_TLVT_DB_RANGE: {
Packit Service db8eaa
		long dbmin, dbmax, prev_submax;
Packit Service db8eaa
		unsigned int pos, len;
Packit Service db8eaa
		len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
Packit Service db8eaa
		if (len < 6 || len > MAX_TLV_RANGE_SIZE)
Packit Service db8eaa
			return -EINVAL;
Packit Service db8eaa
		pos = 2;
Packit Service db8eaa
		prev_submax = 0;
Packit Service db8eaa
		while (pos + 4 <= len) {
Packit Service db8eaa
			long submin, submax;
Packit Service db8eaa
			submin = (int)tlv[pos];
Packit Service db8eaa
			submax = (int)tlv[pos + 1];
Packit Service db8eaa
			if (rangemax < submax)
Packit Service db8eaa
				submax = rangemax;
Packit Service db8eaa
			if (!snd_tlv_get_dB_range(tlv + pos + 2,
Packit Service db8eaa
						  submin, submax,
Packit Service db8eaa
						  &dbmin, &dbmax) &&
Packit Service db8eaa
			    db_gain >= dbmin && db_gain <= dbmax)
Packit Service db8eaa
				return snd_tlv_convert_from_dB(tlv + pos + 2,
Packit Service db8eaa
							       submin, submax,
Packit Service db8eaa
							       db_gain, value, xdir);
Packit Service db8eaa
			else if (db_gain < dbmin) {
Packit Service db8eaa
				*value = xdir > 0 || pos == 2 ? submin : prev_submax;
Packit Service db8eaa
				return 0;
Packit Service db8eaa
			}
Packit Service db8eaa
			prev_submax = submax;
Packit Service db8eaa
			if (rangemax == submax)
Packit Service db8eaa
				break;
Packit Service db8eaa
			pos += int_index(tlv[pos + 3]) + 4;
Packit Service db8eaa
		}
Packit Service db8eaa
		*value = prev_submax;
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
	case SND_CTL_TLVT_DB_SCALE: {
Packit Service db8eaa
		int min, step, max, mute;
Packit Service db8eaa
		min = tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
Packit Service db8eaa
		step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff;
Packit Service db8eaa
		mute = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0x10000;
Packit Service db8eaa
		max = min + (int)(step * (rangemax - rangemin));
Packit Service db8eaa
		if (db_gain <= min)
Packit Service db8eaa
			if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
Packit Service db8eaa
			    mute)
Packit Service db8eaa
				*value = rangemin + 1;
Packit Service db8eaa
			else
Packit Service db8eaa
				*value = rangemin;
Packit Service db8eaa
		else if (db_gain >= max)
Packit Service db8eaa
			*value = rangemax;
Packit Service db8eaa
		else {
Packit Service db8eaa
			long v = (db_gain - min) * (rangemax - rangemin);
Packit Service db8eaa
			if (xdir > 0)
Packit Service db8eaa
				v += (max - min) - 1;
Packit Service db8eaa
			else if (xdir == 0)
Packit Service db8eaa
				v += ((max - min) + 1) / 2;
Packit Service db8eaa
			v = v / (max - min) + rangemin;
Packit Service db8eaa
			*value = v;
Packit Service db8eaa
		}
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX:
Packit Service db8eaa
	case SND_CTL_TLVT_DB_MINMAX_MUTE: {
Packit Service db8eaa
		int min, max;
Packit Service db8eaa
		min = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN];
Packit Service db8eaa
		max = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
Packit Service db8eaa
		if (db_gain <= min)
Packit Service db8eaa
			if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
Packit Service db8eaa
			    type == SND_CTL_TLVT_DB_MINMAX_MUTE)
Packit Service db8eaa
				*value = rangemin + 1;
Packit Service db8eaa
			else
Packit Service db8eaa
				*value = rangemin;
Packit Service db8eaa
		else if (db_gain >= max)
Packit Service db8eaa
			*value = rangemax;
Packit Service db8eaa
		else {
Packit Service db8eaa
			long v = (db_gain - min) * (rangemax - rangemin);
Packit Service db8eaa
			if (xdir > 0)
Packit Service db8eaa
				v += (max - min) - 1;
Packit Service db8eaa
			else if (xdir == 0)
Packit Service db8eaa
				v += ((max - min) + 1) / 2;
Packit Service db8eaa
			v = v / (max - min) + rangemin;
Packit Service db8eaa
			*value = v;
Packit Service db8eaa
		}
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
#ifndef HAVE_SOFT_FLOAT
Packit Service db8eaa
	case SND_CTL_TLVT_DB_LINEAR: {
Packit Service db8eaa
		int min, max;
Packit Service db8eaa
		min = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
Packit Service db8eaa
		max = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
Packit Service db8eaa
		if (db_gain <= min)
Packit Service db8eaa
			*value = rangemin;
Packit Service db8eaa
		else if (db_gain >= max)
Packit Service db8eaa
			*value = rangemax;
Packit Service db8eaa
		else {
Packit Service db8eaa
			/* FIXME: precalculate and cache vmin and vmax */
Packit Service db8eaa
			double vmin, vmax, v;
Packit Service db8eaa
			vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
Packit Service db8eaa
				pow(10.0,  (double)min / 2000.0);
Packit Service db8eaa
			vmax = !max ? 1.0 : pow(10.0,  (double)max / 2000.0);
Packit Service db8eaa
			v = pow(10.0, (double)db_gain / 2000.0);
Packit Service db8eaa
			v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
Packit Service db8eaa
			if (xdir > 0)
Packit Service db8eaa
				v = ceil(v);
Packit Service db8eaa
			else if (xdir == 0)
Packit Service db8eaa
				v = lrint(v);
Packit Service db8eaa
			*value = (long)v + rangemin;
Packit Service db8eaa
		}
Packit Service db8eaa
		return 0;
Packit Service db8eaa
	}
Packit Service db8eaa
#endif
Packit Service db8eaa
	default:
Packit Service db8eaa
		break;
Packit Service db8eaa
	}
Packit Service db8eaa
	return -EINVAL;
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
#ifndef DOC_HIDDEN
Packit Service db8eaa
#define TEMP_TLV_SIZE		4096
Packit Service db8eaa
struct tlv_info {
Packit Service db8eaa
	long minval, maxval;
Packit Service db8eaa
	unsigned int *tlv;
Packit Service db8eaa
	unsigned int buf[TEMP_TLV_SIZE];
Packit Service db8eaa
};
Packit Service db8eaa
#endif
Packit Service db8eaa
Packit Service db8eaa
static int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
Packit Service db8eaa
			struct tlv_info *rec)
Packit Service db8eaa
{
Packit Service db8eaa
	snd_ctl_elem_info_t info = {0};
Packit Service db8eaa
	int err;
Packit Service db8eaa
Packit Service db8eaa
	snd_ctl_elem_info_set_id(&info, id);
Packit Service db8eaa
	err = snd_ctl_elem_info(ctl, &info;;
Packit Service db8eaa
	if (err < 0)
Packit Service db8eaa
		return err;
Packit Service db8eaa
	if (!snd_ctl_elem_info_is_tlv_readable(&info))
Packit Service db8eaa
		return -EINVAL;
Packit Service db8eaa
	if (snd_ctl_elem_info_get_type(&info) != SND_CTL_ELEM_TYPE_INTEGER)
Packit Service db8eaa
		return -EINVAL;
Packit Service db8eaa
	rec->minval = snd_ctl_elem_info_get_min(&info;;
Packit Service db8eaa
	rec->maxval = snd_ctl_elem_info_get_max(&info;;
Packit Service db8eaa
	err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
Packit Service db8eaa
	if (err < 0)
Packit Service db8eaa
		return err;
Packit Service db8eaa
	err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
Packit Service db8eaa
	if (err < 0)
Packit Service db8eaa
		return err;
Packit Service db8eaa
	return 0;
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Get the dB min/max values on the given control element
Packit Service db8eaa
 * \param ctl the control handler
Packit Service db8eaa
 * \param id the element id
Packit Service db8eaa
 * \param min the pointer to store the minimum dB value (in 0.01dB unit)
Packit Service db8eaa
 * \param max the pointer to store the maximum dB value (in 0.01dB unit)
Packit Service db8eaa
 * \return 0 if successful, or a negative error code
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
Packit Service db8eaa
			 long *min, long *max)
Packit Service db8eaa
{
Packit Service db8eaa
	struct tlv_info info;
Packit Service db8eaa
	int err;
Packit Service db8eaa
Packit Service db8eaa
	err = get_tlv_info(ctl, id, &info;;
Packit Service db8eaa
	if (err < 0)
Packit Service db8eaa
		return err;
Packit Service db8eaa
	return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
Packit Service db8eaa
				    min, max);
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Convert the volume value to dB on the given control element
Packit Service db8eaa
 * \param ctl the control handler
Packit Service db8eaa
 * \param id the element id
Packit Service db8eaa
 * \param volume the raw volume value to convert
Packit Service db8eaa
 * \param db_gain the dB gain (in 0.01dB unit)
Packit Service db8eaa
 * \return 0 if successful, or a negative error code
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
Packit Service db8eaa
			  long volume, long *db_gain)
Packit Service db8eaa
{
Packit Service db8eaa
	struct tlv_info info;
Packit Service db8eaa
	int err;
Packit Service db8eaa
Packit Service db8eaa
	err = get_tlv_info(ctl, id, &info;;
Packit Service db8eaa
	if (err < 0)
Packit Service db8eaa
		return err;
Packit Service db8eaa
	return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
Packit Service db8eaa
				     volume, db_gain);
Packit Service db8eaa
}
Packit Service db8eaa
Packit Service db8eaa
/**
Packit Service db8eaa
 * \brief Convert from dB gain to the raw volume value on the given control element
Packit Service db8eaa
 * \param ctl the control handler
Packit Service db8eaa
 * \param id the element id
Packit Service db8eaa
 * \param db_gain the dB gain to convert (in 0.01dB unit)
Packit Service db8eaa
 * \param value the pointer to store the converted raw volume value
Packit Service db8eaa
 * \param xdir the direction for round-up. The value is round up
Packit Service db8eaa
 *        when this is positive.
Packit Service db8eaa
 * \return 0 if successful, or a negative error code
Packit Service db8eaa
 */
Packit Service db8eaa
int snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
Packit Service db8eaa
			    long db_gain, long *value, int xdir)
Packit Service db8eaa
{
Packit Service db8eaa
	struct tlv_info info;
Packit Service db8eaa
	int err;
Packit Service db8eaa
Packit Service db8eaa
	err = get_tlv_info(ctl, id, &info;;
Packit Service db8eaa
	if (err < 0)
Packit Service db8eaa
		return err;
Packit Service db8eaa
	return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
Packit Service db8eaa
				       db_gain, value, xdir);
Packit Service db8eaa
}