From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-ed1-f42.google.com (mail-ed1-f42.google.com [209.85.208.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7419F476CE9 for ; Tue, 1 Sep 2026 09:07:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.42 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788253644; cv=none; b=Tv/pW/6cnWdfu0oPIp1qRfxDLavPAxdc9ePI1GJx8J7sDllTupAfVP8A6x6qrp3Z0Wi9R+VSh+xLXHN9rkqaeXyhFLNFO3CLUDHOXO27XjJEFVXiuLT4hZihlHxXMBoQAgX8nGzH7I7OCNVEEWlHdQBFLHGGzNU0OlLd6o6hfrk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788253644; c=relaxed/simple; bh=Ql194s9MTDFVA3Ex+3M06bmNbM1q3saFdt9gdp3NcIk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ADQWKvvsXpWyRenn271EMd5loMU1IzbIQ3CHH+o0PIR2XT9O8/kxHPlYUxAWE/Q9OGBQMG5Fl2aRd4FbJFiLdVA6giEhZcK856i1UEz8zFQ71WM0dyY5w6jz+shLslxPXuH9Gj/amXb2aQ8A8yotbbOqCYtkGEQY+xNYC5bUuoM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=a7GqSB1j; arc=none smtp.client-ip=209.85.208.42 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) header.d=gmail.com header.i=@gmail.com header.b="a7GqSB1j" Received: by mail-ed1-f42.google.com with SMTP id 4fb4d7f45d1cf-6a61910369bso4474519a12.0 for ; Tue, 01 Sep 2026 02:07:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788253630; x=1788858430; 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=LYTmDR+/RL6EPFEGlfWbvOmFeAwkSBnslfx0lB5HfQM=; b=a7GqSB1jCYa1mvyFySH6yDw4VJdKGxQYZVKAQqLAEJV1xBf/aqBCd598bAYq2Cb3tc fAB7kjbva7vBGbh4ukH+tKzBsRQjWhLsxRLeKo2i8GnNTFonfAIrk4w7q6ZeIbv6yUPq uLVciiMyBbJ7VD07MeDl7ucuio0x/tRlm3/gK0tT1vC9lLchbiZsUX6HWoKai4hEAyvB pWGi/AfOi3yXdU8xOo+A9BBKEvcFKUcoKJ8RR0gmROwbcb5LzyZG4GA6BM0DNPIwCNPC ab2QMLd+mnBiaQMx7raPs6ckrN75Il4KASKX0P8wubg1FSyjqil585Z9LQyH2ttjJweG L5tw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788253630; x=1788858430; 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=LYTmDR+/RL6EPFEGlfWbvOmFeAwkSBnslfx0lB5HfQM=; b=q8D0SWmldSEa4aqD0Pz4GgThl7MHGqBoeBOOpSVbeQIr2ulZUjicOFgCXaRNjaj0wQ L1YkEfpVv/X8eBFqEVm10sOFLaDmp5HqRNuNM4FliKFWgG7U+nf+i5kzSIIUlVuPYE6e uAbWM/mCLtkVSR7Nqq2zvW1AZW3g599+9sd0pEl6kvGUmN7W+mWbYuxtBxiFszfZT024 bN+am39cCC+2h30mQ0yXs3g+Q8L/OycyxK+qJHoNDkDFlxOYWG3cFDSaWNhihfdSW7iY /Hk0OLz3UdL0/86j1hkEc5Ge1jjpWwKDOYN13gRtbIoltN+dShTsnCvzMGamnaeaV0xU AwGw== X-Gm-Message-State: AFuF++n11GTvqv2C/TUzwJlKtZK6e0ac6wWG1EYFh+RRoFS/q+dP5rL6 j6ukYsop73cJ+jm8IbozhaNrfxx90KrWTRUSBRTVZ1/lKxL+c+mgN7/x X-Gm-Gg: AYBFou0xauLPwJva2rxbaFVow8GktGqHAfwm/p2444UZSWa8wuLRL6X+3G501qeFB0p plA1lkGL30UNw+Cv4Y1MPbFRPCdI/cBJ640t+YkuMs4Kii4/3U/xy0yp78tNpm+puxicrO4AEzb FOBaUa480SEL9blb+GLbYJNvWMuy4x/E5AZdJRME352nJC1pp4ro/d2eFOZx3oLNi0xKc9OyZyW zzTj3UXCXQKzpB35Xr4VEbeVgVcKq07dLUfUy1k99J8/cIPC46nTuTq/KS7CXZEsrgxnApmVfiy 7L02M8pVnzgyUIlxTqD5ouSB5RhO8JagyxrB7JJmzTgIaTLer/0dkmEaREu51/Kcrx16ybU749j IttXnI2B7KJikQwIDWGasuVawzhNn2cpmrKUDkY4KHPkp9BcWdJkZiRfq5FsyPTupDaWodz6dbT CIhpOK/b0pDL7YAXEA00/gh82fp9zG3ES6yIQeFKbHE8NkAFEVGMrXpFQTYKNEONsTbfPMrov/h vx4UNzwynvctV/rrCfU7YUgXyE= X-Received: by 2002:a05:6402:5352:10b0:6a6:757d:6311 with SMTP id 4fb4d7f45d1cf-6a6757d6456mr450181a12.9.1788253629763; Tue, 01 Sep 2026 02:07:09 -0700 (PDT) Received: from cachyos ([41.142.112.134]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-48442d6e905sm3124471f8f.23.2026.09.01.02.07.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 01 Sep 2026 02:07:09 -0700 (PDT) From: =?UTF-8?q?Isma=C3=AFl=20Bahloul?= To: linux-sound@vger.kernel.org Cc: linux-usb@vger.kernel.org, alsa-devel@alsa-project.org, perex@perex.cz, tiwai@suse.com, linux-kernel@vger.kernel.org, =?UTF-8?q?Isma=C3=AFl=20Bahloul?= Subject: [RFC PATCH v2 2/4] ALSA: usb: babyface-pro: add the mixer control surface Date: Tue, 1 Sep 2026 10:06:33 +0100 Message-ID: <20260901090635.9208-3-i.bahloul01@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260901090635.9208-1-i.bahloul01@gmail.com> References: <20260901090635.9208-1-i.bahloul01@gmail.com> Precedence: bulk X-Mailing-List: linux-usb@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the ALSA mixer: the six output masters + mutes, the 6x14 crosspoint matrix, the four preamp gains (mic 3.25 dB/step, instrument 0.5 dB/step), phantom power and PAD, pitch/varispeed, loopback, AN1>2, stereo link, MS processor, width, FX send and DIM. The front panel and DSP EQ remain stubbed here; they land in the next patches of the series. Signed-off-by: Ismaïl Bahloul Assisted-by: DeepSeek V4 Flash --- sound/usb/babyfacepro/babyfacepro-ctl.c | 1290 ++++++++++++++++++++++- 1 file changed, 1271 insertions(+), 19 deletions(-) diff --git a/sound/usb/babyfacepro/babyfacepro-ctl.c b/sound/usb/babyfacepro/babyfacepro-ctl.c index ec2640e0b..337aab92f 100644 --- a/sound/usb/babyfacepro/babyfacepro-ctl.c +++ b/sound/usb/babyfacepro/babyfacepro-ctl.c @@ -2,13 +2,13 @@ /* * RME Babyface Pro FS — proprietary-mode USB audio driver * - * ALSA control surface (mixer, front panel, DSP EQ). This is the - * initial slice of the series: the card's PCM stream + lifecycle come - * from babyfacepro.c, while the control surface is stubbed out here so - * the module links. The real mixer, front-panel and DSP EQ land in the - * follow-up patches (mixer, panel, eq) — each replaces its stubs. + * ALSA control surface: mixer (masters, preamp, crosspoints, flags, + * gains), front-panel poll + controls, and the hardware DSP EQ + * (3-band + low cut). * - * See babyfacepro.h for the shared device state and register map. + * 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 @@ -53,54 +53,1302 @@ const struct bf_source bf_sources[14] = { */ const u8 bf_xpoint_block[6] = { 1, 0, 2, 3, 4, 5 }; -/* ── control-surface stubs ────────────────────────────────────── - * Filled in by the mixer / panel / eq patches. The core driver - * (babyfacepro.c) calls these from probe() and the stream/state - * paths, so they must exist for the module to link. Until then the - * card exposes the PCM stream only. +/* Master-register output order — the master map lists AN1/2 first + * (0x03E0) and the Phones master SECOND (0x03E2, HARDWARE-VERIFIED + * 2026-08-24); the crosspoint blocks are in the opposite order + * (Phones = block 0x34 first, hence bf_xpoint_block above). 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 — the + * previous {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 }; +/* The 16-bit master value → the 8-bit companion code (0.5 dB/step). + * Integer-only: half_db = 12·log2(v/0x2000) via ilog2 + an 8-bit + * fractional-octave table (12·log2(1 + n/256), ~0.05 dB resolution — + * fine enough for the ±0.5 dB panel wheel to track the round-trip). + */ +static const u8 bf_lg2_frac[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* 16-bit master → dB×2 (12 half-dB per octave; 0x2000 = 0 dB). + * Shared by the 8-bit companion and the front-panel OUT wheel. + */ +int bf_master_half_db(u16 vol16) +{ + unsigned int k, frac; + + vol16 = clamp(vol16, 1, 0x4000); + k = ilog2(vol16); + frac = ((vol16 - (1u << k)) << 8) >> k; + return 12 * (int)k - 156 + bf_lg2_frac[frac]; +} + +/* dB×2 → 16-bit master (0x2000·2^(half_db/12), rounded). The + * inverse of bf_master_half_db — 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); +} + +u8 bf_master_8bit(u16 vol16) +{ + if (vol16 == 0) + return BF_MASTER_MUTE; + return (u8)clamp(0xf3 + bf_master_half_db(vol16), BF_MASTER_8_MIN, 0xff); +} + +/* 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 it applies TotalMix's factory default: + * every source routed to every output at unity, masters at 0 dB and + * unmuted — the user/TuxMix can restore its own scene on top. + */ int babyface_write_default_mixer(struct snd_usb_babyface *chip) { - return 0; + int out, src, ret; + u16 flag; + + /* Output masters: 0 dB (0x2000) + the unmute companion (0xf3). */ + for (out = 0; out < 6; out++) { + ret = bf_vendor_write(chip, BF_REQ_GAIN, BF_MASTER_UNMUTE, + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_GAIN, BF_MASTER_UNMUTE, + 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_0DB, + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, BF_MASTER_0DB, + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + return ret; + chip->master[out][0] = BF_MASTER_0DB; + chip->master[out][1] = BF_MASTER_0DB; + chip->muted[out] = false; + } + + /* Every source into every output pair, L and R, at 0 dB (the + * standard map; the low map is only a shadow). The addresses use + * the source's idx_l/idx_r on the canonical 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 left at 0; the restore at stream + * start re-writes the same addresses from the cache. + */ + for (out = 0; out < 6; out++) { + unsigned int blk = bf_xpoint_block[out]; + + for (src = 0; src < 14; src++) { + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, BF_FADER_0DB, + (BF_REG_CROSS_BASE_L + + BF_REG_CROSS_STRIDE * blk + + bf_sources[src].idx_l) | flag); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, BF_FADER_0DB, + (BF_REG_CROSS_BASE_R + + BF_REG_CROSS_STRIDE * blk + + bf_sources[src].idx_r) | flag); + if (ret < 0) + return ret; + } + ret = bf_crosspoint_clear_cross(chip, blk); + 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++) { + chip->xpoint[out][src][0] = BF_FADER_0DB; + chip->xpoint[out][src][1] = BF_FADER_0DB; + } + + /* Host settings word: clock Internal (0x0001). */ + return bf_vendor_write(chip, BF_REQ_KEEPALIVE, 0x0001, + BF_REG_KEEPALIVE_SETTINGS); } +/* The device resets its output masters to mute when a stream session + * starts (hardware-verified 2026-08-24: after a stream start the + * output stays silent until a master write lands — only a write + * un-mutes the 8-bit register). Re-apply the six output masters + + * mutes from the cache; also used by the PM restore path. + */ 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 = chip->muted[out] ? BF_MASTER_MUTE : bf_master_8bit(l); + u8 r8 = chip->muted[out] ? BF_MASTER_MUTE : bf_master_8bit(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, l, + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + return ret; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + return ret; + } + return 0; +} + +/* ── mixer controls ──────────────────────── */ + +/* dB TLV for the output masters: 0x2000 = 0 dB, 0x4000 = +6 dB + * (CALIBRATION.md) with the hardware 20*log10(v/0x2000) law — the raw + * 16-bit value IS the linear amplitude. WirePlumber needs this to map + * the volume 1:1 to the hardware control instead of applying a software + * volume on top (which left the output ~30 dB down). + */ +static const DECLARE_TLV_DB_RANGE(bf_master_tlv, + 0, 0x2000, TLV_DB_LINEAR_ITEM(-6500, 0), + 0x2000, 0x4000, TLV_DB_LINEAR_ITEM(0, 600) +); + +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 = 0x4000; /* +6 dB = 2 × 0dB(0x2000) */ + uinfo->value.integer.step = 1; return 0; } -int bf_loopback_write_map(struct snd_usb_babyface *chip, int out, bool on) +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]; + u16 l = ucontrol->value.integer.value[0]; + u16 r = ucontrol->value.integer.value[1]; + u16 flag; + int ret = 0; + + /* The control is declared 0..0x4000 (+6 dB); reject anything outside + * so the 16-bit companion register and the cache stay in spec (the + * ALSA core only enforces this with CONFIG_SND_CTL_INPUT_VALIDATION). + */ + if (l > 0x4000 || r > 0x4000) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (l == chip->master[out][0] && r == chip->master[out][1]) + goto out; + + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + + /* The 8-bit register is the real volume; the 16-bit is its + * companion (kept in sync like TotalMix). + */ + ret = bf_vendor_write(chip, BF_REQ_GAIN, bf_master_8bit(l), + BF_REG_MASTER_8 + 2 * out); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_GAIN, bf_master_8bit(r), + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, + (BF_REG_MASTER_16 + 2 * out + 1) | flag); + if (ret < 0) + goto out; + + chip->master[out][0] = l; + chip->master[out][1] = r; + chip->muted[out] = false; + /* A Phones change while DIM is engaged re-bases the restore point. */ + if (chip->dim && out == 1) { + chip->dim_saved[0] = l; + chip->dim_saved[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_8bit(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_8bit(chip->master[out][1]), + BF_REG_MASTER_8 + 2 * out + 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->master[out][0], + (BF_REG_MASTER_16 + 2 * out) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + 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; +} + int bf_preamp_state_write(struct snd_usb_babyface *chip) { + int ret; + + ret = bf_vendor_write(chip, BF_REQ_PREAMP, chip->preamp, BF_REG_PREAMP); + if (ret < 0) + return ret; + return bf_vendor_write(chip, BF_REQ_PREAMP_COMMIT, 0x0000, 0x0000); +} + +/* ── crosspoint matrix (6 outputs × 14 sources) ────────────── */ + +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; } -int babyface_create_controls(struct snd_usb_babyface *chip) +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; + unsigned int blk = bf_xpoint_block[out]; + const struct bf_source *s = &bf_sources[src]; + u16 l = ucontrol->value.integer.value[0]; + u16 r = ucontrol->value.integer.value[1]; + u16 flag; + int ret = 0; + + if (l > BF_FADER_TOP || r > BF_FADER_TOP) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (l == chip->xpoint[out][src][0] && r == chip->xpoint[out][src][1]) + goto out; + + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + + /* L register = 0x0034 + 0x34·blk + idx, R = 0x004E + 0x34·blk + idx + * (mono sources use the same idx on both sides). + */ + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, + (BF_REG_CROSS_BASE_L + BF_REG_CROSS_STRIDE * blk + + s->idx_l) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, + (BF_REG_CROSS_BASE_R + BF_REG_CROSS_STRIDE * blk + + s->idx_r) | flag); + 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, + .info = bf_xpoint_info, + .get = bf_xpoint_get, + .put = bf_xpoint_put, + .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; +} + +/* ── flags / special controls (pitch, loopback, link, width, FX) ── */ + +static int bf_switch_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int bf_pitch_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -50; /* -5.0 % */ + uinfo->value.integer.max = 50; /* +5.0 % */ + uinfo->value.integer.step = 1; /* 0.1 % */ + return 0; +} + +static int bf_pitch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->pitch; + return 0; +} + +static int bf_pitch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int p = ucontrol->value.integer.value[0]; + u32 dds24, dds16; + u16 frac, b1, b2; + int ret = 0; + + if (p < -50 || p > 50) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (p == chip->pitch) + goto out; + + /* The 0x1B DDS quad (16.8 fixed point, banked). p is 0.1 % steps: + * DDS_24 = round(50000·256/(1+p/1000)) = round(12800000000/(1000+p)). + */ + dds24 = div_u64(12800000000ULL + (u32)(1000 + p) / 2, 1000 + p); + dds16 = dds24 >> 8; + frac = dds24 & 0xff; + b1 = (u16)div_u64(dds16 * 72562ull + 50000, 100000); + b2 = (u16)((dds16 * 2 + 1) / 3); + + ret = bf_vendor_write(chip, BF_REQ_DDS, (u16)dds16, (frac << 8) | 0); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_DDS, b1, 0x0001); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_DDS, b2, 0x0002); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_DDS, 0x7cff, 0x0003); + if (ret < 0) + goto out; + /* Every quad must be followed by the clock keepalive. */ + ret = bf_vendor_write(chip, BF_REQ_KEEPALIVE, 0x0001, + BF_REG_KEEPALIVE_SETTINGS); + if (ret < 0) + goto out; + + chip->pitch = p; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_loopback_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; + + ucontrol->value.integer.value[0] = chip->loopback[out]; + ucontrol->value.integer.value[1] = chip->loopback[out]; + return 0; +} + +/* Write the full 30-channel loopback map: pair (2·out, 2·out+1) at + * `on` (0x0001/0x0000), all other channels cleared — exactly what + * TotalMix sends on every loopback toggle (cap_loopback2.pcap). The + * full-map write is also the reliable OFF (the old per-pair write + * sometimes failed to disengage on the hardware). + */ +int bf_loopback_write_map(struct snd_usb_babyface *chip, int out, + bool on) +{ + int ch, ret; + + for (ch = 0; ch < BF_LOOPBACK_CHANNELS; ch++) { + u16 val = (on && (ch == out * 2 || ch == out * 2 + 1)) + ? 0x0001 : 0x0000; + + ret = bf_vendor_write(chip, BF_REQ_LOOPBACK, val, ch); + if (ret < 0) + return ret; + } + return 0; +} + +static int bf_loopback_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; + bool on = ucontrol->value.integer.value[0]; + int ret = 0; + + mutex_lock(&chip->mutex); + if (on == chip->loopback[out]) + goto out; + ret = bf_loopback_write_map(chip, out, on); + if (ret < 0) + goto out; + /* Single-active model (TotalMix writes one pair at 0x0001, the + * rest 0x0000): toggling one output clears the others. + */ + memset(chip->loopback, 0, sizeof(chip->loopback)); + chip->loopback[out] = on; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_an12_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->an12; + return 0; +} + +static int bf_an12_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + bool an12 = ucontrol->value.integer.value[0]; + u16 v; + int ret = 0; + + mutex_lock(&chip->mutex); + if (an12 == chip->an12) + goto out; + v = (chip->linked ? 0x0400 : 0x0000) | (an12 ? 0x1000 : 0x0000); + ret = bf_vendor_write(chip, BF_REQ_PREAMP, v, 0x1000); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_PREAMP_COMMIT, 0x0000, 0x0000); + if (ret < 0) + goto out; + chip->an12 = an12; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_link_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->linked; + return 0; +} + +static int bf_link_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + bool linked = ucontrol->value.integer.value[0]; + u16 v; + int ret = 0; + + mutex_lock(&chip->mutex); + if (linked == chip->linked) + goto out; + v = (linked ? 0x0400 : 0x0000) | (chip->an12 ? 0x1000 : 0x0000); + ret = bf_vendor_write(chip, BF_REQ_PREAMP, v, 0x1000); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_PREAMP_COMMIT, 0x0000, 0x0000); + if (ret < 0) + goto out; + chip->linked = linked; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_ms_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->ms_proc; + return 0; +} + +/* MS-proc: engage per the cap_ms2.pcap ON pattern — write 0x0000 to + * ALL FOUR AN2 (side) crosspoints: standard map 0x0035/0x004F (L/R) + * + low map 0x0001/0x001B (L/R) — the side path is muted (ear- + * verified 2026-08-26 with the mic on AN2: MS ON = silence); release + * restores the cached fader values (host-side, like TotalMix). + * (The 0x1000/0x0004 writes are the DISENGAGE restore values seen in + * cap_ms2 — the driver had them inverted on the engage path.) + */ +static int bf_ms_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + bool on = ucontrol->value.integer.value[0]; + int ret = 0; + + mutex_lock(&chip->mutex); + if (on == chip->ms_proc) + goto out; + if (on) { + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x0035); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x004f); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x0001); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0000, 0x001b); + if (ret < 0) + goto out; + } else { + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->xpoint[1][1][0], 0x0001); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->xpoint[1][1][0], 0x0035); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->xpoint[1][1][1], 0x001b); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->xpoint[1][1][1], 0x004f); + if (ret < 0) + goto out; + } + chip->ms_proc = on; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +/* DIM — cap_dim2.pcap: an absolute -20 dB on the Phones master + * (out 1: 8-bit 0xCB / 16-bit 0x0333) regardless of the current level, + * plus the 0x17 wVal=0x2000 wIdx=0x2000 flag; release restores the + * pre-DIM master host-side. The master cache keeps the real volume. + */ +static int bf_dim_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->dim; + return 0; +} + +static int bf_dim_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + bool on = ucontrol->value.integer.value[0]; + u16 flag; + int ret = 0; + + mutex_lock(&chip->mutex); + if (on == chip->dim) + goto out; + if (on) { + chip->dim_saved[0] = chip->master[1][0]; + chip->dim_saved[1] = chip->master[1][1]; + ret = bf_vendor_write(chip, BF_REQ_GAIN, 0xcb, + BF_REG_MASTER_8 + 2 * 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_GAIN, 0xcb, + BF_REG_MASTER_8 + 2 * 1 + 1); + if (ret < 0) + goto out; + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0333, + (BF_REG_MASTER_16 + 2 * 1) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, 0x0333, + (BF_REG_MASTER_16 + 2 * 1 + 1) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_PREAMP, 0x2000, 0x2000); + if (ret < 0) + goto out; + } else { + ret = bf_vendor_write(chip, BF_REQ_GAIN, + bf_master_8bit(chip->dim_saved[0]), + BF_REG_MASTER_8 + 2 * 1); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_GAIN, + bf_master_8bit(chip->dim_saved[1]), + BF_REG_MASTER_8 + 2 * 1 + 1); + if (ret < 0) + goto out; + flag = bf_flag_cycle[chip->flag_cnt]; + chip->flag_cnt = (chip->flag_cnt + 1) & 3; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->dim_saved[0], + (BF_REG_MASTER_16 + 2 * 1) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, + chip->dim_saved[1], + (BF_REG_MASTER_16 + 2 * 1 + 1) | flag); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_PREAMP, 0x0000, 0x2000); + if (ret < 0) + goto out; + } + chip->dim = on; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_width_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = -100; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; return 0; } +static int bf_width_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->width; + return 0; +} + +static int bf_width_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int w = ucontrol->value.integer.value[0]; + u16 l, r; + int ret = 0; + + if (w < -100 || w > 100) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (w == chip->width) + goto out; + /* Width spread: L = 0x1000·(1+w), R = 0x1000·(1−w), L+R = 0x2000. + * TotalMix writes the strip's src pair on BOTH maps (cap_width3-7, + * PROTOCOL.md “Width strip mapping”): the low map (0x0000+src L / + * 0x001A+src R) and the std block-0 map (0x0034+src L / + * 0x004E+src R) — the stereo pair spreads L/R in opposition, the + * mirror src (AN2) gets the swapped values. + */ + l = (u16)(((0x2000 * (100 + w) / 2) + 50) / 100); + r = 0x2000 - l; + /* Low map: AN1 L=0x0000, R=0x001A; AN2 L=0x0001, R=0x001B. */ + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, 0x0000); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, 0x001a); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, 0x0001); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, 0x001b); + if (ret < 0) + goto out; + /* Std block-0 map (item 0b, the missing half): AN1 L=0x0034, + * R=0x004E; AN2 L=0x0035, R=0x004F. (The playback strips PB2-6 + * target block n−2 — 0x00AE family — reserved for the per-strip + * controls.) + */ + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, 0x0034); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, 0x004e); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, r, 0x0035); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, l, 0x004f); + if (ret < 0) + goto out; + chip->width = w; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +static int bf_fx_send_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x1000; + uinfo->value.integer.step = 1; + return 0; +} + +static int bf_fx_send_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = chip->fx_send; + return 0; +} + +static int bf_fx_send_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + u16 v = ucontrol->value.integer.value[0]; + int ret = 0; + + if (v > 0x1000) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (v == chip->fx_send) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, v, 0x0138); + if (ret < 0) + goto out; + ret = bf_vendor_write(chip, BF_REQ_CROSSPOINT, v, 0x0153); + if (ret < 0) + goto out; + chip->fx_send = v; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + int babyface_create_flags(struct snd_usb_babyface *chip) { + struct snd_kcontrol *kctl; + int i, err; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Varispeed Pitch", + .info = bf_pitch_info, + .get = bf_pitch_get, + .put = bf_pitch_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + for (i = 0; i < 6; i++) { + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Loopback Switch", + .index = i, + .info = bf_mute_info, + .get = bf_loopback_get, + .put = bf_loopback_put, + .private_value = i, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "AN 1>2 Switch", + .info = bf_switch_info, + .get = bf_an12_get, + .put = bf_an12_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "AN1/2 Link Switch", + .info = bf_switch_info, + .get = bf_link_get, + .put = bf_link_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MS Processor Switch", + .info = bf_switch_info, + .get = bf_ms_get, + .put = bf_ms_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Dim Switch", + .info = bf_switch_info, + .get = bf_dim_get, + .put = bf_dim_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Width", + .info = bf_width_info, + .get = bf_width_get, + .put = bf_width_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "FX Send Volume", + .info = bf_fx_send_info, + .get = bf_fx_send_get, + .put = bf_fx_send_put, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + + return 0; +} + +static int bf_bool_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; return 0; } -int babyface_create_panel(struct snd_usb_babyface *chip) +static int bf_phantom_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) { + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + + ucontrol->value.integer.value[0] = + !!(chip->preamp & kctl->private_value); return 0; } -int babyface_create_eq(struct snd_usb_babyface *chip) +static int bf_phantom_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + u16 bit = kctl->private_value; + bool on = ucontrol->value.integer.value[0]; + bool cur = !!(chip->preamp & bit); + int ret = 0; + + mutex_lock(&chip->mutex); + if (on == cur) + goto out; + chip->preamp = on ? (chip->preamp | bit) : (chip->preamp & ~bit); + ret = bf_preamp_state_write(chip); + if (ret < 0) + goto out; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +/* Gain scales: the mic preamps (AN1/2) span 0-65 dB over raw 0-20 + * (3.25 dB/step); the Hi-Z instrument inputs (AN3/4) are digitally + * limited to 9 dB over raw 0-18 (0.5 dB/step) — manual §10, raw + * ranges verified from cap_gain12/cap_gain34.pcap. Shared by the GUI + * controls and the front-panel gain wheel. + */ +int bf_gain_max_db(int mic) +{ + return mic < 2 ? BF_GAIN_MAX_DB : 9; +} + +int bf_gain_db(int mic, u8 raw) +{ + return mic < 2 ? (raw * 13) / 4 : raw / 2; +} + +u8 bf_gain_raw(int mic, int db) +{ + return mic < 2 ? (db * 8 + 13) / 26 : db * 2; +} + +static int bf_gain_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = bf_gain_max_db(kctl->private_value); + uinfo->value.integer.step = 1; + return 0; +} + +static int bf_gain_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int mic = kctl->private_value; + + /* chip->gain[] tracks the dB (the raw is derived at write time — + * the 3.25 dB/step mic grid would otherwise make a ±1 dB wheel + * stick on a raw boundary). + */ + ucontrol->value.integer.value[0] = chip->gain[mic]; + return 0; +} + +static int bf_gain_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int mic = kctl->private_value; + int db = ucontrol->value.integer.value[0]; + u8 raw, counter; + int ret = 0; + + if (db < 0 || db > bf_gain_max_db(mic)) + return -EINVAL; + + mutex_lock(&chip->mutex); + if (db == chip->gain[mic]) + goto out; + raw = bf_gain_raw(mic, db); + counter = (chip->gain_cycle % 3 == 0) ? 0x20 : + (chip->gain_cycle % 3 == 1) ? 0x00 : 0x40; + chip->gain_cycle = (chip->gain_cycle + 1) % 3; + + ret = bf_vendor_write(chip, BF_REQ_GAIN, + (u16)((raw & 0x1f) | counter), + BF_REG_GAIN + mic); + if (ret < 0) + goto out; + chip->gain[mic] = db; + ret = 1; +out: + mutex_unlock(&chip->mutex); + return ret; +} + +int babyface_create_controls(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; + + 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]); + } + + for (i = 0; i < 2; i++) { + u16 bit = i == 0 ? BF_PREAMP_48V_MIC1 : BF_PREAMP_48V_MIC2; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Phantom Power Mic 1", + .index = i, + .info = bf_bool_info, + .get = bf_phantom_get, + .put = bf_phantom_put, + .private_value = bit, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + + for (i = 0; i < 2; i++) { + u16 bit = i == 0 ? BF_PREAMP_PAD_MIC1 : BF_PREAMP_PAD_MIC2; + + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Pad Mic 1", + .index = i, + .info = bf_bool_info, + .get = bf_phantom_get, + .put = bf_phantom_put, + .private_value = bit, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + + for (i = 0; i < 4; i++) { + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic 1 Capture Volume", + .index = i, + .info = bf_gain_info, + .get = bf_gain_get, + .put = bf_gain_put, + .private_value = i, + }, chip); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + return 0; +} + + +/* ── front-panel stubs ──────────────────────────────────────── + * Replaced by the panel patch. The core driver calls these from + * probe()/disconnect()/suspend()/resume() and the delayed-work init. + */ +int babyface_create_panel(struct snd_usb_babyface *chip) { return 0; } @@ -117,11 +1365,15 @@ void babyface_panel_work(struct work_struct *work) { } -void bf_eq_reupload(struct snd_usb_babyface *chip) +/* ── DSP-EQ stubs ───────────────────────────────────────────── + * Replaced by the eq patch. The core driver calls these from + * probe() and hw_params() (fs-dependent re-upload). + */ +int babyface_create_eq(struct snd_usb_babyface *chip) { + return 0; } -u8 bf_gain_raw(int mic, int db) +void bf_eq_reupload(struct snd_usb_babyface *chip) { - return 0; } -- 2.55.0