From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f44.google.com (mail-wr1-f44.google.com [209.85.221.44]) (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 7E73833D4E9 for ; Tue, 1 Sep 2026 09:07:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.44 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788253645; cv=none; b=Cr0dnjJIqcEvwLuu5koHP5W9k4e/QDxPRoX1AXKKmltmVn6jcnGPmF32XfQD2jxLUKjNdhkK5g4QthZJLB7oU6Qscbg1BwTvVxjmW1H37/Q6/+RsmZEmEMnBSp98SHlqquZ5FiKbzHy7JgDSyuU2zFfnJ9k6SKNUMPHEjXneH2U= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788253645; c=relaxed/simple; bh=DY3aGA71M5brUHY1t9eFomVD0psr5xaRzmYYQvL/GEE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=qjXAw1BQz3RO3rjtSAvaHDJzW2CBWEOENxjq/DeO5LxqeZjrn2RCXpYjQmoXKSUJfJizi8vvhWxkJ7KJBQEt0sgnp0OpUa44DvL4VoUlQqCO17CwFq0vlQGjQZuFEZV0Xvtk0QltPG9YvOB2LFVX2fynKpFJwsU/0e3jvQWWKbQ= 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=dergNup6; arc=none smtp.client-ip=209.85.221.44 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="dergNup6" Received: by mail-wr1-f44.google.com with SMTP id ffacd0b85a97d-48436251906so2511477f8f.0 for ; Tue, 01 Sep 2026 02:07:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1788253633; x=1788858433; 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=KkIIOAYjYAqLcsdb7lFesQWgIS689+8MWdFQ/W4oAJo=; b=dergNup6XW1IumQ/HPI2F3S9xiLY8uzE1kVSmv2nI6B65vPL1VsEt6Mapj7aSvPZZs WKWTsmlnYPnIPq/I7Biuh1yGCEYJUJsCZ8M8PXhctgMQjOpOc6O9xGud3oakKRoW33JU zu+675xeyL49ju9lIXKB1vI0RRP8fLhE8FbpO1VnP9IHSZcKB2dm8QnejNrkAQWleD/k QvuzguO1OBhFm7xJzIxIagZM/6j3ZTzHKJysWYMKbD4Ox6Y9io5fo1njNLIWsvWUfCUK qj1cLjGIja2gQAodVVV6DEss2RmiiocmteUU3UxGpeqJi60C31K9PfEkFL62fl4H2reP HAvw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788253633; x=1788858433; 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=KkIIOAYjYAqLcsdb7lFesQWgIS689+8MWdFQ/W4oAJo=; b=iU9n2YsmxWPvdfz9225q/WrrMbUFvhx7ZHg/vCBvyxrS1N5iVtwb0eAR+1kyn0xn5s OeOqcLQsKZBaK7gWREq/X2h0Qeg/UGB0++AoPlKUGl9zM5o2VIBMstWeoRShhcjGX7f7 B9c2N2IkXVdoTyr2mBazjxyR0YmMFZ4SalYme+xCbjcU58hC56OBPJFZZT8uz00G3Pua 0AxuTRhBMWG/9LRjPE+v4/cY6UMuUMpZy2o9lo/Mioxpthg8BtmfuUUyQ1l4tR86MeL5 FJJUyVjwgGA8kx2sVS1hMEr3UVvO0DDlbFtI+kgMmivVpLh054yZzlOFRAkNH/DPOg7r DmXg== X-Gm-Message-State: AFuF++ltITD7eLfRMPkl8+j5gNSaFC4xvpOWR7bGGk9YJXcwVZivqQDs r7LbGp42N2UDafX5n7Qpa2NIaJ97V9/9JOp8IacEw972sf8uwbEJb+kK X-Gm-Gg: AYBFou0Kh7eeIzCywLW5ifxKvGtoIwEZEpDlPJ4PqT7nw96Pp0w8ZtQNcH0UiiceFHv +FZLgnFVHvWw91SpSG0IEzNBhNb5pMM72gJHWBy7uUhrW7ABMaOIyJIJ23peN1P70Qk6X/wHHXA B59eUFGoujA3fhlaImvwWaSAPS8S8t5Pt0ttMyzjhCszzzGufGEF0iyV50wkNQU4Hv5lV2VP+fU ZrLcCIE6BxVJRM/WygdQhHJoKILou17pPqBHoNekxMWe5ScuQifnPnKi7orKJLHzHjH91zEBjnN qYaS/XJxHO5R8ohb7p7q7OdRn5X6GB5cYLG5A0X6v4+Az1/1cvpZNRT2kK5gDHHQo8R07KMjgTF +qOohAkwWiy0Um23BRjP2R6RUrpFZ6d2Smr9MAjLpjaf3i7oQtQnf8ZKpcwQvAfiU4BQXx2i5uF Wm0DRPOstzYKZbaXspVHB25IerXJs6vNOqxCUPcAenU2hh4up3LzCVplWIxBtk/jMkIUY+UilEm BEIqyJvEW6/ED1Z X-Received: by 2002:a05:6000:bca:b0:47f:eac2:6f55 with SMTP id ffacd0b85a97d-4844101af6amr9961050f8f.19.1788253632527; Tue, 01 Sep 2026 02:07:12 -0700 (PDT) Received: from cachyos ([41.142.112.134]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-48442d6e905sm3124471f8f.23.2026.09.01.02.07.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 01 Sep 2026 02:07:11 -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 4/4] ALSA: usb: babyface-pro: add the hardware DSP EQ Date: Tue, 1 Sep 2026 10:06:35 +0100 Message-ID: <20260901090635.9208-5-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 Program the four EQ strips (3-band bell/shelf + low cut) via the fixed-point Q27 coefficient synthesis (CORDIC sin/cos, exp2) and bulk uploads on EP 0x0A, exposed as the standard ALSA control set. The coefficients depend on the stream rate, so they are re-uploaded on hw_params. Signed-off-by: Ismaïl Bahloul Assisted-by: DeepSeek V4 Flash --- sound/usb/babyfacepro/babyfacepro-ctl.c | 570 +++++++++++++++++++++++- 1 file changed, 565 insertions(+), 5 deletions(-) diff --git a/sound/usb/babyfacepro/babyfacepro-ctl.c b/sound/usb/babyfacepro/babyfacepro-ctl.c index 4904fe486..c28175199 100644 --- a/sound/usb/babyfacepro/babyfacepro-ctl.c +++ b/sound/usb/babyfacepro/babyfacepro-ctl.c @@ -1343,7 +1343,6 @@ int babyface_create_controls(struct snd_usb_babyface *chip) return 0; } - /* Control indices in chip->panel_kctl[] (for snd_ctl_notify). */ enum { BF_PANEL_KCTL_BUTTON, @@ -2278,16 +2277,577 @@ int babyface_create_panel(struct snd_usb_babyface *chip) return 0; } +#define BF_EQ_Q27 (1 << 27) +#define BF_EQ_LC_OFF 0x04000000 +#define BF_EQ_BLOCK_LEN 64 + +/* atan(2^-i) x 2^27 (CORDIC). */ +static const s64 bf_atan_tab[28] = { + 0x6487ED5, 0x3B58CE1, 0x1F5B760, 0xFEADD5, + 0x7FD56F, 0x3FFAAB, 0x1FFF55, 0xFFFEB, + 0x7FFFD, 0x40000, 0x20000, 0x10000, + 0x8000, 0x4000, 0x2000, 0x1000, + 0x800, 0x400, 0x200, 0x100, + 0x80, 0x40, 0x20, 0x10, + 0x8, 0x4, 0x2, 0x1, +}; -/* ── DSP-EQ stubs ───────────────────────────────────────────── - * Replaced by the eq patch. The core driver calls these from - * probe() and hw_params() (fs-dependent re-upload). +/* ---- fixed-point helpers (Q27 in/out, s64 intermediates) ---- */ + +/* sin/cos of an angle in [0, pi/2] (Q27). Simultaneous CORDIC, 28 + * iterations (~1e-8 residual). eq_selftest.c verifies the whole + * pipeline against the double-precision reference. */ -int babyface_create_eq(struct snd_usb_babyface *chip) +static void bf_sincos(s64 ang, s64 *sn, s64 *cs) +{ + s64 x = 0x4DBA76D; /* 1/1.64676 x 2^27 (CORDIC gain) */ + s64 y = 0; + s64 z = ang; + int i; + + for (i = 0; i < 28; i++) { + s64 d = z >= 0 ? 1 : -1; + s64 nx = x - d * (y >> i); + s64 ny = y + d * (x >> i); + + x = nx; + y = ny; + z -= d * bf_atan_tab[i]; + } + *cs = x; + *sn = y; +} + +/* 2^u for u in Q27, u in [-2, 2] (gain-amplitude range). */ +static s64 bf_exp2(s64 u) { + s64 n = u >> 27; + s64 r = u - (n << 27); + s64 rl = (r * 0x58B90C0 + (1 << 26)) >> 27; /* r.ln2 */ + s64 e = BF_EQ_Q27; + s64 term = BF_EQ_Q27; + int k; + + for (k = 1; k <= 10; k++) { + term = div_s64((term * rl + (1 << 26)) >> 27, k); + e += term; + } + return n >= 0 ? e << n : e >> -n; +} + +/* The 5 stored words (c0..c3 + shared c4) for one band. + * type: 1 bell, 2 low shelf, 3 high shelf. freq_hz, fs in Hz; + * q100 = Q x 100; gain_x10 = dB x 10. fs is the stream rate. + */ +void bf_eq_band_words(s32 *w, int type, s32 freq_hz, s32 q100, + s32 gain_x10, s32 fs) +{ + s64 f = freq_hz; + s64 w0, c, s, alpha, A, sq; + s64 b0, b1, b2, a0, a1, a2; + s64 pi = 0x1921FB54; /* pi, Q27 */ + s64 hpi = 0xC90FDAA; /* pi/2, Q27 */ + s64 t; + int both = 0, cflip = 0; + + if (gain_x10 == 0 || q100 <= 0) { + /* Inactive band: identity words (also guards the alpha + * division below against the default Q=0 the controls start + * with — a user setting gain before Q used to hit a kernel + * divide-by-zero oops). + */ + w[0] = 0; + w[1] = 0; + w[2] = 0; + w[3] = 0; + return; + } + + /* w0 = 2.pi.f/fs (Q27), reduced to [0, pi/2]. */ + w0 = div_s64(f * BF_EQ_Q27, fs); + w0 = (w0 * 0x3243F6A9) >> 27; /* x 2.pi */ + t = w0; + if (t > pi) { + t -= pi; + both = 1; + } + if (t > hpi) { + t = pi - t; + cflip = 1; + } + bf_sincos(t, &s, &c); + if (both) { + s = -s; + c = -c; + } + if (cflip) + c = -c; + + alpha = div64_s64(s * 100 + q100, 2 * (s64)q100); /* sin(w0)/(2Q) */ + /* A = 10^(g/40), sqrt(A): g = gain_x10/10 dB */ + A = bf_exp2((s64)gain_x10 * 0x11021E); + sq = bf_exp2((s64)gain_x10 * 0x8810F); + + if (type == 1) { + s64 ta = (alpha * A + (1 << 26)) >> 27; + + b0 = BF_EQ_Q27 + ta; + b1 = -2 * c; + b2 = BF_EQ_Q27 - ta; + a0 = BF_EQ_Q27 + div64_s64(alpha * BF_EQ_Q27 + A / 2, A); + a1 = -2 * c; + a2 = BF_EQ_Q27 - div64_s64(alpha * BF_EQ_Q27 + A / 2, A); + } else { + s64 ap1 = A + BF_EQ_Q27; + s64 am1 = A - BF_EQ_Q27; + s64 cp0 = (am1 * c + (1 << 26)) >> 27; /* (A-1).c */ + s64 cp1 = (ap1 * c + (1 << 26)) >> 27; /* (A+1).c */ + s64 ab = (2 * sq * alpha + (1 << 26)) >> 27; + + if (type == 2) { /* low shelf */ + b0 = (A * (ap1 - cp0 + ab) + (1 << 26)) >> 27; + b1 = (2 * A * (am1 - cp1) + (1 << 26)) >> 27; + b2 = (A * (ap1 - cp0 - ab) + (1 << 26)) >> 27; + a0 = ap1 + cp0 + ab; + a1 = -2 * (am1 + cp1); + a2 = ap1 + cp0 - ab; + } else { /* high shelf */ + b0 = (A * (ap1 + cp0 + ab) + (1 << 26)) >> 27; + b1 = (-2 * A * (am1 + cp1) + (1 << 26)) >> 27; + b2 = (A * (ap1 + cp0 - ab) + (1 << 26)) >> 27; + a0 = ap1 - cp0 + ab; + a1 = -2 * (am1 - cp1); + a2 = ap1 - cp0 - ab; + } + } + + w[0] = (s32)div64_s64(a1 * BF_EQ_Q27 + a0 / 2, a0); + w[1] = (s32)div64_s64(a2 * BF_EQ_Q27 + a0 / 2, a0); + w[2] = (s32)div64_s64(b1 * BF_EQ_Q27 + b0 / 2, b0); + w[3] = (s32)div64_s64(b2 * BF_EQ_Q27 + b0 / 2, b0); + w[4] = (s32)div64_s64(b0 * BF_EQ_Q27 + a0 / 2, a0); +} + +/* ---- low cut ---- */ + +/* Slope byte: 2^n-1 (n poles) -> 6/12/18/24 dB per oct; 0 = off. */ +static u8 bf_eq_lc_slope_byte(s32 slope_db) +{ + switch (slope_db) { + case 6: return 0x01; + case 12: return 0x03; + case 18: return 0x07; + case 24: return 0x0F; + } return 0; } +/* The 0x38 low-cut frequency word: round(K.f'.(11656)/(11656+f')) with + * K = 11508, f' = f x slope-compensation factor (cap_eq9 fit, 0.003%; + * the slope factor keeps the composite -3 dB point constant). + */ +static u32 bf_eq_lc_freq_raw(s32 freq_hz, s32 slope_db) +{ + s64 f, word; + + if (freq_hz <= 0) + return BF_EQ_LC_OFF; + f = freq_hz; + switch (slope_db) { + case 6: + f = f * 15267 / 10000; + break; + + case 18: + f = f * 8061 / 10000; + break; + + case 24: + f = f * 6977 / 10000; + break; + } + word = (11508 * f * 11656 + (11656 + f) / 2) / (11656 + f); + return (u32)word; +} + +/* ---- block build + bulk write ---- */ + +static void bf_eq_build_block(u8 *b, int ch, u8 slope, + const s32 bands[3][4], s32 shared, u32 lc) +{ + int slot, k; + + memset(b, 0, BF_EQ_BLOCK_LEN); + b[0] = ch; + b[1] = slope; + b[2] = ch; + b[3] = 0x80; /* EQ engine active */ + for (slot = 0; slot < 3; slot++) { + for (k = 0; k < 4; k++) { + put_unaligned_le32((u32)bands[slot][k], + b + 0x04 + slot * 0x10 + 4 * k); + } + } + put_unaligned_le32((u32)shared, b + 0x34); + put_unaligned_le32(lc, b + 0x38); +} + +/* Upload one 64-byte block on bulk OUT ep 0x0A (interface 1). */ +static int bf_eq_upload(struct snd_usb_babyface *chip, const u8 *block) +{ + u8 *buf; + int ret, len; + + /* usb_bulk_msg DMA-maps the buffer: it must not be on the stack + * (usb_hcd_map_urb_for_dma returns -EAGAIN for stack buffers). + */ + buf = kmemdup(block, BF_EQ_BLOCK_LEN, GFP_KERNEL); + if (!buf) + return -ENOMEM; + ret = usb_bulk_msg(chip->dev, usb_sndbulkpipe(chip->dev, 0x0a), + buf, BF_EQ_BLOCK_LEN, &len, 1000); + kfree(buf); + if (ret < 0) + dev_err(&chip->dev->dev, "EQ bulk upload failed: %d\n", ret); + return ret; +} + +/* Write the L+R block pair for one strip (channel base = strip x 2). */ +static int bf_eq_write_strip(struct snd_usb_babyface *chip, int strip) +{ + struct bf_eq_channel *e = &chip->eq[strip]; + u8 b[BF_EQ_BLOCK_LEN]; + s32 identity[3][4] = { { 0 }, { 0 }, { 0 } }; + s32 shared = e->on ? e->shared : BF_EQ_Q27; + u32 lc = e->on ? e->lc_raw : BF_EQ_LC_OFF; + /* The header slope byte (b[1]) is only valid while the low cut is + * engaged: a stale slope with 0x38 = off made the device apply a + * garbage-frequency cut (ear-verified: "low cut off" left only + * highs). cap_eq7: byte1 = 0x00 + 0x38 = 0x04000000 when off. + */ + u8 slope = (e->on && e->lc_hz > 0) ? e->slope : 0; + int ch, ret; + + for (ch = 0; ch < 2; ch++) { + bf_eq_build_block(b, strip * 2 + ch, slope, + e->on ? e->words : identity, shared, lc); + ret = bf_eq_upload(chip, b); + if (ret < 0) + return ret; + } + return 0; +} + +/* Recompute one strip's words + low cut from its params, re-upload. + * Lock-free by convention: every caller must already hold chip->mutex + * (bf_eq_put() and bf_eq_reupload() do) — asserting it here catches a + * future caller that forgets, instead of a silent self-deadlock. + */ +static void bf_eq_update_strip(struct snd_usb_babyface *chip, int strip) +{ + struct bf_eq_channel *e = &chip->eq[strip]; + s32 fs = chip->rate ? chip->rate : 48000; + s32 last_c4 = BF_EQ_Q27; + int band, i; + + lockdep_assert_held(&chip->mutex); + + for (band = 0; band < 3; band++) { + s32 w[5]; + + bf_eq_band_words(w, e->band_type[band], e->band_freq[band], + e->band_q[band], e->band_gain[band], fs); + for (i = 0; i < 4; i++) + e->words[band][i] = w[i]; + if (e->band_type[band] && e->band_gain[band]) + last_c4 = w[4]; /* shared scale: the last band */ + } + e->shared = last_c4; + e->lc_raw = bf_eq_lc_freq_raw(e->lc_hz, e->slope_db); + e->slope = bf_eq_lc_slope_byte(e->slope_db); + bf_eq_write_strip(chip, strip); +} + +/* Recompute + re-upload all four strips (rate change). Caller must + * hold chip->mutex — bf_eq_update_strip()/bf_eq_write_strip() are + * lock-free by convention (see bf_eq_put()) and the only caller, + * babyface_pcm_hw_params(), already holds the lock across the rate + * change; locking here too self-deadlocked it (hung-task: "blocked + * on a mutex likely owned by" itself, hit via regress.sh's rate + * sweep). + */ void bf_eq_reupload(struct snd_usb_babyface *chip) { + int strip; + + for (strip = 0; strip < 4; strip++) + bf_eq_update_strip(chip, strip); +} + +/* ---- ALSA controls (4 strips x 19 controls) ---- */ + +#define EQ_STRIP(pv) ((pv) >> 8) +#define EQ_PARAM(pv) ((pv) & 0xff) +/* params: 0 enable, 1-3 type, 4-6 freq, 7-9 q, 10-12 gain, 13 lc freq, 14 lc slope */ + +static const char *const bf_eq_type_texts[] = { + "Off", "Bell", "Low Shelf", "High Shelf", NULL +}; + +static const char *const bf_eq_slope_texts[] = { + "6 dB/oct", "12 dB/oct", "18 dB/oct", "24 dB/oct", NULL +}; + +static int bf_eq_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + int param = EQ_PARAM(kctl->private_value); + + if (param == 0) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + return 0; + } + if (param == 1 || param == 2 || param == 3) + return snd_ctl_enum_info(uinfo, 1, 4, bf_eq_type_texts); + if (param == 14) + return snd_ctl_enum_info(uinfo, 1, 4, bf_eq_slope_texts); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = (param == 10 || param == 11 || + param == 12) ? -240 : + (param == 7 || param == 8 || + param == 9) ? 5 : 0; + uinfo->value.integer.max = (param == 7 || param == 8 || + param == 9) ? 1000 : + (param == 10 || param == 11 || + param == 12) ? 240 : 20000; + uinfo->value.integer.step = 1; + return 0; +} + +static int bf_eq_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + struct bf_eq_channel *e = &chip->eq[EQ_STRIP(kctl->private_value)]; + int param = EQ_PARAM(kctl->private_value); + int band = (param - 1) % 3; + s32 *v = NULL; + + switch (param) { + case 0: + break; + + case 1: + case 2: + case 3: + v = &e->band_type[band]; + break; + + case 4: + case 5: + case 6: + v = &e->band_freq[band]; + break; + + case 7: + case 8: + case 9: + v = &e->band_q[band]; + break; + + case 10: + case 11: + case 12: + v = &e->band_gain[band]; + break; + + case 13: + v = &e->lc_hz; + break; + + case 14: + v = &e->slope_db; + break; + } + if (param == 0) { + ucontrol->value.integer.value[0] = e->on; + } else if (param == 14) { + /* Inverse of put's index->dB map: slope_db stores the raw + * 6/12/18/24 dB/oct value, but an ENUMERATED control's .get + * must return the enum item index (0-3), same as .put + * receives — returning the raw dB value here (the bug this + * replaces) fed back an out-of-range index to every ALSA + * consumer (confirmed via amixer: writing index 1 read back + * as value 12, not 1). + */ + s32 slope = v ? *v : 6; + + ucontrol->value.enumerated.item[0] = + slope >= 24 ? 3 : slope >= 18 ? 2 : slope >= 12 ? 1 : 0; + } else if (param == 1 || param == 2 || param == 3) { + /* ENUMERATED band type: use the enumerated union member. */ + ucontrol->value.enumerated.item[0] = v ? *v : 0; + } else { + ucontrol->value.integer.value[0] = v ? *v : 0; + } + return 0; +} + +static int bf_eq_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_babyface *chip = snd_kcontrol_chip(kctl); + int strip = EQ_STRIP(kctl->private_value); + int param = EQ_PARAM(kctl->private_value); + struct bf_eq_channel *e = &chip->eq[strip]; + int band = (param - 1) % 3; + s32 nv; + s32 *v = NULL; + int ret = 0; + + /* Read from the union member matching the control type: ENUMERATED + * params (band type 1-3, slope 14) use .enumerated.item, everything + * else (BOOL 0, INTEGER) uses .integer.value. + */ + if (param == 1 || param == 2 || param == 3 || param == 14) + nv = (s32)ucontrol->value.enumerated.item[0]; + else + nv = (s32)ucontrol->value.integer.value[0]; + + /* Validate against the bounds bf_eq_info() declares. The ALSA core + * only checks these when CONFIG_SND_CTL_INPUT_VALIDATION is set, so + * an out-of-range value here could otherwise reach the Q27 + * coefficient math (bf_eq_band_words/bf_exp2) and shift by >= width + * (undefined behaviour). + */ + switch (param) { + case 0: + if (nv < 0 || nv > 1) + return -EINVAL; + break; + case 1: + case 2: + case 3: + case 14: + if (nv < 0 || nv > 3) + return -EINVAL; + break; + case 4: + case 5: + case 6: + case 13: + if (nv < 0 || nv > 20000) + return -EINVAL; + break; + case 7: + case 8: + case 9: + if (nv < 5 || nv > 1000) + return -EINVAL; + break; + case 10: + case 11: + case 12: + if (nv < -240 || nv > 240) + return -EINVAL; + break; + } + + switch (param) { + case 0: + v = NULL; + break; + + case 1: + case 2: + case 3: + v = &e->band_type[band]; + break; + + case 4: + case 5: + case 6: + v = &e->band_freq[band]; + break; + + case 7: + case 8: + case 9: + v = &e->band_q[band]; + break; + + case 10: + case 11: + case 12: + v = &e->band_gain[band]; + break; + + case 13: + v = &e->lc_hz; + break; + + case 14: + v = &e->slope_db; + break; + } + if (param == 14) /* slope enum items are 6/12/18/24 */ + nv = nv == 0 ? 6 : nv == 1 ? 12 : nv == 2 ? 18 : 24; + + mutex_lock(&chip->mutex); + if (param == 0) { + if (e->on != !!nv) { + e->on = !!nv; + bf_eq_update_strip(chip, strip); + ret = 1; + } + } else if (v && *v != nv) { + *v = nv; + bf_eq_update_strip(chip, strip); + ret = 1; + } + mutex_unlock(&chip->mutex); + return ret; +} + +int babyface_create_eq(struct snd_usb_babyface *chip) +{ + static const char *const names[4] = { "AN1", "AN2", "AN3", "AN4" }; + static const char *const params[] = { + "EQ Enable", + "EQ Band 1 Type", "EQ Band 2 Type", "EQ Band 3 Type", + "EQ Band 1 Freq", "EQ Band 2 Freq", "EQ Band 3 Freq", + "EQ Band 1 Q", "EQ Band 2 Q", "EQ Band 3 Q", + "EQ Band 1 Gain", "EQ Band 2 Gain", "EQ Band 3 Gain", + "EQ Low Cut Freq", "EQ Low Cut Slope", + }; + int strip, i, err; + + for (strip = 0; strip < 4; strip++) { + for (i = 0; i < 15; i++) { + struct snd_kcontrol *kctl; + char name[64]; + + snprintf(name, sizeof(name), "%s %s", names[strip], + params[i]); + kctl = snd_ctl_new1(&(struct snd_kcontrol_new){ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "EQ", + .index = 0, + .info = bf_eq_info, + .get = bf_eq_get, + .put = bf_eq_put, + .private_value = (strip << 8) | i, + }, chip); + if (!kctl) + return -ENOMEM; + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + err = snd_ctl_add(chip->card, kctl); + if (err < 0) + return err; + } + } + return 0; } -- 2.55.0