]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: hid-oxp: Add Second Generation Gamepad Mode Switch
authorDerek J. Clark <derekjohn.clark@gmail.com>
Sun, 19 Apr 2026 04:26:22 +0000 (21:26 -0700)
committerJiri Kosina <jkosina@suse.com>
Tue, 12 May 2026 15:55:57 +0000 (17:55 +0200)
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 <honjow311@gmail.com>
Tested-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
drivers/hid/hid-oxp.c

index 835de2118e3c5fca35835d3f8e77d3e7a7a0e6ca..2504b56b8f8a8a3e04fb73c4631120b21366bf0e 100644 (file)
 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_STATUS_EVENT =     0xb8,
 };
 
 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;
 
+enum oxp_gamepad_mode_index {
+       OXP_GP_MODE_XINPUT = 0x00,
+       OXP_GP_MODE_DEBUG = 0x03,
+};
+
+static const char *const oxp_gamepad_mode_text[] = {
+       [OXP_GP_MODE_XINPUT] = "xinput",
+       [OXP_GP_MODE_DEBUG] = "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;
 }
 
+static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data, u8 data_size);
+
+static void oxp_mcu_init_fn(struct work_struct *work)
+{
+       u8 gp_mode_data[3] = { OXP_GP_MODE_DEBUG, 0x01, 0x02 };
+       int ret;
+
+       /* Cycle the gamepad mode */
+       ret = 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 == OXP_GP_MODE_DEBUG)
+               return;
+
+       gp_mode_data[0] = OXP_GP_MODE_XINPUT;
+       ret = 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] != OXP_FID_GEN2_STATUS_EVENT)
                return 0;
 
+       /* Sent ~6s after resume event, indicating the MCU has fully reset.
+        * Re-apply our settings after this has been received.
+        */
+       if (data[3] == OXP_EFFECT_MONO_TRUE) {
+               mod_delayed_work(system_wq, &drvdata.oxp_mcu_init, msecs_to_jiffies(50));
+               return 0;
+       }
+
        if (data[3] != OXP_GET_PROPERTY)
                return 0;
 
@@ -289,6 +334,77 @@ static int oxp_gen_2_property_out(enum oxp_function_index fid, u8 *data,
                                footer_size);
 }
 
+static ssize_t gamepad_mode_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 };
+       int ret = -EINVAL;
+       int i;
+
+       if (up != GEN2_USAGE_PAGE)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
+               if (oxp_gamepad_mode_text[i] && sysfs_streq(buf, oxp_gamepad_mode_text[i])) {
+                       ret = i;
+                       break;
+               }
+       }
+       if (ret < 0)
+               return ret;
+
+       data[0] = ret;
+
+       ret = oxp_gen_2_property_out(OXP_FID_GEN2_TOGGLE_MODE, data, 3);
+       if (ret)
+               return ret;
+
+       drvdata.gamepad_mode = 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 = 0;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(oxp_gamepad_mode_text); i++) {
+               if (!oxp_gamepad_mode_text[i] ||
+                   oxp_gamepad_mode_text[i][0] == '\0')
+                       continue;
+
+               count += sysfs_emit_at(buf, count, "%s ", oxp_gamepad_mode_text[i]);
+       }
+
+       if (count)
+               buf[count - 1] = '\n';
+
+       return count;
+}
+static DEVICE_ATTR_RO(gamepad_mode_index);
+
+static struct attribute *oxp_cfg_attrs[] = {
+       &dev_attr_gamepad_mode.attr,
+       &dev_attr_gamepad_mode_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);
@@ -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);
 
+       /* Below features are only implemented in gen 2 */
+       if (up != GEN2_USAGE_PAGE)
+               return 0;
+
 skip_rgb:
+       drvdata.gamepad_mode = 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 = 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;
 }
 
@@ -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);
 }