From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qt1-f177.google.com (mail-qt1-f177.google.com [209.85.160.177]) (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 948693F23A6 for ; Fri, 27 Feb 2026 23:50:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.177 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772236261; cv=none; b=Oocjwq6/ZahN5f6yij1IxVOeyfTy70mAnOSRdfluKgHMXgb0YcEumUYv7dsl1F944kwk/nIIXEkyDTx7sRYeGEz9GTjKzKRD6xjOfHdQyWzpieQL4wT/wf4xbUizeBtSDak666r7a9XPTzNaIlnL8QhOa8mShuBhlquH+bWzufY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772236261; c=relaxed/simple; bh=tIgu/W1Vw1MtIn1d2yUvm4K3lF1alZK5lWzx0kjY2Dk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dtKmdlf6H9gF8ZWeq4ccvsAfC242SJcxxVlsh9WLOIECPPBLR8RoYGBwFOadqHlPYhv92atFYC1xcGWIWoAoaa9ALl4MzVtSWfKLKWBndpUXOPaJfByfivayDOKtj0jynPrdSH/mCnkjZn61MX+9DNfEG+PzfmSAMABjVRbhekc= 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=hANaiHyz; arc=none smtp.client-ip=209.85.160.177 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="hANaiHyz" Received: by mail-qt1-f177.google.com with SMTP id d75a77b69052e-5069b3e0c66so50542891cf.1 for ; Fri, 27 Feb 2026 15:50:57 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1772236256; x=1772841056; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=fiCPPOg7Ij3vGkIzTm4Dp+OQ39FoDVXoCpIrzjwwrl4=; b=hANaiHyzUESOsvDoiT7hLj7SB+qC243dg/p8AlHJaycirng/X/jwwe4M6HgeZy4dwH tdQ7FPKBUYn0OeYktPCnodJB/XjBh7mPSrjtrPwHoOeh0D0B+fPPXrAue7eHQgsoJBmt eJV1elv1DQ0JEX7me43bofWqbq4+Eo7QRDUjaGIKoBr4ogW241hZIcRM14fx4P6ooPAx ygSEpXVoVPFIpmTTQ31EnDqxkx3ArmelWDjYwvcmX4OhMJlBiY9M6hvE5s8sKW4gqT+6 2CjQAV487TW4U3xgY6raKjn9kTli1Z37Yu6vocaJ6/vX6bmvI1LuyB0w25VcNmIpDXnC ximw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1772236256; x=1772841056; h=content-transfer-encoding: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; bh=fiCPPOg7Ij3vGkIzTm4Dp+OQ39FoDVXoCpIrzjwwrl4=; b=YhyMcJ9qchkHjazssTA8151LAb397Vu8dSCNNoERQCLjnBJ60rOQvzaMHsg9wWU+Cb bmq9XwTIGHbku4qkjQZLf6DTMMlnWoLuLmNrFKTIVmvfUB2KfExwVCbG+UWcLvarRsCy bGff/aXYXOfx32t6Sb+A/6rnPz2TdUgGOYgQdVR99Cf/zMBcTU5MxWY4OLwgKP62wUrc LyPC7QaIacp4kXaoE8iogK4tsMN3QNqGvgk0TcqfULPuZtIyA0MyWA1/9CZKqAngGpvM LvlS/XbLW72XmIBoyh7yKaBn5P6WcAshvQtnJLJnj5P8LpRwemcKtFdE6oG8NoiIfUiy QoYg== X-Forwarded-Encrypted: i=1; AJvYcCVdV6OZ5W3zE/rW5dl2y3lZueQlNa8gEWSdlHSrQ5MLBZzL+PodmAebhWpGkcVEBR/MupS8Jz6uOU9tYKg=@vger.kernel.org X-Gm-Message-State: AOJu0YyLRPiiNNoKS0Wk7For13x59IKa0+SpAO5Y/X02OWmAusx33pMy 5RlCJ3dV3FrW0eqRhzi7oNC9kVywuuIJnd2OvAXorK9mWYJlmLpr2xax X-Gm-Gg: ATEYQzyGqiBQcxIvv9TqyEC8XSsneCNLVYW28jhd1e8fvER766bncbsT1rBnk/XONwf RZ8w8OiaSLurJbBhhIt42pzpo92dDKS7HpIcy0lT9q7tcOXuElY7mhTXOHeEl5S1VZXdcL7knyo 7doj6epbwYczMAkFQnaL2151lxMqIG85m4sHNzhucBgfN54P4+4g2CMDPaqu63vWnHsSiunwtUE wCiMxG8qD2vBmIl7g4kdwPmD/CE2zNjS7Qc6N5xJtE5OyCaDxlhrjwGZ995GROZ8MlfGKgnme8b PW9vRfm6EDZlbOj+9I+KvRU70BnKV/p4Gqkram+ZMTatdWQ/zcy986+zH8fP65DJrQStzeBM9ek 2zSHUW/3kIGYfvVbgr7T/5tCorHXKx/W06nbZUCj2tUz/IQO1SyX1AxhMDG7pMG3kPo7YiNt+bO KphV9/l4g161HumP+1gRiucsA4pYiNCf1daRLJlGshD2HsQ/OevQ== X-Received: by 2002:a05:622a:18a3:b0:505:e529:11e9 with SMTP id d75a77b69052e-507444404c7mr114667511cf.36.1772236256466; Fri, 27 Feb 2026 15:50:56 -0800 (PST) Received: from achantapc.tail227c81.ts.net ([128.172.224.28]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-899c715a87esm52397446d6.4.2026.02.27.15.50.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 27 Feb 2026 15:50:56 -0800 (PST) From: Sriman Achanta To: Jiri Kosina , Benjamin Tissoires Cc: linux-input@vger.kernel.org, linux-kernel@vger.kernel.org, Bastien Nocera , Simon Wood , Christian Mayer , Sriman Achanta Subject: [PATCH v3 13/18] HID: steelseries: Add volume limiter ALSA mixer control Date: Fri, 27 Feb 2026 18:50:37 -0500 Message-ID: <20260227235042.410062-14-srimanachanta@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260227235042.410062-1-srimanachanta@gmail.com> References: <20260227235042.410062-1-srimanachanta@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Expose the maximum output volume cap as a writable ALSA boolean mixer control ("Volume Limiter"). The Nova 7 family uses command 0x3a for this setting whereas the Nova 5 family uses 0x27, so a dedicated steelseries_arctis_nova_7_write_setting() is introduced and the Nova 7, Nova 7P, and Nova 7 Gen2 entries are updated to use it instead of the Nova 5 handler. Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries.c | 145 +++++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 1339f965f67f..47ffec481571 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -29,11 +29,13 @@ #define SS_CAP_EXTERNAL_CONFIG BIT(5) #define SS_CAP_SIDETONE BIT(6) #define SS_CAP_MIC_VOLUME BIT(7) +#define SS_CAP_VOLUME_LIMITER BIT(8) #define SS_QUIRK_STATUS_SYNC_POLL BIT(0) #define SS_SETTING_SIDETONE 0 #define SS_SETTING_MIC_VOLUME 1 +#define SS_SETTING_VOLUME_LIMITER 2 struct steelseries_device; @@ -77,11 +79,13 @@ struct steelseries_device { struct snd_ctl_elem_id mic_muted_id; struct snd_ctl_elem_id sidetone_id; struct snd_ctl_elem_id mic_volume_id; + struct snd_ctl_elem_id volume_limiter_id; u8 chatmix_chat; u8 chatmix_game; bool mic_muted; u8 sidetone; u8 mic_volume; + bool volume_limiter; bool bt_enabled; bool bt_device_connected; @@ -531,6 +535,44 @@ static int steelseries_arctis_nova_5_write_setting(struct hid_device *hdev, case SS_SETTING_MIC_VOLUME: cmd = 0x37; break; + case SS_SETTING_VOLUME_LIMITER: + cmd = 0x27; + break; + default: + return -EINVAL; + } + + data[0] = 0x00; + data[1] = cmd; + data[2] = value; + + ret = steelseries_send_output_report(hdev, data, sizeof(data)); + if (ret) + return ret; + + msleep(10); + + return steelseries_send_output_report(hdev, save, sizeof(save)); +} + +static int steelseries_arctis_nova_7_write_setting(struct hid_device *hdev, + u8 setting, u8 value) +{ + const u8 save[] = { 0x00, 0x09 }; + u8 cmd; + int ret; + u8 data[3]; + + switch (setting) { + case SS_SETTING_SIDETONE: + cmd = 0x39; + break; + case SS_SETTING_MIC_VOLUME: + cmd = 0x37; + break; + case SS_SETTING_VOLUME_LIMITER: + cmd = 0x3a; + break; default: return -EINVAL; } @@ -849,13 +891,14 @@ static int steelseries_arctis_nova_7_gen2_request_settings(struct hid_device *hd static void steelseries_arctis_nova_7_gen2_parse_settings( struct steelseries_device *sd, u8 *data, int size) { - if (size < 3) + if (size < 4) return; switch (data[0]) { case 0x20: sd->mic_volume = data[1]; sd->sidetone = data[2]; + sd->volume_limiter = data[3]; break; case 0x37: sd->mic_volume = data[1]; @@ -863,6 +906,9 @@ static void steelseries_arctis_nova_7_gen2_parse_settings( case 0x39: sd->sidetone = data[1]; break; + case 0x3a: + sd->volume_limiter = data[1]; + break; } } @@ -954,7 +1000,8 @@ static const struct steelseries_device_info arctis_nova_3p_info = { static const struct steelseries_device_info arctis_nova_5_info = { .sync_interface = 3, - .capabilities = SS_CAP_BATTERY | SS_CAP_SIDETONE | SS_CAP_MIC_VOLUME, + .capabilities = SS_CAP_BATTERY | SS_CAP_SIDETONE | SS_CAP_MIC_VOLUME | + SS_CAP_VOLUME_LIMITER, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 10, .mic_volume_max = 15, @@ -966,7 +1013,7 @@ static const struct steelseries_device_info arctis_nova_5_info = { static const struct steelseries_device_info arctis_nova_5x_info = { .sync_interface = 3, .capabilities = SS_CAP_BATTERY | SS_CAP_CHATMIX | SS_CAP_SIDETONE | - SS_CAP_MIC_VOLUME, + SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 10, .mic_volume_max = 15, @@ -978,23 +1025,23 @@ static const struct steelseries_device_info arctis_nova_5x_info = { static const struct steelseries_device_info arctis_nova_7_info = { .sync_interface = 3, .capabilities = SS_CAP_BATTERY | SS_CAP_CHATMIX | SS_CAP_SIDETONE | - SS_CAP_MIC_VOLUME, + SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 3, .mic_volume_max = 7, .request_status = steelseries_arctis_nova_request_status, .parse_status = steelseries_arctis_nova_7_parse_status, - .write_setting = steelseries_arctis_nova_5_write_setting, + .write_setting = steelseries_arctis_nova_7_write_setting, }; static const struct steelseries_device_info arctis_nova_7p_info = { .sync_interface = 3, - .capabilities = SS_CAP_BATTERY | SS_CAP_MIC_VOLUME, + .capabilities = SS_CAP_BATTERY | SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .mic_volume_max = 7, .request_status = steelseries_arctis_nova_request_status, .parse_status = steelseries_arctis_nova_7_parse_status, - .write_setting = steelseries_arctis_nova_5_write_setting, + .write_setting = steelseries_arctis_nova_7_write_setting, }; static const struct steelseries_device_info arctis_nova_7_gen2_info = { @@ -1003,14 +1050,14 @@ static const struct steelseries_device_info arctis_nova_7_gen2_info = { .capabilities = SS_CAP_BATTERY | SS_CAP_CHATMIX | SS_CAP_MIC_MUTE | SS_CAP_BT_ENABLED | SS_CAP_BT_DEVICE_CONNECTED | SS_CAP_EXTERNAL_CONFIG | SS_CAP_SIDETONE | - SS_CAP_MIC_VOLUME, + SS_CAP_MIC_VOLUME | SS_CAP_VOLUME_LIMITER, .sidetone_max = 3, .mic_volume_max = 7, .request_status = steelseries_arctis_nova_request_status, .parse_status = steelseries_arctis_nova_7_gen2_parse_status, .request_settings = steelseries_arctis_nova_7_gen2_request_settings, .parse_settings = steelseries_arctis_nova_7_gen2_parse_settings, - .write_setting = steelseries_arctis_nova_5_write_setting, + .write_setting = steelseries_arctis_nova_7_write_setting, }; static const struct steelseries_device_info arctis_nova_pro_info = { @@ -1450,6 +1497,66 @@ static const struct snd_kcontrol_new steelseries_mic_volume_control = { .put = steelseries_mic_volume_put, }; +static int steelseries_volume_limiter_info(struct snd_kcontrol *kcontrol, + 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; + uinfo->value.integer.step = 1; + return 0; +} + +static int steelseries_volume_limiter_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct steelseries_device *sd = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&sd->lock, flags); + ucontrol->value.integer.value[0] = sd->volume_limiter; + spin_unlock_irqrestore(&sd->lock, flags); + return 0; +} + +static int steelseries_volume_limiter_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct steelseries_device *sd = snd_kcontrol_chip(kcontrol); + unsigned long flags; + bool new_volume_limiter; + int ret; + + new_volume_limiter = !!ucontrol->value.integer.value[0]; + + spin_lock_irqsave(&sd->lock, flags); + if (sd->volume_limiter == new_volume_limiter) { + spin_unlock_irqrestore(&sd->lock, flags); + return 0; + } + spin_unlock_irqrestore(&sd->lock, flags); + + ret = sd->info->write_setting(sd->hdev, SS_SETTING_VOLUME_LIMITER, + new_volume_limiter ? 1 : 0); + if (ret) + return ret; + + spin_lock_irqsave(&sd->lock, flags); + sd->volume_limiter = new_volume_limiter; + spin_unlock_irqrestore(&sd->lock, flags); + + return 1; +} + +static const struct snd_kcontrol_new steelseries_volume_limiter_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Volume Limiter", + .info = steelseries_volume_limiter_info, + .get = steelseries_volume_limiter_get, + .put = steelseries_volume_limiter_put, +}; + static int steelseries_snd_register(struct steelseries_device *sd) { struct hid_device *hdev = sd->hdev; @@ -1522,6 +1629,21 @@ static int steelseries_snd_register(struct steelseries_device *sd) sd->mic_volume_id = kctl->id; } + if (sd->info->capabilities & SS_CAP_VOLUME_LIMITER) { + struct snd_kcontrol *kctl; + struct snd_kcontrol_new vol_lim_ctl = steelseries_volume_limiter_control; + + vol_lim_ctl.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (sd->info->capabilities & SS_CAP_EXTERNAL_CONFIG) + vol_lim_ctl.access |= SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + kctl = snd_ctl_new1(&vol_lim_ctl, sd); + ret = snd_ctl_add(sd->card, kctl); + if (ret < 0) + goto err_free_card; + sd->volume_limiter_id = kctl->id; + } + ret = snd_card_register(sd->card); if (ret < 0) goto err_free_card; @@ -1554,6 +1676,7 @@ static int steelseries_raw_event(struct hid_device *hdev, bool old_mic_muted; u8 old_sidetone; u8 old_mic_volume; + bool old_volume_limiter; bool is_async_interface = false; if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) @@ -1570,6 +1693,7 @@ static int steelseries_raw_event(struct hid_device *hdev, old_mic_muted = sd->mic_muted; old_sidetone = sd->sidetone; old_mic_volume = sd->mic_volume; + old_volume_limiter = sd->volume_limiter; if (hid_is_usb(hdev)) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -1636,6 +1760,9 @@ static int steelseries_raw_event(struct hid_device *hdev, if (sd->mic_volume != old_mic_volume) snd_ctl_notify(sd->card, SNDRV_CTL_EVENT_MASK_VALUE, &sd->mic_volume_id); + if (sd->volume_limiter != old_volume_limiter) + snd_ctl_notify(sd->card, SNDRV_CTL_EVENT_MASK_VALUE, + &sd->volume_limiter_id); } return 0; -- 2.53.0