[PATCH 3/4] HID: hid-oxp: Add Second Generation Takeover Mode

From: Derek J. Clark

Date: Sat Mar 21 2026 - 23:17:29 EST


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 <derekjohn.clark@xxxxxxxxx>
---
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 = 0x07,
OXP_FID_GEN1_RGB_REPLY = 0x0f,
+ OXP_FID_GEN2_TOGGLE_MODE = 0xb2,
OXP_FID_GEN2_RGB_EVENT = 0xb8,
};

@@ -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;

+#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_index fid, u8 *data,
footer_size);
}

+static ssize_t button_takeover_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ u16 up = get_usage_page(drvdata.hdev);
+ u8 data[3] = { 0x00, 0x01, 0x02 };
+ u8 val = 0;
+ int ret;
+
+ if (up != GEN2_USAGE_PAGE)
+ return -EINVAL;
+
+ ret = sysfs_match_string(oxp_feature_en_text, buf);
+ if (ret < 0)
+ return ret;
+ val = ret;
+
+ switch (val) {
+ case OXP_FEAT_DISABLED:
+ break;
+ case OXP_FEAT_ENABLED:
+ data[0] = OXP_TAKEOVER_ENABLED_TRUE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = 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_enabled]);
+}
+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 = 0;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(oxp_feature_en_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", oxp_feature_en_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(button_takeover_index);
+
+static struct attribute *oxp_cfg_attrs[] = {
+ &dev_attr_button_takeover.attr,
+ &dev_attr_button_takeover_index.attr,
+ NULL,
+};
+
+static const struct attribute_group oxp_cfg_attrs_group = {
+ .attrs = oxp_cfg_attrs,
+};
+
static int oxp_rgb_status_store(u8 enabled, u8 speed, u8 brightness)
{
u16 up = 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);

+ /* Below features are only implemented in gen 2 */
+ if (up != GEN2_USAGE_PAGE)
+ return 0;
+
+ ret = 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;
}

--
2.53.0