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