summaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorVicki Pfau <vi@endrift.com>2023-01-26 04:01:26 +0100
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>2023-02-06 10:01:33 +0100
commit9cd61c8179781344315c2a9816977ea415c5459e (patch)
treed37b1829946db096b519f1e0ea9a30fa32b4a003 /drivers/hid
parentHID: hid-steam: Add Steam Deck support (diff)
downloadlinux-9cd61c8179781344315c2a9816977ea415c5459e.tar.xz
linux-9cd61c8179781344315c2a9816977ea415c5459e.zip
HID: hid-steam: Add rumble on Deck
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>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig8
-rw-r--r--drivers/hid/hid-steam.c54
2 files changed, 62 insertions, 0 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 20402d0a7eab..ce488cf8765f 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -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
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 09330d9bfee3..aaca390ca299 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -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);