From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ... --- sound/core/vmaster.c | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 sound/core/vmaster.c (limited to 'sound/core/vmaster.c') diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c new file mode 100644 index 000000000..d0f11f378 --- /dev/null +++ b/sound/core/vmaster.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Virtual master and follower controls + * + * Copyright (c) 2008 by Takashi Iwai + */ + +#include +#include +#include +#include +#include + +/* + * a subset of information returned via ctl info callback + */ +struct link_ctl_info { + snd_ctl_elem_type_t type; /* value type */ + int count; /* item count */ + int min_val, max_val; /* min, max values */ +}; + +/* + * link master - this contains a list of follower controls that are + * identical types, i.e. info returns the same value type and value + * ranges, but may have different number of counts. + * + * The master control is so far only mono volume/switch for simplicity. + * The same value will be applied to all followers. + */ +struct link_master { + struct list_head followers; + struct link_ctl_info info; + int val; /* the master value */ + unsigned int tlv[4]; + void (*hook)(void *private_data, int); + void *hook_private_data; +}; + +/* + * link follower - this contains a follower control element + * + * It fakes the control callbacks with additional attenuation by the + * master control. A follower may have either one or two channels. + */ + +struct link_follower { + struct list_head list; + struct link_master *master; + struct link_ctl_info info; + int vals[2]; /* current values */ + unsigned int flags; + struct snd_kcontrol *kctl; /* original kcontrol pointer */ + struct snd_kcontrol follower; /* the copy of original control entry */ +}; + +static int follower_update(struct link_follower *follower) +{ + struct snd_ctl_elem_value *uctl; + int err, ch; + + uctl = kzalloc(sizeof(*uctl), GFP_KERNEL); + if (!uctl) + return -ENOMEM; + uctl->id = follower->follower.id; + err = follower->follower.get(&follower->follower, uctl); + if (err < 0) + goto error; + for (ch = 0; ch < follower->info.count; ch++) + follower->vals[ch] = uctl->value.integer.value[ch]; + error: + kfree(uctl); + return err < 0 ? err : 0; +} + +/* get the follower ctl info and save the initial values */ +static int follower_init(struct link_follower *follower) +{ + struct snd_ctl_elem_info *uinfo; + int err; + + if (follower->info.count) { + /* already initialized */ + if (follower->flags & SND_CTL_FOLLOWER_NEED_UPDATE) + return follower_update(follower); + return 0; + } + + uinfo = kmalloc(sizeof(*uinfo), GFP_KERNEL); + if (!uinfo) + return -ENOMEM; + uinfo->id = follower->follower.id; + err = follower->follower.info(&follower->follower, uinfo); + if (err < 0) { + kfree(uinfo); + return err; + } + follower->info.type = uinfo->type; + follower->info.count = uinfo->count; + if (follower->info.count > 2 || + (follower->info.type != SNDRV_CTL_ELEM_TYPE_INTEGER && + follower->info.type != SNDRV_CTL_ELEM_TYPE_BOOLEAN)) { + pr_err("ALSA: vmaster: invalid follower element\n"); + kfree(uinfo); + return -EINVAL; + } + follower->info.min_val = uinfo->value.integer.min; + follower->info.max_val = uinfo->value.integer.max; + kfree(uinfo); + + return follower_update(follower); +} + +/* initialize master volume */ +static int master_init(struct link_master *master) +{ + struct link_follower *follower; + + if (master->info.count) + return 0; /* already initialized */ + + list_for_each_entry(follower, &master->followers, list) { + int err = follower_init(follower); + if (err < 0) + return err; + master->info = follower->info; + master->info.count = 1; /* always mono */ + /* set full volume as default (= no attenuation) */ + master->val = master->info.max_val; + if (master->hook) + master->hook(master->hook_private_data, master->val); + return 1; + } + return -ENOENT; +} + +static int follower_get_val(struct link_follower *follower, + struct snd_ctl_elem_value *ucontrol) +{ + int err, ch; + + err = follower_init(follower); + if (err < 0) + return err; + for (ch = 0; ch < follower->info.count; ch++) + ucontrol->value.integer.value[ch] = follower->vals[ch]; + return 0; +} + +static int follower_put_val(struct link_follower *follower, + struct snd_ctl_elem_value *ucontrol) +{ + int err, ch, vol; + + err = master_init(follower->master); + if (err < 0) + return err; + + switch (follower->info.type) { + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + for (ch = 0; ch < follower->info.count; ch++) + ucontrol->value.integer.value[ch] &= + !!follower->master->val; + break; + case SNDRV_CTL_ELEM_TYPE_INTEGER: + for (ch = 0; ch < follower->info.count; ch++) { + /* max master volume is supposed to be 0 dB */ + vol = ucontrol->value.integer.value[ch]; + vol += follower->master->val - follower->master->info.max_val; + if (vol < follower->info.min_val) + vol = follower->info.min_val; + else if (vol > follower->info.max_val) + vol = follower->info.max_val; + ucontrol->value.integer.value[ch] = vol; + } + break; + } + return follower->follower.put(&follower->follower, ucontrol); +} + +/* + * ctl callbacks for followers + */ +static int follower_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + return follower->follower.info(&follower->follower, uinfo); +} + +static int follower_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + return follower_get_val(follower, ucontrol); +} + +static int follower_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + int err, ch, changed = 0; + + err = follower_init(follower); + if (err < 0) + return err; + for (ch = 0; ch < follower->info.count; ch++) { + if (follower->vals[ch] != ucontrol->value.integer.value[ch]) { + changed = 1; + follower->vals[ch] = ucontrol->value.integer.value[ch]; + } + } + if (!changed) + return 0; + err = follower_put_val(follower, ucontrol); + if (err < 0) + return err; + return 1; +} + +static int follower_tlv_cmd(struct snd_kcontrol *kcontrol, + int op_flag, unsigned int size, + unsigned int __user *tlv) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + /* FIXME: this assumes that the max volume is 0 dB */ + return follower->follower.tlv.c(&follower->follower, op_flag, size, tlv); +} + +static void follower_free(struct snd_kcontrol *kcontrol) +{ + struct link_follower *follower = snd_kcontrol_chip(kcontrol); + if (follower->follower.private_free) + follower->follower.private_free(&follower->follower); + if (follower->master) + list_del(&follower->list); + kfree(follower); +} + +/* + * Add a follower control to the group with the given master control + * + * All followers must be the same type (returning the same information + * via info callback). The function doesn't check it, so it's your + * responsibility. + * + * Also, some additional limitations: + * - at most two channels + * - logarithmic volume control (dB level), no linear volume + * - master can only attenuate the volume, no gain + */ +int _snd_ctl_add_follower(struct snd_kcontrol *master, + struct snd_kcontrol *follower, + unsigned int flags) +{ + struct link_master *master_link = snd_kcontrol_chip(master); + struct link_follower *srec; + + srec = kzalloc(struct_size(srec, follower.vd, follower->count), + GFP_KERNEL); + if (!srec) + return -ENOMEM; + srec->kctl = follower; + srec->follower = *follower; + memcpy(srec->follower.vd, follower->vd, follower->count * sizeof(*follower->vd)); + srec->master = master_link; + srec->flags = flags; + + /* override callbacks */ + follower->info = follower_info; + follower->get = follower_get; + follower->put = follower_put; + if (follower->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) + follower->tlv.c = follower_tlv_cmd; + follower->private_data = srec; + follower->private_free = follower_free; + + list_add_tail(&srec->list, &master_link->followers); + return 0; +} +EXPORT_SYMBOL(_snd_ctl_add_follower); + +/* + * ctl callbacks for master controls + */ +static int master_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int ret; + + ret = master_init(master); + if (ret < 0) + return ret; + uinfo->type = master->info.type; + uinfo->count = master->info.count; + uinfo->value.integer.min = master->info.min_val; + uinfo->value.integer.max = master->info.max_val; + return 0; +} + +static int master_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int err = master_init(master); + if (err < 0) + return err; + ucontrol->value.integer.value[0] = master->val; + return 0; +} + +static int sync_followers(struct link_master *master, int old_val, int new_val) +{ + struct link_follower *follower; + struct snd_ctl_elem_value *uval; + + uval = kmalloc(sizeof(*uval), GFP_KERNEL); + if (!uval) + return -ENOMEM; + list_for_each_entry(follower, &master->followers, list) { + master->val = old_val; + uval->id = follower->follower.id; + follower_get_val(follower, uval); + master->val = new_val; + follower_put_val(follower, uval); + } + kfree(uval); + return 0; +} + +static int master_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + int err, new_val, old_val; + bool first_init; + + err = master_init(master); + if (err < 0) + return err; + first_init = err; + old_val = master->val; + new_val = ucontrol->value.integer.value[0]; + if (new_val == old_val) + return 0; + + err = sync_followers(master, old_val, new_val); + if (err < 0) + return err; + if (master->hook && !first_init) + master->hook(master->hook_private_data, master->val); + return 1; +} + +static void master_free(struct snd_kcontrol *kcontrol) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + struct link_follower *follower, *n; + + /* free all follower links and retore the original follower kctls */ + list_for_each_entry_safe(follower, n, &master->followers, list) { + struct snd_kcontrol *sctl = follower->kctl; + struct list_head olist = sctl->list; + memcpy(sctl, &follower->follower, sizeof(*sctl)); + memcpy(sctl->vd, follower->follower.vd, + sctl->count * sizeof(*sctl->vd)); + sctl->list = olist; /* keep the current linked-list */ + kfree(follower); + } + kfree(master); +} + + +/** + * snd_ctl_make_virtual_master - Create a virtual master control + * @name: name string of the control element to create + * @tlv: optional TLV int array for dB information + * + * Creates a virtual master control with the given name string. + * + * After creating a vmaster element, you can add the follower controls + * via snd_ctl_add_follower() or snd_ctl_add_follower_uncached(). + * + * The optional argument @tlv can be used to specify the TLV information + * for dB scale of the master control. It should be a single element + * with #SNDRV_CTL_TLVT_DB_SCALE, #SNDRV_CTL_TLV_DB_MINMAX or + * #SNDRV_CTL_TLVT_DB_MINMAX_MUTE type, and should be the max 0dB. + * + * Return: The created control element, or %NULL for errors (ENOMEM). + */ +struct snd_kcontrol *snd_ctl_make_virtual_master(char *name, + const unsigned int *tlv) +{ + struct link_master *master; + struct snd_kcontrol *kctl; + struct snd_kcontrol_new knew; + + memset(&knew, 0, sizeof(knew)); + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = master_info; + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return NULL; + INIT_LIST_HEAD(&master->followers); + + kctl = snd_ctl_new1(&knew, master); + if (!kctl) { + kfree(master); + return NULL; + } + /* override some callbacks */ + kctl->info = master_info; + kctl->get = master_get; + kctl->put = master_put; + kctl->private_free = master_free; + + /* additional (constant) TLV read */ + if (tlv) { + unsigned int type = tlv[SNDRV_CTL_TLVO_TYPE]; + if (type == SNDRV_CTL_TLVT_DB_SCALE || + type == SNDRV_CTL_TLVT_DB_MINMAX || + type == SNDRV_CTL_TLVT_DB_MINMAX_MUTE) { + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + memcpy(master->tlv, tlv, sizeof(master->tlv)); + kctl->tlv.p = master->tlv; + } + } + + return kctl; +} +EXPORT_SYMBOL(snd_ctl_make_virtual_master); + +/** + * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @kcontrol: vmaster kctl element + * @hook: the hook function + * @private_data: the private_data pointer to be saved + * + * Adds the given hook to the vmaster control element so that it's called + * at each time when the value is changed. + * + * Return: Zero. + */ +int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, + void (*hook)(void *private_data, int), + void *private_data) +{ + struct link_master *master = snd_kcontrol_chip(kcontrol); + master->hook = hook; + master->hook_private_data = private_data; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_add_vmaster_hook); + +/** + * snd_ctl_sync_vmaster - Sync the vmaster followers and hook + * @kcontrol: vmaster kctl element + * @hook_only: sync only the hook + * + * Forcibly call the put callback of each follower and call the hook function + * to synchronize with the current value of the given vmaster element. + * NOP when NULL is passed to @kcontrol. + */ +void snd_ctl_sync_vmaster(struct snd_kcontrol *kcontrol, bool hook_only) +{ + struct link_master *master; + bool first_init = false; + + if (!kcontrol) + return; + master = snd_kcontrol_chip(kcontrol); + if (!hook_only) { + int err = master_init(master); + if (err < 0) + return; + first_init = err; + err = sync_followers(master, master->val, master->val); + if (err < 0) + return; + } + + if (master->hook && !first_init) + master->hook(master->hook_private_data, master->val); +} +EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster); + +/** + * snd_ctl_apply_vmaster_followers - Apply function to each vmaster follower + * @kctl: vmaster kctl element + * @func: function to apply + * @arg: optional function argument + * + * Apply the function @func to each follower kctl of the given vmaster kctl. + * + * Return: 0 if successful, or a negative error code + */ +int snd_ctl_apply_vmaster_followers(struct snd_kcontrol *kctl, + int (*func)(struct snd_kcontrol *vfollower, + struct snd_kcontrol *follower, + void *arg), + void *arg) +{ + struct link_master *master; + struct link_follower *follower; + int err; + + master = snd_kcontrol_chip(kctl); + err = master_init(master); + if (err < 0) + return err; + list_for_each_entry(follower, &master->followers, list) { + err = func(follower->kctl, &follower->follower, arg); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_ctl_apply_vmaster_followers); -- cgit v1.2.3