From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.216.54 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793100754; b=tQ0J1Po5XsanI+cGn/dOTY/tPeMaEoPa9GXt8piJgdhctdTFder/rVNL+WUJFJPFj+ow /2sXMmm9Fytce8RQ5JkV8JhM7uxgT+9vSy2U3IquBAzBxtu3pSKozEyPbUpvUNxCAsi3J Do4iVzQq9q4D8ebmLe6fio58Fjl3Bk3cG7NHn23eAapqm3HKWp9ZQ7pUpre5av0DjMMFV L/8gvv1ybfZPuZV8+1G635icoooF9Uf9ChLWSfQmxwN/u39Bq709ETUs4QPOn2ryucA3r ardbWUVjJqyy/bCBdWcZEzW+v6Ip2yzkxqfno3a0gB6gqwoarnlrutdZ6bE7WcfBJwA== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793100754; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Type:Content-Transfer-Encoding; bh=qNTAwFU+tlY3DpTkFEopoDxKLqW3r+c4aweDx94NbQ8=; b=dDMNIuQYRaFtYqaRomeqZCc+PiD9zz1TvsQsa60wsmQWXyFKGq5HLUOBHA7ax4Ay4unI lOCmiO7YErQIMQfDpieDIv9G2E8RodLL7b0qjuTe5fe+w7BFWblw8DhOVrUCZz9TTaj1K tQGYF+e8L6EnoXmZb5lsS1GKhc20jX01zo6O7pEMqgQ9evnz8l3QQUKEQfXTzHC+M43bp 571+MMYnFYiIUluQP5buWcc/EjCCyRZewq7X/shX7B6Avq+8bCC2k9awOeVmWGU9I/3VC 7erNOhftr4/663UTTfktJbUb/bCDLnzi9k4uLG7bL/ApczQ4h3S9C9GKQq9Fj6cweNw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=AYEk6Mhy; arc=none smtp.remote-ip=209.85.216.54 Received: from mail-pj1-f54.google.com (mail-pj1-f54.google.com [209.85.216.54]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 98DDB606A335 for ; Tue, 27 Oct 2026 11:32:32 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 98DDB606A335 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=AYEk6Mhy Received: by mail-pj1-f54.google.com with SMTP id 98e67ed59e1d1-3ac7c7b3a90so1905171a91.3 for ; Tue, 27 Oct 2026 04:32:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1793100752; x=1793705552; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=qNTAwFU+tlY3DpTkFEopoDxKLqW3r+c4aweDx94NbQ8=; b=AYEk6MhybcKLrm/jElkaqbcDi6hE7bGQydb+aAdwBHhVRIwYWD811pzCUjR1iY7D0w 1vp/SWjSKfAHVrzT254C3nYVeg9N01WZ7mK18yDKMCX/vTUskRFz8KlT8bij8BhDLqeR qSBCD0zCzbNZhNv3dhNdJb3+04Ykoajlq8ajOGdbV/mvhi3niFyZb7jLV/Q7Oy9/T9Lx NFXRO3YWpcfYoHozf0C1XaJqD5WyuiRxewlHhiJ+fs000faTuk1uJEvSIN6C1bPVI2yH w6x966oySBPi1Kj4rgu/7IcH2OOcdS1sc0MxXW4fAoE1uD9rICj4e3IP3cvLUaQeD1jV L5sw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793100752; x=1793705552; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=qNTAwFU+tlY3DpTkFEopoDxKLqW3r+c4aweDx94NbQ8=; b=IEOmhXDKHpA4oEzcZPp18hsYTaxYb9hOp+JG5bdebmY0UPRYcyDtmXCIk7NgqSazay oADQTKuIanDScKo4gQZjaFyiWz7Ub/u9Q6dev0PjGr5awEivhihmXqXFHYAUGxwvzUjj WyDPVBawqaNd+Ici9pTY1oWRpa2M82skq3FYVZKGTl7Nl71vTqHf9FkDeV0wrE+qF2d0 CpPlr4HgoRVFejgRO/i0SasXwHdsCQQwff7YkePloFYgg58UWsD9AZCBGHqj/97eaOmy DKq1lVhZ4aHLvfd/6HThLpjmexlJParhdz5jmdBzcxlQdLNb4moZ0zGEDHVAtZWEk7is F9WA== X-Forwarded-Encrypted: i=1; AKwUvBxkpMq0u42kSPXobLf9iMlshjAOhTC/wOgBR5nSCnWC0SXpmg27Yymisvsx39Zgcm/lE+6aLejtRwJThk8=@vger.kernel.org X-Gm-Message-State: AFq9FYLDTWwxxeqEKgAJ2unG/FOmmzfZP7B6rAo35+0HVqzWYMDgqw0A A2rVSAGGj06Dr1e6ZG2B5SGdgL7XHX+Xza+oAtyCUUuidVVckx60kqdl X-Gm-Gg: AYBFou0C9FXUrnSHg8cR8Vxwr83C/3Jej7VNg09DkkJtpHxxOqLe8AmF56dCnYDn2ct oyNU4muwmtTPebBtkH6VYxX5uR/DDkr896hYH/uN3rFIlqC6GpRuXQS+qPlC9v1n8JL/PAvdY7W SVsoG0bq/VSMR88fWseuwiSIiTz/rcxNteRQtyx5axz/2tSf1g+SXfZhNJTjeudX+zqEcs3W3+O 2byiMugM7ENDNrs0EbxZV3FHRbpYHBWtiCbajYeXgmEN+iLdLbfWDSGO3Sjbx0bqAeA4Yrxonqh DbziVyfpIil+5G51eUVbB1Fgmhm6nGt59O7IbgA/cpP9sNIt1YeQSJnbOMPLsQIer5JUoMhF+s9 JXRe/hiJLti+puo+LSSObFac1MbHtGGQNiUOLl+guSxyNg7SOrDsw0ezeVjvlEMLB43xBVVJ9ze 3RfzQefP2+vo63IdHW/4O8JbCuPZQ9vK2jeXFpeax303SN7Jr9tckwFA1u52gZh1m68ofaAYqfs /uiIPgwQd5ThG0o3XP+NYpAq03QEF3Z7KSHxDbfeacejd/Vgmeimn8UpPPYWM4iSulNctfcGo/t JFeSjehSPECS1FhBKWRrPKfqptrGKO3iFw5ZCIsVcb5hWAFq/ZI8IT1J/vQDj+DrR0JGWMQyDkL gsTlcUVu9pkS77vbthkQbq20pfJgwatk3ySxZ1dwLUYaMan2+ X-Received: by 2002:a17:90b:3141:b0:3ac:9a54:e3ce with SMTP id 98e67ed59e1d1-3adeafb073fmr7868673a91.63.1793100751597; Tue, 27 Oct 2026 04:32:31 -0700 (PDT) Received: from cachyos (2a01cb0c82caf900a186612f304bfea5.ipv6.abo.wanadoo.fr. [2a01:cb0c:82ca:f900:a186:612f:304b:fea5]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3ae4041fafcsm4523962a91.0.2026.10.27.04.32.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 27 Oct 2026 04:32:30 -0700 (PDT) From: =?UTF-8?q?Isma=C3=AFl=20Bahloul?= To: linux-sound@vger.kernel.org Cc: tiwai@suse.com, perex@perex.cz, linux-usb@vger.kernel.org, alsa-devel@alsa-project.org, linux-kernel@vger.kernel.org, corbet@lwn.net, skhan@linuxfoundation.org, rdunlap@infradead.org, linux-doc@vger.kernel.org, =?UTF-8?q?Isma=C3=AFl=20Bahloul?= , David Fredman Subject: [PATCH v7 2/8] ALSA: usb: babyfacepro: add output masters and crosspoint routing Date: Tue, 27 Oct 2026 12:31:33 +0100 Message-ID: <20261027113140.134257-3-i.bahloul01@gmail.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261027113140.134257-1-i.bahloul01@gmail.com> References: <20261027113140.134257-1-i.bahloul01@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the first mixer controls: the six output masters (volume + mute) and the 6x14 crosspoint matrix. The device has no readback for any of them, so the driver keeps the state host-side and the controls read that cache. The probe's cold init clears the mixer registers, so probe writes a power-on default: every playback channel into every output at 0 dB, the hardware inputs off until a mixer raises them, and the two analog outputs (AN1/2, PH3/4) at -20 dB, the level the hardware DIM button uses. The AN1/2 output sums from its own "low map" registers, so its crosspoints are written to both maps. A volume change leaves a muted output muted. The masters count the 8-bit register's 0.5 dB steps over the front panel's range: 0 is mute, 1 is -90 dB and BF_MASTER_STEP_MAX is +6 dB. The control value is that step, so no log table converts it, and the 16-bit companion register is derived from the same step. A userspace usbfs claim (PipeWire, a libusb mixer daemon) detaches and re-probes the interface, and the cold init of the re-probe clears the mixer registers; alsactl restores the user's settings once the card is registered again. Co-developed-by: David Fredman Signed-off-by: David Fredman Signed-off-by: Ismaïl Bahloul --- sound/usb/babyfacepro/Makefile | 2 +- sound/usb/babyfacepro/babyfacepro-ctl.c | 656 ++++++++++++++++++++++++ sound/usb/babyfacepro/babyfacepro.c | 64 ++- sound/usb/babyfacepro/babyfacepro.h | 124 ++++- 4 files changed, 843 insertions(+), 3 deletions(-) create mode 100644 sound/usb/babyfacepro/babyfacepro-ctl.c diff --git a/sound/usb/babyfacepro/Makefile b/sound/usb/babyfacepro/Makefile index 5adc6d474..a50647a06 100644 --- a/sound/usb/babyfacepro/Makefile +++ b/sound/usb/babyfacepro/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -snd-usb-babyface-pro-y := babyfacepro.o +snd-usb-babyface-pro-y := babyfacepro.o babyfacepro-ctl.o obj-$(CONFIG_SND_USB_BABYFACE_PRO) += snd-usb-babyface-pro.o diff --git a/sound/usb/babyfacepro/babyfacepro-ctl.c b/sound/usb/babyfacepro/babyfacepro-ctl.c new file mode 100644 index 000000000..9b41dcde5 --- /dev/null +++ b/sound/usb/babyfacepro/babyfacepro-ctl.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RME Babyface Pro / Pro FS - proprietary-mode USB audio driver + * + * ALSA control surface: the hardware mixer (output masters and mutes, + * the crosspoint matrix, preamps, routing flags, varispeed pitch). + * + * See babyfacepro.h for the shared device state and register map, + * and babyfacepro.c for the core driver (protocol, PCM streaming, + * state persistence, card lifecycle). + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "babyfacepro.h" + +/* The flag-cycle counter, its writer and the per-block crosspoint + * "cross" register clear are crosspoint/mixer-only, so they live here + * rather than in the core driver file. + */ + +/* The transaction-flag counter cycle on 16-bit writes. */ +const u16 bf_flag_cycle[4] = { 0xc000, 0x4000, 0x8000, 0x0000 }; + +/* Write with the per-transaction flag-cycle word OR'd into idx. The + * device wants the flag word (0xc000/0x4000/0x8000/0x0000, rotating) + * set on every 0x12/0x1a write; this is the hot path for the mixer + * puts, so it is factored out. + */ +int bf_vendor_write_cycle(struct snd_usb_babyface *chip, u8 req, u16 val, u16 idx) +{ + u16 flag = bf_flag_cycle[chip->flag_cnt]; + + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + return bf_vendor_write(chip, req, val, idx | flag); +} + +/* The 0x16 cold-init clear covers only 0x00-0x3D - the "cross" + * registers of a block (L-reg odd / R-reg even of the stereo + * sources) survive from the previous session and would sum L+R into + * BOTH channels of the output (mono). Zero them explicitly: 10 odd + * L-registers (5,7,...23) + 10 even R-registers (4,6,...22). + */ +int bf_crosspoint_clear_cross(struct snd_usb_babyface *chip, + unsigned int pair) +{ + int ret, k; + + for (k = BF_CROSS_L_FIRST; k <= BF_CROSS_L_LAST; k += 2) { + ret = bf_vendor_write_cycle(chip, BF_REQ_CROSSPOINT, 0x0000, + BF_REG_XPOINT_BASE_L + + BF_REG_XPOINT_STRIDE * pair + k); + if (ret < 0) + return ret; + } + for (k = BF_CROSS_R_FIRST; k <= BF_CROSS_R_LAST; k += 2) { + ret = bf_vendor_write_cycle(chip, BF_REQ_CROSSPOINT, 0x0000, + BF_REG_XPOINT_BASE_R + + BF_REG_XPOINT_STRIDE * pair + k); + if (ret < 0) + return ret; + } + return 0; +} + +const struct bf_source bf_sources[14] = { + { "AN1", 0, 0 }, + { "AN2", 1, 1 }, + { "AN3", 2, 2 }, + { "AN4", 3, 3 }, + { "AS1/2", 4, 5 }, + { "ADAT3/4", 6, 7 }, + { "ADAT5/6", 8, 9 }, + { "ADAT7/8", 10, 11 }, + { "PB1", 12, 13 }, + { "PB2", 14, 15 }, + { "PB3", 16, 17 }, + { "PB4", 18, 19 }, + { "PB5", 20, 21 }, + { "PB6", 22, 23 }, +}; + +/* The crosspoint blocks are in the canonical output order (AN1/2, PH3/4, + * AS1/2, ...), one per output pair at 0x34 * pair, see BF_REG_XPOINT_BASE_L. + * They used to be read as starting at 0x34, with the first two swapped, + * which put every pair from AS1/2 on the wrong output (see that comment). + */ + +/* Master-register output order - the master map lists AN1/2 first + * (0x03E0) and the Phones master SECOND (0x03E2, HARDWARE-VERIFIED + * 2026-08-24). Control index -> canonical output (AN1/2=0, PH3/4=1, ...) + * = the master register position directly: the names 'AN1/2 Playback + * Volume' etc. must match the register they write (corrected 2026-08-26 + * - a {1,0,...} swap made 'AN1/2' drive the Phones and 'PH3/4' drive + * the AN1/2 analog out). + */ +static const u8 bf_master_out[6] = { 0, 1, 2, 3, 4, 5 }; + +/* dBx2 -> 16-bit master (0x2000*2^(half_db/12), rounded), from the + * 12th-root table 2^(n/12). + */ +static const u16 bf_twelfth[12] = { + 0x1000, 0x10f4, 0x11f6, 0x1307, 0x1429, 0x155c, + 0x16a1, 0x17f9, 0x1966, 0x1ae9, 0x1c82, 0x1e34, +}; + +int bf_master_16bit(int half_db) +{ + int k = half_db / 12; + int n = half_db % 12; + u32 v; + + if (n < 0) { + n += 12; + k--; + } + v = (u32)bf_twelfth[n] << 1; /* 0x2000*2^(n/12) */ + if (k >= 0) { + v <<= k; + } else { + v += 1u << (-k - 1); /* round-half-up */ + v >>= -k; + } + return (u16)clamp(v, 1, 0x4000); +} + +/* A master control step (BF_MASTER_STEP_*) -> the two registers. */ +u8 bf_master_reg8(unsigned int step) +{ + if (step == 0) + return BF_MASTER_MUTE; + return (u8)clamp((int)step - BF_MASTER_STEP_0DB + BF_MASTER_8_0DB, + BF_MASTER_8_MIN, 0xff); +} + +u16 bf_master_reg16(unsigned int step) +{ + if (step == 0) + return 0; + return bf_master_16bit((int)step - BF_MASTER_STEP_0DB); +} + +/* The cold-init register clear zeroes the mixer registers TotalMix + * re-uploads afterwards. The kernel driver has no saved scene (no + * readback for faders), so on a probe with no saved state it applies a + * factory routing so the card makes sound without any user-space mixer: + * the six playback channels feed every output at unity, and the + * hardware inputs (AN1-4, AS1/2, ADAT) are NOT routed - raising an + * input's crosspoint in a mixer is what monitors it. + * + * The inputs are left out deliberately. Routing all 14 sources into + * every output at unity (the earlier default) summed a live mic or line + * input straight into the main out and the headphones the moment the + * module loaded, before alsa-restore had a chance to put the user's own + * levels back - the feedback/level hazard issue #4 raised, and the "mic + * audible in the phones" surprise. The failure is asymmetric: a + * default that is too quiet is turned up in a second, one that is too + * loud cannot be taken back. + * + * The two analog masters (AN1/2, the main out; PH3/4, the headphone + * out) come up at -20 dB rather than TotalMix's 0 dB, for the same + * reason - the six playback channels into an output at unity sum, and + * -20 dB is plainly audible but safe. It is not an invented number: it + * is the exact 8-bit/16-bit pair the hardware's own DIM button writes. + * + * The other four outputs (AS1/2, ADAT3/4, ADAT5/6, ADAT7/8) are all + * digital, carried over the single optical port - nothing downstream + * of them can be damaged by a loud signal the way a speaker or a pair + * of headphones can, so there is no hazard to mitigate, only a + * digital feed that would otherwise arrive 20 dB quiet for no reason + * a downstream device could infer. They keep TotalMix's own 0 dB + * default (raised 2026-09-15 after David Fredman pointed out the + * blanket -20 dB reached them too, on his report of the AN1/2/PH3/4 + * default - issue #4). + */ +int babyface_write_default_mixer(struct snd_usb_babyface *chip) +{ + int out, src, ret; + u16 flag; + u16 level; + + /* Output masters: the two analog outputs at -20 dB, the four + * digital ones at 0 dB (see the comment above). Unmuted either + * way. + */ + for (out = 0; out < 6; out++) { + bool analog = out < 2; + u8 gain8 = analog ? BF_MASTER_MINUS20_8 : BF_MASTER_UNMUTE; + u16 gain16 = analog ? BF_MASTER_MINUS20_16 : BF_MASTER_0DB; + + ret = bf_vendor_write(chip, BF_REQ_GAIN, gain8, + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_GAIN, gain8, + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + return ret; + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, gain16, + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, gain16, + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + return ret; + chip->master[out][0] = analog ? BF_MASTER_STEP_MINUS20 : + BF_MASTER_STEP_0DB; + chip->master[out][1] = chip->master[out][0]; + chip->muted[out] = false; + } + + /* Every playback source into every output pair, L and R, at 0 dB, + * and the hardware inputs off. The addresses use the source's + * idx_l/idx_r on the pair's block - writing the raw index on both + * bases would put PB1 R on the L side and PB1 L on the R side (L+R + * on both = mono). The "cross" registers (L-reg idx_r / R-reg + * idx_l) are cleared. + */ + for (out = 0; out < 6; out++) { + for (src = 0; src < 14; src++) { + /* Only the playback channels are routed out of the + * box; a hardware input stays out of every output + * until a mixer raises its crosspoint. + */ + level = src < BF_SRC_PB1 ? 0 : BF_FADER_0DB; + ret = bf_xpoint_write(chip, out, src, level, level); + if (ret < 0) + return ret; + } + ret = bf_crosspoint_clear_cross(chip, out); + if (ret < 0) + return ret; + } + + /* Mirror the defaults into the control cache (14 controls/output). */ + for (out = 0; out < 6; out++) + for (src = 0; src < 14; src++) { + level = src < BF_SRC_PB1 ? 0 : BF_FADER_0DB; + chip->xpoint[out][src][0] = level; + chip->xpoint[out][src][1] = level; + } + + /* Host settings word (clock source Internal). */ + return bf_settings_write(chip); +} + +/* The cold init's 0x16 clear leaves the 8-bit masters muted: the + * output stays silent until a master write lands (hardware-verified + * 2026-08-24 - only a write un-mutes the 8-bit register). Re-apply + * the six output masters + mutes from the cache; used by the state + * restore that follows the cold init. + */ +int bf_apply_masters(struct snd_usb_babyface *chip) +{ + int out, ret; + u16 flag; + + for (out = 0; out < 6; out++) { + u16 l = chip->muted[out] ? 0 : chip->master[out][0]; + u16 r = chip->muted[out] ? 0 : chip->master[out][1]; + u8 l8 = bf_master_reg8(l); + u8 r8 = bf_master_reg8(r); + + ret = bf_vendor_write(chip, BF_REQ_GAIN, l8, + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_GAIN, r8, + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + return ret; + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, bf_master_reg16(l), + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, bf_master_reg16(r), + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + return ret; + } + return 0; +} + +/* dB TLV for the output masters: 0.5 dB per step, step 0 = mute, + * step 1 = -90 dB, 181 = 0 dB, and the top step 193 = +6 dB, above + * unity as on the OUT wheel (BF_MASTER_STEP_*). WirePlumber needs it + * to map the volume 1:1 to the hardware control instead of applying a + * software volume on top. + */ +static const DECLARE_TLV_DB_SCALE(bf_master_tlv, -9050, 50, 1); + +static int bf_master_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = BF_MASTER_STEP_MAX; + uinfo->value.integer.step = 1; + return 0; +} + +static int bf_master_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int out = bf_master_out[kctl->private_value]; + + ucontrol->value.integer.value[0] = chip->master[out][0]; + ucontrol->value.integer.value[1] = chip->master[out][1]; + return 0; +} + +static int bf_master_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int out = bf_master_out[kctl->private_value]; + long nl = ucontrol->value.integer.value[0]; + long nr = ucontrol->value.integer.value[1]; + u16 l, r, wire_l, wire_r, flag; + int ret = 0; + + /* Reject anything outside the declared range, so the registers and + * the cache stay in spec (the ALSA core only enforces this with + * CONFIG_SND_CTL_INPUT_VALIDATION). Check before narrowing: + * .value.integer.value[] is a long. + */ + if (nl < 0 || nl > BF_MASTER_STEP_MAX || + nr < 0 || nr > BF_MASTER_STEP_MAX) + return -EINVAL; + l = nl; + r = nr; + + mutex_lock(&chip->mutex); + if (l == chip->master[out][0] && r == chip->master[out][1]) + goto out; + + wire_l = chip->muted[out] ? 0 : l; + wire_r = chip->muted[out] ? 0 : r; + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + + /* Both registers, as TotalMix writes them: the 8-bit is the analog + * level, the 16-bit the digital one. + */ + ret = bf_vendor_write(chip, BF_REQ_GAIN, bf_master_reg8(wire_l), + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_GAIN, bf_master_reg8(wire_r), + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, bf_master_reg16(wire_l), + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, bf_master_reg16(wire_r), + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + goto out; + + chip->master[out][0] = l; + chip->master[out][1] = r; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_mute_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int bf_mute_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int out = bf_master_out[kctl->private_value]; + + /* ALSA convention: 1 = enabled (sound on) = not muted. */ + ucontrol->value.integer.value[0] = !chip->muted[out]; + ucontrol->value.integer.value[1] = !chip->muted[out]; + return 0; +} + +static int bf_mute_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int out = bf_master_out[kctl->private_value]; + bool muted = !ucontrol->value.integer.value[0]; + u16 flag; + int ret = 0; + + mutex_lock(&chip->mutex); + if (muted == chip->muted[out]) + goto out; + + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + + if (muted) { + ret = bf_vendor_write(chip, BF_REQ_GAIN, BF_MASTER_MUTE, + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_GAIN, BF_MASTER_MUTE, + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + goto out; + } else { + /* Unmute restores the cached volume (TotalMix keeps the + * pre-mute fader value host-side), 8-bit + 16-bit. + */ + ret = bf_vendor_write(chip, BF_REQ_GAIN, + bf_master_reg8(chip->master[out][0]), + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_GAIN, + bf_master_reg8(chip->master[out][1]), + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + bf_master_reg16(chip->master[out][0]), + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + bf_master_reg16(chip->master[out][1]), + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + goto out; + } + chip->muted[out] = muted; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +/* The crosspoint fader is linear in amplitude: BF_FADER_0DB (0x16a0) is + * unity and BF_FADER_TOP (0x2d41) is exactly twice that, i.e. +6 dB; the + * whole fader span follows raw = BF_FADER_0DB * 10^(dB/20). Raw 0 is + * off. + */ +static const DECLARE_TLV_DB_LINEAR(bf_xpoint_tlv, TLV_DB_GAIN_MUTE, 600); + +/* Write a crosspoint slot on the wire: the pair's block of the map, see + * BF_REG_XPOINT_BASE_L. + * + * AN1/2 is the block at 0x0000. It used to be written as a "low map" + * beside a "standard map" that this function put at 0x0068 (AN1/2 was + * thought to be the second block, the Phones the first), and every other + * pair was written one block off. Measured on a Pro FS through the six + * digital loopbacks (2026-10-26), one source routed to one pair at a time: + * the AN1/2 control came out on AN1/2 and AS1/2, AS1/2 on ADAT3/4, ADAT3/4 + * on ADAT5/6, ADAT5/6 on ADAT7/8, and ADAT7/8 on nothing (0x0138 is the FX + * send). That also explains the finding of 2026-09-14 that sweeping the + * "standard map" alone changed nothing on AN1/2: it was AS1/2's submix. + * + * The AN1/2 block has only ever been written without the rotating + * transaction bits of wIndex, and keeps being: issue #31 reads them as the + * gain's lowest bits, which is a separate question. + */ +int bf_xpoint_write(struct snd_usb_babyface *chip, int out, int src, + u16 l, u16 r) +{ + const struct bf_source *s = &bf_sources[src]; + u16 flag = 0; + int ret; + + if (out) { + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + } + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, + (BF_REG_XPOINT_BASE_L + + BF_REG_XPOINT_STRIDE * out + s->idx_l) | flag); + if (ret < 0) + return ret; + return bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, + (BF_REG_XPOINT_BASE_R + + BF_REG_XPOINT_STRIDE * out + s->idx_r) | flag); +} + +static int bf_xpoint_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = BF_FADER_TOP; /* +6 dB fader top */ + uinfo->value.integer.step = 1; + return 0; +} + +static int bf_xpoint_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int out = kctl->private_value >> 8; + int src = kctl->private_value & 0xff; + + ucontrol->value.integer.value[0] = chip->xpoint[out][src][0]; + ucontrol->value.integer.value[1] = chip->xpoint[out][src][1]; + return 0; +} + +static int bf_xpoint_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int out = kctl->private_value >> 8; + int src = kctl->private_value & 0xff; + long nl = ucontrol->value.integer.value[0]; + long nr = ucontrol->value.integer.value[1]; + u16 l, r; + int ret = 0; + + /* Check before narrowing: a value of 0x10000 or more would wrap to a + * passing u16 (see the note in bf_master_put()). + */ + if (nl < 0 || nl > BF_FADER_TOP || nr < 0 || nr > BF_FADER_TOP) + return -EINVAL; + l = nl; + r = nr; + + mutex_lock(&chip->mutex); + if (l == chip->xpoint[out][src][0] && r == chip->xpoint[out][src][1]) + goto out; + + ret = bf_xpoint_write(chip, out, src, l, r); + if (ret < 0) + goto out; + + chip->xpoint[out][src][0] = l; + chip->xpoint[out][src][1] = r; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +int babyface_create_xpoints(struct snd_usb_babyface *chip) +{ + struct snd_kcontrol *kctl; + int out, src, err; + + for (out = 0; out < 6; out++) { + for (src = 0; src < 14; src++) { + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback Volume", + .index = out * 14 + src, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = bf_xpoint_info, + .get = bf_xpoint_get, + .put = bf_xpoint_put, + .tlv.p = bf_xpoint_tlv, + .private_value = (out << 8) | src, + }, chip); + /* Name the control by its source: "AN1 Playback Volume", + * "PB1 Playback Volume"... with a unique index. + */ + strscpy(kctl->id.name, bf_sources[src].name, + sizeof(kctl->id.name)); + strlcat(kctl->id.name, " Playback Volume", + sizeof(kctl->id.name)); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + } + + return 0; +} + +int babyface_create_masters(struct snd_usb_babyface *chip) +{ + static const char * const out_names[6] = { + "AN1/2", "PH3/4", "AS1/2", "ADAT3/4", "ADAT5/6", "ADAT7/8" + }; + struct snd_kcontrol *kctl; + int i, err; + + for (i = 0; i < 6; i++) { + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = out_names[i], + .index = i, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = bf_master_info, + .get = bf_master_get, + .put = bf_master_put, + .tlv.p = bf_master_tlv, + .private_value = i, + }, chip); + strlcat(kctl->id.name, " Playback Volume", sizeof(kctl->id.name)); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + chip->master_kctl[i] = kctl; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = out_names[i], + .index = i, + .info = bf_mute_info, + .get = bf_mute_get, + .put = bf_mute_put, + .private_value = i, + }, chip); + strlcat(kctl->id.name, " Playback Switch", sizeof(kctl->id.name)); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + dev_dbg(&chip->dev->dev, "output %d = %s\n", i, out_names[i]); + } + + return 0; +} diff --git a/sound/usb/babyfacepro/babyfacepro.c b/sound/usb/babyfacepro/babyfacepro.c index 81a371462..de1659307 100644 --- a/sound/usb/babyfacepro/babyfacepro.c +++ b/sound/usb/babyfacepro/babyfacepro.c @@ -5,7 +5,8 @@ * Core driver: USB vendor requests + cold init, interrupt-URB PCM * streaming, and the card lifecycle (probe/disconnect/module entry). * - * See babyfacepro.h for the shared device state and register map. + * See babyfacepro.h for the shared device state and register map, + * and babyfacepro-ctl.c for the ALSA control surface. */ #include #include @@ -249,6 +250,46 @@ int bf_cold_init(struct snd_usb_babyface *chip) return 0; } +/* Re-apply the whole cached mixer state to the device, whose registers + * the cold init cleared. Caller holds chip->mutex. + */ +int babyface_restore_state(struct snd_usb_babyface *chip) +{ + int out, src, ret; + + /* Masters (8-bit = the real volume) + mutes. */ + ret = bf_apply_masters(chip); + if (ret < 0) + return ret; + + /* Crosspoints: one block per output pair, see BF_REG_XPOINT_BASE_L. */ + for (out = 0; out < 6; out++) { + for (src = 0; src < 14; src++) { + ret = bf_xpoint_write(chip, out, src, + chip->xpoint[out][src][0], + chip->xpoint[out][src][1]); + if (ret < 0) + return ret; + } + ret = bf_crosspoint_clear_cross(chip, out); + if (ret < 0) + return ret; + } + + /* The rate (family register), then the varispeed ratio, which is + * sticky in the device and so must be re-sent even at pitch 0 in + * case something else wrote it while we were detached. + */ + ret = bf_clock_write(chip); + if (ret < 0) + return ret; + return bf_pitch_write(chip, chip->pitch); +} + +/* Copy a saved state (if any) into a freshly probed chip and push it + * to the device. Returns 1 when restored, -ENOENT when there is none, + * or a negative error from the vendor writes. + */ static bool babyface_capture_copy(struct snd_usb_babyface *chip, struct snd_pcm_substream *subs, const u8 *data, unsigned int frames) @@ -1388,6 +1429,15 @@ static int babyface_probe(struct usb_interface *intf, goto error; } + /* The 0x16 clear zeroed the mixer registers: apply the factory + * default routing to keep the outputs live out of the box. alsactl + * restores the user's settings once the card is registered. + */ + err = babyface_write_default_mixer(chip); + if (err < 0) { + dev_err(&intf->dev, "default mixer restore failed: %d\n", err); + goto error; + } /* Keep the allocation size independent of the active USB mode. */ urbsize = BF_WORDS_PER_FRAME * sizeof(u32) * chip->frames_per_urb; chip->urbs_in = kcalloc(chip->nurbs, sizeof(*chip->urbs_in), GFP_KERNEL); @@ -1432,6 +1482,18 @@ static int babyface_probe(struct usb_interface *intf, goto error; } + err = babyface_create_masters(chip); + if (err < 0) { + dev_err(&intf->dev, "master control creation failed: %d\n", err); + goto error; + } + + err = babyface_create_xpoints(chip); + if (err < 0) { + dev_err(&intf->dev, "crosspoint creation failed: %d\n", err); + goto error; + } + err = snd_card_register(chip->card); if (err < 0) { dev_err(&intf->dev, "snd_card_register failed: %d\n", err); diff --git a/sound/usb/babyfacepro/babyfacepro.h b/sound/usb/babyfacepro/babyfacepro.h index 93ef4c3a1..a3b118bed 100644 --- a/sound/usb/babyfacepro/babyfacepro.h +++ b/sound/usb/babyfacepro/babyfacepro.h @@ -11,7 +11,10 @@ * standalone (snd-usb-caiaq-style interrupt streaming) instead of an * snd-usb-audio quirk. * - * babyfacepro.c holds the card lifecycle and the PCM stream. + * babyfacepro.c holds the card lifecycle, the PCM stream and the + * mixer-state persistence across re-probes; + * babyfacepro-ctl.c holds the ALSA control surface - the hardware + * mixer. * * The protocol (vendor requests + 14x32-bit frame layout) was * reverse-engineered from Windows captures and validated on hardware - @@ -90,8 +93,10 @@ /* Vendor requests (bmRequestType 0x40, value in wValue, no data phase). */ #define BF_REQ_KEEPALIVE 0x10 /* settings word / stream trigger */ +#define BF_REQ_CROSSPOINT 0x12 /* 16-bit crosspoint / master */ #define BF_REQ_REG_CLEAR 0x16 /* cold-init register clear */ #define BF_REQ_PREAMP 0x17 /* 48V/PAD state + readback */ +#define BF_REQ_GAIN 0x1a /* 8-bit gain / master companion */ #define BF_REQ_DDS 0x1b /* clock quads */ #define BF_REQ_STATUS_2 0x1c /* read 4 B */ #define BF_REQ_SESSION_START 0x1d @@ -114,6 +119,96 @@ */ #define BF_SETTINGS_CLOCK_INTERNAL 0x0001 +/* Register addresses (masters + crosspoint matrix). */ +#define BF_REG_MASTER_16 0x03e0 /* + 2*out (bReq 0x12) */ +#define BF_REG_MASTER_8 0x0004 /* + 2*out (bReq 0x1a) */ +/* The crosspoint map, one block per output pair, in the canonical order + * (AN1/2, PH3/4, AS1/2, ADAT3/4, ADAT5/6, ADAT7/8), then the FX send bus: + * + * left side = 0x0000 + 0x34 * pair + idx_l + * right side = 0x001a + 0x34 * pair + idx_r (bReq 0x12) + * + * Measured on a Pro FS through the six digital loopbacks (2026-10-26, and + * issue #31 from captures of RME's driver): the block at 0x0034 feeds + * PH3/4, 0x0068 AS1/2, 0x009c ADAT3/4, 0x00d0 ADAT5/6, 0x0104 ADAT7/8 and + * 0x0138 the FX send. AN1/2 is the block at 0x0000, the one this file + * used to call the "low map": it is not a layer beside a standard map but + * the first pair of the one map. The map used to start at 0x0034 with + * the blocks swapped, which fed every pair from AS1/2 on the wrong output. + */ +#define BF_REG_XPOINT_BASE_L 0x0000 /* + 0x34*pair + idx_l */ +#define BF_REG_XPOINT_BASE_R 0x001a /* + 0x34*pair + idx_r */ +#define BF_REG_XPOINT_STRIDE 0x0034 +/* The AN1/2 block, for the features that only reach that output. */ +#define BF_REG_LOWMAP_BASE_L BF_REG_XPOINT_BASE_L +#define BF_REG_LOWMAP_BASE_R BF_REG_XPOINT_BASE_R + +/* The "cross" register block within each output: the L-registers sit at + * odd offsets 5..23 and the R-registers at even offsets 4..22 (the stereo + * source pairs that can be cross-linked). bf_crosspoint_clear_cross() + * zeroes them because the cold-init clear does not cover them. + */ +#define BF_CROSS_L_FIRST 5 +#define BF_CROSS_L_LAST 23 +#define BF_CROSS_R_FIRST 4 +#define BF_CROSS_R_LAST 22 + +/* Calibrated master value: 0 dB = 0x2000 (+6 dB = 0x4000). See + * CALIBRATION.md. The crosspoint fader curve is DIFFERENT (0 dB = + * 0x16a0, top 0x2d41 - see below). + */ +#define BF_MASTER_0DB 0x2000 + +/* Each output has two master registers, and TotalMix writes both. The + * 8-bit one is the analog level (hardware-verified 2026-08-24: writing + * it changes the level of an analog output, the 16-bit does not), in + * 0.5 dB steps: 0xf3 = 0 dB (the scene-load default), bottom 0x73 = + * -64 dB (silence), top 0xff = +6 dB, mute 0x3B. The 16-bit one is + * linear (BF_MASTER_0DB) and is the level of the digital outputs. + */ +#define BF_MASTER_8_0DB 0xf3 +#define BF_MASTER_8_MIN 0x73 +#define BF_MASTER_MUTE 0x3b +#define BF_MASTER_UNMUTE 0xf3 +/* -20 dB master, 8-bit and 16-bit: the exact pair the hardware DIM + * button writes (cap_dim2.pcap), reused as the power-on default. + */ +#define BF_MASTER_MINUS20_8 0xcb +#define BF_MASTER_MINUS20_16 0x0333 + +/* The output master controls count the 8-bit register's 0.5 dB steps, + * over the front-panel OUT wheel's range: 0 is mute, 1 is -90 dB and + * BF_MASTER_STEP_MAX is +6 dB. Below -64 dB the 8-bit register stays + * at its bottom and only the 16-bit one goes lower. + */ +#define BF_MASTER_STEP_0DB 181 +#define BF_MASTER_STEP_MAX 193 +#define BF_MASTER_STEP_MINUS20 (BF_MASTER_STEP_0DB - 40) + +/* Crosspoint fader curve: 0 dB = 0x16a0, +6 dB = 0x2d41 (fader curve, + * DIFFERENT from the master 0x4000 top - see CALIBRATION.md). + */ +#define BF_FADER_0DB 0x16a0 +#define BF_FADER_TOP 0x2d41 + +/* The crosspoint matrix sources (14 controls per output). */ +struct bf_source { + const char *name; + u8 idx_l; + u8 idx_r; +}; + +/* Crosspoint-source order (babyfacepro-ctl.c). */ +extern const struct bf_source bf_sources[14]; + +/* The first playback source in bf_sources[]: the four analog inputs, the + * optical input and the three ADAT pairs come first, the six playback + * channels last. babyface_write_default_mixer() routes only the + * playback out of the box; a hardware input is not monitored into any + * output until a mixer raises its crosspoint. + */ +#define BF_SRC_PB1 8 + struct snd_usb_babyface { struct snd_card *card; struct usb_device *dev; @@ -160,6 +255,13 @@ struct snd_usb_babyface { */ ktime_t in_done; unsigned int out_inflight; + + /* mixer state (no gain readback exists - host-side mirror) */ + u8 flag_cnt; /* 0xc000/0x4000/0x8000/0x0000 */ + struct snd_kcontrol *master_kctl[6]; + u16 master[6][2]; /* cached masters, in BF_MASTER_STEP_* */ + bool muted[6]; + u16 xpoint[6][14][2]; /* cached crosspoints (out, src, L/R) */ }; struct bf_rate { @@ -185,3 +287,23 @@ void babyface_stream_kill(struct snd_usb_babyface *chip); void babyface_pcm_stop_both(struct snd_usb_babyface *chip, snd_pcm_state_t state); void babyface_stream_work(struct work_struct *work); extern const struct snd_pcm_hw_constraint_list bf_rates_constraint; + +/* Mixer-state persistence across interface re-probes. */ +int babyface_restore_state(struct snd_usb_babyface *chip); + +/* babyfacepro-ctl.c */ +extern const u16 bf_flag_cycle[4]; +int bf_vendor_write_cycle(struct snd_usb_babyface *chip, u8 req, u16 val, u16 idx); +int bf_crosspoint_clear_cross(struct snd_usb_babyface *chip, + unsigned int pair); +int babyface_write_default_mixer(struct snd_usb_babyface *chip); +int bf_apply_masters(struct snd_usb_babyface *chip); +int bf_xpoint_write(struct snd_usb_babyface *chip, int out, int src, + u16 l, u16 r); +int babyface_create_masters(struct snd_usb_babyface *chip); +int babyface_create_xpoints(struct snd_usb_babyface *chip); + +/* Master gain-law helpers (babyfacepro-ctl.c). */ +int bf_master_16bit(int half_db); /* dBx2 -> 16-bit master */ +u8 bf_master_reg8(unsigned int step); /* master step -> 8-bit register */ +u16 bf_master_reg16(unsigned int step); /* master step -> 16-bit register */ -- 2.56.0