1d5ac70f0Sopenharmony_ci/**
2d5ac70f0Sopenharmony_ci * \file control/tlv.c
3d5ac70f0Sopenharmony_ci * \brief dB conversion functions from control TLV information
4d5ac70f0Sopenharmony_ci * \author Takashi Iwai <tiwai@suse.de>
5d5ac70f0Sopenharmony_ci * \date 2007
6d5ac70f0Sopenharmony_ci */
7d5ac70f0Sopenharmony_ci/*
8d5ac70f0Sopenharmony_ci *  Control Interface - dB conversion functions from control TLV information
9d5ac70f0Sopenharmony_ci *
10d5ac70f0Sopenharmony_ci *  Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
11d5ac70f0Sopenharmony_ci *
12d5ac70f0Sopenharmony_ci *
13d5ac70f0Sopenharmony_ci *   This library is free software; you can redistribute it and/or modify
14d5ac70f0Sopenharmony_ci *   it under the terms of the GNU Lesser General Public License as
15d5ac70f0Sopenharmony_ci *   published by the Free Software Foundation; either version 2.1 of
16d5ac70f0Sopenharmony_ci *   the License, or (at your option) any later version.
17d5ac70f0Sopenharmony_ci *
18d5ac70f0Sopenharmony_ci *   This program is distributed in the hope that it will be useful,
19d5ac70f0Sopenharmony_ci *   but WITHOUT ANY WARRANTY; without even the implied warranty of
20d5ac70f0Sopenharmony_ci *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21d5ac70f0Sopenharmony_ci *   GNU Lesser General Public License for more details.
22d5ac70f0Sopenharmony_ci *
23d5ac70f0Sopenharmony_ci *   You should have received a copy of the GNU Lesser General Public
24d5ac70f0Sopenharmony_ci *   License along with this library; if not, write to the Free Software
25d5ac70f0Sopenharmony_ci *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26d5ac70f0Sopenharmony_ci *
27d5ac70f0Sopenharmony_ci */
28d5ac70f0Sopenharmony_ci
29d5ac70f0Sopenharmony_ci#include "control_local.h"
30d5ac70f0Sopenharmony_ci#include <stdio.h>
31d5ac70f0Sopenharmony_ci#include <stdlib.h>
32d5ac70f0Sopenharmony_ci#include <unistd.h>
33d5ac70f0Sopenharmony_ci#include <string.h>
34d5ac70f0Sopenharmony_ci#ifndef HAVE_SOFT_FLOAT
35d5ac70f0Sopenharmony_ci#include <math.h>
36d5ac70f0Sopenharmony_ci#endif
37d5ac70f0Sopenharmony_ci
38d5ac70f0Sopenharmony_ci#ifndef DOC_HIDDEN
39d5ac70f0Sopenharmony_ci/* convert to index of integer array */
40d5ac70f0Sopenharmony_ci#define int_index(size)	(((size) + sizeof(int) - 1) / sizeof(int))
41d5ac70f0Sopenharmony_ci/* max size of a TLV entry for dB information (including compound one) */
42d5ac70f0Sopenharmony_ci#define MAX_TLV_RANGE_SIZE	256
43d5ac70f0Sopenharmony_ci#endif
44d5ac70f0Sopenharmony_ci
45d5ac70f0Sopenharmony_ci/**
46d5ac70f0Sopenharmony_ci * \brief Parse TLV stream and retrieve dB information
47d5ac70f0Sopenharmony_ci * \param tlv the TLV source
48d5ac70f0Sopenharmony_ci * \param tlv_size the byte size of TLV source
49d5ac70f0Sopenharmony_ci * \param db_tlvp the pointer stored the dB TLV information
50d5ac70f0Sopenharmony_ci * \return the byte size of dB TLV information if found in the given
51d5ac70f0Sopenharmony_ci *   TLV source, or a negative error code.
52d5ac70f0Sopenharmony_ci *
53d5ac70f0Sopenharmony_ci * This function parses the given TLV source and stores the TLV start
54d5ac70f0Sopenharmony_ci * point if the TLV information regarding dB conversion is found.
55d5ac70f0Sopenharmony_ci * The stored TLV pointer can be passed to the convesion functions
56d5ac70f0Sopenharmony_ci * #snd_tlv_convert_to_dB(), #snd_tlv_convert_from_dB() and
57d5ac70f0Sopenharmony_ci * #snd_tlv_get_dB_range().
58d5ac70f0Sopenharmony_ci */
59d5ac70f0Sopenharmony_ciint snd_tlv_parse_dB_info(unsigned int *tlv,
60d5ac70f0Sopenharmony_ci			  unsigned int tlv_size,
61d5ac70f0Sopenharmony_ci			  unsigned int **db_tlvp)
62d5ac70f0Sopenharmony_ci{
63d5ac70f0Sopenharmony_ci	unsigned int type;
64d5ac70f0Sopenharmony_ci	unsigned int size;
65d5ac70f0Sopenharmony_ci	int err;
66d5ac70f0Sopenharmony_ci
67d5ac70f0Sopenharmony_ci	*db_tlvp = NULL;
68d5ac70f0Sopenharmony_ci	type = tlv[SNDRV_CTL_TLVO_TYPE];
69d5ac70f0Sopenharmony_ci	size = tlv[SNDRV_CTL_TLVO_LEN];
70d5ac70f0Sopenharmony_ci	tlv_size -= 2 * sizeof(int);
71d5ac70f0Sopenharmony_ci	if (size > tlv_size) {
72d5ac70f0Sopenharmony_ci		SNDERR("TLV size error");
73d5ac70f0Sopenharmony_ci		return -EINVAL;
74d5ac70f0Sopenharmony_ci	}
75d5ac70f0Sopenharmony_ci	switch (type) {
76d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_CONTAINER:
77d5ac70f0Sopenharmony_ci		size = int_index(size) * sizeof(int);
78d5ac70f0Sopenharmony_ci		tlv += 2;
79d5ac70f0Sopenharmony_ci		while (size > 0) {
80d5ac70f0Sopenharmony_ci			unsigned int len;
81d5ac70f0Sopenharmony_ci			err = snd_tlv_parse_dB_info(tlv, size, db_tlvp);
82d5ac70f0Sopenharmony_ci			if (err < 0)
83d5ac70f0Sopenharmony_ci				return err; /* error */
84d5ac70f0Sopenharmony_ci			if (err > 0)
85d5ac70f0Sopenharmony_ci				return err; /* found */
86d5ac70f0Sopenharmony_ci			len = int_index(tlv[SNDRV_CTL_TLVO_LEN]) + 2;
87d5ac70f0Sopenharmony_ci			size -= len * sizeof(int);
88d5ac70f0Sopenharmony_ci			tlv += len;
89d5ac70f0Sopenharmony_ci		}
90d5ac70f0Sopenharmony_ci		break;
91d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_SCALE:
92d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX:
93d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX_MUTE:
94d5ac70f0Sopenharmony_ci#ifndef HAVE_SOFT_FLOAT
95d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_LINEAR:
96d5ac70f0Sopenharmony_ci#endif
97d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_RANGE: {
98d5ac70f0Sopenharmony_ci		unsigned int minsize;
99d5ac70f0Sopenharmony_ci		if (type == SND_CTL_TLVT_DB_RANGE)
100d5ac70f0Sopenharmony_ci			minsize = 4 * sizeof(int);
101d5ac70f0Sopenharmony_ci		else
102d5ac70f0Sopenharmony_ci			minsize = 2 * sizeof(int);
103d5ac70f0Sopenharmony_ci		if (size < minsize) {
104d5ac70f0Sopenharmony_ci			SNDERR("Invalid dB_scale TLV size");
105d5ac70f0Sopenharmony_ci			return -EINVAL;
106d5ac70f0Sopenharmony_ci		}
107d5ac70f0Sopenharmony_ci		if (size > MAX_TLV_RANGE_SIZE) {
108d5ac70f0Sopenharmony_ci			SNDERR("Too big dB_scale TLV size: %d", size);
109d5ac70f0Sopenharmony_ci			return -EINVAL;
110d5ac70f0Sopenharmony_ci		}
111d5ac70f0Sopenharmony_ci		*db_tlvp = tlv;
112d5ac70f0Sopenharmony_ci		return size + sizeof(int) * 2;
113d5ac70f0Sopenharmony_ci	}
114d5ac70f0Sopenharmony_ci	default:
115d5ac70f0Sopenharmony_ci		break;
116d5ac70f0Sopenharmony_ci	}
117d5ac70f0Sopenharmony_ci	return -EINVAL; /* not found */
118d5ac70f0Sopenharmony_ci}
119d5ac70f0Sopenharmony_ci
120d5ac70f0Sopenharmony_ci/**
121d5ac70f0Sopenharmony_ci * \brief Get the dB min/max values
122d5ac70f0Sopenharmony_ci * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
123d5ac70f0Sopenharmony_ci * \param rangemin the minimum value of the raw volume
124d5ac70f0Sopenharmony_ci * \param rangemax the maximum value of the raw volume
125d5ac70f0Sopenharmony_ci * \param min the pointer to store the minimum dB value (in 0.01dB unit)
126d5ac70f0Sopenharmony_ci * \param max the pointer to store the maximum dB value (in 0.01dB unit)
127d5ac70f0Sopenharmony_ci * \return 0 if successful, or a negative error code
128d5ac70f0Sopenharmony_ci */
129d5ac70f0Sopenharmony_ciint snd_tlv_get_dB_range(unsigned int *tlv, long rangemin, long rangemax,
130d5ac70f0Sopenharmony_ci			 long *min, long *max)
131d5ac70f0Sopenharmony_ci{
132d5ac70f0Sopenharmony_ci	int err;
133d5ac70f0Sopenharmony_ci
134d5ac70f0Sopenharmony_ci	switch (tlv[SNDRV_CTL_TLVO_TYPE]) {
135d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_RANGE: {
136d5ac70f0Sopenharmony_ci		unsigned int pos, len;
137d5ac70f0Sopenharmony_ci		len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
138d5ac70f0Sopenharmony_ci		if (len > MAX_TLV_RANGE_SIZE)
139d5ac70f0Sopenharmony_ci			return -EINVAL;
140d5ac70f0Sopenharmony_ci		pos = 2;
141d5ac70f0Sopenharmony_ci		while (pos + 4 <= len) {
142d5ac70f0Sopenharmony_ci			long rmin, rmax;
143d5ac70f0Sopenharmony_ci			long submin, submax;
144d5ac70f0Sopenharmony_ci			submin = (int)tlv[pos];
145d5ac70f0Sopenharmony_ci			submax = (int)tlv[pos + 1];
146d5ac70f0Sopenharmony_ci			if (rangemax < submax)
147d5ac70f0Sopenharmony_ci				submax = rangemax;
148d5ac70f0Sopenharmony_ci			err = snd_tlv_get_dB_range(tlv + pos + 2,
149d5ac70f0Sopenharmony_ci						   submin, submax,
150d5ac70f0Sopenharmony_ci						   &rmin, &rmax);
151d5ac70f0Sopenharmony_ci			if (err < 0)
152d5ac70f0Sopenharmony_ci				return err;
153d5ac70f0Sopenharmony_ci			if (pos > 2) {
154d5ac70f0Sopenharmony_ci				if (rmin < *min)
155d5ac70f0Sopenharmony_ci					*min = rmin;
156d5ac70f0Sopenharmony_ci				if (rmax > *max)
157d5ac70f0Sopenharmony_ci					*max = rmax;
158d5ac70f0Sopenharmony_ci			} else {
159d5ac70f0Sopenharmony_ci				*min = rmin;
160d5ac70f0Sopenharmony_ci				*max = rmax;
161d5ac70f0Sopenharmony_ci			}
162d5ac70f0Sopenharmony_ci			if (rangemax == submax)
163d5ac70f0Sopenharmony_ci				return 0;
164d5ac70f0Sopenharmony_ci			pos += int_index(tlv[pos + 3]) + 4;
165d5ac70f0Sopenharmony_ci		}
166d5ac70f0Sopenharmony_ci		return 0;
167d5ac70f0Sopenharmony_ci	}
168d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_SCALE: {
169d5ac70f0Sopenharmony_ci		int step;
170d5ac70f0Sopenharmony_ci		if (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0x10000)
171d5ac70f0Sopenharmony_ci			*min = SND_CTL_TLV_DB_GAIN_MUTE;
172d5ac70f0Sopenharmony_ci		else
173d5ac70f0Sopenharmony_ci			*min = (int)tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
174d5ac70f0Sopenharmony_ci		step = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff);
175d5ac70f0Sopenharmony_ci		*max = (int)tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN] +
176d5ac70f0Sopenharmony_ci						step * (rangemax - rangemin);
177d5ac70f0Sopenharmony_ci		return 0;
178d5ac70f0Sopenharmony_ci	}
179d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX:
180d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_LINEAR:
181d5ac70f0Sopenharmony_ci		*min = (int)tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
182d5ac70f0Sopenharmony_ci		*max = (int)tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
183d5ac70f0Sopenharmony_ci		return 0;
184d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX_MUTE:
185d5ac70f0Sopenharmony_ci		*min = SND_CTL_TLV_DB_GAIN_MUTE;
186d5ac70f0Sopenharmony_ci		*max = (int)tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
187d5ac70f0Sopenharmony_ci		return 0;
188d5ac70f0Sopenharmony_ci	}
189d5ac70f0Sopenharmony_ci	return -EINVAL;
190d5ac70f0Sopenharmony_ci}
191d5ac70f0Sopenharmony_ci
192d5ac70f0Sopenharmony_ci/**
193d5ac70f0Sopenharmony_ci * \brief Convert the given raw volume value to a dB gain
194d5ac70f0Sopenharmony_ci * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
195d5ac70f0Sopenharmony_ci * \param rangemin the minimum value of the raw volume
196d5ac70f0Sopenharmony_ci * \param rangemax the maximum value of the raw volume
197d5ac70f0Sopenharmony_ci * \param volume the raw volume value to convert
198d5ac70f0Sopenharmony_ci * \param db_gain the dB gain (in 0.01dB unit)
199d5ac70f0Sopenharmony_ci * \return 0 if successful, or a negative error code
200d5ac70f0Sopenharmony_ci */
201d5ac70f0Sopenharmony_ciint snd_tlv_convert_to_dB(unsigned int *tlv, long rangemin, long rangemax,
202d5ac70f0Sopenharmony_ci			  long volume, long *db_gain)
203d5ac70f0Sopenharmony_ci{
204d5ac70f0Sopenharmony_ci	unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE];
205d5ac70f0Sopenharmony_ci
206d5ac70f0Sopenharmony_ci	switch (type) {
207d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_RANGE: {
208d5ac70f0Sopenharmony_ci		unsigned int pos, len;
209d5ac70f0Sopenharmony_ci		len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
210d5ac70f0Sopenharmony_ci		if (len > MAX_TLV_RANGE_SIZE)
211d5ac70f0Sopenharmony_ci			return -EINVAL;
212d5ac70f0Sopenharmony_ci		pos = 2;
213d5ac70f0Sopenharmony_ci		while (pos + 4 <= len) {
214d5ac70f0Sopenharmony_ci			rangemin = (int)tlv[pos];
215d5ac70f0Sopenharmony_ci			rangemax = (int)tlv[pos + 1];
216d5ac70f0Sopenharmony_ci			if (volume >= rangemin && volume <= rangemax)
217d5ac70f0Sopenharmony_ci				return snd_tlv_convert_to_dB(tlv + pos + 2,
218d5ac70f0Sopenharmony_ci							     rangemin, rangemax,
219d5ac70f0Sopenharmony_ci							     volume, db_gain);
220d5ac70f0Sopenharmony_ci			pos += int_index(tlv[pos + 3]) + 4;
221d5ac70f0Sopenharmony_ci		}
222d5ac70f0Sopenharmony_ci		return -EINVAL;
223d5ac70f0Sopenharmony_ci	}
224d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_SCALE: {
225d5ac70f0Sopenharmony_ci		int min, step, mute;
226d5ac70f0Sopenharmony_ci		min = tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
227d5ac70f0Sopenharmony_ci		step = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff);
228d5ac70f0Sopenharmony_ci		mute = (tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] >> 16) & 1;
229d5ac70f0Sopenharmony_ci		if (mute && volume <= rangemin)
230d5ac70f0Sopenharmony_ci			*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
231d5ac70f0Sopenharmony_ci		else
232d5ac70f0Sopenharmony_ci			*db_gain = (volume - rangemin) * step + min;
233d5ac70f0Sopenharmony_ci		return 0;
234d5ac70f0Sopenharmony_ci	}
235d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX:
236d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX_MUTE: {
237d5ac70f0Sopenharmony_ci		int mindb, maxdb;
238d5ac70f0Sopenharmony_ci		mindb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN];
239d5ac70f0Sopenharmony_ci		maxdb = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
240d5ac70f0Sopenharmony_ci		if (volume <= rangemin || rangemax <= rangemin) {
241d5ac70f0Sopenharmony_ci			if (type == SND_CTL_TLVT_DB_MINMAX_MUTE)
242d5ac70f0Sopenharmony_ci				*db_gain = SND_CTL_TLV_DB_GAIN_MUTE;
243d5ac70f0Sopenharmony_ci			else
244d5ac70f0Sopenharmony_ci				*db_gain = mindb;
245d5ac70f0Sopenharmony_ci		} else if (volume >= rangemax)
246d5ac70f0Sopenharmony_ci			*db_gain = maxdb;
247d5ac70f0Sopenharmony_ci		else
248d5ac70f0Sopenharmony_ci			*db_gain = (maxdb - mindb) * (volume - rangemin) /
249d5ac70f0Sopenharmony_ci				(rangemax - rangemin) + mindb;
250d5ac70f0Sopenharmony_ci		return 0;
251d5ac70f0Sopenharmony_ci	}
252d5ac70f0Sopenharmony_ci#ifndef HAVE_SOFT_FLOAT
253d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_LINEAR: {
254d5ac70f0Sopenharmony_ci		int mindb = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
255d5ac70f0Sopenharmony_ci		int maxdb = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
256d5ac70f0Sopenharmony_ci		if (volume <= rangemin || rangemax <= rangemin)
257d5ac70f0Sopenharmony_ci			*db_gain = mindb;
258d5ac70f0Sopenharmony_ci		else if (volume >= rangemax)
259d5ac70f0Sopenharmony_ci			*db_gain = maxdb;
260d5ac70f0Sopenharmony_ci		else {
261d5ac70f0Sopenharmony_ci			double val = (double)(volume - rangemin) /
262d5ac70f0Sopenharmony_ci				(double)(rangemax - rangemin);
263d5ac70f0Sopenharmony_ci			if (mindb <= SND_CTL_TLV_DB_GAIN_MUTE)
264d5ac70f0Sopenharmony_ci				*db_gain = (long)(100.0 * 20.0 * log10(val)) +
265d5ac70f0Sopenharmony_ci					maxdb;
266d5ac70f0Sopenharmony_ci			else {
267d5ac70f0Sopenharmony_ci				/* FIXME: precalculate and cache these values */
268d5ac70f0Sopenharmony_ci				double lmin = pow(10.0, mindb/2000.0);
269d5ac70f0Sopenharmony_ci				double lmax = pow(10.0, maxdb/2000.0);
270d5ac70f0Sopenharmony_ci				val = (lmax - lmin) * val + lmin;
271d5ac70f0Sopenharmony_ci				*db_gain = (long)(100.0 * 20.0 * log10(val));
272d5ac70f0Sopenharmony_ci			}
273d5ac70f0Sopenharmony_ci		}
274d5ac70f0Sopenharmony_ci		return 0;
275d5ac70f0Sopenharmony_ci	}
276d5ac70f0Sopenharmony_ci#endif
277d5ac70f0Sopenharmony_ci	}
278d5ac70f0Sopenharmony_ci	return -EINVAL;
279d5ac70f0Sopenharmony_ci}
280d5ac70f0Sopenharmony_ci
281d5ac70f0Sopenharmony_ci/**
282d5ac70f0Sopenharmony_ci * \brief Convert from dB gain to the corresponding raw value
283d5ac70f0Sopenharmony_ci * \param tlv the TLV source returned by #snd_tlv_parse_dB_info()
284d5ac70f0Sopenharmony_ci * \param rangemin the minimum value of the raw volume
285d5ac70f0Sopenharmony_ci * \param rangemax the maximum value of the raw volume
286d5ac70f0Sopenharmony_ci * \param db_gain the dB gain to convert (in 0.01dB unit)
287d5ac70f0Sopenharmony_ci * \param value the pointer to store the converted raw volume value
288d5ac70f0Sopenharmony_ci * \param xdir the direction for round-up. The value is round up
289d5ac70f0Sopenharmony_ci *        when this is positive. A negative value means round down.
290d5ac70f0Sopenharmony_ci *        Zero means round-up to nearest.
291d5ac70f0Sopenharmony_ci * \return 0 if successful, or a negative error code
292d5ac70f0Sopenharmony_ci */
293d5ac70f0Sopenharmony_ciint snd_tlv_convert_from_dB(unsigned int *tlv, long rangemin, long rangemax,
294d5ac70f0Sopenharmony_ci			    long db_gain, long *value, int xdir)
295d5ac70f0Sopenharmony_ci{
296d5ac70f0Sopenharmony_ci	unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE];
297d5ac70f0Sopenharmony_ci
298d5ac70f0Sopenharmony_ci	switch (type) {
299d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_RANGE: {
300d5ac70f0Sopenharmony_ci		long dbmin, dbmax, prev_submax;
301d5ac70f0Sopenharmony_ci		unsigned int pos, len;
302d5ac70f0Sopenharmony_ci		len = int_index(tlv[SNDRV_CTL_TLVO_LEN]);
303d5ac70f0Sopenharmony_ci		if (len < 6 || len > MAX_TLV_RANGE_SIZE)
304d5ac70f0Sopenharmony_ci			return -EINVAL;
305d5ac70f0Sopenharmony_ci		pos = 2;
306d5ac70f0Sopenharmony_ci		prev_submax = 0;
307d5ac70f0Sopenharmony_ci		while (pos + 4 <= len) {
308d5ac70f0Sopenharmony_ci			long submin, submax;
309d5ac70f0Sopenharmony_ci			submin = (int)tlv[pos];
310d5ac70f0Sopenharmony_ci			submax = (int)tlv[pos + 1];
311d5ac70f0Sopenharmony_ci			if (rangemax < submax)
312d5ac70f0Sopenharmony_ci				submax = rangemax;
313d5ac70f0Sopenharmony_ci			if (!snd_tlv_get_dB_range(tlv + pos + 2,
314d5ac70f0Sopenharmony_ci						  submin, submax,
315d5ac70f0Sopenharmony_ci						  &dbmin, &dbmax) &&
316d5ac70f0Sopenharmony_ci			    db_gain >= dbmin && db_gain <= dbmax)
317d5ac70f0Sopenharmony_ci				return snd_tlv_convert_from_dB(tlv + pos + 2,
318d5ac70f0Sopenharmony_ci							       submin, submax,
319d5ac70f0Sopenharmony_ci							       db_gain, value, xdir);
320d5ac70f0Sopenharmony_ci			else if (db_gain < dbmin) {
321d5ac70f0Sopenharmony_ci				*value = xdir > 0 || pos == 2 ? submin : prev_submax;
322d5ac70f0Sopenharmony_ci				return 0;
323d5ac70f0Sopenharmony_ci			}
324d5ac70f0Sopenharmony_ci			prev_submax = submax;
325d5ac70f0Sopenharmony_ci			if (rangemax == submax)
326d5ac70f0Sopenharmony_ci				break;
327d5ac70f0Sopenharmony_ci			pos += int_index(tlv[pos + 3]) + 4;
328d5ac70f0Sopenharmony_ci		}
329d5ac70f0Sopenharmony_ci		*value = prev_submax;
330d5ac70f0Sopenharmony_ci		return 0;
331d5ac70f0Sopenharmony_ci	}
332d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_SCALE: {
333d5ac70f0Sopenharmony_ci		int min, step, max, mute;
334d5ac70f0Sopenharmony_ci		min = tlv[SNDRV_CTL_TLVO_DB_SCALE_MIN];
335d5ac70f0Sopenharmony_ci		step = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0xffff;
336d5ac70f0Sopenharmony_ci		mute = tlv[SNDRV_CTL_TLVO_DB_SCALE_MUTE_AND_STEP] & 0x10000;
337d5ac70f0Sopenharmony_ci		max = min + (int)(step * (rangemax - rangemin));
338d5ac70f0Sopenharmony_ci		if (db_gain <= min)
339d5ac70f0Sopenharmony_ci			if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
340d5ac70f0Sopenharmony_ci			    mute)
341d5ac70f0Sopenharmony_ci				*value = rangemin + 1;
342d5ac70f0Sopenharmony_ci			else
343d5ac70f0Sopenharmony_ci				*value = rangemin;
344d5ac70f0Sopenharmony_ci		else if (db_gain >= max)
345d5ac70f0Sopenharmony_ci			*value = rangemax;
346d5ac70f0Sopenharmony_ci		else {
347d5ac70f0Sopenharmony_ci			long v = (db_gain - min) * (rangemax - rangemin);
348d5ac70f0Sopenharmony_ci			if (xdir > 0)
349d5ac70f0Sopenharmony_ci				v += (max - min) - 1;
350d5ac70f0Sopenharmony_ci			else if (xdir == 0)
351d5ac70f0Sopenharmony_ci				v += ((max - min) + 1) / 2;
352d5ac70f0Sopenharmony_ci			v = v / (max - min) + rangemin;
353d5ac70f0Sopenharmony_ci			*value = v;
354d5ac70f0Sopenharmony_ci		}
355d5ac70f0Sopenharmony_ci		return 0;
356d5ac70f0Sopenharmony_ci	}
357d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX:
358d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_MINMAX_MUTE: {
359d5ac70f0Sopenharmony_ci		int min, max;
360d5ac70f0Sopenharmony_ci		min = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN];
361d5ac70f0Sopenharmony_ci		max = tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX];
362d5ac70f0Sopenharmony_ci		if (db_gain <= min)
363d5ac70f0Sopenharmony_ci			if (db_gain > SND_CTL_TLV_DB_GAIN_MUTE && xdir > 0 &&
364d5ac70f0Sopenharmony_ci			    type == SND_CTL_TLVT_DB_MINMAX_MUTE)
365d5ac70f0Sopenharmony_ci				*value = rangemin + 1;
366d5ac70f0Sopenharmony_ci			else
367d5ac70f0Sopenharmony_ci				*value = rangemin;
368d5ac70f0Sopenharmony_ci		else if (db_gain >= max)
369d5ac70f0Sopenharmony_ci			*value = rangemax;
370d5ac70f0Sopenharmony_ci		else {
371d5ac70f0Sopenharmony_ci			long v = (db_gain - min) * (rangemax - rangemin);
372d5ac70f0Sopenharmony_ci			if (xdir > 0)
373d5ac70f0Sopenharmony_ci				v += (max - min) - 1;
374d5ac70f0Sopenharmony_ci			else if (xdir == 0)
375d5ac70f0Sopenharmony_ci				v += ((max - min) + 1) / 2;
376d5ac70f0Sopenharmony_ci			v = v / (max - min) + rangemin;
377d5ac70f0Sopenharmony_ci			*value = v;
378d5ac70f0Sopenharmony_ci		}
379d5ac70f0Sopenharmony_ci		return 0;
380d5ac70f0Sopenharmony_ci	}
381d5ac70f0Sopenharmony_ci#ifndef HAVE_SOFT_FLOAT
382d5ac70f0Sopenharmony_ci	case SND_CTL_TLVT_DB_LINEAR: {
383d5ac70f0Sopenharmony_ci		int min, max;
384d5ac70f0Sopenharmony_ci		min = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MIN];
385d5ac70f0Sopenharmony_ci		max = tlv[SNDRV_CTL_TLVO_DB_LINEAR_MAX];
386d5ac70f0Sopenharmony_ci		if (db_gain <= min)
387d5ac70f0Sopenharmony_ci			*value = rangemin;
388d5ac70f0Sopenharmony_ci		else if (db_gain >= max)
389d5ac70f0Sopenharmony_ci			*value = rangemax;
390d5ac70f0Sopenharmony_ci		else {
391d5ac70f0Sopenharmony_ci			/* FIXME: precalculate and cache vmin and vmax */
392d5ac70f0Sopenharmony_ci			double vmin, vmax, v;
393d5ac70f0Sopenharmony_ci			vmin = (min <= SND_CTL_TLV_DB_GAIN_MUTE) ? 0.0 :
394d5ac70f0Sopenharmony_ci				pow(10.0,  (double)min / 2000.0);
395d5ac70f0Sopenharmony_ci			vmax = !max ? 1.0 : pow(10.0,  (double)max / 2000.0);
396d5ac70f0Sopenharmony_ci			v = pow(10.0, (double)db_gain / 2000.0);
397d5ac70f0Sopenharmony_ci			v = (v - vmin) * (rangemax - rangemin) / (vmax - vmin);
398d5ac70f0Sopenharmony_ci			if (xdir > 0)
399d5ac70f0Sopenharmony_ci				v = ceil(v);
400d5ac70f0Sopenharmony_ci			else if (xdir == 0)
401d5ac70f0Sopenharmony_ci				v = lrint(v);
402d5ac70f0Sopenharmony_ci			*value = (long)v + rangemin;
403d5ac70f0Sopenharmony_ci		}
404d5ac70f0Sopenharmony_ci		return 0;
405d5ac70f0Sopenharmony_ci	}
406d5ac70f0Sopenharmony_ci#endif
407d5ac70f0Sopenharmony_ci	default:
408d5ac70f0Sopenharmony_ci		break;
409d5ac70f0Sopenharmony_ci	}
410d5ac70f0Sopenharmony_ci	return -EINVAL;
411d5ac70f0Sopenharmony_ci}
412d5ac70f0Sopenharmony_ci
413d5ac70f0Sopenharmony_ci#ifndef DOC_HIDDEN
414d5ac70f0Sopenharmony_ci#define TEMP_TLV_SIZE		4096
415d5ac70f0Sopenharmony_cistruct tlv_info {
416d5ac70f0Sopenharmony_ci	long minval, maxval;
417d5ac70f0Sopenharmony_ci	unsigned int *tlv;
418d5ac70f0Sopenharmony_ci	unsigned int buf[TEMP_TLV_SIZE];
419d5ac70f0Sopenharmony_ci};
420d5ac70f0Sopenharmony_ci#endif
421d5ac70f0Sopenharmony_ci
422d5ac70f0Sopenharmony_cistatic int get_tlv_info(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
423d5ac70f0Sopenharmony_ci			struct tlv_info *rec)
424d5ac70f0Sopenharmony_ci{
425d5ac70f0Sopenharmony_ci	snd_ctl_elem_info_t info = {0};
426d5ac70f0Sopenharmony_ci	int err;
427d5ac70f0Sopenharmony_ci
428d5ac70f0Sopenharmony_ci	snd_ctl_elem_info_set_id(&info, id);
429d5ac70f0Sopenharmony_ci	err = snd_ctl_elem_info(ctl, &info);
430d5ac70f0Sopenharmony_ci	if (err < 0)
431d5ac70f0Sopenharmony_ci		return err;
432d5ac70f0Sopenharmony_ci	if (!snd_ctl_elem_info_is_tlv_readable(&info))
433d5ac70f0Sopenharmony_ci		return -EINVAL;
434d5ac70f0Sopenharmony_ci	if (snd_ctl_elem_info_get_type(&info) != SND_CTL_ELEM_TYPE_INTEGER)
435d5ac70f0Sopenharmony_ci		return -EINVAL;
436d5ac70f0Sopenharmony_ci	rec->minval = snd_ctl_elem_info_get_min(&info);
437d5ac70f0Sopenharmony_ci	rec->maxval = snd_ctl_elem_info_get_max(&info);
438d5ac70f0Sopenharmony_ci	err = snd_ctl_elem_tlv_read(ctl, id, rec->buf, sizeof(rec->buf));
439d5ac70f0Sopenharmony_ci	if (err < 0)
440d5ac70f0Sopenharmony_ci		return err;
441d5ac70f0Sopenharmony_ci	err = snd_tlv_parse_dB_info(rec->buf, sizeof(rec->buf), &rec->tlv);
442d5ac70f0Sopenharmony_ci	if (err < 0)
443d5ac70f0Sopenharmony_ci		return err;
444d5ac70f0Sopenharmony_ci	return 0;
445d5ac70f0Sopenharmony_ci}
446d5ac70f0Sopenharmony_ci
447d5ac70f0Sopenharmony_ci/**
448d5ac70f0Sopenharmony_ci * \brief Get the dB min/max values on the given control element
449d5ac70f0Sopenharmony_ci * \param ctl the control handler
450d5ac70f0Sopenharmony_ci * \param id the element id
451d5ac70f0Sopenharmony_ci * \param min the pointer to store the minimum dB value (in 0.01dB unit)
452d5ac70f0Sopenharmony_ci * \param max the pointer to store the maximum dB value (in 0.01dB unit)
453d5ac70f0Sopenharmony_ci * \return 0 if successful, or a negative error code
454d5ac70f0Sopenharmony_ci */
455d5ac70f0Sopenharmony_ciint snd_ctl_get_dB_range(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
456d5ac70f0Sopenharmony_ci			 long *min, long *max)
457d5ac70f0Sopenharmony_ci{
458d5ac70f0Sopenharmony_ci	struct tlv_info info;
459d5ac70f0Sopenharmony_ci	int err;
460d5ac70f0Sopenharmony_ci
461d5ac70f0Sopenharmony_ci	err = get_tlv_info(ctl, id, &info);
462d5ac70f0Sopenharmony_ci	if (err < 0)
463d5ac70f0Sopenharmony_ci		return err;
464d5ac70f0Sopenharmony_ci	return snd_tlv_get_dB_range(info.tlv, info.minval, info.maxval,
465d5ac70f0Sopenharmony_ci				    min, max);
466d5ac70f0Sopenharmony_ci}
467d5ac70f0Sopenharmony_ci
468d5ac70f0Sopenharmony_ci/**
469d5ac70f0Sopenharmony_ci * \brief Convert the volume value to dB on the given control element
470d5ac70f0Sopenharmony_ci * \param ctl the control handler
471d5ac70f0Sopenharmony_ci * \param id the element id
472d5ac70f0Sopenharmony_ci * \param volume the raw volume value to convert
473d5ac70f0Sopenharmony_ci * \param db_gain the dB gain (in 0.01dB unit)
474d5ac70f0Sopenharmony_ci * \return 0 if successful, or a negative error code
475d5ac70f0Sopenharmony_ci */
476d5ac70f0Sopenharmony_ciint snd_ctl_convert_to_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
477d5ac70f0Sopenharmony_ci			  long volume, long *db_gain)
478d5ac70f0Sopenharmony_ci{
479d5ac70f0Sopenharmony_ci	struct tlv_info info;
480d5ac70f0Sopenharmony_ci	int err;
481d5ac70f0Sopenharmony_ci
482d5ac70f0Sopenharmony_ci	err = get_tlv_info(ctl, id, &info);
483d5ac70f0Sopenharmony_ci	if (err < 0)
484d5ac70f0Sopenharmony_ci		return err;
485d5ac70f0Sopenharmony_ci	return snd_tlv_convert_to_dB(info.tlv, info.minval, info.maxval,
486d5ac70f0Sopenharmony_ci				     volume, db_gain);
487d5ac70f0Sopenharmony_ci}
488d5ac70f0Sopenharmony_ci
489d5ac70f0Sopenharmony_ci/**
490d5ac70f0Sopenharmony_ci * \brief Convert from dB gain to the raw volume value on the given control element
491d5ac70f0Sopenharmony_ci * \param ctl the control handler
492d5ac70f0Sopenharmony_ci * \param id the element id
493d5ac70f0Sopenharmony_ci * \param db_gain the dB gain to convert (in 0.01dB unit)
494d5ac70f0Sopenharmony_ci * \param value the pointer to store the converted raw volume value
495d5ac70f0Sopenharmony_ci * \param xdir the direction for round-up. The value is round up
496d5ac70f0Sopenharmony_ci *        when this is positive.
497d5ac70f0Sopenharmony_ci * \return 0 if successful, or a negative error code
498d5ac70f0Sopenharmony_ci */
499d5ac70f0Sopenharmony_ciint snd_ctl_convert_from_dB(snd_ctl_t *ctl, const snd_ctl_elem_id_t *id,
500d5ac70f0Sopenharmony_ci			    long db_gain, long *value, int xdir)
501d5ac70f0Sopenharmony_ci{
502d5ac70f0Sopenharmony_ci	struct tlv_info info;
503d5ac70f0Sopenharmony_ci	int err;
504d5ac70f0Sopenharmony_ci
505d5ac70f0Sopenharmony_ci	err = get_tlv_info(ctl, id, &info);
506d5ac70f0Sopenharmony_ci	if (err < 0)
507d5ac70f0Sopenharmony_ci		return err;
508d5ac70f0Sopenharmony_ci	return snd_tlv_convert_from_dB(info.tlv, info.minval, info.maxval,
509d5ac70f0Sopenharmony_ci				       db_gain, value, xdir);
510d5ac70f0Sopenharmony_ci}
511