From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-qv1-f54.google.com (mail-qv1-f54.google.com [209.85.219.54]) (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 E51353EFD12 for ; Fri, 27 Feb 2026 23:50:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.54 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772236259; cv=none; b=jFVIltSxqvghM+NwoDtu0H4XIT/WpM7zya11EB+hdCtel862Q0id09NdotOR7BFxIm+4gDVaNbojVg825+AF/MFOUzEcLv/PiP+7SkbkkCzBtjYEY+hik3ZDXrTDJrFjn0k4FJGKuJurd5jwUVgxypzh7AxxkmMMBVTMrSTrVpE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772236259; c=relaxed/simple; bh=LINZzjkNXcR//uhObtuT6l57p2ZuCZQdmQ+ds+iTdHg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lfRAAHIL6I72qO6DIBVFrqpNFtRkST60enLwzcJdxhiQ/VgVdkU0fIJe6dwI8CniCkFxgo3+4H/w28mrGYcICb0uniPeiOcFxD65pt88R0hs3iGRX0MCM4gw+RhjqVRQj8w+IBHOe8V55rJU09c1T2vhE+MzrQnznhWuZnPreF4= 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=aCsNpK0P; arc=none smtp.client-ip=209.85.219.54 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="aCsNpK0P" Received: by mail-qv1-f54.google.com with SMTP id 6a1803df08f44-896f5af3d8aso39698196d6.1 for ; Fri, 27 Feb 2026 15:50:56 -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=wVqVKskRD6Dk7+Y8KsoAvkjKrzzikKhtPqfB8TrOqao=; b=aCsNpK0Ps6ZFaGUiG12eN5YiPLSg6R/ulfQ3bnNs3JFmu4uV/bvcDjQ6/HUJAZdGt8 AZx0RVAVxGfmh1POdmXciq1Q5Rn1dyKLHzmRy0pp5ph2IltKyE3FSie5q+ccvUpbR9Oj H8GHQJ+7D+w37WbC874s4OSQNrphLihX9yfBsv/fK2HntD3l6n+5e5PwICsTeTQL0H/C I3u1ET+U1F+i9xdMGlSphWXGZlmCzUeW+znBkMFa496lJd28PVJ4QOHaSwbGM31wRSsf I93qmHrP5QuwmPTXsxPy/BI6PPFOK1ibxZ/aQTf00Fy1aBMYcT0Kd6Bs9onzfzaGpXfQ ssiA== 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=wVqVKskRD6Dk7+Y8KsoAvkjKrzzikKhtPqfB8TrOqao=; b=WG8rjFDwAStuBiTbesnnNfgcDppA0wFXviQEa+6pGucM8e4j0ewg69Orldt1FT2ctF 9pR5IFze46wW+gBKwmd+NPxoKrcbFxKrcrFpaDQeDrxoF+H3kdtjFXugbr4H4rMI2yiq o7WUOO9FL+sIh2fmAQ8AxpYZ0kuQON5q37gCh0OHAua6bfdThSODflDCfTY+l2VcX1tX aujG5sjtde3Zebnx3b0pc0bSuRPw5oBPUTP+2vOJBi9+pcIvTh+IyswQWLaTjirBez/O tynQpeen6GLb+ZRh5mM3uWYvIUD/GRVA+CEp+k5QwHE0ezYuFef/9/OTAzeJbfep/nxc jAWQ== X-Forwarded-Encrypted: i=1; AJvYcCWx6CVf2LASrg/zJl0Lh9Xr13A+hEVxHuFTWdWbdXAeUpnT8QdGnwEOZIJW/N5N+V3V761zCdw91ex9b/4=@vger.kernel.org X-Gm-Message-State: AOJu0YytkeFxgr5XqBQE5zjTbUZQ9qrfIznonK9OMZFXAyZ69c5JRNMd eqE+AB4UFnw9+d7ZH5DeSAUbJMr9dAmNciTMC61FYwGHoyw9l2ijBUiL X-Gm-Gg: ATEYQzwtZQbTn+8W8ErOF24rx5+UmBr2SlY+Q3PsMblJMPnxX235x3Q0AWsHeBzRdwh rwvNihEiRvfYyNHz8/guJXdcDCUnPNcPSrkACXTjq2fzr6XHgV7H2vywON8RU9zkCKXAjg8WljS 69SjgQClU0HN9QDJcZ1USLd15MoMB5StjrpZLmfSvEavMsNOEmCnDp3jqEOnqpGJSPbVu9FcSjz K4gUEk5VQ+XaOW4gwKf2GFmCa4OfYroZ1e+tX6LPvYLuHSIiv3u0ludSM2da1M6ttmYR6fhpxkr ak/VxU5UI1lSAve0xE4jA6SfYUKnzccGtHfyAwSdbuJux3gr51es0W1KToesshwuTAc5PkDNTS2 TztIwrHlACqYSZ2uhBJFEhvZ0QAvknUGTd7N0szWBs8AVZW1NZ96vNdrD2vbky1uxt7AQaCp7Yy 0+ynCySJ9D6EUpLKCGNRCagNCFOtPWnZYEDfQiAOTmLW3d7Bhpe3pu+03IaBjv X-Received: by 2002:ad4:5ce7:0:b0:899:c555:9e65 with SMTP id 6a1803df08f44-899d1dc0635mr69980056d6.30.1772236255823; Fri, 27 Feb 2026 15:50:55 -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:55 -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 12/18] HID: steelseries: Add mic volume ALSA mixer control Date: Fri, 27 Feb 2026 18:50:36 -0500 Message-ID: <20260227235042.410062-13-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 microphone gain as a writable ALSA integer mixer control ("Mic Volume"). The valid range is per-device; mic_volume_min and mic_volume_max are added to the device info struct to accommodate models with a non-zero minimum (e.g. the Arctis Nova Pro, which has a range of 1-10). The write command (0x37) is added to the Nova 3P, Nova 5, and Nova Pro write handlers. On the Nova Pro, the current mic volume is recovered from the 0x06/0xb0 settings response via a new parse_settings hook. Signed-off-by: Sriman Achanta --- drivers/hid/hid-steelseries.c | 141 ++++++++++++++++++++++++++++++++-- 1 file changed, 134 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 2bdf772432d0..1339f965f67f 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -28,10 +28,12 @@ #define SS_CAP_BT_DEVICE_CONNECTED BIT(4) #define SS_CAP_EXTERNAL_CONFIG BIT(5) #define SS_CAP_SIDETONE BIT(6) +#define SS_CAP_MIC_VOLUME BIT(7) #define SS_QUIRK_STATUS_SYNC_POLL BIT(0) #define SS_SETTING_SIDETONE 0 +#define SS_SETTING_MIC_VOLUME 1 struct steelseries_device; @@ -43,6 +45,8 @@ struct steelseries_device_info { u8 async_interface; u8 sidetone_max; + u8 mic_volume_min; + u8 mic_volume_max; int (*request_status)(struct hid_device *hdev); void (*parse_status)(struct steelseries_device *sd, u8 *data, int size); @@ -72,10 +76,12 @@ struct steelseries_device { struct snd_ctl_elem_id chatmix_game_id; struct snd_ctl_elem_id mic_muted_id; struct snd_ctl_elem_id sidetone_id; + struct snd_ctl_elem_id mic_volume_id; u8 chatmix_chat; u8 chatmix_game; bool mic_muted; u8 sidetone; + u8 mic_volume; bool bt_enabled; bool bt_device_connected; @@ -493,6 +499,9 @@ static int steelseries_arctis_nova_3p_write_setting(struct hid_device *hdev, case SS_SETTING_SIDETONE: cmd = 0x39; break; + case SS_SETTING_MIC_VOLUME: + cmd = 0x37; + break; default: return -EINVAL; } @@ -519,6 +528,9 @@ static int steelseries_arctis_nova_5_write_setting(struct hid_device *hdev, case SS_SETTING_SIDETONE: cmd = 0x39; break; + case SS_SETTING_MIC_VOLUME: + cmd = 0x37; + break; default: return -EINVAL; } @@ -548,6 +560,9 @@ static int steelseries_arctis_nova_pro_write_setting(struct hid_device *hdev, case SS_SETTING_SIDETONE: cmd = 0x39; break; + case SS_SETTING_MIC_VOLUME: + cmd = 0x37; + break; default: return -EINVAL; } @@ -839,14 +854,28 @@ static void steelseries_arctis_nova_7_gen2_parse_settings( switch (data[0]) { case 0x20: + sd->mic_volume = data[1]; sd->sidetone = data[2]; break; + case 0x37: + sd->mic_volume = data[1]; + break; case 0x39: sd->sidetone = data[1]; break; } } +static void steelseries_arctis_nova_pro_parse_settings( + struct steelseries_device *sd, u8 *data, int size) +{ + if (size < 10) + return; + + if (data[0] == 0x06 && data[1] == 0xb0) + sd->mic_volume = data[9]; +} + static void steelseries_arctis_nova_pro_parse_status(struct steelseries_device *sd, u8 *data, int size) { @@ -914,9 +943,10 @@ static const struct steelseries_device_info arctis_9_info = { static const struct steelseries_device_info arctis_nova_3p_info = { .sync_interface = 4, - .capabilities = SS_CAP_BATTERY | SS_CAP_SIDETONE, + .capabilities = SS_CAP_BATTERY | SS_CAP_SIDETONE | SS_CAP_MIC_VOLUME, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 10, + .mic_volume_max = 14, .request_status = steelseries_arctis_nova_3p_request_status, .parse_status = steelseries_arctis_nova_3p_parse_status, .write_setting = steelseries_arctis_nova_3p_write_setting, @@ -924,9 +954,10 @@ 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, + .capabilities = SS_CAP_BATTERY | SS_CAP_SIDETONE | SS_CAP_MIC_VOLUME, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 10, + .mic_volume_max = 15, .request_status = steelseries_arctis_nova_request_status, .parse_status = steelseries_arctis_nova_5_parse_status, .write_setting = steelseries_arctis_nova_5_write_setting, @@ -934,9 +965,11 @@ 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, + .capabilities = SS_CAP_BATTERY | SS_CAP_CHATMIX | SS_CAP_SIDETONE | + SS_CAP_MIC_VOLUME, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 10, + .mic_volume_max = 15, .request_status = steelseries_arctis_nova_request_status, .parse_status = steelseries_arctis_nova_5x_parse_status, .write_setting = steelseries_arctis_nova_5_write_setting, @@ -944,9 +977,11 @@ 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, + .capabilities = SS_CAP_BATTERY | SS_CAP_CHATMIX | SS_CAP_SIDETONE | + SS_CAP_MIC_VOLUME, .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, @@ -954,10 +989,12 @@ static const struct steelseries_device_info arctis_nova_7_info = { static const struct steelseries_device_info arctis_nova_7p_info = { .sync_interface = 3, - .capabilities = SS_CAP_BATTERY, + .capabilities = SS_CAP_BATTERY | SS_CAP_MIC_VOLUME, .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, }; static const struct steelseries_device_info arctis_nova_7_gen2_info = { @@ -965,8 +1002,10 @@ static const struct steelseries_device_info arctis_nova_7_gen2_info = { .async_interface = 5, .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_EXTERNAL_CONFIG | SS_CAP_SIDETONE | + SS_CAP_MIC_VOLUME, .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, @@ -978,11 +1017,14 @@ static const struct steelseries_device_info arctis_nova_pro_info = { .sync_interface = 4, .capabilities = SS_CAP_BATTERY | SS_CAP_CHATMIX | SS_CAP_MIC_MUTE | SS_CAP_BT_ENABLED | SS_CAP_BT_DEVICE_CONNECTED | - SS_CAP_SIDETONE, + SS_CAP_SIDETONE | SS_CAP_MIC_VOLUME, .quirks = SS_QUIRK_STATUS_SYNC_POLL, .sidetone_max = 3, + .mic_volume_min = 1, + .mic_volume_max = 10, .request_status = steelseries_arctis_nova_pro_request_status, .parse_status = steelseries_arctis_nova_pro_parse_status, + .parse_settings = steelseries_arctis_nova_pro_parse_settings, .write_setting = steelseries_arctis_nova_pro_write_setting, }; @@ -1343,6 +1385,71 @@ static const struct snd_kcontrol_new steelseries_sidetone_control = { .put = steelseries_sidetone_put, }; +static int steelseries_mic_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct steelseries_device *sd = snd_kcontrol_chip(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = sd->info->mic_volume_min; + uinfo->value.integer.max = sd->info->mic_volume_max; + uinfo->value.integer.step = 1; + return 0; +} + +static int steelseries_mic_volume_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->mic_volume; + spin_unlock_irqrestore(&sd->lock, flags); + return 0; +} + +static int steelseries_mic_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct steelseries_device *sd = snd_kcontrol_chip(kcontrol); + unsigned long flags; + u8 new_mic_volume; + int ret; + + new_mic_volume = ucontrol->value.integer.value[0]; + if (new_mic_volume < sd->info->mic_volume_min || + new_mic_volume > sd->info->mic_volume_max) + return -EINVAL; + + spin_lock_irqsave(&sd->lock, flags); + if (sd->mic_volume == new_mic_volume) { + spin_unlock_irqrestore(&sd->lock, flags); + return 0; + } + spin_unlock_irqrestore(&sd->lock, flags); + + ret = sd->info->write_setting(sd->hdev, SS_SETTING_MIC_VOLUME, + new_mic_volume); + if (ret) + return ret; + + spin_lock_irqsave(&sd->lock, flags); + sd->mic_volume = new_mic_volume; + spin_unlock_irqrestore(&sd->lock, flags); + + return 1; +} + +static const struct snd_kcontrol_new steelseries_mic_volume_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Volume", + .info = steelseries_mic_volume_info, + .get = steelseries_mic_volume_get, + .put = steelseries_mic_volume_put, +}; + static int steelseries_snd_register(struct steelseries_device *sd) { struct hid_device *hdev = sd->hdev; @@ -1400,6 +1507,21 @@ static int steelseries_snd_register(struct steelseries_device *sd) sd->sidetone_id = kctl->id; } + if (sd->info->capabilities & SS_CAP_MIC_VOLUME) { + struct snd_kcontrol *kctl; + struct snd_kcontrol_new mic_vol_ctl = steelseries_mic_volume_control; + + mic_vol_ctl.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (sd->info->capabilities & SS_CAP_EXTERNAL_CONFIG) + mic_vol_ctl.access |= SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + kctl = snd_ctl_new1(&mic_vol_ctl, sd); + ret = snd_ctl_add(sd->card, kctl); + if (ret < 0) + goto err_free_card; + sd->mic_volume_id = kctl->id; + } + ret = snd_card_register(sd->card); if (ret < 0) goto err_free_card; @@ -1431,6 +1553,7 @@ static int steelseries_raw_event(struct hid_device *hdev, u8 old_chatmix_game; bool old_mic_muted; u8 old_sidetone; + u8 old_mic_volume; bool is_async_interface = false; if (hdev->product == USB_DEVICE_ID_STEELSERIES_SRWS1) @@ -1446,6 +1569,7 @@ static int steelseries_raw_event(struct hid_device *hdev, old_chatmix_game = sd->chatmix_game; old_mic_muted = sd->mic_muted; old_sidetone = sd->sidetone; + old_mic_volume = sd->mic_volume; if (hid_is_usb(hdev)) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -1509,6 +1633,9 @@ static int steelseries_raw_event(struct hid_device *hdev, if (sd->sidetone != old_sidetone) snd_ctl_notify(sd->card, SNDRV_CTL_EVENT_MASK_VALUE, &sd->sidetone_id); + if (sd->mic_volume != old_mic_volume) + snd_ctl_notify(sd->card, SNDRV_CTL_EVENT_MASK_VALUE, + &sd->mic_volume_id); } return 0; -- 2.53.0