From nobody Tue Apr 7 14:30:26 2026 Received: from mail-dl1-f47.google.com (mail-dl1-f47.google.com [74.125.82.47]) (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 956A7377EC7 for ; Tue, 7 Apr 2026 04:13:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775535241; cv=none; b=k5VCy/Aa6ed5I26gULsIiQsyeUBWc1sWhNeX/e3K1RgRMcxsrf7+mf769retE5PxgH/LAQeOGgsEUwRHmHpTiPZZwVjWiB40hzPa4JtWr89eJTVf7E7yeMQU2qdKM61nD8hRuffiv0Z0csHarKsEWwjIOaN37icDhsOkQtMikdg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775535241; c=relaxed/simple; bh=usmYDq97Sg76UklSs3ptf29YaZhkgp2uHecyG8pdypk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cMWZo8rew/8sUmhCQ6FKZTjYiE8hAv6AHkdJZSWfVKx12ukhKr5X+cJmnG0cjZnJGXJdehnhj0NaZti8khLq6rgI9vtPv9ld/X/RmCcz/fNsljZMXXE9bew76Gr1s2OLI5F1rz1DKRVUUiTmvnM9KmU9dSMtRqWh3s8/pBpyVew= 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=XdW3elJF; arc=none smtp.client-ip=74.125.82.47 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="XdW3elJF" Received: by mail-dl1-f47.google.com with SMTP id a92af1059eb24-128b9b7e3edso2689739c88.0 for ; Mon, 06 Apr 2026 21:13:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775535239; x=1776140039; 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=+BUPfatehd4OwQWVH3wip0eKYYTenln556l3fMYEsrA=; b=XdW3elJF1M3ShVzMu80KD6Ff9fOYK7e/qVN1DPniXSk4VAgZZism0wQtu06lZHZ4qj mMN+6k+s0mrzQ7IVGMmtv/g0aIn8idrbgwIU093DLtxilzwT3wik3dl93IgorItyLK1h eT/TWY6/Laeplvto9BsdIb4fNX0kmvIRws3ESvoTeXkO7NsFPIn2iJdaftfqxJz2LnZ8 M60wm9xZ8SZtbR0akdcr2B2fEnfXdHaRUw8vcpl/WgO7QomnREkko0gQndRQLe7IK1G1 SMJZgkLHH7R4cLwCQ7I+iyxv/MpeKgdluRuXO/Df5vdxM6XGdxpuMO2GvZagk/iGnbO3 ZZ6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775535239; x=1776140039; 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=+BUPfatehd4OwQWVH3wip0eKYYTenln556l3fMYEsrA=; b=eIp3jaCvktz+X2ggzM8Ywdnxck+mwHlyXEywhWyaVXI72CyjGFt//KrIpc977Vkcjj VhzAIzxgJB2h06jyHNjy0ZdcLiA2XMYulO+6Jljb0kY9fto1vjceKNIExGtM3XjyvjuK ezAX6doMtT44jxbdVK085ULBFJqNdLMiKExIOEL2pVvkADIc1BQvXMsTZbz1x35D3bxl 38qirO/qo40Jj74d9ynpW5O8ERGThLzYuy7Y01VlmRm/+xw8s7tRk1uTB7+HSiS+HoFH 7a+ha+UgnN5Zobscna/GgI0XSpmkl/TjOxRqrYKOZbwFnow4/IsvMtgw32xfpFC6ZiCs bt1g== X-Forwarded-Encrypted: i=1; AJvYcCVGm5F2/hIeTXK3zK4E9yiWGQuwIxgkG3CE5jFYzpE28YNpot0Ju+zzycUrMMTjfWJb9cNTqqiKSSU1QaU=@vger.kernel.org X-Gm-Message-State: AOJu0Yyq98QlLMrtwo9kYhCNz8YRQ6yBL87ZGmu7e2qaRzJD7GpqJx4V 4BzuHLpWxLKXBr0v1Mghleb5mLEFuA3H8y+of/bfJDhSn/wRsNQaGfoi X-Gm-Gg: AeBDiev2yVMVfk6Z9/QlTDD0nBea2BQka3N7OZInYaHPAxVilbQxu1Glag2Vb64rI9z 2Kjm2u9kb8YUVlfWfVwwHNV1dffIQTSHt8xsocAW0zQ630F1KRDTmuB2XL9n2uTdMG7fIMl8wJt 8EmbSSZnAV/Fj8o3yfYKqyDvxScUk6UxVHE5jcjg9RA9ZrkzMqW3JHjEa/+mRzsYafdYdzgFxZB qOV3ICG4MxqbqRfu/+ypnc0lIoDIgQJCOIn3XLLYHxoMdzbdFXFakPHjx1txSf785wNg1O0DsdM fE1yZOajCiYgtTD+G5+aBZFvXXxy8GdPo3ItzedEoY1KHm9Oyuustq+jAteKHLH+n+Wj4DdTF19 ZZXqiBOEfgE9qLOzIJp7GtpGQVhKY6bFEYHPne9LIyg8P0W0YaEjE/2gHsxslE5cGBDH3zVjxvE IItq4ijteDyj8I0+iqUD5rCVuDxq3wCNJvlDIHiENNWpYPZNa+pbzQcxGL2puxoURZjFZvARPAZ YVuqqPjex5UcGA= X-Received: by 2002:a05:7022:388a:b0:123:348d:8576 with SMTP id a92af1059eb24-12bfb6ec50bmr8556077c88.6.1775535238604; Mon, 06 Apr 2026 21:13:58 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-12bed93f861sm18523808c88.0.2026.04.06.21.13.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Apr 2026 21:13:58 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: "Pierre-Loup A . Griffais" , Lambert Fan , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v2 2/5] HID: hid-oxp: Add Second Generation RGB Control Date: Tue, 7 Apr 2026 04:13:51 +0000 Message-ID: <20260407041354.2283201-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260407041354.2283201-1-derekjohn.clark@gmail.com> References: <20260407041354.2283201-1-derekjohn.clark@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: quoted-printable Content-Type: text/plain; charset="utf-8" Adds support for the second generation of RGB Control for OneXPlayer devices. The interface mirrors the first generation, with some differences to how messages are formatted. Some devices have both a GEN1 MCU for RGB control and a GEN2 MCU for button mapping. To avoid conflicts, quirk these devices to skip RGB setup for the GEN2_USAGE_PAGE. Signed-off-by: Derek J. Clark --- v2: - Add DMI quirks table. --- drivers/hid/Kconfig | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-oxp.c | 151 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 2deaec9f467d..b779088b80b6 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -924,6 +924,7 @@ config HID_OXP depends on USB_HID depends on LEDS_CLASS depends on LEDS_CLASS_MULTICOLOR + depends on DMI help Say Y here if you would like to enable support for OneXPlayer handheld devices that come with RGB LED rings around the joysticks and macro but= tons. diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index dcc5a3a70eaf..0d1ff879e959 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1134,6 +1134,9 @@ #define USB_VENDOR_ID_CRSC 0x1a2c #define USB_DEVICE_ID_ONEXPLAYER_GEN1 0xb001 =20 +#define USB_VENDOR_ID_WCH 0x1a86 +#define USB_DEVICE_ID_ONEXPLAYER_GEN2 0xfe00 + #define USB_VENDOR_ID_ONTRAK 0x0a07 #define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 =20 diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index c4219ecd8d71..25214356163e 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -24,12 +25,15 @@ #define OXP_PACKET_SIZE 64 =20 #define GEN1_MESSAGE_ID 0xff +#define GEN2_MESSAGE_ID 0x3f =20 #define GEN1_USAGE_PAGE 0xff01 +#define GEN2_USAGE_PAGE 0xff00 =20 enum oxp_function_index { OXP_FID_GEN1_RGB_SET =3D 0x07, OXP_FID_GEN1_RGB_REPLY =3D 0x0f, + OXP_FID_GEN2_STATUS_EVENT =3D 0xb8, }; =20 static struct oxp_hid_cfg { @@ -121,6 +125,22 @@ struct oxp_gen_1_rgb_report { u8 blue; } __packed; =20 +struct oxp_gen_2_rgb_report { + u8 report_id; + u8 header_id; + u8 padding_2; + u8 message_id; + u8 padding_4[2]; + u8 enabled; + u8 speed; + u8 brightness; + u8 red; + u8 green; + u8 blue; + u8 padding_12[3]; + u8 effect; +} __packed; + static u16 get_usage_page(struct hid_device *hdev) { return hdev->collection[0].usage >> 16; @@ -161,6 +181,44 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *= hdev, return 0; } =20 +static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, + struct hid_report *report, u8 *data, + int size) +{ + struct led_classdev_mc *led_mc =3D drvdata.led_mc; + struct oxp_gen_2_rgb_report *rgb_rep; + + if (data[0] !=3D OXP_FID_GEN2_STATUS_EVENT) + return 0; + + if (data[3] !=3D OXP_GET_PROPERTY) + return 0; + + rgb_rep =3D (struct oxp_gen_2_rgb_report *)data; + /* Ensure we save monocolor as the list value */ + drvdata.rgb_effect =3D rgb_rep->effect =3D=3D OXP_EFFECT_MONO_TRUE ? + OXP_EFFECT_MONO_LIST : + rgb_rep->effect; + drvdata.rgb_speed =3D rgb_rep->speed; + drvdata.rgb_en =3D rgb_rep->enabled =3D=3D 0 ? OXP_FEAT_DISABLED : + OXP_FEAT_ENABLED; + drvdata.rgb_brightness =3D rgb_rep->brightness; + led_mc->led_cdev.brightness =3D rgb_rep->brightness / 4 * + led_mc->led_cdev.max_brightness; + /* If monocolor had less than 100% brightness on the previous boot, + * there will be no reliable way to determine the real intensity. + * Since intensity scaling is used with a hardware brightness set at max, + * our brightness will always look like 100%. Use the last set value to + * prevent successive boots from lowering the brightness further. + * Brightness will be "wrong" but the effect will remain the same visuall= y. + */ + led_mc->subled_info[0].intensity =3D rgb_rep->red; + led_mc->subled_info[1].intensity =3D rgb_rep->green; + led_mc->subled_info[2].intensity =3D rgb_rep->blue; + + return 0; +} + static int oxp_hid_raw_event(struct hid_device *hdev, struct hid_report *r= eport, u8 *data, int size) { @@ -171,6 +229,8 @@ static int oxp_hid_raw_event(struct hid_device *hdev, s= truct hid_report *report, switch (up) { case GEN1_USAGE_PAGE: return oxp_hid_raw_event_gen_1(hdev, report, data, size); + case GEN2_USAGE_PAGE: + return oxp_hid_raw_event_gen_2(hdev, report, data, size); default: break; } @@ -216,6 +276,18 @@ static int oxp_gen_1_property_out(enum oxp_function_in= dex fid, u8 *data, return mcu_property_out(header, header_size, data, data_size, NULL, 0); } =20 +static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, + u8 data_size) +{ + u8 header[] =3D { fid, GEN2_MESSAGE_ID, 0x01 }; + u8 footer[] =3D { GEN2_MESSAGE_ID, fid }; + size_t header_size =3D ARRAY_SIZE(header); + size_t footer_size =3D ARRAY_SIZE(footer); + + return mcu_property_out(header, header_size, data, data_size, footer, + footer_size); +} + static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) { u16 up =3D get_usage_page(drvdata.hdev); @@ -230,6 +302,11 @@ static int oxp_rgb_status_store(u8 enabled, u8 speed, = u8 brightness) if (drvdata.rgb_effect =3D=3D OXP_EFFECT_MONO_LIST) data[3] =3D 0x04; return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 4); + case GEN2_USAGE_PAGE: + data =3D (u8[6]) { OXP_SET_PROPERTY, 0x00, 0x02, enabled, speed, brightn= ess }; + if (drvdata.rgb_effect =3D=3D OXP_EFFECT_MONO_LIST) + data[5] =3D 0x04; + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 6); default: return -ENODEV; } @@ -244,6 +321,9 @@ static ssize_t oxp_rgb_status_show(void) case GEN1_USAGE_PAGE: data =3D (u8[1]) { OXP_GET_PROPERTY }; return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); + case GEN2_USAGE_PAGE: + data =3D (u8[3]) { OXP_GET_PROPERTY, 0x00, 0x02 }; + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); default: return -ENODEV; } @@ -274,6 +354,16 @@ static int oxp_rgb_color_set(void) data[3 * i + 3] =3D blue; } return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size); + case GEN2_USAGE_PAGE: + size =3D 57; + data =3D (u8[57]) { OXP_EFFECT_MONO_TRUE, 0x00, 0x02 }; + + for (i =3D 1; i < size / 3; i++) { + data[3 * i] =3D red; + data[3 * i + 1] =3D green; + data[3 * i + 2] =3D blue; + } + return oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, size); default: return -ENODEV; } @@ -310,6 +400,10 @@ static int oxp_rgb_effect_set(u8 effect) data =3D (u8[1]) { effect }; ret =3D oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); break; + case GEN2_USAGE_PAGE: + data =3D (u8[3]) { effect, 0x00, 0x02 }; + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_STATUS_EVENT, data, 3); + break; default: ret =3D -ENODEV; } @@ -560,6 +654,56 @@ static struct led_classdev_mc oxp_cdev_rgb =3D { .subled_info =3D oxp_rgb_subled_info, }; =20 +struct quirk_entry { + bool hybrid_mcu; +}; + +static struct quirk_entry quirk_hybrid_mcu =3D { + .hybrid_mcu =3D true, +}; + +static const struct dmi_system_id oxp_hybrid_mcu_list[] =3D { + { + .ident =3D "OneXPlayer Apex", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER APEX"), + }, + .driver_data =3D &quirk_hybrid_mcu, + }, + { + .ident =3D "OneXPlayer G1 AMD", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"), + }, + .driver_data =3D &quirk_hybrid_mcu, + }, + { + .ident =3D "OneXPlayer G1 Intel", + .matches =3D { + DMI_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"), + }, + .driver_data =3D &quirk_hybrid_mcu, + }, + {}, +}; + +static bool oxp_hybrid_mcu_device(void) +{ + const struct dmi_system_id *dmi_id; + struct quirk_entry *quirks; + + dmi_id =3D dmi_first_match(oxp_hybrid_mcu_list); + if (!dmi_id) + return false; + + quirks =3D dmi_id->driver_data; + + return quirks->hybrid_mcu; +} + static int oxp_cfg_probe(struct hid_device *hdev, u16 up) { int ret; @@ -567,6 +711,10 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 = up) hid_set_drvdata(hdev, &drvdata); mutex_init(&drvdata.cfg_mutex); drvdata.hdev =3D hdev; + + if (up =3D=3D GEN2_USAGE_PAGE && oxp_hybrid_mcu_device()) + goto skip_rgb; + drvdata.led_mc =3D &oxp_cdev_rgb; =20 ret =3D devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb); @@ -585,6 +733,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 u= p) dev_warn(drvdata.led_mc->led_cdev.dev, "Failed to query RGB initial state: %i\n", ret); =20 +skip_rgb: return 0; } =20 @@ -613,6 +762,7 @@ static int oxp_hid_probe(struct hid_device *hdev, =20 switch (up) { case GEN1_USAGE_PAGE: + case GEN2_USAGE_PAGE: ret =3D oxp_cfg_probe(hdev, up); if (ret) { hid_hw_close(hdev); @@ -633,6 +783,7 @@ static void oxp_hid_remove(struct hid_device *hdev) =20 static const struct hid_device_id oxp_devices[] =3D { { HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) }, + { HID_USB_DEVICE(USB_VENDOR_ID_WCH, USB_DEVICE_ID_ONEXPLAYER_GEN2) }, {} }; =20 --=20 2.53.0