18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Virtual master and follower controls 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2008 by Takashi Iwai <tiwai@suse.de> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/slab.h> 98c2ecf20Sopenharmony_ci#include <linux/export.h> 108c2ecf20Sopenharmony_ci#include <sound/core.h> 118c2ecf20Sopenharmony_ci#include <sound/control.h> 128c2ecf20Sopenharmony_ci#include <sound/tlv.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci/* 158c2ecf20Sopenharmony_ci * a subset of information returned via ctl info callback 168c2ecf20Sopenharmony_ci */ 178c2ecf20Sopenharmony_cistruct link_ctl_info { 188c2ecf20Sopenharmony_ci snd_ctl_elem_type_t type; /* value type */ 198c2ecf20Sopenharmony_ci int count; /* item count */ 208c2ecf20Sopenharmony_ci int min_val, max_val; /* min, max values */ 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* 248c2ecf20Sopenharmony_ci * link master - this contains a list of follower controls that are 258c2ecf20Sopenharmony_ci * identical types, i.e. info returns the same value type and value 268c2ecf20Sopenharmony_ci * ranges, but may have different number of counts. 278c2ecf20Sopenharmony_ci * 288c2ecf20Sopenharmony_ci * The master control is so far only mono volume/switch for simplicity. 298c2ecf20Sopenharmony_ci * The same value will be applied to all followers. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_cistruct link_master { 328c2ecf20Sopenharmony_ci struct list_head followers; 338c2ecf20Sopenharmony_ci struct link_ctl_info info; 348c2ecf20Sopenharmony_ci int val; /* the master value */ 358c2ecf20Sopenharmony_ci unsigned int tlv[4]; 368c2ecf20Sopenharmony_ci void (*hook)(void *private_data, int); 378c2ecf20Sopenharmony_ci void *hook_private_data; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* 418c2ecf20Sopenharmony_ci * link follower - this contains a follower control element 428c2ecf20Sopenharmony_ci * 438c2ecf20Sopenharmony_ci * It fakes the control callbacks with additional attenuation by the 448c2ecf20Sopenharmony_ci * master control. A follower may have either one or two channels. 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistruct link_follower { 488c2ecf20Sopenharmony_ci struct list_head list; 498c2ecf20Sopenharmony_ci struct link_master *master; 508c2ecf20Sopenharmony_ci struct link_ctl_info info; 518c2ecf20Sopenharmony_ci int vals[2]; /* current values */ 528c2ecf20Sopenharmony_ci unsigned int flags; 538c2ecf20Sopenharmony_ci struct snd_kcontrol *kctl; /* original kcontrol pointer */ 548c2ecf20Sopenharmony_ci struct snd_kcontrol follower; /* the copy of original control entry */ 558c2ecf20Sopenharmony_ci}; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int follower_update(struct link_follower *follower) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *uctl; 608c2ecf20Sopenharmony_ci int err, ch; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); 638c2ecf20Sopenharmony_ci if (!uctl) 648c2ecf20Sopenharmony_ci return -ENOMEM; 658c2ecf20Sopenharmony_ci uctl->id = follower->follower.id; 668c2ecf20Sopenharmony_ci err = follower->follower.get(&follower->follower, uctl); 678c2ecf20Sopenharmony_ci if (err < 0) 688c2ecf20Sopenharmony_ci goto error; 698c2ecf20Sopenharmony_ci for (ch = 0; ch < follower->info.count; ch++) 708c2ecf20Sopenharmony_ci follower->vals[ch] = uctl->value.integer.value[ch]; 718c2ecf20Sopenharmony_ci error: 728c2ecf20Sopenharmony_ci kfree(uctl); 738c2ecf20Sopenharmony_ci return err < 0 ? err : 0; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* get the follower ctl info and save the initial values */ 778c2ecf20Sopenharmony_cistatic int follower_init(struct link_follower *follower) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo; 808c2ecf20Sopenharmony_ci int err; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci if (follower->info.count) { 838c2ecf20Sopenharmony_ci /* already initialized */ 848c2ecf20Sopenharmony_ci if (follower->flags & SND_CTL_FOLLOWER_NEED_UPDATE) 858c2ecf20Sopenharmony_ci return follower_update(follower); 868c2ecf20Sopenharmony_ci return 0; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL); 908c2ecf20Sopenharmony_ci if (!uinfo) 918c2ecf20Sopenharmony_ci return -ENOMEM; 928c2ecf20Sopenharmony_ci uinfo->id = follower->follower.id; 938c2ecf20Sopenharmony_ci err = follower->follower.info(&follower->follower, uinfo); 948c2ecf20Sopenharmony_ci if (err < 0) { 958c2ecf20Sopenharmony_ci kfree(uinfo); 968c2ecf20Sopenharmony_ci return err; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci follower->info.type = uinfo->type; 998c2ecf20Sopenharmony_ci follower->info.count = uinfo->count; 1008c2ecf20Sopenharmony_ci if (follower->info.count > 2 || 1018c2ecf20Sopenharmony_ci (follower->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER && 1028c2ecf20Sopenharmony_ci follower->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) { 1038c2ecf20Sopenharmony_ci pr_err("ALSA: vmaster: invalid follower element\n"); 1048c2ecf20Sopenharmony_ci kfree(uinfo); 1058c2ecf20Sopenharmony_ci return -EINVAL; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci follower->info.min_val = uinfo->value.integer.min; 1088c2ecf20Sopenharmony_ci follower->info.max_val = uinfo->value.integer.max; 1098c2ecf20Sopenharmony_ci kfree(uinfo); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci return follower_update(follower); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci/* initialize master volume */ 1158c2ecf20Sopenharmony_cistatic int master_init(struct link_master *master) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci struct link_follower *follower; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (master->info.count) 1208c2ecf20Sopenharmony_ci return 0; /* already initialized */ 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci list_for_each_entry(follower, &master->followers, list) { 1238c2ecf20Sopenharmony_ci int err = follower_init(follower); 1248c2ecf20Sopenharmony_ci if (err < 0) 1258c2ecf20Sopenharmony_ci return err; 1268c2ecf20Sopenharmony_ci master->info = follower->info; 1278c2ecf20Sopenharmony_ci master->info.count = 1; /* always mono */ 1288c2ecf20Sopenharmony_ci /* set full volume as default (= no attenuation) */ 1298c2ecf20Sopenharmony_ci master->val = master->info.max_val; 1308c2ecf20Sopenharmony_ci if (master->hook) 1318c2ecf20Sopenharmony_ci master->hook(master->hook_private_data, master->val); 1328c2ecf20Sopenharmony_ci return 1; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci return -ENOENT; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic int follower_get_val(struct link_follower *follower, 1388c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci int err, ch; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci err = follower_init(follower); 1438c2ecf20Sopenharmony_ci if (err < 0) 1448c2ecf20Sopenharmony_ci return err; 1458c2ecf20Sopenharmony_ci for (ch = 0; ch < follower->info.count; ch++) 1468c2ecf20Sopenharmony_ci ucontrol->value.integer.value[ch] = follower->vals[ch]; 1478c2ecf20Sopenharmony_ci return 0; 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic int follower_put_val(struct link_follower *follower, 1518c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci int err, ch, vol; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci err = master_init(follower->master); 1568c2ecf20Sopenharmony_ci if (err < 0) 1578c2ecf20Sopenharmony_ci return err; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci switch (follower->info.type) { 1608c2ecf20Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_BOOLEAN: 1618c2ecf20Sopenharmony_ci for (ch = 0; ch < follower->info.count; ch++) 1628c2ecf20Sopenharmony_ci ucontrol->value.integer.value[ch] &= 1638c2ecf20Sopenharmony_ci !!follower->master->val; 1648c2ecf20Sopenharmony_ci break; 1658c2ecf20Sopenharmony_ci case SNDRV_CTL_ELEM_TYPE_INTEGER: 1668c2ecf20Sopenharmony_ci for (ch = 0; ch < follower->info.count; ch++) { 1678c2ecf20Sopenharmony_ci /* max master volume is supposed to be 0 dB */ 1688c2ecf20Sopenharmony_ci vol = ucontrol->value.integer.value[ch]; 1698c2ecf20Sopenharmony_ci vol += follower->master->val - follower->master->info.max_val; 1708c2ecf20Sopenharmony_ci if (vol < follower->info.min_val) 1718c2ecf20Sopenharmony_ci vol = follower->info.min_val; 1728c2ecf20Sopenharmony_ci else if (vol > follower->info.max_val) 1738c2ecf20Sopenharmony_ci vol = follower->info.max_val; 1748c2ecf20Sopenharmony_ci ucontrol->value.integer.value[ch] = vol; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci break; 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci return follower->follower.put(&follower->follower, ucontrol); 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci/* 1828c2ecf20Sopenharmony_ci * ctl callbacks for followers 1838c2ecf20Sopenharmony_ci */ 1848c2ecf20Sopenharmony_cistatic int follower_info(struct snd_kcontrol *kcontrol, 1858c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci struct link_follower *follower = snd_kcontrol_chip(kcontrol); 1888c2ecf20Sopenharmony_ci return follower->follower.info(&follower->follower, uinfo); 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic int follower_get(struct snd_kcontrol *kcontrol, 1928c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct link_follower *follower = snd_kcontrol_chip(kcontrol); 1958c2ecf20Sopenharmony_ci return follower_get_val(follower, ucontrol); 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int follower_put(struct snd_kcontrol *kcontrol, 1998c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci struct link_follower *follower = snd_kcontrol_chip(kcontrol); 2028c2ecf20Sopenharmony_ci int err, ch, changed = 0; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci err = follower_init(follower); 2058c2ecf20Sopenharmony_ci if (err < 0) 2068c2ecf20Sopenharmony_ci return err; 2078c2ecf20Sopenharmony_ci for (ch = 0; ch < follower->info.count; ch++) { 2088c2ecf20Sopenharmony_ci if (follower->vals[ch] != ucontrol->value.integer.value[ch]) { 2098c2ecf20Sopenharmony_ci changed = 1; 2108c2ecf20Sopenharmony_ci follower->vals[ch] = ucontrol->value.integer.value[ch]; 2118c2ecf20Sopenharmony_ci } 2128c2ecf20Sopenharmony_ci } 2138c2ecf20Sopenharmony_ci if (!changed) 2148c2ecf20Sopenharmony_ci return 0; 2158c2ecf20Sopenharmony_ci err = follower_put_val(follower, ucontrol); 2168c2ecf20Sopenharmony_ci if (err < 0) 2178c2ecf20Sopenharmony_ci return err; 2188c2ecf20Sopenharmony_ci return 1; 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic int follower_tlv_cmd(struct snd_kcontrol *kcontrol, 2228c2ecf20Sopenharmony_ci int op_flag, unsigned int size, 2238c2ecf20Sopenharmony_ci unsigned int __user *tlv) 2248c2ecf20Sopenharmony_ci{ 2258c2ecf20Sopenharmony_ci struct link_follower *follower = snd_kcontrol_chip(kcontrol); 2268c2ecf20Sopenharmony_ci /* FIXME: this assumes that the max volume is 0 dB */ 2278c2ecf20Sopenharmony_ci return follower->follower.tlv.c(&follower->follower, op_flag, size, tlv); 2288c2ecf20Sopenharmony_ci} 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic void follower_free(struct snd_kcontrol *kcontrol) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct link_follower *follower = snd_kcontrol_chip(kcontrol); 2338c2ecf20Sopenharmony_ci if (follower->follower.private_free) 2348c2ecf20Sopenharmony_ci follower->follower.private_free(&follower->follower); 2358c2ecf20Sopenharmony_ci if (follower->master) 2368c2ecf20Sopenharmony_ci list_del(&follower->list); 2378c2ecf20Sopenharmony_ci kfree(follower); 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci/* 2418c2ecf20Sopenharmony_ci * Add a follower control to the group with the given master control 2428c2ecf20Sopenharmony_ci * 2438c2ecf20Sopenharmony_ci * All followers must be the same type (returning the same information 2448c2ecf20Sopenharmony_ci * via info callback). The function doesn't check it, so it's your 2458c2ecf20Sopenharmony_ci * responsibility. 2468c2ecf20Sopenharmony_ci * 2478c2ecf20Sopenharmony_ci * Also, some additional limitations: 2488c2ecf20Sopenharmony_ci * - at most two channels 2498c2ecf20Sopenharmony_ci * - logarithmic volume control (dB level), no linear volume 2508c2ecf20Sopenharmony_ci * - master can only attenuate the volume, no gain 2518c2ecf20Sopenharmony_ci */ 2528c2ecf20Sopenharmony_ciint _snd_ctl_add_follower(struct snd_kcontrol *master, 2538c2ecf20Sopenharmony_ci struct snd_kcontrol *follower, 2548c2ecf20Sopenharmony_ci unsigned int flags) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct link_master *master_link = snd_kcontrol_chip(master); 2578c2ecf20Sopenharmony_ci struct link_follower *srec; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci srec = kzalloc(struct_size(srec, follower.vd, follower->count), 2608c2ecf20Sopenharmony_ci GFP_KERNEL); 2618c2ecf20Sopenharmony_ci if (!srec) 2628c2ecf20Sopenharmony_ci return -ENOMEM; 2638c2ecf20Sopenharmony_ci srec->kctl = follower; 2648c2ecf20Sopenharmony_ci srec->follower = *follower; 2658c2ecf20Sopenharmony_ci memcpy(srec->follower.vd, follower->vd, follower->count * sizeof(*follower->vd)); 2668c2ecf20Sopenharmony_ci srec->master = master_link; 2678c2ecf20Sopenharmony_ci srec->flags = flags; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci /* override callbacks */ 2708c2ecf20Sopenharmony_ci follower->info = follower_info; 2718c2ecf20Sopenharmony_ci follower->get = follower_get; 2728c2ecf20Sopenharmony_ci follower->put = follower_put; 2738c2ecf20Sopenharmony_ci if (follower->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) 2748c2ecf20Sopenharmony_ci follower->tlv.c = follower_tlv_cmd; 2758c2ecf20Sopenharmony_ci follower->private_data = srec; 2768c2ecf20Sopenharmony_ci follower->private_free = follower_free; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci list_add_tail(&srec->list, &master_link->followers); 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ciEXPORT_SYMBOL(_snd_ctl_add_follower); 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci/* 2848c2ecf20Sopenharmony_ci * ctl callbacks for master controls 2858c2ecf20Sopenharmony_ci */ 2868c2ecf20Sopenharmony_cistatic int master_info(struct snd_kcontrol *kcontrol, 2878c2ecf20Sopenharmony_ci struct snd_ctl_elem_info *uinfo) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci struct link_master *master = snd_kcontrol_chip(kcontrol); 2908c2ecf20Sopenharmony_ci int ret; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci ret = master_init(master); 2938c2ecf20Sopenharmony_ci if (ret < 0) 2948c2ecf20Sopenharmony_ci return ret; 2958c2ecf20Sopenharmony_ci uinfo->type = master->info.type; 2968c2ecf20Sopenharmony_ci uinfo->count = master->info.count; 2978c2ecf20Sopenharmony_ci uinfo->value.integer.min = master->info.min_val; 2988c2ecf20Sopenharmony_ci uinfo->value.integer.max = master->info.max_val; 2998c2ecf20Sopenharmony_ci return 0; 3008c2ecf20Sopenharmony_ci} 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_cistatic int master_get(struct snd_kcontrol *kcontrol, 3038c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 3048c2ecf20Sopenharmony_ci{ 3058c2ecf20Sopenharmony_ci struct link_master *master = snd_kcontrol_chip(kcontrol); 3068c2ecf20Sopenharmony_ci int err = master_init(master); 3078c2ecf20Sopenharmony_ci if (err < 0) 3088c2ecf20Sopenharmony_ci return err; 3098c2ecf20Sopenharmony_ci ucontrol->value.integer.value[0] = master->val; 3108c2ecf20Sopenharmony_ci return 0; 3118c2ecf20Sopenharmony_ci} 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_cistatic int sync_followers(struct link_master *master, int old_val, int new_val) 3148c2ecf20Sopenharmony_ci{ 3158c2ecf20Sopenharmony_ci struct link_follower *follower; 3168c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *uval; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci uval = kmalloc(sizeof(*uval), GFP_KERNEL); 3198c2ecf20Sopenharmony_ci if (!uval) 3208c2ecf20Sopenharmony_ci return -ENOMEM; 3218c2ecf20Sopenharmony_ci list_for_each_entry(follower, &master->followers, list) { 3228c2ecf20Sopenharmony_ci master->val = old_val; 3238c2ecf20Sopenharmony_ci uval->id = follower->follower.id; 3248c2ecf20Sopenharmony_ci follower_get_val(follower, uval); 3258c2ecf20Sopenharmony_ci master->val = new_val; 3268c2ecf20Sopenharmony_ci follower_put_val(follower, uval); 3278c2ecf20Sopenharmony_ci } 3288c2ecf20Sopenharmony_ci kfree(uval); 3298c2ecf20Sopenharmony_ci return 0; 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_cistatic int master_put(struct snd_kcontrol *kcontrol, 3338c2ecf20Sopenharmony_ci struct snd_ctl_elem_value *ucontrol) 3348c2ecf20Sopenharmony_ci{ 3358c2ecf20Sopenharmony_ci struct link_master *master = snd_kcontrol_chip(kcontrol); 3368c2ecf20Sopenharmony_ci int err, new_val, old_val; 3378c2ecf20Sopenharmony_ci bool first_init; 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci err = master_init(master); 3408c2ecf20Sopenharmony_ci if (err < 0) 3418c2ecf20Sopenharmony_ci return err; 3428c2ecf20Sopenharmony_ci first_init = err; 3438c2ecf20Sopenharmony_ci old_val = master->val; 3448c2ecf20Sopenharmony_ci new_val = ucontrol->value.integer.value[0]; 3458c2ecf20Sopenharmony_ci if (new_val == old_val) 3468c2ecf20Sopenharmony_ci return 0; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci err = sync_followers(master, old_val, new_val); 3498c2ecf20Sopenharmony_ci if (err < 0) 3508c2ecf20Sopenharmony_ci return err; 3518c2ecf20Sopenharmony_ci if (master->hook && !first_init) 3528c2ecf20Sopenharmony_ci master->hook(master->hook_private_data, master->val); 3538c2ecf20Sopenharmony_ci return 1; 3548c2ecf20Sopenharmony_ci} 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_cistatic void master_free(struct snd_kcontrol *kcontrol) 3578c2ecf20Sopenharmony_ci{ 3588c2ecf20Sopenharmony_ci struct link_master *master = snd_kcontrol_chip(kcontrol); 3598c2ecf20Sopenharmony_ci struct link_follower *follower, *n; 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci /* free all follower links and retore the original follower kctls */ 3628c2ecf20Sopenharmony_ci list_for_each_entry_safe(follower, n, &master->followers, list) { 3638c2ecf20Sopenharmony_ci struct snd_kcontrol *sctl = follower->kctl; 3648c2ecf20Sopenharmony_ci struct list_head olist = sctl->list; 3658c2ecf20Sopenharmony_ci memcpy(sctl, &follower->follower, sizeof(*sctl)); 3668c2ecf20Sopenharmony_ci memcpy(sctl->vd, follower->follower.vd, 3678c2ecf20Sopenharmony_ci sctl->count * sizeof(*sctl->vd)); 3688c2ecf20Sopenharmony_ci sctl->list = olist; /* keep the current linked-list */ 3698c2ecf20Sopenharmony_ci kfree(follower); 3708c2ecf20Sopenharmony_ci } 3718c2ecf20Sopenharmony_ci kfree(master); 3728c2ecf20Sopenharmony_ci} 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci/** 3768c2ecf20Sopenharmony_ci * snd_ctl_make_virtual_master - Create a virtual master control 3778c2ecf20Sopenharmony_ci * @name: name string of the control element to create 3788c2ecf20Sopenharmony_ci * @tlv: optional TLV int array for dB information 3798c2ecf20Sopenharmony_ci * 3808c2ecf20Sopenharmony_ci * Creates a virtual master control with the given name string. 3818c2ecf20Sopenharmony_ci * 3828c2ecf20Sopenharmony_ci * After creating a vmaster element, you can add the follower controls 3838c2ecf20Sopenharmony_ci * via snd_ctl_add_follower() or snd_ctl_add_follower_uncached(). 3848c2ecf20Sopenharmony_ci * 3858c2ecf20Sopenharmony_ci * The optional argument @tlv can be used to specify the TLV information 3868c2ecf20Sopenharmony_ci * for dB scale of the master control. It should be a single element 3878c2ecf20Sopenharmony_ci * with #SNDRV_CTL_TLVT_DB_SCALE, #SNDRV_CTL_TLV_DB_MINMAX or 3888c2ecf20Sopenharmony_ci * #SNDRV_CTL_TLVT_DB_MINMAX_MUTE type, and should be the max 0dB. 3898c2ecf20Sopenharmony_ci * 3908c2ecf20Sopenharmony_ci * Return: The created control element, or %NULL for errors (ENOMEM). 3918c2ecf20Sopenharmony_ci */ 3928c2ecf20Sopenharmony_cistruct snd_kcontrol *snd_ctl_make_virtual_master(char *name, 3938c2ecf20Sopenharmony_ci const unsigned int *tlv) 3948c2ecf20Sopenharmony_ci{ 3958c2ecf20Sopenharmony_ci struct link_master *master; 3968c2ecf20Sopenharmony_ci struct snd_kcontrol *kctl; 3978c2ecf20Sopenharmony_ci struct snd_kcontrol_new knew; 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci memset(&knew, 0, sizeof(knew)); 4008c2ecf20Sopenharmony_ci knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 4018c2ecf20Sopenharmony_ci knew.name = name; 4028c2ecf20Sopenharmony_ci knew.info = master_info; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci master = kzalloc(sizeof(*master), GFP_KERNEL); 4058c2ecf20Sopenharmony_ci if (!master) 4068c2ecf20Sopenharmony_ci return NULL; 4078c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&master->followers); 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_ci kctl = snd_ctl_new1(&knew, master); 4108c2ecf20Sopenharmony_ci if (!kctl) { 4118c2ecf20Sopenharmony_ci kfree(master); 4128c2ecf20Sopenharmony_ci return NULL; 4138c2ecf20Sopenharmony_ci } 4148c2ecf20Sopenharmony_ci /* override some callbacks */ 4158c2ecf20Sopenharmony_ci kctl->info = master_info; 4168c2ecf20Sopenharmony_ci kctl->get = master_get; 4178c2ecf20Sopenharmony_ci kctl->put = master_put; 4188c2ecf20Sopenharmony_ci kctl->private_free = master_free; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci /* additional (constant) TLV read */ 4218c2ecf20Sopenharmony_ci if (tlv) { 4228c2ecf20Sopenharmony_ci unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE]; 4238c2ecf20Sopenharmony_ci if (type == SNDRV_CTL_TLVT_DB_SCALE || 4248c2ecf20Sopenharmony_ci type == SNDRV_CTL_TLVT_DB_MINMAX || 4258c2ecf20Sopenharmony_ci type == SNDRV_CTL_TLVT_DB_MINMAX_MUTE) { 4268c2ecf20Sopenharmony_ci kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; 4278c2ecf20Sopenharmony_ci memcpy(master->tlv, tlv, sizeof(master->tlv)); 4288c2ecf20Sopenharmony_ci kctl->tlv.p = master->tlv; 4298c2ecf20Sopenharmony_ci } 4308c2ecf20Sopenharmony_ci } 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci return kctl; 4338c2ecf20Sopenharmony_ci} 4348c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_ctl_make_virtual_master); 4358c2ecf20Sopenharmony_ci 4368c2ecf20Sopenharmony_ci/** 4378c2ecf20Sopenharmony_ci * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control 4388c2ecf20Sopenharmony_ci * @kcontrol: vmaster kctl element 4398c2ecf20Sopenharmony_ci * @hook: the hook function 4408c2ecf20Sopenharmony_ci * @private_data: the private_data pointer to be saved 4418c2ecf20Sopenharmony_ci * 4428c2ecf20Sopenharmony_ci * Adds the given hook to the vmaster control element so that it's called 4438c2ecf20Sopenharmony_ci * at each time when the value is changed. 4448c2ecf20Sopenharmony_ci * 4458c2ecf20Sopenharmony_ci * Return: Zero. 4468c2ecf20Sopenharmony_ci */ 4478c2ecf20Sopenharmony_ciint snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, 4488c2ecf20Sopenharmony_ci void (*hook)(void *private_data, int), 4498c2ecf20Sopenharmony_ci void *private_data) 4508c2ecf20Sopenharmony_ci{ 4518c2ecf20Sopenharmony_ci struct link_master *master = snd_kcontrol_chip(kcontrol); 4528c2ecf20Sopenharmony_ci master->hook = hook; 4538c2ecf20Sopenharmony_ci master->hook_private_data = private_data; 4548c2ecf20Sopenharmony_ci return 0; 4558c2ecf20Sopenharmony_ci} 4568c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_add_vmaster_hook); 4578c2ecf20Sopenharmony_ci 4588c2ecf20Sopenharmony_ci/** 4598c2ecf20Sopenharmony_ci * snd_ctl_sync_vmaster - Sync the vmaster followers and hook 4608c2ecf20Sopenharmony_ci * @kcontrol: vmaster kctl element 4618c2ecf20Sopenharmony_ci * @hook_only: sync only the hook 4628c2ecf20Sopenharmony_ci * 4638c2ecf20Sopenharmony_ci * Forcibly call the put callback of each follower and call the hook function 4648c2ecf20Sopenharmony_ci * to synchronize with the current value of the given vmaster element. 4658c2ecf20Sopenharmony_ci * NOP when NULL is passed to @kcontrol. 4668c2ecf20Sopenharmony_ci */ 4678c2ecf20Sopenharmony_civoid snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only) 4688c2ecf20Sopenharmony_ci{ 4698c2ecf20Sopenharmony_ci struct link_master *master; 4708c2ecf20Sopenharmony_ci bool first_init = false; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci if (!kcontrol) 4738c2ecf20Sopenharmony_ci return; 4748c2ecf20Sopenharmony_ci master = snd_kcontrol_chip(kcontrol); 4758c2ecf20Sopenharmony_ci if (!hook_only) { 4768c2ecf20Sopenharmony_ci int err = master_init(master); 4778c2ecf20Sopenharmony_ci if (err < 0) 4788c2ecf20Sopenharmony_ci return; 4798c2ecf20Sopenharmony_ci first_init = err; 4808c2ecf20Sopenharmony_ci err = sync_followers(master, master->val, master->val); 4818c2ecf20Sopenharmony_ci if (err < 0) 4828c2ecf20Sopenharmony_ci return; 4838c2ecf20Sopenharmony_ci } 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci if (master->hook && !first_init) 4868c2ecf20Sopenharmony_ci master->hook(master->hook_private_data, master->val); 4878c2ecf20Sopenharmony_ci} 4888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster); 4898c2ecf20Sopenharmony_ci 4908c2ecf20Sopenharmony_ci/** 4918c2ecf20Sopenharmony_ci * snd_ctl_apply_vmaster_followers - Apply function to each vmaster follower 4928c2ecf20Sopenharmony_ci * @kctl: vmaster kctl element 4938c2ecf20Sopenharmony_ci * @func: function to apply 4948c2ecf20Sopenharmony_ci * @arg: optional function argument 4958c2ecf20Sopenharmony_ci * 4968c2ecf20Sopenharmony_ci * Apply the function @func to each follower kctl of the given vmaster kctl. 4978c2ecf20Sopenharmony_ci * Returns 0 if successful, or a negative error code. 4988c2ecf20Sopenharmony_ci */ 4998c2ecf20Sopenharmony_ciint snd_ctl_apply_vmaster_followers(struct snd_kcontrol *kctl, 5008c2ecf20Sopenharmony_ci int (*func)(struct snd_kcontrol *vfollower, 5018c2ecf20Sopenharmony_ci struct snd_kcontrol *follower, 5028c2ecf20Sopenharmony_ci void *arg), 5038c2ecf20Sopenharmony_ci void *arg) 5048c2ecf20Sopenharmony_ci{ 5058c2ecf20Sopenharmony_ci struct link_master *master; 5068c2ecf20Sopenharmony_ci struct link_follower *follower; 5078c2ecf20Sopenharmony_ci int err; 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci master = snd_kcontrol_chip(kctl); 5108c2ecf20Sopenharmony_ci err = master_init(master); 5118c2ecf20Sopenharmony_ci if (err < 0) 5128c2ecf20Sopenharmony_ci return err; 5138c2ecf20Sopenharmony_ci list_for_each_entry(follower, &master->followers, list) { 5148c2ecf20Sopenharmony_ci err = func(follower->kctl, &follower->follower, arg); 5158c2ecf20Sopenharmony_ci if (err < 0) 5168c2ecf20Sopenharmony_ci return err; 5178c2ecf20Sopenharmony_ci } 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci return 0; 5208c2ecf20Sopenharmony_ci} 5218c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ctl_apply_vmaster_followers); 522