From nobody Fri Jun 12 10:13:28 2026 Received: from mail-dy1-f180.google.com (mail-dy1-f180.google.com [74.125.82.180]) (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 9C69D2FA0C4 for ; Sun, 19 Apr 2026 04:26:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572792; cv=none; b=Z0iQ9x5iKxFrlUtXrhKO/m+IfiurXS5GnfFPutkgZZ8ZXOV5Q2qeuLp/ZBVEA7I2JKkaE3eZOd1AETQcSjjExornatuuIR0MxTATkVXj5AKCAqfeAvsDmUzsDbcg2l62EzvsANaqs59fJOeL49cQXVy14ma2/XLrnzzhLtz4J5U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572792; c=relaxed/simple; bh=FfeemacpmkiAeAi8ML14/RSj3gpPVIHzfAivZosCjXM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HtkmoRyaHzRh30DcWR98nBMfvm7vrju1NaqC2gPxazDDcmbEeKb12wDW79H6Mr5TI9lq4IIuF0oP+RyXgUorEFioJ79ckGwatVd/dopW0duq3JawpBLFPlCsgnSMM4/p9mIB0peVYAkqGg1JxB0mK0sRkJAHVfqLbwFWwKwjZSo= 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=jJPMXMw/; arc=none smtp.client-ip=74.125.82.180 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="jJPMXMw/" Received: by mail-dy1-f180.google.com with SMTP id 5a478bee46e88-2bdcf5970cdso1689774eec.0 for ; Sat, 18 Apr 2026 21:26:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776572787; x=1777177587; 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=eAvY3pnahmg/h6/OPayXStEZ6mAEg25Wq+PDNaLAalA=; b=jJPMXMw/GlVhQ6Ri+X9tzwDn60IT1aQQjSFFzthMKn7j/zZkOLIFlCBY9kAjfYDESI cTqnSgzrPzsg3BjGFNPS+jN+aCJteZ+ZwYMv2UNkoDXyIpuGRQSG180hOUHt21135O7F O9b6dciyv76TNl3KUaPQeRSSTVjgnIWbsitBL4ww2WqVm6V2NurhBPeHMRCIwUbxAS5p hxvLn2p1/FIByW18RtFRQc3u3AfdoOMUJwjvNrHuQb+Z1JX0JpkRZbgZxenQH+RFAQtB EjQrTrdUtFjOATGwrGFvd/Z4J0qXh2R/3jCIlhNdMPtoyIREre1Xtv/R56Ig5IhCgZZu 3/tQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776572787; x=1777177587; 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=eAvY3pnahmg/h6/OPayXStEZ6mAEg25Wq+PDNaLAalA=; b=MYQuIA7dsOF0WVfHtK3F7xfdvQ2jDkefTlkUxKyBFoDkTmwKmcyWLXOrfxWCEibzkr QFabGclD6xKqIJYJ3kWZNePdy3ARnLTM+ybhd5W1W2e0Rq3XlZD2NvDQqQ5h75RadPPL WPruYmSyZz8M7zcrWwD7Ed+dDLM4IQMp8pgirVVLPjri/yz5KnMBXiAxvJFSWhseCgFN UIMpYT8ryuS3uRUiebILkSorR/oQUKpeI658++dKFOreyyB7oPSjL9hZG1d3T+pZW3N+ safZ9RrQuUMCh3cqI+K9Ef1bGAk6Xec8XuL4mX06JJirisDW9M5/kAdXbquAYsaY2CCX pw+A== X-Forwarded-Encrypted: i=1; AFNElJ8kXPuk0Ktm/QCnSN8Yo2phm0uxFORIqwPCKeHcAuLGQSTffYU/OF37BlYaNUOCuueAt2LVCayRMbaMa4M=@vger.kernel.org X-Gm-Message-State: AOJu0Yxbw9XlwoeuoUinBq6xjb6LbBD/aXnYCLXLoScg7CmrBlNe8/ix JQq2uuCv40whU3LHxjdcfBiVphuPwUEt1C5zhN0wg80/4Crrx3DzZQ3Q X-Gm-Gg: AeBDiet3CnEg0tVPWHnVFi3A5cc0R77LsK9vzZhFy4nhrl2yadVAlOWjF7IoZhiqk0k u/CPjqfSCDC2rAXmbBeFdugmY7NKK0yONzLpYJW6+H8Gs6AifScrhz2G/zHNnEFP7bTWl43kU3S mA2QH0rtLenaALKk6sfwyuX0wLTQeXAJpW0nyja+lhDtbkhhnRRgWb83tVgEkR9hdxVLOAtDlNH bSnnmSjuQeHIUdoJFI1kJwsodaU99g8D7P8p/RZWRsyV4MyJW6mmel3CL6CEN2qsxaINyAXbhQp oOj4dqjNo/i4gcxiC6uowEGEt/lAy0Huv+j1j0PJsGgCicnZ7PsFfy6sapqGjPVbF39rxfiHa1T xqcJjpIGzCOwcW+iXjUsWCcQKD6UNcdNBgBN3B8ZCXlTGP5fLV710uD4v+xVBYzCrI1F9FgO/kQ 871uqRdWlzzK3ImvJUfoxHRcy09z1VmlXanbWBK4zEqlnhpPqEWSwPGIOaV4I8n8bxLinqo1nY5 V8tjyukZZQuBSA= X-Received: by 2002:a05:7300:ad2d:b0:2d9:1c9d:fc22 with SMTP id 5a478bee46e88-2e43ca252ffmr3369663eec.21.1776572787297; Sat, 18 Apr 2026 21:26:27 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2e53dcb487bsm8796469eec.31.2026.04.18.21.26.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Apr 2026 21:26:27 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: "Pierre-Loup A . Griffais" , Lambert Fan , Zhouwang Huang , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 1/5] HID: hid-oxp: Add OneXPlayer configuration driver Date: Sat, 18 Apr 2026 21:26:20 -0700 Message-ID: <20260419042624.625746-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com> References: <20260419042624.625746-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 OneXPlayer HID configuration driver. In this initial driver patch, add the RGB interface for the first generation of HID based RGB control. This interface provides the following attributes: - brightness: provided by the LED core, this works in a fairly unique way on this device. The hardware accepts 5 brightness values (0-4), which affects the brightness of the multicolor and animated effects built into the MCU firmware. For monocolor settings, the device expects the hardware brightness value to be pushed to maximum, then we apply brightness adjustments mathematically based on % (0-100). This leads to some odd conversion as we need the brightness slider to reach the full range, but it has no affect when incrementing between the division points for other effects. - multi-intensity: provided by the LED core for red, green, and blue. - effect: Allows the MCU to set 19 individual effects. - effect_index: Lists the 19 valid effect names for the interface. - enabled: Allows the MCU to toggle the RGB interface on/off. - enabled_index: Lists the valid states for enabled. - speed: Allows the MCU to set the animation rate for the various effects. - speed_range: Lists the valid range of speed (0-9). The MCU also has a few odd quirks that make sending multiple synchronous events challenging. It will essentially freeze if it receives another message before it has finished processing the last command. It also will not reply if you wait on it using a completion. To get around this, we do a 200ms sleep inside a work queue thread and debounce all but the most recent message using a 50ms mod_delayed_work. This will cache the last write, queue the work, then return so userspace can release its write thread. The work queue is only used for brightness/multi-intensity as that is the path likely to receive rapid successive writes. Reviewed-by: Zhouwang Huang Tested-by: Zhouwang Huang Signed-off-by: Derek J. Clark --- v4: - Make oxp_rgb_queue delayed work struct part of drvdata and properly init, add cancel during remove. --- MAINTAINERS | 6 + drivers/hid/Kconfig | 12 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-oxp.c | 652 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 674 insertions(+) create mode 100644 drivers/hid/hid-oxp.c diff --git a/MAINTAINERS b/MAINTAINERS index 6f6517bf4f97..dae814192fa4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19707,6 +19707,12 @@ S: Maintained F: drivers/mtd/nand/onenand/ F: include/linux/mtd/onenand*.h =20 +ONEXPLAYER HID DRIVER +M: Derek J. Clark +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-oxp.c + ONEXPLAYER PLATFORM EC DRIVER M: Antheas Kapenekakis M: Derek John Clark diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 3c034cd32fa8..2deaec9f467d 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -919,6 +919,18 @@ config HID_ORTEK - Ortek WKB-2000 - Skycable wireless presenter =20 +config HID_OXP + tristate "OneXPlayer handheld controller configuration support" + depends on USB_HID + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + 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. + + To compile this driver as a module, choose M here: the module will + be called hid-oxp. + config HID_PANTHERLORD tristate "Pantherlord/GreenAsia game controller" help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 03ef72ec4499..bda8a24c9257 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_HID_NTI) +=3D hid-nti.o obj-$(CONFIG_HID_NTRIG) +=3D hid-ntrig.o obj-$(CONFIG_HID_NVIDIA_SHIELD) +=3D hid-nvidia-shield.o obj-$(CONFIG_HID_ORTEK) +=3D hid-ortek.o +obj-$(CONFIG_HID_OXP) +=3D hid-oxp.o obj-$(CONFIG_HID_PRODIKEYS) +=3D hid-prodikeys.o obj-$(CONFIG_HID_PANTHERLORD) +=3D hid-pl.o obj-$(CONFIG_HID_PENMOUNT) +=3D hid-penmount.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5bad81222c6e..dcc5a3a70eaf 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1131,6 +1131,9 @@ #define USB_VENDOR_ID_NVIDIA 0x0955 #define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER 0x7214 =20 +#define USB_VENDOR_ID_CRSC 0x1a2c +#define USB_DEVICE_ID_ONEXPLAYER_GEN1 0xb001 + #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 new file mode 100644 index 000000000000..f72bc74a7e6e --- /dev/null +++ b/drivers/hid/hid-oxp.c @@ -0,0 +1,652 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for OneXPlayer gamepad configuration devices. + * + * Copyright (c) 2026 Valve Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define OXP_PACKET_SIZE 64 + +#define GEN1_MESSAGE_ID 0xff + +#define GEN1_USAGE_PAGE 0xff01 + +enum oxp_function_index { + OXP_FID_GEN1_RGB_SET =3D 0x07, + OXP_FID_GEN1_RGB_REPLY =3D 0x0f, +}; + +static struct oxp_hid_cfg { + struct delayed_work oxp_rgb_queue; + struct led_classdev_mc *led_mc; + struct hid_device *hdev; + struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 rgb_brightness; + u8 rgb_effect; + u8 rgb_speed; + u8 rgb_en; +} drvdata; + +enum oxp_feature_en_index { + OXP_FEAT_DISABLED, + OXP_FEAT_ENABLED, +}; + +static const char *const oxp_feature_en_text[] =3D { + [OXP_FEAT_DISABLED] =3D "false", + [OXP_FEAT_ENABLED] =3D "true", +}; + +enum oxp_rgb_effect_index { + OXP_UNKNOWN, + OXP_EFFECT_AURORA, + OXP_EFFECT_BIRTHDAY, + OXP_EFFECT_FLOWING, + OXP_EFFECT_CHROMA_1, + OXP_EFFECT_NEON, + OXP_EFFECT_CHROMA_2, + OXP_EFFECT_DREAMY, + OXP_EFFECT_WARM, + OXP_EFFECT_CYBERPUNK, + OXP_EFFECT_SEA, + OXP_EFFECT_SUNSET, + OXP_EFFECT_COLORFUL, + OXP_EFFECT_MONSTER, + OXP_EFFECT_GREEN, + OXP_EFFECT_BLUE, + OXP_EFFECT_YELLOW, + OXP_EFFECT_TEAL, + OXP_EFFECT_PURPLE, + OXP_EFFECT_FOGGY, + OXP_EFFECT_MONO_LIST, /* placeholder for effect_index_show */ +}; + +/* These belong to rgb_effect_index, but we want to hide them from + * rgb_effect_text + */ + +#define OXP_GET_PROPERTY 0xfc +#define OXP_SET_PROPERTY 0xfd +#define OXP_EFFECT_MONO_TRUE 0xfe /* actual index for monocolor */ + +static const char *const oxp_rgb_effect_text[] =3D { + [OXP_UNKNOWN] =3D "unknown", + [OXP_EFFECT_AURORA] =3D "aurora", + [OXP_EFFECT_BIRTHDAY] =3D "birthday_cake", + [OXP_EFFECT_FLOWING] =3D "flowing_light", + [OXP_EFFECT_CHROMA_1] =3D "chroma_popping", + [OXP_EFFECT_NEON] =3D "neon", + [OXP_EFFECT_CHROMA_2] =3D "chroma_breathing", + [OXP_EFFECT_DREAMY] =3D "dreamy", + [OXP_EFFECT_WARM] =3D "warm_sun", + [OXP_EFFECT_CYBERPUNK] =3D "cyberpunk", + [OXP_EFFECT_SEA] =3D "sea_foam", + [OXP_EFFECT_SUNSET] =3D "sunset_afterglow", + [OXP_EFFECT_COLORFUL] =3D "colorful", + [OXP_EFFECT_MONSTER] =3D "monster_woke", + [OXP_EFFECT_GREEN] =3D "green_breathing", + [OXP_EFFECT_BLUE] =3D "blue_breathing", + [OXP_EFFECT_YELLOW] =3D "yellow_breathing", + [OXP_EFFECT_TEAL] =3D "teal_breathing", + [OXP_EFFECT_PURPLE] =3D "purple_breathing", + [OXP_EFFECT_FOGGY] =3D "foggy_haze", + [OXP_EFFECT_MONO_LIST] =3D "monocolor", +}; + +struct oxp_gen_1_rgb_report { + u8 report_id; + u8 message_id; + u8 padding_2[2]; + u8 effect; + u8 enabled; + u8 speed; + u8 brightness; + u8 red; + u8 green; + u8 blue; +} __packed; + +static u16 get_usage_page(struct hid_device *hdev) +{ + return hdev->collection[0].usage >> 16; +} + +static int oxp_hid_raw_event_gen_1(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_1_rgb_report *rgb_rep; + + if (data[1] !=3D OXP_FID_GEN1_RGB_REPLY) + return 0; + + rgb_rep =3D (struct oxp_gen_1_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) +{ + u16 up =3D get_usage_page(hdev); + + dev_dbg(&hdev->dev, "raw event data: [%*ph]\n", OXP_PACKET_SIZE, data); + + switch (up) { + case GEN1_USAGE_PAGE: + return oxp_hid_raw_event_gen_1(hdev, report, data, size); + default: + break; + } + + return 0; +} + +static int mcu_property_out(u8 *header, size_t header_size, u8 *data, + size_t data_size, u8 *footer, size_t footer_size) +{ + unsigned char *dmabuf __free(kfree) =3D kzalloc(OXP_PACKET_SIZE, GFP_KERN= EL); + int ret; + + if (!dmabuf) + return -ENOMEM; + + if (header_size + data_size + footer_size > OXP_PACKET_SIZE) + return -EINVAL; + + guard(mutex)(&drvdata.cfg_mutex); + memcpy(dmabuf, header, header_size); + memcpy(dmabuf + header_size, data, data_size); + if (footer_size) + memcpy(dmabuf + OXP_PACKET_SIZE - footer_size, footer, footer_size); + + dev_dbg(&drvdata.hdev->dev, "raw data: [%*ph]\n", OXP_PACKET_SIZE, dmabuf= ); + + ret =3D hid_hw_output_report(drvdata.hdev, dmabuf, OXP_PACKET_SIZE); + if (ret < 0) + return ret; + + /* MCU takes 200ms to be ready for another command. */ + msleep(200); + return ret =3D=3D OXP_PACKET_SIZE ? 0 : -EIO; +} + +static int oxp_gen_1_property_out(enum oxp_function_index fid, u8 *data, + u8 data_size) +{ + u8 header[] =3D { fid, GEN1_MESSAGE_ID }; + size_t header_size =3D ARRAY_SIZE(header); + + return mcu_property_out(header, header_size, data, data_size, NULL, 0); +} + +static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) +{ + u16 up =3D get_usage_page(drvdata.hdev); + u8 *data; + + /* Always default to max brightness and use intensity scaling when in + * monocolor mode. + */ + switch (up) { + case GEN1_USAGE_PAGE: + data =3D (u8[4]) { OXP_SET_PROPERTY, enabled, speed, 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); + default: + return -ENODEV; + } +} + +static ssize_t oxp_rgb_status_show(void) +{ + u16 up =3D get_usage_page(drvdata.hdev); + u8 *data; + + switch (up) { + case GEN1_USAGE_PAGE: + data =3D (u8[1]) { OXP_GET_PROPERTY }; + return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); + default: + return -ENODEV; + } +} + +static int oxp_rgb_color_set(void) +{ + u8 max_br =3D drvdata.led_mc->led_cdev.max_brightness; + u8 br =3D drvdata.led_mc->led_cdev.brightness; + u16 up =3D get_usage_page(drvdata.hdev); + u8 green, red, blue; + size_t size; + u8 *data; + int i; + + red =3D br * drvdata.led_mc->subled_info[0].intensity / max_br; + green =3D br * drvdata.led_mc->subled_info[1].intensity / max_br; + blue =3D br * drvdata.led_mc->subled_info[2].intensity / max_br; + + switch (up) { + case GEN1_USAGE_PAGE: + size =3D 55; + data =3D (u8[55]) { OXP_EFFECT_MONO_TRUE }; + + for (i =3D 0; i < (size - 1) / 3; i++) { + data[3 * i + 1] =3D red; + data[3 * i + 2] =3D green; + data[3 * i + 3] =3D blue; + } + return oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, size); + default: + return -ENODEV; + } +} + +static int oxp_rgb_effect_set(u8 effect) +{ + u16 up =3D get_usage_page(drvdata.hdev); + u8 *data; + int ret; + + switch (effect) { + case OXP_EFFECT_AURORA: + case OXP_EFFECT_BIRTHDAY: + case OXP_EFFECT_FLOWING: + case OXP_EFFECT_CHROMA_1: + case OXP_EFFECT_NEON: + case OXP_EFFECT_CHROMA_2: + case OXP_EFFECT_DREAMY: + case OXP_EFFECT_WARM: + case OXP_EFFECT_CYBERPUNK: + case OXP_EFFECT_SEA: + case OXP_EFFECT_SUNSET: + case OXP_EFFECT_COLORFUL: + case OXP_EFFECT_MONSTER: + case OXP_EFFECT_GREEN: + case OXP_EFFECT_BLUE: + case OXP_EFFECT_YELLOW: + case OXP_EFFECT_TEAL: + case OXP_EFFECT_PURPLE: + case OXP_EFFECT_FOGGY: + switch (up) { + case GEN1_USAGE_PAGE: + data =3D (u8[1]) { effect }; + ret =3D oxp_gen_1_property_out(OXP_FID_GEN1_RGB_SET, data, 1); + break; + default: + ret =3D -ENODEV; + } + break; + case OXP_EFFECT_MONO_LIST: + ret =3D oxp_rgb_color_set(); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + drvdata.rgb_effect =3D effect; + + return 0; +} + +static ssize_t enabled_store(struct device *dev, struct device_attribute *= attr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret =3D sysfs_match_string(oxp_feature_en_text, buf); + if (ret < 0) + return ret; + val =3D ret; + + ret =3D oxp_rgb_status_store(val, drvdata.rgb_speed, + drvdata.rgb_brightness); + if (ret) + return ret; + + drvdata.rgb_en =3D val; + return count; +} + +static ssize_t enabled_show(struct device *dev, struct device_attribute *a= ttr, + char *buf) +{ + int ret; + + ret =3D oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_en >=3D ARRAY_SIZE(oxp_feature_en_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.rgb_en]); +} +static DEVICE_ATTR_RW(enabled); + +static ssize_t enabled_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count =3D 0; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(oxp_feature_en_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} +static DEVICE_ATTR_RO(enabled_index); + +static ssize_t effect_store(struct device *dev, struct device_attribute *a= ttr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret =3D sysfs_match_string(oxp_rgb_effect_text, buf); + if (ret < 0) + return ret; + + val =3D ret; + + ret =3D oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, + drvdata.rgb_brightness); + if (ret) + return ret; + + ret =3D oxp_rgb_effect_set(val); + if (ret) + return ret; + + return count; +} + +static ssize_t effect_show(struct device *dev, struct device_attribute *at= tr, + char *buf) +{ + int ret; + + ret =3D oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_effect >=3D ARRAY_SIZE(oxp_rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_rgb_effect_text[drvdata.rgb_effect]); +} + +static DEVICE_ATTR_RW(effect); + +static ssize_t effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + size_t count =3D 0; + unsigned int i; + + for (i =3D 1; i < ARRAY_SIZE(oxp_rgb_effect_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", oxp_rgb_effect_text[i]); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} +static DEVICE_ATTR_RO(effect_index); + +static ssize_t speed_store(struct device *dev, struct device_attribute *at= tr, + const char *buf, size_t count) +{ + int ret; + u8 val; + + ret =3D kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 9) + return -EINVAL; + + ret =3D oxp_rgb_status_store(drvdata.rgb_en, val, drvdata.rgb_brightness); + if (ret) + return ret; + + drvdata.rgb_speed =3D val; + return count; +} + +static ssize_t speed_show(struct device *dev, struct device_attribute *att= r, + char *buf) +{ + int ret; + + ret =3D oxp_rgb_status_show(); + if (ret) + return ret; + + if (drvdata.rgb_speed > 9) + return -EINVAL; + + return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed); +} +static DEVICE_ATTR_RW(speed); + +static ssize_t speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-9\n"); +} +static DEVICE_ATTR_RO(speed_range); + +static void oxp_rgb_queue_fn(struct work_struct *work) +{ + unsigned int max_brightness =3D drvdata.led_mc->led_cdev.max_brightness; + unsigned int brightness =3D drvdata.led_mc->led_cdev.brightness; + u8 val =3D 4 * brightness / max_brightness; + int ret; + + if (drvdata.rgb_brightness !=3D val) { + ret =3D oxp_rgb_status_store(drvdata.rgb_en, drvdata.rgb_speed, val); + if (ret) + dev_err(drvdata.led_mc->led_cdev.dev, + "Error: Failed to write RGB Status: %i\n", ret); + + drvdata.rgb_brightness =3D val; + } + + if (drvdata.rgb_effect !=3D OXP_EFFECT_MONO_LIST) + return; + + ret =3D oxp_rgb_effect_set(drvdata.rgb_effect); + if (ret) + dev_err(drvdata.led_mc->led_cdev.dev, "Error: Failed to write RGB color:= %i\n", + ret); +} + +static void oxp_rgb_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + led_cdev->brightness =3D brightness; + mod_delayed_work(system_wq, &drvdata.oxp_rgb_queue, msecs_to_jiffies(50)); +} + +static struct attribute *oxp_rgb_attrs[] =3D { + &dev_attr_effect.attr, + &dev_attr_effect_index.attr, + &dev_attr_enabled.attr, + &dev_attr_enabled_index.attr, + &dev_attr_speed.attr, + &dev_attr_speed_range.attr, + NULL, +}; + +static const struct attribute_group oxp_rgb_attr_group =3D { + .attrs =3D oxp_rgb_attrs, +}; + +static struct mc_subled oxp_rgb_subled_info[] =3D { + { + .color_index =3D LED_COLOR_ID_RED, + .intensity =3D 0x24, + .channel =3D 0x1, + }, + { + .color_index =3D LED_COLOR_ID_GREEN, + .intensity =3D 0x22, + .channel =3D 0x2, + }, + { + .color_index =3D LED_COLOR_ID_BLUE, + .intensity =3D 0x99, + .channel =3D 0x3, + }, +}; + +static struct led_classdev_mc oxp_cdev_rgb =3D { + .led_cdev =3D { + .name =3D "oxp:rgb:joystick_rings", + .color =3D LED_COLOR_ID_RGB, + .brightness =3D 0x64, + .max_brightness =3D 0x64, + .brightness_set =3D oxp_rgb_brightness_set, + }, + .num_colors =3D ARRAY_SIZE(oxp_rgb_subled_info), + .subled_info =3D oxp_rgb_subled_info, +}; + +static int oxp_cfg_probe(struct hid_device *hdev, u16 up) +{ + int ret; + + hid_set_drvdata(hdev, &drvdata); + mutex_init(&drvdata.cfg_mutex); + drvdata.hdev =3D hdev; + drvdata.led_mc =3D &oxp_cdev_rgb; + + INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn); + ret =3D devm_led_classdev_multicolor_register(&hdev->dev, &oxp_cdev_rgb); + if (ret) + return dev_err_probe(&hdev->dev, ret, + "Failed to create RGB device\n"); + + ret =3D devm_device_add_group(drvdata.led_mc->led_cdev.dev, + &oxp_rgb_attr_group); + if (ret) + return dev_err_probe(drvdata.led_mc->led_cdev.dev, ret, + "Failed to create RGB configuration attributes\n"); + + ret =3D oxp_rgb_status_show(); + if (ret) + dev_warn(drvdata.led_mc->led_cdev.dev, + "Failed to query RGB initial state: %i\n", ret); + + return 0; +} + +static int oxp_hid_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int ret; + u16 up; + + ret =3D hid_parse(hdev); + if (ret) + return dev_err_probe(&hdev->dev, ret, "Failed to parse HID device\n"); + + ret =3D hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + return dev_err_probe(&hdev->dev, ret, "Failed to start HID device\n"); + + ret =3D hid_hw_open(hdev); + if (ret) { + hid_hw_stop(hdev); + return dev_err_probe(&hdev->dev, ret, "Failed to open HID device\n"); + } + + up =3D get_usage_page(hdev); + dev_dbg(&hdev->dev, "Got usage page %04x\n", up); + + switch (up) { + case GEN1_USAGE_PAGE: + ret =3D oxp_cfg_probe(hdev, up); + if (ret) { + hid_hw_close(hdev); + hid_hw_stop(hdev); + } + + return ret; + default: + return 0; + } +} + +static void oxp_hid_remove(struct hid_device *hdev) +{ + cancel_delayed_work(&drvdata.oxp_rgb_queue); + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static const struct hid_device_id oxp_devices[] =3D { + { HID_USB_DEVICE(USB_VENDOR_ID_CRSC, USB_DEVICE_ID_ONEXPLAYER_GEN1) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, oxp_devices); +static struct hid_driver hid_oxp =3D { + .name =3D "hid-oxp", + .id_table =3D oxp_devices, + .probe =3D oxp_hid_probe, + .remove =3D oxp_hid_remove, + .raw_event =3D oxp_hid_raw_event, +}; +module_hid_driver(hid_oxp); + +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("Driver for OneXPlayer HID Interfaces"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Fri Jun 12 10:13:28 2026 Received: from mail-dy1-f172.google.com (mail-dy1-f172.google.com [74.125.82.172]) (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 255D935E940 for ; Sun, 19 Apr 2026 04:26:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572793; cv=none; b=Lyhf+izRRUohIDndN1xqFmFCILZn7h8PjkoLe6KoyLJEvaxcOAYoQ9Pcmqt3VA2HTp5zR3zByoDg1zRQ0APHdLd0zS8fbQ1cu0SvfZcT+4rG4f5Yfgcuo/cpEozmDn0ID343UWnVLuV3PVtD1/cKXRm5ee2WvnyLLoOS5WQhHTg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572793; c=relaxed/simple; bh=L4XGYV1xGNK5tvTGZ+Q0ZsLPHiEPp41+QXbzo0cVLdE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=erjhDpknO4S81A6wBAPPFi08IYwy3LzGcLrqQM5gotQeVznM7FdX1Ib4slmZwSf7KLB5dYus4BMk9lHEIiOvfvXIicTkgcAFmBxwA/czCpEmWppHEiOUhcxFGwhG+nsHTZBcQ1tNbUBHYwD+2ydo+JxWNkZwmJE7WwCU/0yt7S8= 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=QMyOE43H; arc=none smtp.client-ip=74.125.82.172 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="QMyOE43H" Received: by mail-dy1-f172.google.com with SMTP id 5a478bee46e88-2d832f2f44cso2190941eec.0 for ; Sat, 18 Apr 2026 21:26:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776572788; x=1777177588; 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=U0tmU4kwx0IKZDOyDiuG2KSwchT5ymVC5ZFjoriu4A8=; b=QMyOE43H4iUNV1anUbcHpCfMKqRDe4wZw7DxG6jzz1U8p2Vf4ZrU0/w4EAT0eji5cZ jVhHuhWT7PhcY/pgjKYs4IQb5yyU9Ef4aIjZMi704EUig4A0JmEEytKZwrBv232/SVAW gkDOfhrYMxdgW6wHo4rhAj8aB/UBrTJfIb6ewncM193HncTYfLTX+pSipFsacSpn/u+B QlaLEs/dkATRSEPfcYBn3kUoI5jsTy4T+D2OADPyWFUfbU36ezGJXdOiG6ZmaqmIfrc7 u41joCjTSR/ZpEulc0q78ZrffhrYiVOurcKlB3kX/9j/ewbBkuX9lSZvKXt66ZFjwtc+ t6TA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776572788; x=1777177588; 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=U0tmU4kwx0IKZDOyDiuG2KSwchT5ymVC5ZFjoriu4A8=; b=P9OljNeyQcN4fiEKAXz6GP4S23cVz+VCw5T4kDD8qqwoBcXh+0tNM5wKZ4VNfBoSnI ZjkHWtBEwtSkMa3OtXJ5gHXZordhCmOXs/3/aEg1pVs66gtp0QkpQTc5cuXaY4DoRkt/ 0TEqTAB+aUHDnxD3nYqgj3w+Nqm+utVEO3ikyTU25SqPXcmRe17rTSK1/WVW4GX8Xltq rPnPivrcg4ia1aDaAbXC25TauuJOOVniYyKx16KhHBV9vu0cC2V5IlTgu+cUN/wuE8JT wSAnYJVNALD6zk0ubQ4dQbDAjJKW1XtSn8JdxQMgaCUTFUIWmPZQEI4+d28GIhC27VDK pr6w== X-Forwarded-Encrypted: i=1; AFNElJ8Pk2+wH9HJ6GN9mVK3T8VufUe1PGrxSLGzc1KPXYQxgtt+/nte8NGhW0hR2rnIjaYMviDWtF5DkRg86DQ=@vger.kernel.org X-Gm-Message-State: AOJu0YxTGEz2CkAGRR8Li/j7hSzwRycDuWLqznfxmuMEUVYohNMnaQTi EpaIWmZzG3MpIvPXV2jgcG3sSx2EEBslv1997KzvsGdTHMx/02DX17D0 X-Gm-Gg: AeBDiesMLpHugJ4C4xkavRSuJ6yrqe0KT4oO8wpWgiyD1+081GWA1nxKG0zkQ/vW8Ft RC6sSRtfEPSrwYN85+IHVRGrpuKjVsIxwcV8WaYY5a2/rHTlChOzMmp1b5dzPRL/PIo6+labDcD aHcW8+edWNnzeeFnMd2fS2Wu0BDfqSyEvHOZxxXkKiQ5Yf8P1iXpfTb9AtX9i891E1tnxsxRIP9 Ml49yhd+dzXh6+8xebRQ4Fp5SvnCjUnTwg4k/ozw0taeWnFm+55KhbCK+y6XeZ8DWiHX7q3N55p Qmb9dsg7HiH9W7RcC7/WBZkYXKD8CymiK5AGEA5js/k6C2S23XUtRyRgy5k+1jUu5+5wMqSJxvJ p3+Gn46qnedGTwysCErafySQq12XxWX+7sFqtaGjCIqmibxGbDBQHZuBrX3WzYG/m/yFksO/GTF hrBXR8T3KXIY5ErhcaAHQmdl/f36bjDGXB5lvUXCHYG60CqkgFFz6XhUeXUHei0nD+t5NDtl/yr /zHt7YfnXnkXFwkPsTnIPBMhg== X-Received: by 2002:a05:693c:2b08:b0:2c1:7afc:df06 with SMTP id 5a478bee46e88-2e464ea7057mr4009022eec.5.1776572787893; Sat, 18 Apr 2026 21:26:27 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2e53dcb487bsm8796469eec.31.2026.04.18.21.26.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Apr 2026 21:26:27 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: "Pierre-Loup A . Griffais" , Lambert Fan , Zhouwang Huang , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 2/5] HID: hid-oxp: Add Second Generation RGB Control Date: Sat, 18 Apr 2026 21:26:21 -0700 Message-ID: <20260419042624.625746-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com> References: <20260419042624.625746-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. Reviewed-by: Zhouwang Huang Tested-by: Zhouwang Huang 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 f72bc74a7e6e..835de2118e3c 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 { @@ -122,6 +126,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; @@ -162,6 +182,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) { @@ -172,6 +230,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; } @@ -217,6 +277,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); @@ -231,6 +303,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; } @@ -245,6 +322,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; } @@ -275,6 +355,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; } @@ -311,6 +401,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; } @@ -559,6 +653,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; @@ -566,6 +710,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 INIT_DELAYED_WORK(&drvdata.oxp_rgb_queue, oxp_rgb_queue_fn); @@ -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); @@ -634,6 +784,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 From nobody Fri Jun 12 10:13:28 2026 Received: from mail-dy1-f173.google.com (mail-dy1-f173.google.com [74.125.82.173]) (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 BFB0F372EFB for ; Sun, 19 Apr 2026 04:26:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.173 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572793; cv=none; b=iZBEtyDcnjC9T9mxAtb7ymnVcPd23ZAC9dRfExc6vXLT8KHUvcoD58yiMGC2pdoTPLpcGE+v+ZMALw2io6RuXv4hUcCVTNBFR9HBuaCfrt0wfyPvAMHP4R1JSBt4QyEzNQz0WYzPbg6vtUzeI5yFz6GRFhxYGWXpmIYIeRh7wiQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572793; c=relaxed/simple; bh=Vi9PqZV0aXECK80MyjVb3o/Dg8qbSiQd83dlKewCK2Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X/VVedc0CqeSQ/qRWt365KnJp5PL8P1GDGnRcalrCG+gHFX70CXizrMqTfidk33p6lP4dfOkYII1VENpYhQ1vG8+UeS6xNkSYNZJiTPj6yVuYfRyzB7Wrp2egOfVYySjhwziJwUkdk7wO6p24Va3p5MDFDR1mqISFEk4mRsl2II= 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=jro1ctLi; arc=none smtp.client-ip=74.125.82.173 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="jro1ctLi" Received: by mail-dy1-f173.google.com with SMTP id 5a478bee46e88-2e221a71e19so1738436eec.0 for ; Sat, 18 Apr 2026 21:26:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776572789; x=1777177589; 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=g1JsOiO2bFXAeWBZ1uKhtK3IbGV3pZa0fjeBJoVwYso=; b=jro1ctLiNMP24BXEBsaLeXFbaYxCGxAGYxjVlybS2VmUpYVBm/BNlA0RPnAkEexBdu 582t8FNrLBbPL90Co+yynAIcOck1vTRA9/g9g7EZ1dwvtUGd0fahA97igJxCo+oL7Rru hqQM4rYod02yptnUJWXga8cXx5HSGBGcBU2anv9ruRyaP5STRFhQ6pYJCGfFXJMU7/ii 6ZoNDor2nafNG4QNLU2lR3mDJrlQT3j9D6cJBx7ELIL0aZrxh8xudCn3IjMuuRmIQVs3 S3Kk6D60s2yW/BjjFyWKuIYwKECHQ7Iofit+o4T2NRQRQyxMqQpie1oqh4JWMojDKyQ0 y0Cw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776572789; x=1777177589; 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=g1JsOiO2bFXAeWBZ1uKhtK3IbGV3pZa0fjeBJoVwYso=; b=N3ZPQaXI3n3SPnAo+YaaLVxC/++FNdDdk8SuRmWOCkKD1CmsJdEyl1pdwBEUW3zZ5+ rrH40N7/pRkfJJZk7vWi3nOOxjf+idJlyL6lq6PCcGS3lufB0o0Ql7xyD0HnclBov2bv 8Y17i21lcuJP/XAM7QRoIxpOemYEPmMcjEgbrJHY3K4kYctJ+b+dGY/O1PQuqa4qz7vh 6iClSuu/iFiZEx6u7VZD8sbRD+hVAzkhuvn0W3vYmo4lQxNiAFF91kSa+4EPQi+UDXBd 5ayVoAFs5DLZ6fiI/Jj362CsugsDqa8c8WHcgYjYwcopYxmDJQ62sSt4rFDcq/agJISw 1gkQ== X-Forwarded-Encrypted: i=1; AFNElJ8vauWp/PjilVlwFvybzHOAdcPTOGDMqTgFZIPCya41ff6uibccl3c8wQso1ELJGhb4mt0SOBB9MFhK3uI=@vger.kernel.org X-Gm-Message-State: AOJu0YwI8f+IUEgRTuWbyIn0BaY1hwaxbfc2cGujlA3RPavo1aYFIqzp 6GlXiKeDHBY3HfgU2NuuIaxLQo6TH+sgeXqGCcEuov954NeEyXYZ5XBI X-Gm-Gg: AeBDieubsTSTHI5MvhN4/i42NdMMiILb0Wg+tM4S4T25LpPehH4kjNamB8fpSnO8QXT gxHqc6zwnaIGHCjkH/sLpHEyDu/LTirdSbmFH2uH90EEj6GAPBmzUe1sbgJUSY7cNGyk5o7PYx9 lKG8+hZuVSVK2fbmszO3sdpBwBIdnKqjPTmQjLpguoN3ebECDkveqlkDIo//LPC1MjPZIhA5FT6 6td3UtmNVhUT8MPsYkyUOfD779BINmzUzfgWbOfVvdPaHhytq3Sy2NcaTCJl0oqYlW+ZS37EQdn iNRwxZClPIE1ih+0GgF+u7M89GnRQ7L7ylBITH+IEie1GuxFfaBJQ5PzVJYnb1GCG6Y/IxuZ7xV MZmdsF8g0lrN0KYkANy6o4v7OwUDo+os+Di3RFHGXS4CAKi8c4lkvqhTgF1C61fJnGd3FJCmzNo 84F9dRvg8G+4ieK/+TLoApXbY2zHFdYjBlO3BlxCjphjpBHzh2WsKClMxFBdjSZ4BOZCDjVQ/p2 0jNGpvHLvKrJtZ35nOqeu7SBg== X-Received: by 2002:a05:7301:d17:b0:2c5:50fe:c78f with SMTP id 5a478bee46e88-2e47816b33dmr3945383eec.12.1776572788578; Sat, 18 Apr 2026 21:26:28 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2e53dcb487bsm8796469eec.31.2026.04.18.21.26.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Apr 2026 21:26:28 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: "Pierre-Loup A . Griffais" , Lambert Fan , Zhouwang Huang , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 3/5] HID: hid-oxp: Add Second Generation Gamepad Mode Switch Date: Sat, 18 Apr 2026 21:26:22 -0700 Message-ID: <20260419042624.625746-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com> References: <20260419042624.625746-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 "gamepad_mode" attribute to second generation OneXPlayer configuration HID devices. This attribute initiates a mode shift in the device MCU that puts it into a state where all events are routed to an hidraw interface instead of the xpad evdev interface. This allows for debugging the hardware input mapping added in the next patch. Reviewed-by: Zhouwang Huang Tested-by: Zhouwang Huang Signed-off-by: Derek J. Clark --- v4: - Add oxp_mcu_init delayed work struct to drvdata & properly init, add cancel delayed work during remove. v2: - Rename to gamepad_mode & show relevant gamepad modes instead of using a debug enable/disable paradigm, to match other drivers. --- drivers/hid/hid-oxp.c | 131 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index 835de2118e3c..2504b56b8f8a 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -33,20 +33,33 @@ enum oxp_function_index { OXP_FID_GEN1_RGB_SET =3D 0x07, OXP_FID_GEN1_RGB_REPLY =3D 0x0f, + OXP_FID_GEN2_TOGGLE_MODE =3D 0xb2, OXP_FID_GEN2_STATUS_EVENT =3D 0xb8, }; =20 static struct oxp_hid_cfg { struct delayed_work oxp_rgb_queue; + struct delayed_work oxp_mcu_init; struct led_classdev_mc *led_mc; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 rgb_brightness; + u8 gamepad_mode; u8 rgb_effect; u8 rgb_speed; u8 rgb_en; } drvdata; =20 +enum oxp_gamepad_mode_index { + OXP_GP_MODE_XINPUT =3D 0x00, + OXP_GP_MODE_DEBUG =3D 0x03, +}; + +static const char *const oxp_gamepad_mode_text[] =3D { + [OXP_GP_MODE_XINPUT] =3D "xinput", + [OXP_GP_MODE_DEBUG] =3D "debug", +}; + enum oxp_feature_en_index { OXP_FEAT_DISABLED, OXP_FEAT_ENABLED, @@ -182,6 +195,30 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *= hdev, return 0; } =20 +static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u= 8 data_size); + +static void oxp_mcu_init_fn(struct work_struct *work) +{ + u8 gp_mode_data[3] =3D { OXP_GP_MODE_DEBUG, 0x01, 0x02 }; + int ret; + + /* Cycle the gamepad mode */ + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set gamepad mode: %i\n", ret); + + /* Remainder only applies for xinput mode */ + if (drvdata.gamepad_mode =3D=3D OXP_GP_MODE_DEBUG) + return; + + gp_mode_data[0] =3D OXP_GP_MODE_XINPUT; + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set gamepad mode: %i\n", ret); +} + static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) @@ -192,6 +229,14 @@ static int oxp_hid_raw_event_gen_2(struct hid_device *= hdev, if (data[0] !=3D OXP_FID_GEN2_STATUS_EVENT) return 0; =20 + /* Sent ~6s after resume event, indicating the MCU has fully reset. + * Re-apply our settings after this has been received. + */ + if (data[3] =3D=3D OXP_EFFECT_MONO_TRUE) { + mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + return 0; + } + if (data[3] !=3D OXP_GET_PROPERTY) return 0; =20 @@ -289,6 +334,77 @@ static int oxp_gen_2_property_out(enum oxp_function_in= dex fid, u8 *data, footer_size); } =20 +static ssize_t gamepad_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u16 up =3D get_usage_page(drvdata.hdev); + u8 data[3] =3D { 0x00, 0x01, 0x02 }; + int ret =3D -EINVAL; + int i; + + if (up !=3D GEN2_USAGE_PAGE) + return ret; + + for (i =3D 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { + if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i= ])) { + ret =3D i; + break; + } + } + if (ret < 0) + return ret; + + data[0] =3D ret; + + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3); + if (ret) + return ret; + + drvdata.gamepad_mode =3D data[0]; + + return count; +} + +static ssize_t gamepad_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", oxp_gamepad_mode_text[drvdata.gamepad_mode= ]); +} +static DEVICE_ATTR_RW(gamepad_mode); + +static ssize_t gamepad_mode_index_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) { + if (!oxp_gamepad_mode_text[i] || + oxp_gamepad_mode_text[i][0] =3D=3D '\0') + continue; + + count +=3D sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]); + } + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} +static DEVICE_ATTR_RO(gamepad_mode_index); + +static struct attribute *oxp_cfg_attrs[] =3D { + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + NULL, +}; + +static const struct attribute_group oxp_cfg_attrs_group =3D { + .attrs =3D oxp_cfg_attrs, +}; + static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness) { u16 up =3D get_usage_page(drvdata.hdev); @@ -733,7 +849,21 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16 = up) dev_warn(drvdata.led_mc->led_cdev.dev, "Failed to query RGB initial state: %i\n", ret); =20 + /* Below features are only implemented in gen 2 */ + if (up !=3D GEN2_USAGE_PAGE) + return 0; + skip_rgb: + drvdata.gamepad_mode =3D OXP_GP_MODE_XINPUT; + + INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); + mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); + + ret =3D devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group); + if (ret) + return dev_err_probe(&hdev->dev, ret, + "Failed to attach configuration attributes\n"); + return 0; } =20 @@ -778,6 +908,7 @@ static int oxp_hid_probe(struct hid_device *hdev, static void oxp_hid_remove(struct hid_device *hdev) { cancel_delayed_work(&drvdata.oxp_rgb_queue); + cancel_delayed_work(&drvdata.oxp_mcu_init); hid_hw_close(hdev); hid_hw_stop(hdev); } --=20 2.53.0 From nobody Fri Jun 12 10:13:28 2026 Received: from mail-dl1-f45.google.com (mail-dl1-f45.google.com [74.125.82.45]) (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 B1E98372ECD for ; Sun, 19 Apr 2026 04:26:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572795; cv=none; b=uBhX6DrDnrL8ELIjXzEaJ0YozJqCw9E7GgukpobI9cRBD5kuJ+iCfMonWU4QEYVC+Fk3KJtZXcTrnHlI2WOJvDrOp61oJjkHP9KpA6Kic20U8WwJaGxPRIgfsUKfq646dB4btm5TtoidNOZmTQ2Bc9DOvU+/8akwb9hSdjgluNI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572795; c=relaxed/simple; bh=LgD3FIRCAIMuLvLzwoWuA9qEwZCrrdtdgq0Dnd/lxQU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IYJrrFw/foOVoc2bPBK1sbwteWUfkTYTch7+47TUmD9h/VWpSAQ5b3ZioKPs0Whr0RSQomRJECf0zDRuuwtHBppD3LCjx/ymiCV36y9rjtMUJ0leQYwGBYhcUK9gmiMtKNz9U7lRg5K31NneDStex9XWKX00wVN5EBeSJ2X0LXg= 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=L/INHZzv; arc=none smtp.client-ip=74.125.82.45 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="L/INHZzv" Received: by mail-dl1-f45.google.com with SMTP id a92af1059eb24-1273349c56bso2459162c88.0 for ; Sat, 18 Apr 2026 21:26:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776572790; x=1777177590; 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=SxtWDnDOWq4HcRCVvL6TV/DscorWncP2mVxrDVFzMj8=; b=L/INHZzvnoAwNFJ/ZuAP08yC978n3GO1OiTHj4bUoLTg60mmSm3PsBHFPiUo9BjfTQ JYVyUVQyGjwZ9gaANwkZHNsbLxMiCxVrFF87pRAeAmhYsDxOOW5Vl/kg7g1mYYvW9j99 R0mXD0Hp8Mh+jerSEWSOiTcdiRYW1agJHaplfFjUokBpbYvTfseVTvuAvaLC94Y18Cjl BnO2aW5KPB/s/gXVKl21XLGhgaIwXMTPaY+aticmmbQb4c8v/zFbudfpD5rEhW9Z4SBl oEmbHr4xROX0Mywu+uTv1hbqDuYF8XhGjIUX5jld4WpHUwkgaPcgl+6+K/VH7/CQv5Em OV4Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776572790; x=1777177590; 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=SxtWDnDOWq4HcRCVvL6TV/DscorWncP2mVxrDVFzMj8=; b=NR9/9tnlHMCZMAMt9UeBQAIm51n7fvhKGnVmgPT2IBJNKcfF+AVU0e+ehaLiz0WvwD +x39BF4JAehyV4g0t3V0Tv+QML99Xv8dSYdoevXUAwWF39HOB2BnegMXuhBkjXge2AQl PcHNa+9gcgy4F1W58PYyiPOSV074Pjr95KQGjZm+K5CYnzWL0GapznBGpKVVZ2/PAxuc im1yDfow6w9K1IGew7guDKQUfIhShs4nKgfLCOyzylIGvdrUth/qvUXziNV2PH0wd02Q 6bN4CIrOugJYpiqDXFzBRDS5EgDsqLi/NMLf1QuLWi0rNK7lrJ4n4HnMS3u3z9tZU8Fn Iw2Q== X-Forwarded-Encrypted: i=1; AFNElJ9pxmfTh3eRjVfAXg1NTX5pCkhMH0VTJC9aH9yTxRKAE2qhnXGbeVqh4vsjQm9o4llc/ljAlqzXP2kxfpo=@vger.kernel.org X-Gm-Message-State: AOJu0Ywof2sC6MB3qGFktM/C1+Y460aRxqApi5lS7Zkjfghx3AdzIBta AxNUkUW3u6guZIkkEeX7rQgr0vmQLM8ebmuGmZI8gWHB5mIr1Bkj2AfI X-Gm-Gg: AeBDieszWjtSwfck3LWENOmxI30e6MFmhiWnx9JUG1Oh+SsLz6JTaz1AMLrpLD8weFR jRnE8djq019GE3rvCdexjoPf5KItpBWVZ3ergtLgiRIyisW6SNsEem4cjcj2kCcPgkpCTHYhf6N 0Zk/EkNQNa4JyjAn0pcVbv1wKR6/kdrTK1k1nsAxfTKOEDQAdpIpEkIdjJ17/Ea5g/ODrSF184O cPIxZIDcfRMjE0msjOjbS09Ydaz+AVvZ2v08cl6j9kAj9Me3MF7iEGm2eBG6RmtgwMn/n8T+45y pGoPW2FJCKEerhYBEgQKsrkOaG0Gjlcr5BvS8gpuvbRFfj//eSS+yDcJIOe4iiGINnbXgCq0YWc VmdYxSE1c9vM3Sk454eh9sG0TsLiGW1Uvy0NwmU90yYdRXAuNoTmjgcZ7oOcJf0bFdgQhu1ncoE 7Ax2vF7GUo1SjJsXscpse2HnFuA0SMlJQKQqsDUHynU7l+eknd51+Rx421yxYdUJ6T9qbfGa2oa vlkvmHnSj9983w= X-Received: by 2002:a05:7022:90a:b0:127:33e0:ea40 with SMTP id a92af1059eb24-12c73f7fbaamr3689707c88.15.1776572789321; Sat, 18 Apr 2026 21:26:29 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2e53dcb487bsm8796469eec.31.2026.04.18.21.26.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Apr 2026 21:26:28 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: "Pierre-Loup A . Griffais" , Lambert Fan , Zhouwang Huang , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 4/5] HID: hid-oxp: Add Button Mapping Interface Date: Sat, 18 Apr 2026 21:26:23 -0700 Message-ID: <20260419042624.625746-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com> References: <20260419042624.625746-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 button mapping interface for second generation OneXPlayer configuration HID interfaces. This interface allows the MCU to swap button mappings at the hardware level. The current state cannot be retrieved, and the mappings may have been modified in Windows prior, so we reset the button mapping at init and expose an attribute to allow userspace to do this again at any time. The interface requires two pages of button mapping data to be sent before the settings will take place. Since the MCU requires a 200ms delay after each message (total 400ms for these attributes) use the same debounce work queue method we used for RGB. This will allow for userspace or udev rules to rapidly map all buttons. The values will be cached before the final write is finally sent to the device. Reviewed-by: Zhouwang Huang Tested-by: Zhouwang Huang Signed-off-by: Derek J. Clark --- v4: - Make oxp_btn_queue delayed work struct part of drvdata, add cancel delayed work during remove. v3: - Ensure default button map is properly init during probe. v2: - Add detection of post-suspend MCU init to trigger setting the button map again. --- drivers/hid/hid-oxp.c | 568 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 568 insertions(+) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index 2504b56b8f8a..52002d4cbd0b 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -34,11 +34,147 @@ enum oxp_function_index { OXP_FID_GEN1_RGB_SET =3D 0x07, OXP_FID_GEN1_RGB_REPLY =3D 0x0f, OXP_FID_GEN2_TOGGLE_MODE =3D 0xb2, + OXP_FID_GEN2_KEY_STATE =3D 0xb4, OXP_FID_GEN2_STATUS_EVENT =3D 0xb8, }; =20 +#define OXP_MAPPING_GAMEPAD 0x01 +#define OXP_MAPPING_KEYBOARD 0x02 + +struct oxp_button_data { + u8 mode; + u8 index; + u8 key_id; + u8 padding[2]; +} __packed; + +struct oxp_button_entry { + struct oxp_button_data data; + const char *name; +}; + +static const struct oxp_button_entry oxp_button_table[] =3D { + /* Gamepad Buttons */ + { { OXP_MAPPING_GAMEPAD, 0x01 }, "BTN_A" }, + { { OXP_MAPPING_GAMEPAD, 0x02 }, "BTN_B" }, + { { OXP_MAPPING_GAMEPAD, 0x03 }, "BTN_X" }, + { { OXP_MAPPING_GAMEPAD, 0x04 }, "BTN_Y" }, + { { OXP_MAPPING_GAMEPAD, 0x05 }, "BTN_LB" }, + { { OXP_MAPPING_GAMEPAD, 0x06 }, "BTN_RB" }, + { { OXP_MAPPING_GAMEPAD, 0x07 }, "BTN_LT" }, + { { OXP_MAPPING_GAMEPAD, 0x08 }, "BTN_RT" }, + { { OXP_MAPPING_GAMEPAD, 0x09 }, "BTN_START" }, + { { OXP_MAPPING_GAMEPAD, 0x0a }, "BTN_SELECT" }, + { { OXP_MAPPING_GAMEPAD, 0x0b }, "BTN_L3" }, + { { OXP_MAPPING_GAMEPAD, 0x0c }, "BTN_R3" }, + { { OXP_MAPPING_GAMEPAD, 0x0d }, "DPAD_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x0e }, "DPAD_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x0f }, "DPAD_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x10 }, "DPAD_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x11 }, "JOY_L_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x12 }, "JOY_L_UP_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x13 }, "JOY_L_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x14 }, "JOY_L_DOWN_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x15 }, "JOY_L_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x16 }, "JOY_L_DOWN_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x17 }, "JOY_L_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x18 }, "JOY_L_UP_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x19 }, "JOY_R_UP" }, + { { OXP_MAPPING_GAMEPAD, 0x1a }, "JOY_R_UP_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1b }, "JOY_R_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1c }, "JOY_R_DOWN_RIGHT" }, + { { OXP_MAPPING_GAMEPAD, 0x1d }, "JOY_R_DOWN" }, + { { OXP_MAPPING_GAMEPAD, 0x1e }, "JOY_R_DOWN_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x1f }, "JOY_R_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x20 }, "JOY_R_UP_LEFT" }, + { { OXP_MAPPING_GAMEPAD, 0x22 }, "BTN_GUIDE" }, + /* Keyboard Keys */ + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5a }, "KEY_F1" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5b }, "KEY_F2" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5c }, "KEY_F3" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5d }, "KEY_F4" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5e }, "KEY_F5" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x5f }, "KEY_F6" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x60 }, "KEY_F7" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x61 }, "KEY_F8" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x62 }, "KEY_F9" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x63 }, "KEY_F10" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x64 }, "KEY_F11" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x65 }, "KEY_F12" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x66 }, "KEY_F13" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x67 }, "KEY_F14" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x68 }, "KEY_F15" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x69 }, "KEY_F16" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6a }, "KEY_F17" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6b }, "KEY_F18" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6c }, "KEY_F19" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6d }, "KEY_F20" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6e }, "KEY_F21" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x6f }, "KEY_F22" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x70 }, "KEY_F23" }, + { { OXP_MAPPING_KEYBOARD, 0x01, 0x71 }, "KEY_F24" }, +}; + +enum oxp_joybutton_index { + BUTTON_A =3D 0x01, + BUTTON_B, + BUTTON_X, + BUTTON_Y, + BUTTON_LB, + BUTTON_RB, + BUTTON_LT, + BUTTON_RT, + BUTTON_START, + BUTTON_SELECT, + BUTTON_L3, + BUTTON_R3, + BUTTON_DUP, + BUTTON_DDOWN, + BUTTON_DLEFT, + BUTTON_DRIGHT, + BUTTON_M1 =3D 0x22, + BUTTON_M2, + /* These are unused currently, reserved for future devices */ + BUTTON_M3, + BUTTON_M4, + BUTTON_M5, + BUTTON_M6, +}; + +struct oxp_button_idx { + enum oxp_joybutton_index button_idx; + u8 mapping_idx; +} __packed; + +struct oxp_bmap_page_1 { + struct oxp_button_idx btn_a; + struct oxp_button_idx btn_b; + struct oxp_button_idx btn_x; + struct oxp_button_idx btn_y; + struct oxp_button_idx btn_lb; + struct oxp_button_idx btn_rb; + struct oxp_button_idx btn_lt; + struct oxp_button_idx btn_rt; + struct oxp_button_idx btn_start; +} __packed; + +struct oxp_bmap_page_2 { + struct oxp_button_idx btn_select; + struct oxp_button_idx btn_l3; + struct oxp_button_idx btn_r3; + struct oxp_button_idx btn_dup; + struct oxp_button_idx btn_ddown; + struct oxp_button_idx btn_dleft; + struct oxp_button_idx btn_dright; + struct oxp_button_idx btn_m1; + struct oxp_button_idx btn_m2; +} __packed; + static struct oxp_hid_cfg { struct delayed_work oxp_rgb_queue; + struct delayed_work oxp_btn_queue; + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; struct delayed_work oxp_mcu_init; struct led_classdev_mc *led_mc; struct hid_device *hdev; @@ -50,6 +186,10 @@ static struct oxp_hid_cfg { u8 rgb_en; } drvdata; =20 +#define OXP_FILL_PAGE_SLOT(page, btn) \ + { .button_idx =3D (page)->btn.button_idx, \ + .mapping_idx =3D (page)->btn.mapping_idx } + enum oxp_gamepad_mode_index { OXP_GP_MODE_XINPUT =3D 0x00, OXP_GP_MODE_DEBUG =3D 0x03, @@ -155,6 +295,10 @@ struct oxp_gen_2_rgb_report { u8 effect; } __packed; =20 +struct oxp_attr { + u8 index; +}; + static u16 get_usage_page(struct hid_device *hdev) { return hdev->collection[0].usage >> 16; @@ -196,12 +340,19 @@ static int oxp_hid_raw_event_gen_1(struct hid_device = *hdev, } =20 static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u= 8 data_size); +static int oxp_set_buttons(void); =20 static void oxp_mcu_init_fn(struct work_struct *work) { u8 gp_mode_data[3] =3D { OXP_GP_MODE_DEBUG, 0x01, 0x02 }; int ret; =20 + /* Re-apply the button mapping */ + ret =3D oxp_set_buttons(); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set button mapping: %i\n", ret); + /* Cycle the gamepad mode */ ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, gp_mode_data, 3); if (ret) @@ -395,9 +546,408 @@ static ssize_t gamepad_mode_index_show(struct device = *dev, } static DEVICE_ATTR_RO(gamepad_mode_index); =20 +static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap) +{ + bmap->btn_a.button_idx =3D BUTTON_A; + bmap->btn_a.mapping_idx =3D 0; + bmap->btn_b.button_idx =3D BUTTON_B; + bmap->btn_b.mapping_idx =3D 1; + bmap->btn_x.button_idx =3D BUTTON_X; + bmap->btn_x.mapping_idx =3D 2; + bmap->btn_y.button_idx =3D BUTTON_Y; + bmap->btn_y.mapping_idx =3D 3; + bmap->btn_lb.button_idx =3D BUTTON_LB; + bmap->btn_lb.mapping_idx =3D 4; + bmap->btn_rb.button_idx =3D BUTTON_RB; + bmap->btn_rb.mapping_idx =3D 5; + bmap->btn_lt.button_idx =3D BUTTON_LT; + bmap->btn_lt.mapping_idx =3D 6; + bmap->btn_rt.button_idx =3D BUTTON_RT; + bmap->btn_rt.mapping_idx =3D 7; + bmap->btn_start.button_idx =3D BUTTON_START; + bmap->btn_start.mapping_idx =3D 8; +} + +static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap) +{ + bmap->btn_select.button_idx =3D BUTTON_SELECT; + bmap->btn_select.mapping_idx =3D 9; + bmap->btn_l3.button_idx =3D BUTTON_L3; + bmap->btn_l3.mapping_idx =3D 10; + bmap->btn_r3.button_idx =3D BUTTON_R3; + bmap->btn_r3.mapping_idx =3D 11; + bmap->btn_dup.button_idx =3D BUTTON_DUP; + bmap->btn_dup.mapping_idx =3D 12; + bmap->btn_ddown.button_idx =3D BUTTON_DDOWN; + bmap->btn_ddown.mapping_idx =3D 13; + bmap->btn_dleft.button_idx =3D BUTTON_DLEFT; + bmap->btn_dleft.mapping_idx =3D 14; + bmap->btn_dright.button_idx =3D BUTTON_DRIGHT; + bmap->btn_dright.mapping_idx =3D 15; + bmap->btn_m1.button_idx =3D BUTTON_M1; + bmap->btn_m1.mapping_idx =3D 48; /* KEY_F15 */ + bmap->btn_m2.button_idx =3D BUTTON_M2; + bmap->btn_m2.mapping_idx =3D 49; /* KEY_F16 */ +} + +static void oxp_page_fill_data(char *buf, const struct oxp_button_idx *but= tons, + size_t len) +{ + size_t offset_increment =3D sizeof(u8) + sizeof(struct oxp_button_idx); + size_t offset =3D 5; + unsigned int i; + + for (i =3D 0; i < len; i++, offset +=3D offset_increment) { + buf[offset] =3D (u8)buttons[i].button_idx; + memcpy(buf + offset + 1, + &oxp_button_table[buttons[i].mapping_idx].data, + sizeof(struct oxp_button_data)); + } +} + +static int oxp_set_buttons(void) +{ + u8 page_1[59] =3D { 0x02, 0x38, 0x20, 0x01, 0x01 }; + u8 page_2[59] =3D { 0x02, 0x38, 0x20, 0x02, 0x01 }; + u16 up =3D get_usage_page(drvdata.hdev); + int ret; + + if (up !=3D GEN2_USAGE_PAGE) + return -EINVAL; + + const struct oxp_button_idx p1[] =3D { + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_a), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_b), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_x), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_y), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lb), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rb), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_lt), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_rt), + OXP_FILL_PAGE_SLOT(drvdata.bmap_1, btn_start), + }; + + const struct oxp_button_idx p2[] =3D { + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_select), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_l3), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_r3), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dup), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_ddown), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dleft), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_dright), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m1), + OXP_FILL_PAGE_SLOT(drvdata.bmap_2, btn_m2), + }; + + oxp_page_fill_data(page_1, p1, ARRAY_SIZE(p1)); + oxp_page_fill_data(page_2, p2, ARRAY_SIZE(p2)); + + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_1, ARRAY_SIZE= (page_1)); + if (ret) + return ret; + + return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, page_2, ARRAY_SIZE(= page_2)); +} + +static void oxp_reset_buttons(void) +{ + oxp_set_defaults_bmap_1(drvdata.bmap_1); + oxp_set_defaults_bmap_2(drvdata.bmap_2); +} + +static ssize_t reset_buttons_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int val, ret; + + ret =3D kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val !=3D 1) + return -EINVAL; + + oxp_reset_buttons(); + ret =3D oxp_set_buttons(); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(reset_buttons); + +static void oxp_btn_queue_fn(struct work_struct *work) +{ + int ret; + + ret =3D oxp_set_buttons(); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to write button mapping: %i\n", ret); +} + +static int oxp_button_idx_from_str(const char *buf) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(oxp_button_table); i++) + if (sysfs_streq(buf, oxp_button_table[i].name)) + return i; + + return -EINVAL; +} + +static ssize_t map_button_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count, u8 index) +{ + int idx; + + idx =3D oxp_button_idx_from_str(buf); + if (idx < 0) + return idx; + + switch (index) { + case BUTTON_A: + drvdata.bmap_1->btn_a.mapping_idx =3D idx; + break; + case BUTTON_B: + drvdata.bmap_1->btn_b.mapping_idx =3D idx; + break; + case BUTTON_X: + drvdata.bmap_1->btn_x.mapping_idx =3D idx; + break; + case BUTTON_Y: + drvdata.bmap_1->btn_y.mapping_idx =3D idx; + break; + case BUTTON_LB: + drvdata.bmap_1->btn_lb.mapping_idx =3D idx; + break; + case BUTTON_RB: + drvdata.bmap_1->btn_rb.mapping_idx =3D idx; + break; + case BUTTON_LT: + drvdata.bmap_1->btn_lt.mapping_idx =3D idx; + break; + case BUTTON_RT: + drvdata.bmap_1->btn_rt.mapping_idx =3D idx; + break; + case BUTTON_START: + drvdata.bmap_1->btn_start.mapping_idx =3D idx; + break; + case BUTTON_SELECT: + drvdata.bmap_2->btn_select.mapping_idx =3D idx; + break; + case BUTTON_L3: + drvdata.bmap_2->btn_l3.mapping_idx =3D idx; + break; + case BUTTON_R3: + drvdata.bmap_2->btn_r3.mapping_idx =3D idx; + break; + case BUTTON_DUP: + drvdata.bmap_2->btn_dup.mapping_idx =3D idx; + break; + case BUTTON_DDOWN: + drvdata.bmap_2->btn_ddown.mapping_idx =3D idx; + break; + case BUTTON_DLEFT: + drvdata.bmap_2->btn_dleft.mapping_idx =3D idx; + break; + case BUTTON_DRIGHT: + drvdata.bmap_2->btn_dright.mapping_idx =3D idx; + break; + case BUTTON_M1: + drvdata.bmap_2->btn_m1.mapping_idx =3D idx; + break; + case BUTTON_M2: + drvdata.bmap_2->btn_m2.mapping_idx =3D idx; + break; + default: + return -EINVAL; + } + mod_delayed_work(system_wq, &drvdata.oxp_btn_queue, msecs_to_jiffies(50)); + return count; +} + +static ssize_t map_button_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 index) +{ + u8 i; + + switch (index) { + case BUTTON_A: + i =3D drvdata.bmap_1->btn_a.mapping_idx; + break; + case BUTTON_B: + i =3D drvdata.bmap_1->btn_b.mapping_idx; + break; + case BUTTON_X: + i =3D drvdata.bmap_1->btn_x.mapping_idx; + break; + case BUTTON_Y: + i =3D drvdata.bmap_1->btn_y.mapping_idx; + break; + case BUTTON_LB: + i =3D drvdata.bmap_1->btn_lb.mapping_idx; + break; + case BUTTON_RB: + i =3D drvdata.bmap_1->btn_rb.mapping_idx; + break; + case BUTTON_LT: + i =3D drvdata.bmap_1->btn_lt.mapping_idx; + break; + case BUTTON_RT: + i =3D drvdata.bmap_1->btn_rt.mapping_idx; + break; + case BUTTON_START: + i =3D drvdata.bmap_1->btn_start.mapping_idx; + break; + case BUTTON_SELECT: + i =3D drvdata.bmap_2->btn_select.mapping_idx; + break; + case BUTTON_L3: + i =3D drvdata.bmap_2->btn_l3.mapping_idx; + break; + case BUTTON_R3: + i =3D drvdata.bmap_2->btn_r3.mapping_idx; + break; + case BUTTON_DUP: + i =3D drvdata.bmap_2->btn_dup.mapping_idx; + break; + case BUTTON_DDOWN: + i =3D drvdata.bmap_2->btn_ddown.mapping_idx; + break; + case BUTTON_DLEFT: + i =3D drvdata.bmap_2->btn_dleft.mapping_idx; + break; + case BUTTON_DRIGHT: + i =3D drvdata.bmap_2->btn_dright.mapping_idx; + break; + case BUTTON_M1: + i =3D drvdata.bmap_2->btn_m1.mapping_idx; + break; + case BUTTON_M2: + i =3D drvdata.bmap_2->btn_m2.mapping_idx; + break; + default: + return -EINVAL; + } + + if (i >=3D ARRAY_SIZE(oxp_button_table)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_button_table[i].name); +} + +static ssize_t button_mapping_options_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count =3D 0; + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(oxp_button_table); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", oxp_button_table[i].name); + + if (count) + buf[count - 1] =3D '\n'; + + return count; +} +static DEVICE_ATTR_RO(button_mapping_options); + +#define OXP_DEVICE_ATTR_RW(_name, _group) = \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return _group##_store(dev, attr, buf, count, _name.index); \ + } \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return _group##_show(dev, attr, buf, _name.index); \ + } \ + static DEVICE_ATTR_RW(_name) + +static struct oxp_attr button_a =3D { BUTTON_A }; +OXP_DEVICE_ATTR_RW(button_a, map_button); + +static struct oxp_attr button_b =3D { BUTTON_B }; +OXP_DEVICE_ATTR_RW(button_b, map_button); + +static struct oxp_attr button_x =3D { BUTTON_X }; +OXP_DEVICE_ATTR_RW(button_x, map_button); + +static struct oxp_attr button_y =3D { BUTTON_Y }; +OXP_DEVICE_ATTR_RW(button_y, map_button); + +static struct oxp_attr button_lb =3D { BUTTON_LB }; +OXP_DEVICE_ATTR_RW(button_lb, map_button); + +static struct oxp_attr button_rb =3D { BUTTON_RB }; +OXP_DEVICE_ATTR_RW(button_rb, map_button); + +static struct oxp_attr button_lt =3D { BUTTON_LT }; +OXP_DEVICE_ATTR_RW(button_lt, map_button); + +static struct oxp_attr button_rt =3D { BUTTON_RT }; +OXP_DEVICE_ATTR_RW(button_rt, map_button); + +static struct oxp_attr button_start =3D { BUTTON_START }; +OXP_DEVICE_ATTR_RW(button_start, map_button); + +static struct oxp_attr button_select =3D { BUTTON_SELECT }; +OXP_DEVICE_ATTR_RW(button_select, map_button); + +static struct oxp_attr button_l3 =3D { BUTTON_L3 }; +OXP_DEVICE_ATTR_RW(button_l3, map_button); + +static struct oxp_attr button_r3 =3D { BUTTON_R3 }; +OXP_DEVICE_ATTR_RW(button_r3, map_button); + +static struct oxp_attr button_d_up =3D { BUTTON_DUP }; +OXP_DEVICE_ATTR_RW(button_d_up, map_button); + +static struct oxp_attr button_d_down =3D { BUTTON_DDOWN }; +OXP_DEVICE_ATTR_RW(button_d_down, map_button); + +static struct oxp_attr button_d_left =3D { BUTTON_DLEFT }; +OXP_DEVICE_ATTR_RW(button_d_left, map_button); + +static struct oxp_attr button_d_right =3D { BUTTON_DRIGHT }; +OXP_DEVICE_ATTR_RW(button_d_right, map_button); + +static struct oxp_attr button_m1 =3D { BUTTON_M1 }; +OXP_DEVICE_ATTR_RW(button_m1, map_button); + +static struct oxp_attr button_m2 =3D { BUTTON_M2 }; +OXP_DEVICE_ATTR_RW(button_m2, map_button); + static struct attribute *oxp_cfg_attrs[] =3D { + &dev_attr_button_a.attr, + &dev_attr_button_b.attr, + &dev_attr_button_d_down.attr, + &dev_attr_button_d_left.attr, + &dev_attr_button_d_right.attr, + &dev_attr_button_d_up.attr, + &dev_attr_button_l3.attr, + &dev_attr_button_lb.attr, + &dev_attr_button_lt.attr, + &dev_attr_button_m1.attr, + &dev_attr_button_m2.attr, + &dev_attr_button_mapping_options.attr, + &dev_attr_button_r3.attr, + &dev_attr_button_rb.attr, + &dev_attr_button_rt.attr, + &dev_attr_button_select.attr, + &dev_attr_button_start.attr, + &dev_attr_button_x.attr, + &dev_attr_button_y.attr, &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, + &dev_attr_reset_buttons.attr, NULL, }; =20 @@ -821,6 +1371,8 @@ static bool oxp_hybrid_mcu_device(void) =20 static int oxp_cfg_probe(struct hid_device *hdev, u16 up) { + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; int ret; =20 hid_set_drvdata(hdev, &drvdata); @@ -854,6 +1406,21 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16= up) return 0; =20 skip_rgb: + bmap_1 =3D devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_1), GFP_K= ERNEL); + if (!bmap_1) + return dev_err_probe(&hdev->dev, -ENOMEM, + "Unable to allocate button map page 1\n"); + + bmap_2 =3D devm_kzalloc(&hdev->dev, sizeof(struct oxp_bmap_page_2), GFP_K= ERNEL); + if (!bmap_2) + return dev_err_probe(&hdev->dev, -ENOMEM, + "Unable to allocate button map page 2\n"); + + drvdata.bmap_1 =3D bmap_1; + drvdata.bmap_2 =3D bmap_2; + oxp_reset_buttons(); + INIT_DELAYED_WORK(&drvdata.oxp_btn_queue, oxp_btn_queue_fn); + drvdata.gamepad_mode =3D OXP_GP_MODE_XINPUT; =20 INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); @@ -908,6 +1475,7 @@ static int oxp_hid_probe(struct hid_device *hdev, static void oxp_hid_remove(struct hid_device *hdev) { cancel_delayed_work(&drvdata.oxp_rgb_queue); + cancel_delayed_work(&drvdata.oxp_btn_queue); cancel_delayed_work(&drvdata.oxp_mcu_init); hid_hw_close(hdev); hid_hw_stop(hdev); --=20 2.53.0 From nobody Fri Jun 12 10:13:28 2026 Received: from mail-dy1-f171.google.com (mail-dy1-f171.google.com [74.125.82.171]) (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 A455637474B for ; Sun, 19 Apr 2026 04:26:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572794; cv=none; b=J5/PrtjvnDgJfBzWOv/aeX6MQjr/bbfK4b1nbpmdlV0PHu7X7frNn5DAj1pCVAjY8GNHeKc8Jl5uAW8ByzFnzuEXBPb+I6NI7Pp/J/f2FM7FtKqnWVVsRvm0GZ1m2FBKQpbFAB5rCyjqK9Q9fKPZuDmbaObtBX2TCdPXXvNX+B0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776572794; c=relaxed/simple; bh=S508cP1yZslNSbKv0FAXNIDkX2zVJDf06fpTroAG+R0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=e4PipVvxPa5NOF+2I3mUF/TwBwj8NlSpnRSW0dm7gniLS/ApQqD8DGosZOBrZOse7Ooi/H+hO0F8wxpTAnTkaEIhcjAv6S5AXqWvfzNVnrPkaiNPjRTeeqOKhAkpwpoFXa3N7TFNRLsZjb2fcTDbYv4eLQDVgPD6a7fyUpRyaHE= 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=oNzwooKU; arc=none smtp.client-ip=74.125.82.171 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="oNzwooKU" Received: by mail-dy1-f171.google.com with SMTP id 5a478bee46e88-2d9916deb14so3759595eec.0 for ; Sat, 18 Apr 2026 21:26:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1776572790; x=1777177590; 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=Dns9bM6fc8u8pLco2XqNcLByz5759WEqyjVFudH5F6U=; b=oNzwooKU+U23CZQT43mPFlglQSgViP/ki+XiqMWgPx7J0bgPamiCZLwktXGmxAjAKm 49nMHYxSrEoGPPLF5C3D9Bm055bxf6ArZxKFa5BUCDRjt1fkbfIQtRd0f5nyYi5aC0qI XG2atAfCnc424vvB6QeO4temjoXrqae9qqlEUSC1vWAinLcSe2Pebgak/b7PFZBPKvVc P3pn9HLgrM4wCkIKNbzAe92hvE2474u2yp3bmEzSmJioUhf6nH4MT0r5Hi+iR8sblDhJ V9+q7f8/JMaomOLw1eB4jnt1DWPl/wFA0Fu9+95sOmHYlWBlDTGAMPT7MWQ7AZjMqSHQ 598A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776572790; x=1777177590; 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=Dns9bM6fc8u8pLco2XqNcLByz5759WEqyjVFudH5F6U=; b=ZhwWC7HUi36yX9ug8fnQliIgKxnxReSPRL63JYg3nbIrsXWmOLqKdlymk1HaM1xmC8 vCE9HaKEjjlf3+EOHVTxijWXONnGD/rhWxcVlJchKv953fizmBaJBrlRTTc8XRjd3f4T wU62y4q9aPlR6AP/6IXhgtz+r0YcpccCLv/0OCIs4n+ZefXiG5HBE7NplXmgOc5FcstM Ez12m2cBGhg92033hr0irgdlFwlX+47forGA7/k90ovdCSbmSftJDFwkaHSv38TdBwsg 9f0P8WhAmM1UUq1CoYIpQwgQGflg8CEGET/zVwgK2ghWP2BnJ7M4B9yrQ1Bxr2+Kk1Zv Wdvg== X-Forwarded-Encrypted: i=1; AFNElJ/GgflGuktY16GVgQb+0vzUAGswhahprjcbpQ0mpFe1KQMzE4XQcdN/ecDsARzh33R2pe2LDHtT7+RDrNo=@vger.kernel.org X-Gm-Message-State: AOJu0YwYsFT4BRmYm8g9NzfyELNxYtX74S+lTUmZGFZZ7iUBbgGwa+vr RuAjmwfzU0ML+qXNnLqoaj29/Re5Vx5QFrmrQ/o5R3crVn+X9wz95rjW X-Gm-Gg: AeBDieu8h9w5RDHvW2ji31TpJSUZlIs4Y/0QPKfOGjMul5RzD2/7vtIoqJ4kax/BUvM PHiuVkqCplzS39wJqQHyM3RNxc6uBpPN4aiI2GPHX0epH+oo9Rc5UB7HAv8MMfas40ITnFVWta8 Ap//EHayMaRhCVKw5MhR9uj33/XtyRApfjA5k5XFHK3/KpicAv9teOcr1Y1JYMfkZWFge3VzaZG G7K94bg4vKSlqYK5QH86iQiMcVN/pmjVlMaGvYl0409k918wzS+/68wGpzhpii9CXJp7Hr99mHn hRuFNUKUS9ecBSZN4260CPYn6VrzElMmX15uCMRqqDJYkAdzJYqJ8xFGmokD5bE6CWm0L1UG8cl sF89cZafkHbVcEtFoS/f4rX5OSxa60dnh7nrGNRH/QdYSshl7DdHCFEWGXgf/9O5mUmQEI2xOeZ cXOu1cCkFkohoRfYhJGyFfJ8Ef70+ZA2IXz+zkO/C/e+QU/1jEyrqYNn9gAy/W9Kd8jm/tnoTOH jhkuXocArydhC0= X-Received: by 2002:a05:7300:d717:b0:2e2:a64b:63b5 with SMTP id 5a478bee46e88-2e479c08fa4mr5027351eec.18.1776572790122; Sat, 18 Apr 2026 21:26:30 -0700 (PDT) Received: from lappy (108-228-232-20.lightspeed.sndgca.sbcglobal.net. [108.228.232.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-2e53dcb487bsm8796469eec.31.2026.04.18.21.26.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Apr 2026 21:26:29 -0700 (PDT) From: "Derek J. Clark" To: Jiri Kosina , Benjamin Tissoires Cc: "Pierre-Loup A . Griffais" , Lambert Fan , Zhouwang Huang , "Derek J . Clark" , linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v4 5/5] HID: hid-oxp: Add Vibration Intensity Attribute Date: Sat, 18 Apr 2026 21:26:24 -0700 Message-ID: <20260419042624.625746-6-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260419042624.625746-1-derekjohn.clark@gmail.com> References: <20260419042624.625746-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 attribute for setting the rumble intensity level. This setting must be re-applied after the gamepad mode is set as doing so resets this to the default value. Reviewed-by: Zhouwang Huang Tested-by: Zhouwang Huang Signed-off-by: Derek J. Clark --- drivers/hid/hid-oxp.c | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index 52002d4cbd0b..20a54f337220 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -34,6 +34,7 @@ enum oxp_function_index { OXP_FID_GEN1_RGB_SET =3D 0x07, OXP_FID_GEN1_RGB_REPLY =3D 0x0f, OXP_FID_GEN2_TOGGLE_MODE =3D 0xb2, + OXP_FID_GEN2_RUMBLE_SET =3D 0xb3, OXP_FID_GEN2_KEY_STATE =3D 0xb4, OXP_FID_GEN2_STATUS_EVENT =3D 0xb8, }; @@ -181,6 +182,7 @@ static struct oxp_hid_cfg { struct mutex cfg_mutex; /*ensure single synchronous output report*/ u8 rgb_brightness; u8 gamepad_mode; + u8 rumble_intensity; u8 rgb_effect; u8 rgb_speed; u8 rgb_en; @@ -266,6 +268,11 @@ static const char *const oxp_rgb_effect_text[] =3D { [OXP_EFFECT_MONO_LIST] =3D "monocolor", }; =20 +enum oxp_rumble_side_index { + OXP_RUMBLE_LEFT =3D 0x00, + OXP_RUMBLE_RIGHT, +}; + struct oxp_gen_1_rgb_report { u8 report_id; u8 message_id; @@ -341,6 +348,7 @@ static int oxp_hid_raw_event_gen_1(struct hid_device *h= dev, =20 static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u= 8 data_size); static int oxp_set_buttons(void); +static int oxp_rumble_intensity_set(u8 intensity); =20 static void oxp_mcu_init_fn(struct work_struct *work) { @@ -368,6 +376,12 @@ static void oxp_mcu_init_fn(struct work_struct *work) if (ret) dev_err(&drvdata.hdev->dev, "Error: Failed to set gamepad mode: %i\n", ret); + + /* Set vibration level */ + ret =3D oxp_rumble_intensity_set(drvdata.rumble_intensity); + if (ret) + dev_err(&drvdata.hdev->dev, + "Error: Failed to set rumble intensity: %i\n", ret); } =20 static int oxp_hid_raw_event_gen_2(struct hid_device *hdev, @@ -514,6 +528,14 @@ static ssize_t gamepad_mode_store(struct device *dev, =20 drvdata.gamepad_mode =3D data[0]; =20 + if (drvdata.gamepad_mode =3D=3D OXP_GP_MODE_DEBUG) + return count; + + /* Re-apply rumble settings as switching gamepad mode will override */ + ret =3D oxp_rumble_intensity_set(drvdata.rumble_intensity); + if (ret) + return ret; + return count; } =20 @@ -857,6 +879,59 @@ static ssize_t button_mapping_options_show(struct devi= ce *dev, } static DEVICE_ATTR_RO(button_mapping_options); =20 +static int oxp_rumble_intensity_set(u8 intensity) +{ + u8 header[15] =3D { 0x02, 0x38, 0x02, 0xe3, 0x39, 0xe3, 0x39, 0xe3, + 0x39, 0x01, intensity, 0x05, 0xe3, 0x39, 0xe3 }; + u8 footer[9] =3D { 0x39, 0xe3, 0x39, 0xe3, 0xe3, 0x02, 0x04, 0x39, 0x39 }; + size_t footer_size =3D ARRAY_SIZE(footer); + size_t header_size =3D ARRAY_SIZE(header); + u8 data[59] =3D { 0x0 }; + size_t data_size =3D ARRAY_SIZE(data); + + memcpy(data, header, header_size); + memcpy(data + data_size - footer_size, footer, footer_size); + + return oxp_gen_2_property_out(OXP_FID_GEN2_RUMBLE_SET, data, data_size); +} + +static ssize_t rumble_intensity_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int ret; + u8 val; + + ret =3D kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val < 0 || val > 5) + return -EINVAL; + + ret =3D oxp_rumble_intensity_set(val); + if (ret) + return ret; + + drvdata.rumble_intensity =3D val; + + return count; +} + +static ssize_t rumble_intensity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%i\n", drvdata.rumble_intensity); +} +static DEVICE_ATTR_RW(rumble_intensity); + +static ssize_t rumble_intensity_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-5\n"); +} +static DEVICE_ATTR_RO(rumble_intensity_range); + #define OXP_DEVICE_ATTR_RW(_name, _group) = \ static ssize_t _name##_store(struct device *dev, \ struct device_attribute *attr, \ @@ -948,6 +1023,8 @@ static struct attribute *oxp_cfg_attrs[] =3D { &dev_attr_gamepad_mode.attr, &dev_attr_gamepad_mode_index.attr, &dev_attr_reset_buttons.attr, + &dev_attr_rumble_intensity.attr, + &dev_attr_rumble_intensity_range.attr, NULL, }; =20 @@ -1422,6 +1499,7 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16= up) INIT_DELAYED_WORK(&drvdata.oxp_btn_queue, oxp_btn_queue_fn); =20 drvdata.gamepad_mode =3D OXP_GP_MODE_XINPUT; + drvdata.rumble_intensity =3D 5; =20 INIT_DELAYED_WORK(&drvdata.oxp_mcu_init, oxp_mcu_init_fn); mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50)); --=20 2.53.0