HID: hid-steam: Add rumble on Deck
authorVicki Pfau <vi@endrift.com>
Thu, 26 Jan 2023 03:01:26 +0000 (19:01 -0800)
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>
Mon, 6 Feb 2023 09:01:33 +0000 (10:01 +0100)
The Steam Deck includes a new report that allows for emulating XInput-style
rumble motors with the Deck's actuators. This adds support for passing these
values directly to the Deck.

Signed-off-by: Vicki Pfau <vi@endrift.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
Link: https://lore.kernel.org/r/20230126030126.895670-3-vi@endrift.com
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
drivers/hid/Kconfig
drivers/hid/hid-steam.c

index 20402d0a7eabf415d678a07c7fa5af4f11559107..ce488cf8765f8bfd1a2ccbdbb20de0403b8ead54 100644 (file)
@@ -1025,6 +1025,14 @@ config HID_STEAM
        without running the Steam Client. It supports both the wired and
        the wireless adaptor.
 
+config STEAM_FF
+       bool "Steam Deck force feedback support"
+       depends on HID_STEAM
+       select INPUT_FF_MEMLESS
+       help
+       Say Y here if you want to enable force feedback support for the Steam
+       Deck.
+
 config HID_STEELSERIES
        tristate "Steelseries SRW-S1 steering wheel support"
        help
index 09330d9bfee3c01d9de61ec3a372acdc7362ebe5..aaca390ca2997fa2b4bbe6924a28bca368aba94d 100644 (file)
@@ -91,6 +91,7 @@ static LIST_HEAD(steam_devices);
 #define STEAM_CMD_FORCEFEEDBAK         0x8f
 #define STEAM_CMD_REQUEST_COMM_STATUS  0xb4
 #define STEAM_CMD_GET_SERIAL           0xae
+#define STEAM_CMD_HAPTIC_RUMBLE                0xeb
 
 /* Some useful register ids */
 #define STEAM_REG_LPAD_MODE            0x07
@@ -134,6 +135,9 @@ struct steam_device {
        u8 battery_charge;
        u16 voltage;
        struct delayed_work heartbeat;
+       struct work_struct rumble_work;
+       u16 rumble_left;
+       u16 rumble_right;
 };
 
 static int steam_recv_report(struct steam_device *steam,
@@ -290,6 +294,45 @@ static inline int steam_request_conn_status(struct steam_device *steam)
        return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
 }
 
+static inline int steam_haptic_rumble(struct steam_device *steam,
+                               u16 intensity, u16 left_speed, u16 right_speed,
+                               u8 left_gain, u8 right_gain)
+{
+       u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
+
+       report[3] = intensity & 0xFF;
+       report[4] = intensity >> 8;
+       report[5] = left_speed & 0xFF;
+       report[6] = left_speed >> 8;
+       report[7] = right_speed & 0xFF;
+       report[8] = right_speed >> 8;
+       report[9] = left_gain;
+       report[10] = right_gain;
+
+       return steam_send_report(steam, report, sizeof(report));
+}
+
+static void steam_haptic_rumble_cb(struct work_struct *work)
+{
+       struct steam_device *steam = container_of(work, struct steam_device,
+                                                       rumble_work);
+       steam_haptic_rumble(steam, 0, steam->rumble_left,
+               steam->rumble_right, 2, 0);
+}
+
+#ifdef CONFIG_STEAM_FF
+static int steam_play_effect(struct input_dev *dev, void *data,
+                               struct ff_effect *effect)
+{
+       struct steam_device *steam = input_get_drvdata(dev);
+
+       steam->rumble_left = effect->u.rumble.strong_magnitude;
+       steam->rumble_right = effect->u.rumble.weak_magnitude;
+
+       return schedule_work(&steam->rumble_work);
+}
+#endif
+
 static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
 {
        if (enable) {
@@ -540,6 +583,15 @@ static int steam_input_register(struct steam_device *steam)
        input_abs_set_res(input, ABS_HAT0X, STEAM_PAD_RESOLUTION);
        input_abs_set_res(input, ABS_HAT0Y, STEAM_PAD_RESOLUTION);
 
+#ifdef CONFIG_STEAM_FF
+       if (steam->quirks & STEAM_QUIRK_DECK) {
+               input_set_capability(input, EV_FF, FF_RUMBLE);
+               ret = input_ff_create_memless(input, NULL, steam_play_effect);
+               if (ret)
+                       goto input_register_fail;
+       }
+#endif
+
        ret = input_register_device(input);
        if (ret)
                goto input_register_fail;
@@ -841,6 +893,7 @@ static int steam_probe(struct hid_device *hdev,
        INIT_WORK(&steam->work_connect, steam_work_connect_cb);
        INIT_LIST_HEAD(&steam->list);
        INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
+       INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
 
        steam->client_hdev = steam_create_client_hid(hdev);
        if (IS_ERR(steam->client_hdev)) {
@@ -897,6 +950,7 @@ hid_hw_start_fail:
 client_hdev_fail:
        cancel_work_sync(&steam->work_connect);
        cancel_delayed_work_sync(&steam->heartbeat);
+       cancel_work_sync(&steam->rumble_work);
 steam_alloc_fail:
        hid_err(hdev, "%s: failed with error %d\n",
                        __func__, ret);