From nobody Sat Apr 4 00:05:58 2026 Received: from mail-dy1-f174.google.com (mail-dy1-f174.google.com [74.125.82.174]) (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 77068336EC6 for ; Sun, 22 Mar 2026 03:16:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149383; cv=none; b=WgjZxMdjUaznjUfqShYrq8HWEQdQj3pKKhTQQXgqTYsKkHNaYTU2ZlBm+K2zn5NMu5NZ1eYUoPntpA4PdQrZaE7i1Bkg0RqEM2C9GjEkp5MJjE+OAbv7/gRKEdwgoBj/GSyvx6H4gxBM/w5EN+gE61j25WgtXX2ansKcPbc6LsY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149383; c=relaxed/simple; bh=sSvDEWhlhM7/3uebxmK+jw1KS5oppVW4vUl6fNnFDFM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SYz1dsYJe16ckMqAq1oKoeqCH9F2Fuf0Z+zO1JXMAXliVVrDIZmctponFzt5rsWhawURfAb/LS5a8OGWSSCeJ4exp42IHT8T32UygikePZVbEkb5iuM6sTiZQVEFrD0AJp8lahVc18Oen+Ax8KN6hcgLpqoeNxKIADUf9SGBLNE= 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=Nn3ZPcCi; arc=none smtp.client-ip=74.125.82.174 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="Nn3ZPcCi" Received: by mail-dy1-f174.google.com with SMTP id 5a478bee46e88-2bdcf5970cdso2430531eec.0 for ; Sat, 21 Mar 2026 20:16:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1774149380; x=1774754180; 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=eebKHbS4BPjgtJjasAOUTVkh/Oyj9i1rm5NNCiW4xfk=; b=Nn3ZPcCiPX8QtdZmZSauqaQXFljNJLgI2pYHuj+fQwz8Eb2frWTM35s/n+2bbhQc84 NqtgvH1xBCqFSmJIxSgxv6V/SDgMOcZwc/BeTPqpoZC7oNDgvmf+1mK4tMJxOTuHifre FRN0TjbcqpSFg7429alA4y/7BgyUjVCM6jtwunVMt/9Ywh8xZG3W/9nRbvQfJPVnQC3A ysGzAd4YX7Q7S661DdWxzKZrnKGKmYw0JmFOetqffrU23TNWh0UG1gWwPG0htXB3zzIX OCJZAHfsc8U32EnxVzvy0YfXjRWZEQnDxtdksAIyGrS6HMMmvFg7hId0FYdyH5L1tgfz mN5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774149380; x=1774754180; 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=eebKHbS4BPjgtJjasAOUTVkh/Oyj9i1rm5NNCiW4xfk=; b=ONwZMjUFoi5qSB2SwkeP7md9QA75d9EpOFmGfMtbzVvGM3S2cXqLGYGh5Rh413GJ3H rspKVLb5NJvo5+3b2ICs1Z38Lr5s2g50H6S4izqknFWZH1Hj0tGeV4tPy+Nb4VsZ9Jkh vvMAKCcI36PokEP+X1HRR5WW0nGxU9u5ASmSkNcwsQNyJDDqepVfvIgHcXhNJ5eqNz8P mGg+nNS/bnlLlX4E43gLBUdim+oiVWiOre2jO4VAvDwP+wSpY64iMYh4vbPlPgtEnJ5H dzsnql7PULIyIkJHa5ojcnHVVyx2PO9q2MFZ109OcgafiERPqSz9y89X18IPK9MyL1mr 6J0A== X-Forwarded-Encrypted: i=1; AJvYcCW5xB7Y0TTBSfowGqHkQ/lGjJWXJ8mvy258filtm98FUs40Qv6cqU4udGmtNPkphi/fCsNkyjNZL8BXtz4=@vger.kernel.org X-Gm-Message-State: AOJu0Ywh8SoyitxNdpQkoranlakLuUm6sMUFT01Cafo09fqEGKWs5/h1 userN+a1cA2L+xnIVzY66H/IPbdEgNi/Q8bs2WfqYRz6BLULROlO36ZF X-Gm-Gg: ATEYQzx+RdVQkEZ5CNHL8Ff8wIMr3ziXcZiekAoXb7syDhIcVikym9OKxCk8CVBaw5v l+O8CheknwpBLv2LXTaXmQoLwc5UejtDYDkKV8/P8guTZvMXxM0R7eh3MBntnH9FNAaSWzgJgDq IWhVblGqUbG9ydzt5DlHLDrCCu3Tm8R02sxnGbPPKzb3K0UW+WPs/fZ/Ve6itVJeiyUfHm1/tjY /yd4/PJrJVU1WfHdVO4UAXJowxfRi6gwj0ji5pLHbHluyYrcGAqKm2lvvsGbRl1oKw5p7Aba707 LxUyCdHKoYOBllhWI+yirlzT1mjd5AQMqxtSu0jL2DgmHlNmeZPhTQFb0HOs1wnG7HbOb6rMdew uYdJQYrxx+y6UR97c2TKHC5eDnwxCdF6ECLiE5hLicSC9pPJAOKNQx2ji7Xmbsb/AtL85OIEX8f oaV9WAREWrqBQTZLaMgMHjOEF9V23YDA6F+pVRm37hZFYWjczku3JZUiYlHK4O8s5Wy1AAFteQ0 LEySrGchaFwa4I= X-Received: by 2002:a05:7300:cb13:b0:2a6:a306:efdb with SMTP id 5a478bee46e88-2c1096e2545mr3386818eec.3.1774149379127; Sat, 21 Mar 2026 20:16:19 -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-2c10b31bef1sm11220460eec.26.2026.03.21.20.16.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Mar 2026 20:16:18 -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 1/4] HID: hid-oxp: Add OneXPlayer configuration driver Date: Sun, 22 Mar 2026 03:16:12 +0000 Message-ID: <20260322031615.1524307-2-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com> References: <20260322031615.1524307-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. Signed-off-by: Derek J. Clark --- 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 eeb8fcfa32eb..ba44ab2452be 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19228,6 +19228,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 a797549b580e..42af8fc15476 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -920,6 +920,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 95fac34e8499..52e26a1d9df7 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 f01020569dea..8b272d1ab9ba 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1102,6 +1102,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..391de2798320 --- /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 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 DECLARE_DELAYED_WORK(oxp_rgb_queue, oxp_rgb_queue_fn); + +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, &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); + drvdata.hdev =3D hdev; + drvdata.led_mc =3D &oxp_cdev_rgb; + mutex_init(&drvdata.cfg_mutex); + + 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) +{ + 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 Sat Apr 4 00:05:58 2026 Received: from mail-dy1-f170.google.com (mail-dy1-f170.google.com [74.125.82.170]) (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 D09AE34F46B for ; Sun, 22 Mar 2026 03:16:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149383; cv=none; b=UgdlgIguHu5Fce5lg0eJaJC1EKqx9X8ZtrJCApw9gFpFgnhBXMCLYUBq8SbzkJbKFxKJOGszBf8BxqaEWdEIoBa148aMwsXrKo9FZqL5qEqhhzcpx05NXkybQ3bnh1IdnubWLJ9OF4p6A7l/brypKvq7BIZ9pVU2jqRtsi5chN4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149383; c=relaxed/simple; bh=8gNYXpQGmKI1m3W5oqjVsx8k92Qrt1pu/7hbhyiLEXc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IMK8snHXDZhAczVVZBaT4PjQdeYjJeJydgWkNcWym658q4sSoiizVDBLC6zlOec78ApRoS+tseXO4YVEI0P4JfMs0YBLR4YLy/ENHCPKltHanSx+rQ1AsERbnjk8SkgzeQ7KqS66rG0rIdw8x/ozAcUS0zMrcmnoLSlRhezLRs8= 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=bKKwtJyw; arc=none smtp.client-ip=74.125.82.170 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="bKKwtJyw" Received: by mail-dy1-f170.google.com with SMTP id 5a478bee46e88-2c1092cc08cso4774744eec.1 for ; Sat, 21 Mar 2026 20:16:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1774149380; x=1774754180; 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=CpOppp7pshHB2mWvGps3l7V3xeQrRabavfMVMwqvkLM=; b=bKKwtJywVuCbrGQa/bXHCVoNm2DuyA075XhDNf2Yz6sEctOU3pErCvMAz4vf62lg1Z lFmFN3DOB8b1RO9TTrRMrMZgvFgJlQKAxyJvcdMot8Sf2xrkL/6boCmHNLSTLRW1Se8d pPUwsHN0Na1WwVW344fROt5k6honayFEbhwNNeDO6pQCFBA39M0QfjwciQYb22gQ/mGk 8r1AcxAQ/7SQ2MoiYfwrExl8zgpr12VOg+OlT+bjNAswp+3+SMulFHM5QPoHF9ddcXum XrxOAQd2zUsnLx2Pig+al5yPHwgQPG2HuTC1ln/dpeuwtwh1r6AGXCq5bpCT8pRhZmh0 YB6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774149380; x=1774754180; 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=CpOppp7pshHB2mWvGps3l7V3xeQrRabavfMVMwqvkLM=; b=LotmYgNa2zOjCGtpAjBvDRkjKMLTiQZ9t6WQMU0sLwlhYCAlEzjFr4Ml58mhecI6fV 3K08UFzR4S4cIXp+TIZUCMiIHjddfSR5hIStMjx62DvSik1fm/4CYOHAofTYtelhqTGw rgutsm+LLl7TT8hT1uFGdcQtX/8uCNScC1FHl56oikCTKtJvJySqNh32fHSQnkw5K08m Zzvjuf6QbjKRD7+InmOUINmk4Cn04dNy3mFbXGGqq8lz1CFkGA0jTeuSw5/cU6Xm7xl4 RrV1IgpVy9/OK4Zh4b5aMzLYL8nESiOQPjurs4YwyKG1YB0ECIIOerPznFh9/GlEvHfm T94A== X-Forwarded-Encrypted: i=1; AJvYcCU5mxzDqKYQIv5EIqsELxjLqo2vSkF8DkBZcX6RNbGcFuk2eX8Zh/2tlJVDbKwgqlGFD2o+zDW1Ho+S9UM=@vger.kernel.org X-Gm-Message-State: AOJu0Yz2w6AXO2QF5EjZ0omOjiVMkTcIrPKth1Zho6uM1edST6ecTtMN POqblLWqGCNym5Nb+jfb2zgzEZ65E5Sc3lw4h0HDVu5WDTJsftpp2t7j X-Gm-Gg: ATEYQzy+7bUSQi2WlqjqILYX2RlTKXFNVFbxx4hZCYd+diRT2KqaisAen2WrhFz3TY3 9IfIkO0xnTIQWUiCU9rxU1MjDmaceLonCvO+YjKkn73lFkNElLQ5hjTQuoFwyJOdOz8Bzl0tAFr mAIhsr56JvAccAn7B+L97PDVE7PoLeJDMeeaXRtyin17GRG7MerA6qkJw7xIYRah73kvUSwmiJB 3FVuy1wjFdIAFvSLXbVZA+uoFoBomZRlMQ4zQyOv0esNIzcfCBQyfDOUsMa76GplPIrLQrS7sn7 9moblQRZD2wb9Hzc1gClbLxh77fjvYITEQZqzFY0aWVFtaB5DaRBuWlsmvFwdMYR4L/0ebMdwPZ DakRyXJXXTNHbz54kbN18NNter5jhV+fKdkpgB7Z+0bhH9ak/bzykasneyaFGjCb/yVYhGBVML2 9DGDzuH9Zu6y5sQvexnjCCCDIxbPrT6mh5CQbpZLFFK/fGWCoafB6Y0DYvGam5FV6wVZrw6yHjg HKX/7m2yPhzbzQ= X-Received: by 2002:a05:7300:e2cc:b0:2c1:27c:75a6 with SMTP id 5a478bee46e88-2c1095fbaa9mr3769234eec.10.1774149379723; Sat, 21 Mar 2026 20:16:19 -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-2c10b31bef1sm11220460eec.26.2026.03.21.20.16.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Mar 2026 20:16:19 -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 2/4] HID: hid-oxp: Add Second Generation RGB Control Date: Sun, 22 Mar 2026 03:16:13 +0000 Message-ID: <20260322031615.1524307-3-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com> References: <20260322031615.1524307-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. Signed-off-by: Derek J. Clark --- drivers/hid/hid-ids.h | 3 ++ drivers/hid/hid-oxp.c | 96 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8b272d1ab9ba..b33782ba6556 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1105,6 +1105,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 391de2798320..587e0d57c85f 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -24,12 +24,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_RGB_EVENT =3D 0xb8, }; =20 static struct oxp_hid_cfg { @@ -121,6 +124,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 +181,45 @@ 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_RGB_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_RGB_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_RGB_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_RGB_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_RGB_EVENT, data, 3); + break; default: ret =3D -ENODEV; } @@ -614,6 +708,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 +729,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 Sat Apr 4 00:05:58 2026 Received: from mail-dy1-f181.google.com (mail-dy1-f181.google.com [74.125.82.181]) (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 405F22741C9 for ; Sun, 22 Mar 2026 03:16:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149384; cv=none; b=f8dYNDtGWTQyZJnX8km0EtYPCf6m2IyQV6qG9fWlbn06m+gCGgv/yD9powFdrDj9yk9fKy5OxoKvBvoMd7Gi/QNgLSSFDqcsDmUmauGxs7eNQlY2jUHJvR2G/yml6p2oL+6IGudAjsX/D6Q8iLrXCXNw+S2Zenc3VG8UNFxSRz4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149384; c=relaxed/simple; bh=PAz+jORhNkt0zBVszAhrT9bv/0URLsybiRVH5ZrKrkk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cIaNWrfJJxXgwKfO5wx4oh+0EQFyQ3OD442GwLLRnmWQ7KRhKd+IyquD26rzMxPFN/98TOnu9dWMkFmNoWeXXXesFH6EBzUCG/NrQcuCZ56CrwSkt/xj6nD8kCG2U+NOEFuSYN3HUZqykCDoB39qxIKhOpjpszRVq+5sd7YCgME= 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=IvNt8u9C; arc=none smtp.client-ip=74.125.82.181 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="IvNt8u9C" Received: by mail-dy1-f181.google.com with SMTP id 5a478bee46e88-2c0ecaae7dfso5344172eec.1 for ; Sat, 21 Mar 2026 20:16:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1774149380; x=1774754180; 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=2b6kol1RuqZ0ewRBN2du10oKoyZWNZPlgpHx1CYYLuk=; b=IvNt8u9CPp4zI3Ha09F5n4PcyPJzYe0SyHs0Mi4+uBrkwO9RdX9YDDlEtVA3m5jFiy ZXRrLYhQg2U6uGn8EqIcMPL3rOvEikWftuOsGcjzdrltJS7zdPoDyirJq4LPtr0ldjKM bVA7G63jOdEBqTU/81Pq5ASKk2ECuX2tKapQZ9mBapYvzt66SmIuG1FB8o26LrhUBz1S qRlDzR4iUxkycRFA+Wk4FJRDowU9tjXCBZrAjG0ImEVr5V70kowWnlPk6vNYPr14qH41 z7Ba4TAAIARqULXX2S+bo1iAdyMofC8PN+CKWNYj8ARhI2fZga8AI+aJSnJNCfOwrgsL ur0A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774149380; x=1774754180; 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=2b6kol1RuqZ0ewRBN2du10oKoyZWNZPlgpHx1CYYLuk=; b=SebpO1ubHwBNIpP01HBBE3r5v+y41+w3vohF8HplF40DIPRf3rTApoYHzVRaBuqAtU MSQw+SMtU9aspuAXIsVQ533Jt9n9nB6QKbG9od6YHElfuiHMcZ/k3my5UJi01aDg0OSO k4qq4eceqA9C4+E1hfLh2xriMkYpG5RQI3zBiqhGlDCuJUdGC3aronX5/n0m+2lDA1vB u/LLpuNlNhlZhsPF0ZhgRO8JJVmLR9iMCm73J9ozlf3TQXzJkrT2Ha5f+DIXRjwFjmoc HCWOVrqbECVeM6Zw7HUMR+0K7cVczsRn0+ZjGNYAbxCeTC/CbI7ZDiijVmNluCB3vSrn vrBQ== X-Forwarded-Encrypted: i=1; AJvYcCWqWRwd+/0DwyqnDkCRpskcy0YJ91sB+Yt1Y3JSm4hSL5NKAoREl8TcVz7OoPu7PsDyuMxNscYiwCrMtfo=@vger.kernel.org X-Gm-Message-State: AOJu0YyBSm5WssTkmfPCLEoMND0MHUyZZIdGG0rZxF/iTfdTZEKWFHBZ PuYwtNYj6yUz1ehWHVP60erfL5CKAxBhs4wkIU+KNhyMPMa11OvycOe9 X-Gm-Gg: ATEYQzxUa24E4gSVevmYXYN3NhIz41jFT/p76Hsjqvw3OhO+zv0Qh+VQPTbOT222a2O GEFPFLcSBDYZV/dfG4xaKMFodohfTg/UwSMpbMlFfbBS7XgrNx+A6ggQQJNHvpAxjkg/2ZaYxHw cSOBvv5YvlIR8xEgtwb+SJiubGipGIHWrqpnyFLmUXmLOBXOZeD73PwTnzPsmQBtTHK9KIdM5G5 Sqx0atib39eX3D4ptmZrrnYe+hLCrHWjGoOXeCyo9EH4QihmulVWqDIBO76uGRQlUNmknRj/dRJ kav7u9KMYn0XyEHj6pNeH43JRXHR8yKZ84puGCZ6EADR3ZI+hOgyS8ZuL2+lV0b0RybGuuhyCqJ u83JjkxdBsR4PWLTAPEBJv9CMXwzXQ04So7QbPUX37lWOXY7vhMPl+tZPS5TD6Iuya7nofNPv1l lY4YTvj4ud675yzTOHKsl8+jPL5GRSYwF2izOM86FS4jGJJjvP3oWiqq5ALeQWzAvPDpzbuERTP MOY X-Received: by 2002:a05:7301:2f86:b0:2c0:ae1b:4568 with SMTP id 5a478bee46e88-2c1095684fdmr3703901eec.7.1774149380263; Sat, 21 Mar 2026 20:16:20 -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-2c10b31bef1sm11220460eec.26.2026.03.21.20.16.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Mar 2026 20:16:20 -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 3/4] HID: hid-oxp: Add Second Generation Takeover Mode Date: Sun, 22 Mar 2026 03:16:14 +0000 Message-ID: <20260322031615.1524307-4-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com> References: <20260322031615.1524307-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 "takeover_enabled" 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, and allows some userspace tools to consume the interface to add support for features that are unable to be exposed through the evdev, such as treating the M1 and M2 accessory buttons as unique inputs. Signed-off-by: Derek J. Clark --- drivers/hid/hid-oxp.c | 81 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index 587e0d57c85f..5fed2799a2ad 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -32,6 +32,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_RGB_EVENT =3D 0xb8, }; =20 @@ -39,12 +40,15 @@ static struct oxp_hid_cfg { struct led_classdev_mc *led_mc; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ + u8 takeover_enabled; u8 rgb_brightness; u8 rgb_effect; u8 rgb_speed; u8 rgb_en; } drvdata; =20 +#define OXP_TAKEOVER_ENABLED_TRUE 0x03 + enum oxp_feature_en_index { OXP_FEAT_DISABLED, OXP_FEAT_ENABLED, @@ -289,6 +293,74 @@ static int oxp_gen_2_property_out(enum oxp_function_in= dex fid, u8 *data, footer_size); } =20 +static ssize_t button_takeover_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 }; + u8 val =3D 0; + int ret; + + if (up !=3D GEN2_USAGE_PAGE) + return -EINVAL; + + ret =3D sysfs_match_string(oxp_feature_en_text, buf); + if (ret < 0) + return ret; + val =3D ret; + + switch (val) { + case OXP_FEAT_DISABLED: + break; + case OXP_FEAT_ENABLED: + data[0] =3D OXP_TAKEOVER_ENABLED_TRUE; + break; + default: + return -EINVAL; + } + + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3); + if (ret) + return ret; + + return count; +} + +static ssize_t button_takeover_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%s\n", oxp_feature_en_text[drvdata.takeover_enabl= ed]); +} +static DEVICE_ATTR_RW(button_takeover); + +static ssize_t button_takeover_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_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(button_takeover_index); + +static struct attribute *oxp_cfg_attrs[] =3D { + &dev_attr_button_takeover.attr, + &dev_attr_button_takeover_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); @@ -680,6 +752,15 @@ 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; + + 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 --=20 2.53.0 From nobody Sat Apr 4 00:05:58 2026 Received: from mail-dy1-f181.google.com (mail-dy1-f181.google.com [74.125.82.181]) (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 A6F9C37C91F for ; Sun, 22 Mar 2026 03:16:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.181 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149383; cv=none; b=ZWcldvCvYXMTZw4W2veIOLwYyNzSo4D/zBk2CjEBMCKZZJ2XT9wWzZ+9zyoUAhZWaIQLFHC85sXqJxCOfJSC3pc+ElQZGzCBWPAwK7plUYewMhkHwSaKGqz4hP9cDPFH/Qw/D0zf1CJbi+8BCqZSXw1bsfdGzGC8R8hA6u09E1M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774149383; c=relaxed/simple; bh=W1rLdAGagHNMf2g+c+r233cuQ/o3gDmRftmWdFY8IDg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YMReTZ1+n+jfjXWW53xuMuUKLltbypcU0seTZll7i14a/aQIlNUbaBqa/zblH55bs/DH2UGDnyjyldsndwNXgNGmyMEaVKMT38d6IzLUoL88+aDNZB+y189bucKU30XjOXWwqoI5wdWyNvlH3uIHa0EAtO4p72z7bjd/DIhdYms= 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=aBI/y+tG; arc=none smtp.client-ip=74.125.82.181 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="aBI/y+tG" Received: by mail-dy1-f181.google.com with SMTP id 5a478bee46e88-2c107ef474fso2937625eec.0 for ; Sat, 21 Mar 2026 20:16:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1774149381; x=1774754181; 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=os0SrvdIH4z45ssqOZ2Hn0vhnIJqpjEjlIqduxgwWG0=; b=aBI/y+tGBTXbdut4ZpltePlZUlIvaJQQoT+HACy+1aatd/rLZtuV5xIY3C9TvheWxv pknF09qp79YFg4XZ2VKijbz3a82qEauXP10DBplZnXTXt6Q9Qr7/KMy2KL2pKGh9Fx3d 3H2hK8MXhWziCldVflUFaa59k4pZNSdm85x+HUow1I6lOPeg3uOFrAgn4ElhNnN2mYLu Tb7zjZNHzZ17qRxA80i72x1HrWaMEdKqK1cB6k05hjyX4eE5KSXsD3WSwSNl4ZTuy3XS 05uZCrlgl5YefGzeiGjuHSfd9fABtqS/u2MOLFxg+Dk2d+QykUrCJz9jkvUuGMRxrDoP TozQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774149381; x=1774754181; 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=os0SrvdIH4z45ssqOZ2Hn0vhnIJqpjEjlIqduxgwWG0=; b=EtmarqKdtdWjSh24oN6XxaYg6Wcbgfau4Xe5sdBYQuFKKYCmi4UodeTzTIDMCTZviY gvhTDAFI5f+rryuc/xp+kFIeWzk46IQRKzF2DUEenSaJh3pA+qBm8ZHRY1yr7OZvtHAk 5yqJL0UzckAHQziUx3eySTYNcXLMnLUzDTAoCsnycKOHiKQrHd1otkNWEwpKBRSBIgY9 6rosqx/XGPPMA6156+MdznGwEn8BVaOEphDHksqkW7Wb/+xdUfGKxga8Nb5KIkTnncm1 SLeYIxyb6gdr+Tpzz+8Qlbtv30T81LrHoUoOERbEBmIlOR9vEA8iDoePBuBIyVKjQPmN aQ9A== X-Forwarded-Encrypted: i=1; AJvYcCVuWcqEhJbC3xJHqRInOONzpEE+B8KJWAiTH4+5AICXyarXVAAsmN7/B6/z6ys4wog7BMwj3Dgzlp7ID80=@vger.kernel.org X-Gm-Message-State: AOJu0YzuwYNMLD6zXRu7sETrk6nwT5dJVRahLEAnpGNuVbXieHGTFaCs L53pAYlt7BZwZ2w7paFlqmWEISXRnKV9VVTL2v79gXx7PwUubZFsE7VU1HGkGA== X-Gm-Gg: ATEYQzw8D+5XxF3L6qrMfRXuMe/TSQa5qtjxZ82zIEmldbaNvldPb6SKos2GTiWamOF vEw1y8Dp/vcPxAMcn+Qg7rO6Gs6a+ztIgBn9FFilU80b9gikIuBRvKpTJ/LhLz5R7hzgSKH7Bvh I/AWSyblBpK1Y5t3ya7I4QyL9OCM1PfbAd2et57HNIt1AtFVr34EirSK3UikoEEuSHF8xzwQzMU TWc9vjiLMQEsH0bmtkA4+Z5Uv5wEOF8Wm7YYdzxEOnH8KwJ3zOFYWnw376rmckWiVLSFG/Hb0eD 7Pg2FafDb0LREwZDhwVGccAdwRyoOXmUi2F16WasDAsaRdjozRumr87fMUxEY1A7117Rh+W+7V8 /Iw20TEPlXNx+E0HXEex87mSu0HN7Fi8CKG/iC8NAx6EVxuvNnvFJeo+pVzByUoSv9VW11GjDLR nsUSWxupwlzd3IyLDu4oGGdNsLYtRFTNuMxqAWVyfjiB7+5NoaM0htyFZtj0vgj1i0c0cCCuHwx 1j3F+qH67GzhG4= X-Received: by 2002:a05:7301:4198:b0:2c0:e31b:1814 with SMTP id 5a478bee46e88-2c10961ef26mr3439331eec.10.1774149380856; Sat, 21 Mar 2026 20:16:20 -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-2c10b31bef1sm11220460eec.26.2026.03.21.20.16.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Mar 2026 20:16:20 -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 4/4] HID: hid-oxp: Add Button Mapping Interface Date: Sun, 22 Mar 2026 03:16:15 +0000 Message-ID: <20260322031615.1524307-5-derekjohn.clark@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260322031615.1524307-1-derekjohn.clark@gmail.com> References: <20260322031615.1524307-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. Signed-off-by: Derek J. Clark --- drivers/hid/hid-oxp.c | 510 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 510 insertions(+) diff --git a/drivers/hid/hid-oxp.c b/drivers/hid/hid-oxp.c index 5fed2799a2ad..915c17b97db0 100644 --- a/drivers/hid/hid-oxp.c +++ b/drivers/hid/hid-oxp.c @@ -33,10 +33,126 @@ 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_RGB_EVENT =3D 0xb8, }; =20 +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, + JOY_L_UP, + JOY_L_UP_RIGHT, + JOY_L_RIGHT, + JOY_L_DOWN_RIGHT, + JOY_L_DOWN, + JOY_L_DOWN_LEFT, + JOY_L_LEFT, + JOY_L_UP_LEFT, + JOY_R_UP, + JOY_R_UP_RIGHT, + JOY_R_RIGHT, + JOY_R_DOWN_RIGHT, + JOY_R_DOWN, + JOY_R_DOWN_LEFT, + JOY_R_LEFT, + JOY_R_UP_LEFT, + BUTTON_GUIDE =3D 0x22, +}; + +static const char *const oxp_joybutton_text[] =3D { + [BUTTON_A] =3D "a", + [BUTTON_B] =3D "b", + [BUTTON_X] =3D "x", + [BUTTON_Y] =3D "y", + [BUTTON_LB] =3D "lb", + [BUTTON_RB] =3D "rb", + [BUTTON_LT] =3D "lt", + [BUTTON_RT] =3D "rt", + [BUTTON_START] =3D "start", + [BUTTON_SELECT] =3D "select", + [BUTTON_L3] =3D "l3", + [BUTTON_R3] =3D "r3", + [BUTTON_DUP] =3D "d_up", + [BUTTON_DDOWN] =3D "d_down", + [BUTTON_DLEFT] =3D "d_left", + [BUTTON_DRIGHT] =3D "d_right", + [JOY_L_UP] =3D "joy_l_up", + [JOY_L_UP_RIGHT] =3D "joy_l_up_right", + [JOY_L_RIGHT] =3D "joy_l_right", + [JOY_L_DOWN_RIGHT] =3D "joy_l_down_right", + [JOY_L_DOWN] =3D "joy_l_down", + [JOY_L_DOWN_LEFT] =3D "joy_l_down_left", + [JOY_L_LEFT] =3D "joy_l_left", + [JOY_L_UP_LEFT] =3D "joy_l_up_left", + [JOY_R_UP] =3D "joy_r_up", + [JOY_R_UP_RIGHT] =3D "joy_r_up_right", + [JOY_R_RIGHT] =3D "joy_r_right", + [JOY_R_DOWN_RIGHT] =3D "joy_r_down_right", + [JOY_R_DOWN] =3D "joy_r_down", + [JOY_R_DOWN_LEFT] =3D "joy_r_down_left", + [JOY_R_LEFT] =3D "joy_r_left", + [JOY_R_UP_LEFT] =3D "joy_r_up_left", + [BUTTON_GUIDE] =3D "guide", +}; + +enum oxp_custom_button_index { + 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 { + u8 index; + u8 mode; + u8 mapping; + u8 padding[3]; +} __packed; + +struct oxp_bmap_page_1 { + struct oxp_button btn_a; + struct oxp_button btn_b; + struct oxp_button btn_x; + struct oxp_button btn_y; + struct oxp_button btn_lb; + struct oxp_button btn_rb; + struct oxp_button btn_lt; + struct oxp_button btn_rt; + struct oxp_button btn_start; +} __packed; + +struct oxp_bmap_page_2 { + struct oxp_button btn_select; + struct oxp_button btn_l3; + struct oxp_button btn_r3; + struct oxp_button btn_dup; + struct oxp_button btn_ddown; + struct oxp_button btn_dleft; + struct oxp_button btn_dright; + struct oxp_button btn_m1; + struct oxp_button btn_m2; +} __packed; + static struct oxp_hid_cfg { + struct oxp_bmap_page_1 *bmap_1; + struct oxp_bmap_page_2 *bmap_2; struct led_classdev_mc *led_mc; struct hid_device *hdev; struct mutex cfg_mutex; /*ensure single synchronous output report*/ @@ -144,6 +260,10 @@ struct oxp_gen_2_rgb_report { u8 effect; } __packed; =20 +struct oxp_button_attr { + u8 index; +}; + static u16 get_usage_page(struct hid_device *hdev) { return hdev->collection[0].usage >> 16; @@ -351,9 +471,380 @@ static ssize_t button_takeover_index_show(struct devi= ce *dev, } static DEVICE_ATTR_RO(button_takeover_index); =20 +static void oxp_set_defaults_bmap_1(struct oxp_bmap_page_1 *bmap) +{ + bmap->btn_a.index =3D BUTTON_A; + bmap->btn_a.mode =3D 0x01; + bmap->btn_a.mapping =3D BUTTON_A; + bmap->btn_b.index =3D BUTTON_B; + bmap->btn_b.mode =3D 0x01; + bmap->btn_b.mapping =3D BUTTON_B; + bmap->btn_x.index =3D BUTTON_X; + bmap->btn_x.mode =3D 0x01; + bmap->btn_x.mapping =3D BUTTON_X; + bmap->btn_y.index =3D BUTTON_Y; + bmap->btn_y.mode =3D 0x01; + bmap->btn_y.mapping =3D BUTTON_Y; + bmap->btn_lb.index =3D BUTTON_LB; + bmap->btn_lb.mode =3D 0x01; + bmap->btn_lb.mapping =3D BUTTON_LB; + bmap->btn_rb.index =3D BUTTON_RB; + bmap->btn_rb.mode =3D 0x01; + bmap->btn_rb.mapping =3D BUTTON_RB; + bmap->btn_lt.index =3D BUTTON_LT; + bmap->btn_lt.mode =3D 0x01; + bmap->btn_lt.mapping =3D BUTTON_LT; + bmap->btn_rt.index =3D BUTTON_RT; + bmap->btn_rt.mode =3D 0x01; + bmap->btn_rt.mapping =3D BUTTON_RT; + bmap->btn_start.index =3D BUTTON_START; + bmap->btn_start.mode =3D 0x01; + bmap->btn_start.mapping =3D BUTTON_START; +} + +static void oxp_set_defaults_bmap_2(struct oxp_bmap_page_2 *bmap) +{ + bmap->btn_select.index =3D BUTTON_SELECT; + bmap->btn_select.mode =3D 0x01; + bmap->btn_select.mapping =3D BUTTON_SELECT; + bmap->btn_l3.index =3D BUTTON_L3; + bmap->btn_l3.mode =3D 0x01; + bmap->btn_l3.mapping =3D BUTTON_L3; + bmap->btn_r3.index =3D BUTTON_R3; + bmap->btn_r3.mode =3D 0x01; + bmap->btn_r3.mapping =3D BUTTON_R3; + bmap->btn_dup.index =3D BUTTON_DUP; + bmap->btn_dup.mode =3D 0x01; + bmap->btn_dup.mapping =3D BUTTON_DUP; + bmap->btn_ddown.index =3D BUTTON_DDOWN; + bmap->btn_ddown.mode =3D 0x01; + bmap->btn_ddown.mapping =3D BUTTON_DDOWN; + bmap->btn_dleft.index =3D BUTTON_DLEFT; + bmap->btn_dleft.mode =3D 0x01; + bmap->btn_dleft.mapping =3D BUTTON_DLEFT; + bmap->btn_dright.index =3D BUTTON_DRIGHT; + bmap->btn_dright.mode =3D 0x01; + bmap->btn_dright.mapping =3D BUTTON_DRIGHT; + bmap->btn_m1.index =3D BUTTON_M1; + bmap->btn_m1.mode =3D 0x01; + bmap->btn_m1.mapping =3D BUTTON_LT; + bmap->btn_m2.index =3D BUTTON_M2; + bmap->btn_m2.mode =3D 0x01; + bmap->btn_m2.mapping =3D BUTTON_RT; +} + +static int oxp_set_buttons(void) +{ + u8 data[59] =3D { 0x02, 0x00, 0x00, 0x00, 0x01 }; + u16 up =3D get_usage_page(drvdata.hdev); + int ret; + + if (up !=3D GEN2_USAGE_PAGE) + return -EINVAL; + + memcpy(data + 5, drvdata.bmap_1, sizeof(struct oxp_bmap_page_1)); + ret =3D oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, data, ARRAY_SIZE(d= ata)); + if (ret) + return ret; + + memcpy(data + 5, drvdata.bmap_2, sizeof(struct oxp_bmap_page_2)); + return oxp_gen_2_property_out(OXP_FID_GEN2_KEY_STATE, data, ARRAY_SIZE(da= ta)); +} + +static int oxp_reset_buttons(void) +{ + oxp_set_defaults_bmap_1(drvdata.bmap_1); + oxp_set_defaults_bmap_2(drvdata.bmap_2); + return oxp_set_buttons(); +} + +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; + + ret =3D oxp_reset_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 DECLARE_DELAYED_WORK(oxp_btn_queue, oxp_btn_queue_fn); + +static ssize_t map_button_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count, u8 index) +{ + int ret; + u8 val; + + ret =3D sysfs_match_string(oxp_joybutton_text, buf); + if (ret < 0) + return ret; + + val =3D ret; + + switch (index) { + case BUTTON_A: + drvdata.bmap_1->btn_a.mapping =3D val; + break; + case BUTTON_B: + drvdata.bmap_1->btn_b.mapping =3D val; + break; + case BUTTON_X: + drvdata.bmap_1->btn_x.mapping =3D val; + break; + case BUTTON_Y: + drvdata.bmap_1->btn_y.mapping =3D val; + break; + case BUTTON_LB: + drvdata.bmap_1->btn_lb.mapping =3D val; + break; + case BUTTON_RB: + drvdata.bmap_1->btn_rb.mapping =3D val; + break; + case BUTTON_LT: + drvdata.bmap_1->btn_lt.mapping =3D val; + break; + case BUTTON_RT: + drvdata.bmap_1->btn_rt.mapping =3D val; + break; + case BUTTON_START: + drvdata.bmap_1->btn_start.mapping =3D val; + break; + case BUTTON_SELECT: + drvdata.bmap_2->btn_select.mapping =3D val; + break; + case BUTTON_L3: + drvdata.bmap_2->btn_l3.mapping =3D val; + break; + case BUTTON_R3: + drvdata.bmap_2->btn_r3.mapping =3D val; + break; + case BUTTON_DUP: + drvdata.bmap_2->btn_dup.mapping =3D val; + break; + case BUTTON_DDOWN: + drvdata.bmap_2->btn_ddown.mapping =3D val; + break; + case BUTTON_DLEFT: + drvdata.bmap_2->btn_dleft.mapping =3D val; + break; + case BUTTON_DRIGHT: + drvdata.bmap_2->btn_dright.mapping =3D val; + break; + case BUTTON_M1: + drvdata.bmap_2->btn_m1.mapping =3D val; + break; + case BUTTON_M2: + drvdata.bmap_2->btn_m2.mapping =3D val; + break; + default: + return -EINVAL; + } + + mod_delayed_work(system_wq, &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; + break; + case BUTTON_B: + i =3D drvdata.bmap_1->btn_b.mapping; + break; + case BUTTON_X: + i =3D drvdata.bmap_1->btn_x.mapping; + break; + case BUTTON_Y: + i =3D drvdata.bmap_1->btn_y.mapping; + break; + case BUTTON_LB: + i =3D drvdata.bmap_1->btn_lb.mapping; + break; + case BUTTON_RB: + i =3D drvdata.bmap_1->btn_rb.mapping; + break; + case BUTTON_LT: + i =3D drvdata.bmap_1->btn_lt.mapping; + break; + case BUTTON_RT: + i =3D drvdata.bmap_1->btn_rt.mapping; + break; + case BUTTON_START: + i =3D drvdata.bmap_1->btn_start.mapping; + break; + case BUTTON_SELECT: + i =3D drvdata.bmap_2->btn_select.mapping; + break; + case BUTTON_L3: + i =3D drvdata.bmap_2->btn_l3.mapping; + break; + case BUTTON_R3: + i =3D drvdata.bmap_2->btn_r3.mapping; + break; + case BUTTON_DUP: + i =3D drvdata.bmap_2->btn_dup.mapping; + break; + case BUTTON_DDOWN: + i =3D drvdata.bmap_2->btn_ddown.mapping; + break; + case BUTTON_DLEFT: + i =3D drvdata.bmap_2->btn_dleft.mapping; + break; + case BUTTON_DRIGHT: + i =3D drvdata.bmap_2->btn_dright.mapping; + break; + case BUTTON_M1: + i =3D drvdata.bmap_2->btn_m1.mapping; + break; + case BUTTON_M2: + i =3D drvdata.bmap_2->btn_m2.mapping; + break; + default: + return -EINVAL; + } + + if (i >=3D ARRAY_SIZE(oxp_joybutton_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", oxp_joybutton_text[i]); +} + +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_joybutton_text); i++) + count +=3D sysfs_emit_at(buf, count, "%s ", oxp_joybutton_text[i]); + + 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_button_attr button_a =3D { BUTTON_A }; +OXP_DEVICE_ATTR_RW(button_a, map_button); + +static struct oxp_button_attr button_b =3D { BUTTON_B }; +OXP_DEVICE_ATTR_RW(button_b, map_button); + +static struct oxp_button_attr button_x =3D { BUTTON_X }; +OXP_DEVICE_ATTR_RW(button_x, map_button); + +static struct oxp_button_attr button_y =3D { BUTTON_Y }; +OXP_DEVICE_ATTR_RW(button_y, map_button); + +static struct oxp_button_attr button_lb =3D { BUTTON_LB }; +OXP_DEVICE_ATTR_RW(button_lb, map_button); + +static struct oxp_button_attr button_rb =3D { BUTTON_RB }; +OXP_DEVICE_ATTR_RW(button_rb, map_button); + +static struct oxp_button_attr button_lt =3D { BUTTON_LT }; +OXP_DEVICE_ATTR_RW(button_lt, map_button); + +static struct oxp_button_attr button_rt =3D { BUTTON_RT }; +OXP_DEVICE_ATTR_RW(button_rt, map_button); + +static struct oxp_button_attr button_start =3D { BUTTON_START }; +OXP_DEVICE_ATTR_RW(button_start, map_button); + +static struct oxp_button_attr button_select =3D { BUTTON_SELECT }; +OXP_DEVICE_ATTR_RW(button_select, map_button); + +static struct oxp_button_attr button_l3 =3D { BUTTON_L3 }; +OXP_DEVICE_ATTR_RW(button_l3, map_button); + +static struct oxp_button_attr button_r3 =3D { BUTTON_R3 }; +OXP_DEVICE_ATTR_RW(button_r3, map_button); + +static struct oxp_button_attr button_d_up =3D { BUTTON_DUP }; +OXP_DEVICE_ATTR_RW(button_d_up, map_button); + +static struct oxp_button_attr button_d_down =3D { BUTTON_DDOWN }; +OXP_DEVICE_ATTR_RW(button_d_down, map_button); + +static struct oxp_button_attr button_d_left =3D { BUTTON_DLEFT }; +OXP_DEVICE_ATTR_RW(button_d_left, map_button); + +static struct oxp_button_attr button_d_right =3D { BUTTON_DRIGHT }; +OXP_DEVICE_ATTR_RW(button_d_right, map_button); + +static struct oxp_button_attr button_m1 =3D { BUTTON_M1 }; +OXP_DEVICE_ATTR_RW(button_m1, map_button); + +static struct oxp_button_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_takeover.attr, &dev_attr_button_takeover_index.attr, + &dev_attr_button_x.attr, + &dev_attr_button_y.attr, + &dev_attr_reset_buttons.attr, NULL, }; =20 @@ -729,6 +1220,8 @@ static struct led_classdev_mc oxp_cdev_rgb =3D { =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); @@ -756,6 +1249,23 @@ static int oxp_cfg_probe(struct hid_device *hdev, u16= up) if (up !=3D GEN2_USAGE_PAGE) return 0; =20 + 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; + ret =3D oxp_reset_buttons(); + if (ret) + return dev_err_probe(&hdev->dev, ret, + "Failed to reset button mapping\n"); + ret =3D devm_device_add_group(&hdev->dev, &oxp_cfg_attrs_group); if (ret) return dev_err_probe(&hdev->dev, ret, --=20 2.53.0