]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
HID: hid-lenovo-go: Add Rumble and Haptic Settings
authorDerek J. Clark <derekjohn.clark@gmail.com>
Tue, 10 Mar 2026 07:29:22 +0000 (07:29 +0000)
committerJiri Kosina <jkosina@suse.com>
Tue, 10 Mar 2026 16:53:17 +0000 (17:53 +0100)
Adds attributes that control the handles rumble mode and intensity, as
well as touchpad haptic feedback settings.

Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
drivers/hid/hid-lenovo-go.c

index d7d47db8362c56dde6e5391195c8055f4e7d65ba..f2a54865cfbb175da1bdf30ada6cfc23ffc24d90 100644 (file)
@@ -41,6 +41,8 @@ static struct hid_go_cfg {
        struct mutex cfg_mutex; /*ensure single synchronous output report*/
        u8 fps_mode;
        u8 gp_left_auto_sleep_time;
+       u8 gp_left_notify_en;
+       u8 gp_left_rumble_mode;
        u32 gp_left_version_firmware;
        u8 gp_left_version_gen;
        u32 gp_left_version_hardware;
@@ -48,11 +50,14 @@ static struct hid_go_cfg {
        u32 gp_left_version_protocol;
        u8 gp_mode;
        u8 gp_right_auto_sleep_time;
+       u8 gp_right_notify_en;
+       u8 gp_right_rumble_mode;
        u32 gp_right_version_firmware;
        u8 gp_right_version_gen;
        u32 gp_right_version_hardware;
        u32 gp_right_version_product;
        u32 gp_right_version_protocol;
+       u8 gp_rumble_intensity;
        u8 imu_left_bypass_en;
        u8 imu_left_sensor_en;
        u8 imu_right_bypass_en;
@@ -64,6 +69,8 @@ static struct hid_go_cfg {
        u32 mcu_version_protocol;
        u8 rgb_en;
        u8 tp_en;
+       u8 tp_vibration_en;
+       u8 tp_vibration_intensity;
        u32 tx_dongle_version_firmware;
        u8 tx_dongle_version_gen;
        u32 tx_dongle_version_hardware;
@@ -170,6 +177,49 @@ static const char *const gamepad_mode_text[] = {
        [DINPUT] = "dinput",
 };
 
+enum motor_cfg_index {
+       MOTOR_CFG_ALL = 0x01,
+       MOTOR_INTENSITY,
+       VIBRATION_NOTIFY_ENABLE,
+       RUMBLE_MODE,
+       TP_VIBRATION_ENABLE,
+       TP_VIBRATION_INTENSITY,
+};
+
+enum intensity_index {
+       INTENSITY_UNKNOWN,
+       INTENSITY_OFF,
+       INTENSITY_LOW,
+       INTENSITY_MEDIUM,
+       INTENSITY_HIGH,
+};
+
+static const char *const intensity_text[] = {
+       [INTENSITY_UNKNOWN] = "unknown",
+       [INTENSITY_OFF] = "off",
+       [INTENSITY_LOW] = "low",
+       [INTENSITY_MEDIUM] = "medium",
+       [INTENSITY_HIGH] = "high",
+};
+
+enum rumble_mode_index {
+       RUMBLE_MODE_UNKNOWN,
+       RUMBLE_MODE_FPS,
+       RUMBLE_MODE_RACE,
+       RUMBLE_MODE_AVERAGE,
+       RUMBLE_MODE_SPG,
+       RUMBLE_MODE_RPG,
+};
+
+static const char *const rumble_mode_text[] = {
+       [RUMBLE_MODE_UNKNOWN] = "unknown",
+       [RUMBLE_MODE_FPS] = "fps",
+       [RUMBLE_MODE_RACE] = "racing",
+       [RUMBLE_MODE_AVERAGE] = "standard",
+       [RUMBLE_MODE_SPG] = "spg",
+       [RUMBLE_MODE_RPG] = "rpg",
+};
+
 static int hid_go_version_event(struct command_report *cmd_rep)
 {
        switch (cmd_rep->sub_cmd) {
@@ -336,6 +386,47 @@ static int hid_go_feature_status_event(struct command_report *cmd_rep)
        }
 }
 
+static int hid_go_motor_event(struct command_report *cmd_rep)
+{
+       switch (cmd_rep->sub_cmd) {
+       case MOTOR_CFG_ALL:
+               return -EINVAL;
+       case MOTOR_INTENSITY:
+               drvdata.gp_rumble_intensity = cmd_rep->data[0];
+               return 0;
+       case VIBRATION_NOTIFY_ENABLE:
+               switch (cmd_rep->device_type) {
+               case LEFT_CONTROLLER:
+                       drvdata.gp_left_notify_en = cmd_rep->data[0];
+                       return 0;
+               case RIGHT_CONTROLLER:
+                       drvdata.gp_right_notify_en = cmd_rep->data[0];
+                       return 0;
+               default:
+                       return -EINVAL;
+               };
+               break;
+       case RUMBLE_MODE:
+               switch (cmd_rep->device_type) {
+               case LEFT_CONTROLLER:
+                       drvdata.gp_left_rumble_mode = cmd_rep->data[0];
+                       return 0;
+               case RIGHT_CONTROLLER:
+                       drvdata.gp_right_rumble_mode = cmd_rep->data[0];
+                       return 0;
+               default:
+                       return -EINVAL;
+               };
+       case TP_VIBRATION_ENABLE:
+               drvdata.tp_vibration_en = cmd_rep->data[0];
+               return 0;
+       case TP_VIBRATION_INTENSITY:
+               drvdata.tp_vibration_intensity = cmd_rep->data[0];
+               return 0;
+       }
+       return -EINVAL;
+}
+
 static int hid_go_set_event_return(struct command_report *cmd_rep)
 {
        if (cmd_rep->data[0] != 0)
@@ -383,7 +474,11 @@ static int hid_go_raw_event(struct hid_device *hdev, struct hid_report *report,
                case GET_FEATURE_STATUS:
                        ret = hid_go_feature_status_event(cmd_rep);
                        break;
+               case GET_MOTOR_CFG:
+                       ret = hid_go_motor_event(cmd_rep);
+                       break;
                case SET_FEATURE_STATUS:
+               case SET_MOTOR_CFG:
                        ret = hid_go_set_event_return(cmd_rep);
                        break;
                default:
@@ -759,6 +854,168 @@ static ssize_t feature_status_options(struct device *dev,
        return count;
 }
 
+static ssize_t motor_config_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t count,
+                                 enum motor_cfg_index index,
+                                 enum dev_type device_type)
+{
+       size_t size = 1;
+       u8 val = 0;
+       int ret;
+
+       switch (index) {
+       case MOTOR_CFG_ALL:
+               return -EINVAL;
+       case MOTOR_INTENSITY:
+               ret = sysfs_match_string(intensity_text, buf);
+               val = ret;
+               break;
+       case VIBRATION_NOTIFY_ENABLE:
+               ret = sysfs_match_string(enabled_status_text, buf);
+               val = ret;
+               break;
+       case RUMBLE_MODE:
+               ret = sysfs_match_string(rumble_mode_text, buf);
+               val = ret;
+               break;
+       case TP_VIBRATION_ENABLE:
+               ret = sysfs_match_string(enabled_status_text, buf);
+               val = ret;
+               break;
+       case TP_VIBRATION_INTENSITY:
+               ret = sysfs_match_string(intensity_text, buf);
+               val = ret;
+               break;
+       };
+
+       if (ret < 0)
+               return ret;
+
+       if (!val)
+               size = 0;
+
+       ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, SET_MOTOR_CFG,
+                              index, device_type, &val, size);
+       if (ret < 0)
+               return ret;
+
+       return count;
+}
+
+static ssize_t motor_config_show(struct device *dev,
+                                struct device_attribute *attr, char *buf,
+                                enum motor_cfg_index index,
+                                enum dev_type device_type)
+{
+       ssize_t count = 0;
+       int ret;
+       u8 i;
+
+       ret = mcu_property_out(drvdata.hdev, MCU_CONFIG_DATA, GET_MOTOR_CFG,
+                              index, device_type, NULL, 0);
+       if (ret)
+               return ret;
+
+       switch (index) {
+       case MOTOR_CFG_ALL:
+               return -EINVAL;
+       case MOTOR_INTENSITY:
+               i = drvdata.gp_rumble_intensity;
+               if (i >= ARRAY_SIZE(intensity_text))
+                       return -EINVAL;
+
+               count = sysfs_emit(buf, "%s\n", intensity_text[i]);
+               break;
+       case VIBRATION_NOTIFY_ENABLE:
+               switch (device_type) {
+               case LEFT_CONTROLLER:
+                       i = drvdata.gp_left_notify_en;
+                       break;
+               case RIGHT_CONTROLLER:
+                       i = drvdata.gp_right_notify_en;
+                       break;
+               default:
+                       return -EINVAL;
+               };
+               if (i >= ARRAY_SIZE(enabled_status_text))
+                       return -EINVAL;
+
+               count = sysfs_emit(buf, "%s\n", enabled_status_text[i]);
+               break;
+       case RUMBLE_MODE:
+               switch (device_type) {
+               case LEFT_CONTROLLER:
+                       i = drvdata.gp_left_rumble_mode;
+                       break;
+               case RIGHT_CONTROLLER:
+                       i = drvdata.gp_right_rumble_mode;
+                       break;
+               default:
+                       return -EINVAL;
+               };
+               if (i >= ARRAY_SIZE(rumble_mode_text))
+                       return -EINVAL;
+
+               count = sysfs_emit(buf, "%s\n", rumble_mode_text[i]);
+               break;
+       case TP_VIBRATION_ENABLE:
+               i = drvdata.tp_vibration_en;
+               if (i >= ARRAY_SIZE(enabled_status_text))
+                       return -EINVAL;
+
+               count = sysfs_emit(buf, "%s\n", enabled_status_text[i]);
+               break;
+       case TP_VIBRATION_INTENSITY:
+               i = drvdata.tp_vibration_intensity;
+               if (i >= ARRAY_SIZE(intensity_text))
+                       return -EINVAL;
+
+               count = sysfs_emit(buf, "%s\n", intensity_text[i]);
+               break;
+       };
+
+       return count;
+}
+
+static ssize_t motor_config_options(struct device *dev,
+                                   struct device_attribute *attr, char *buf,
+                                   enum motor_cfg_index index)
+{
+       ssize_t count = 0;
+       unsigned int i;
+
+       switch (index) {
+       case MOTOR_CFG_ALL:
+               break;
+       case RUMBLE_MODE:
+               for (i = 1; i < ARRAY_SIZE(rumble_mode_text); i++) {
+                       count += sysfs_emit_at(buf, count, "%s ",
+                                              rumble_mode_text[i]);
+               }
+               break;
+       case MOTOR_INTENSITY:
+       case TP_VIBRATION_INTENSITY:
+               for (i = 1; i < ARRAY_SIZE(intensity_text); i++) {
+                       count += sysfs_emit_at(buf, count, "%s ",
+                                              intensity_text[i]);
+               }
+               break;
+       case VIBRATION_NOTIFY_ENABLE:
+       case TP_VIBRATION_ENABLE:
+               for (i = 1; i < ARRAY_SIZE(enabled_status_text); i++) {
+                       count += sysfs_emit_at(buf, count, "%s ",
+                                              enabled_status_text[i]);
+               }
+               break;
+       };
+
+       if (count)
+               buf[count - 1] = '\n';
+
+       return count;
+}
+
 #define LEGO_DEVICE_ATTR_RW(_name, _attrname, _dtype, _rtype, _group)         \
        static ssize_t _name##_store(struct device *dev,                      \
                                     struct device_attribute *attr,           \
@@ -824,10 +1081,18 @@ static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index");
 static struct go_cfg_attr reset_mcu = { FEATURE_RESET_GAMEPAD };
 LEGO_DEVICE_ATTR_WO(reset_mcu, "reset_mcu", USB_MCU, feature_status);
 
+static struct go_cfg_attr gamepad_rumble_intensity = { MOTOR_INTENSITY };
+LEGO_DEVICE_ATTR_RW(gamepad_rumble_intensity, "rumble_intensity", UNSPECIFIED,
+                   index, motor_config);
+static DEVICE_ATTR_RO_NAMED(gamepad_rumble_intensity_index,
+                           "rumble_intensity_index");
+
 static struct attribute *mcu_attrs[] = {
        &dev_attr_fps_switch_status.attr,
        &dev_attr_gamepad_mode.attr,
        &dev_attr_gamepad_mode_index.attr,
+       &dev_attr_gamepad_rumble_intensity.attr,
+       &dev_attr_gamepad_rumble_intensity_index.attr,
        &dev_attr_reset_mcu.attr,
        &dev_attr_version_firmware_mcu.attr,
        &dev_attr_version_gen_mcu.attr,
@@ -910,6 +1175,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_left_index, "imu_enabled_index");
 static struct go_cfg_attr reset_left = { FEATURE_RESET_GAMEPAD };
 LEGO_DEVICE_ATTR_WO(reset_left, "reset", LEFT_CONTROLLER, feature_status);
 
+static struct go_cfg_attr rumble_mode_left = { RUMBLE_MODE };
+LEGO_DEVICE_ATTR_RW(rumble_mode_left, "rumble_mode", LEFT_CONTROLLER, index,
+                   motor_config);
+static DEVICE_ATTR_RO_NAMED(rumble_mode_left_index, "rumble_mode_index");
+
+static struct go_cfg_attr rumble_notification_left = { VIBRATION_NOTIFY_ENABLE };
+LEGO_DEVICE_ATTR_RW(rumble_notification_left, "rumble_notification",
+                   LEFT_CONTROLLER, index, motor_config);
+static DEVICE_ATTR_RO_NAMED(rumble_notification_left_index,
+                           "rumble_notification_index");
+
 static struct attribute *left_gamepad_attrs[] = {
        &dev_attr_auto_sleep_time_left.attr,
        &dev_attr_auto_sleep_time_left_range.attr,
@@ -918,6 +1194,10 @@ static struct attribute *left_gamepad_attrs[] = {
        &dev_attr_imu_enabled_left.attr,
        &dev_attr_imu_enabled_left_index.attr,
        &dev_attr_reset_left.attr,
+       &dev_attr_rumble_mode_left.attr,
+       &dev_attr_rumble_mode_left_index.attr,
+       &dev_attr_rumble_notification_left.attr,
+       &dev_attr_rumble_notification_left_index.attr,
        &dev_attr_version_hardware_left.attr,
        &dev_attr_version_firmware_left.attr,
        &dev_attr_version_gen_left.attr,
@@ -966,6 +1246,17 @@ static DEVICE_ATTR_RO_NAMED(imu_enabled_right_index, "imu_enabled_index");
 static struct go_cfg_attr reset_right = { FEATURE_RESET_GAMEPAD };
 LEGO_DEVICE_ATTR_WO(reset_right, "reset", LEFT_CONTROLLER, feature_status);
 
+static struct go_cfg_attr rumble_mode_right = { RUMBLE_MODE };
+LEGO_DEVICE_ATTR_RW(rumble_mode_right, "rumble_mode", RIGHT_CONTROLLER, index,
+                   motor_config);
+static DEVICE_ATTR_RO_NAMED(rumble_mode_right_index, "rumble_mode_index");
+
+static struct go_cfg_attr rumble_notification_right = { VIBRATION_NOTIFY_ENABLE };
+LEGO_DEVICE_ATTR_RW(rumble_notification_right, "rumble_notification",
+                   RIGHT_CONTROLLER, index, motor_config);
+static DEVICE_ATTR_RO_NAMED(rumble_notification_right_index,
+                           "rumble_notification_index");
+
 static struct attribute *right_gamepad_attrs[] = {
        &dev_attr_auto_sleep_time_right.attr,
        &dev_attr_auto_sleep_time_right_range.attr,
@@ -974,6 +1265,10 @@ static struct attribute *right_gamepad_attrs[] = {
        &dev_attr_imu_enabled_right.attr,
        &dev_attr_imu_enabled_right_index.attr,
        &dev_attr_reset_right.attr,
+       &dev_attr_rumble_mode_right.attr,
+       &dev_attr_rumble_mode_right_index.attr,
+       &dev_attr_rumble_notification_right.attr,
+       &dev_attr_rumble_notification_right_index.attr,
        &dev_attr_version_hardware_right.attr,
        &dev_attr_version_firmware_right.attr,
        &dev_attr_version_gen_right.attr,
@@ -993,9 +1288,26 @@ LEGO_DEVICE_ATTR_RW(touchpad_enabled, "enabled", UNSPECIFIED, index,
                    feature_status);
 static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index");
 
+static struct go_cfg_attr touchpad_vibration_enabled = { TP_VIBRATION_ENABLE };
+LEGO_DEVICE_ATTR_RW(touchpad_vibration_enabled, "vibration_enabled", UNSPECIFIED,
+                   index, motor_config);
+static DEVICE_ATTR_RO_NAMED(touchpad_vibration_enabled_index,
+                           "vibration_enabled_index");
+
+static struct go_cfg_attr touchpad_vibration_intensity = { TP_VIBRATION_INTENSITY };
+LEGO_DEVICE_ATTR_RW(touchpad_vibration_intensity, "vibration_intensity",
+                   UNSPECIFIED, index, motor_config);
+static DEVICE_ATTR_RO_NAMED(touchpad_vibration_intensity_index,
+                           "vibration_intensity_index");
+
 static struct attribute *touchpad_attrs[] = {
        &dev_attr_touchpad_enabled.attr,
        &dev_attr_touchpad_enabled_index.attr,
+       &dev_attr_touchpad_vibration_enabled.attr,
+       &dev_attr_touchpad_vibration_enabled_index.attr,
+       &dev_attr_touchpad_vibration_intensity.attr,
+       &dev_attr_touchpad_vibration_intensity_index.attr,
+       NULL,
 };
 
 static const struct attribute_group touchpad_attr_group = {