diff mbox series

[2/2] HID: hid-steam: Add rumble on Deck

Message ID 20230111012336.2915827-2-vi@endrift.com (mailing list archive)
State New, archived
Delegated to: Jiri Kosina
Headers show
Series [1/2] HID: hid-steam: Add Steam Deck support | expand

Commit Message

Vicki Pfau Jan. 11, 2023, 1:23 a.m. UTC
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>
---
 drivers/hid/Kconfig     |  8 ++++++
 drivers/hid/hid-steam.c | 55 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+)

Comments

Lyude Paul Jan. 17, 2023, 11:16 p.m. UTC | #1
On Tue, 2023-01-10 at 17:23 -0800, Vicki Pfau wrote:
> 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>
> ---
>  drivers/hid/Kconfig     |  8 ++++++
>  drivers/hid/hid-steam.c | 55 +++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 63 insertions(+)
> 
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index e2a5d30c8895..e9de0a2d3cd3 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 efd192d6c51a..788b5baaf145 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) {
> @@ -541,6 +584,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 init_ff_fail;
> +	}
> +#endif
> +
>  	ret = input_register_device(input);
>  	if (ret)
>  		goto input_register_fail;
> @@ -549,6 +601,7 @@ static int steam_input_register(struct steam_device *steam)
>  	return 0;
>  
>  input_register_fail:
> +init_ff_fail:

JFYI, this actually causes a compilation warning with CONFIG_STEAM_FF
disabled:

drivers/hid/hid-steam.c: In function ‘steam_input_register’:
drivers/hid/hid-steam.c:604:1: warning: label ‘init_ff_fail’ defined but not
used [-Wunused-label]
  604 | init_ff_fail:
      | ^~~~~~~~~~~~

TBH I think we should be fine just reusing the input_register_fail: jump label
for this instead of adding another label.

FWIW as well if you want: you could just drop the Kconfig option for this
entirely, which bentiss may or may not want. It would at least leave a little
less chance for compilation warnings like this, since the more Kconfig options
you have for a module the higher the chance you'll leave a warning by mistake
in some random kernel config.

If you end up deciding to leave the Kconfig in I'd at least update the commit
message to mention explicitly you added it so people notice it even if they
don't look at the diff (e.g. maintainers just merging reviewed patches).

I have no hard opinion either way as long as we fix the compilation warning
:). With the issues mentioned here addressed, this patch is:

Reviewed-by: Lyude Paul <lyude@redhat.com>

>  	input_free_device(input);
>  	return ret;
>  }
> @@ -842,6 +895,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)) {
> @@ -898,6 +952,7 @@ static int steam_probe(struct hid_device *hdev,
>  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);
Benjamin Tissoires Jan. 25, 2023, 8:41 a.m. UTC | #2
On Wed, Jan 18, 2023 at 12:16 AM Lyude Paul <lyude@redhat.com> wrote:
>
> On Tue, 2023-01-10 at 17:23 -0800, Vicki Pfau wrote:
> > 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>
> > ---
> >  drivers/hid/Kconfig     |  8 ++++++
> >  drivers/hid/hid-steam.c | 55 +++++++++++++++++++++++++++++++++++++++++
> >  2 files changed, 63 insertions(+)
> >
> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > index e2a5d30c8895..e9de0a2d3cd3 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 efd192d6c51a..788b5baaf145 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) {
> > @@ -541,6 +584,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 init_ff_fail;
> > +     }
> > +#endif
> > +
> >       ret = input_register_device(input);
> >       if (ret)
> >               goto input_register_fail;
> > @@ -549,6 +601,7 @@ static int steam_input_register(struct steam_device *steam)
> >       return 0;
> >
> >  input_register_fail:
> > +init_ff_fail:
>
> JFYI, this actually causes a compilation warning with CONFIG_STEAM_FF
> disabled:
>
> drivers/hid/hid-steam.c: In function ‘steam_input_register’:
> drivers/hid/hid-steam.c:604:1: warning: label ‘init_ff_fail’ defined but not
> used [-Wunused-label]
>   604 | init_ff_fail:
>       | ^~~~~~~~~~~~
>
> TBH I think we should be fine just reusing the input_register_fail: jump label
> for this instead of adding another label.
>
> FWIW as well if you want: you could just drop the Kconfig option for this
> entirely, which bentiss may or may not want. It would at least leave a little
> less chance for compilation warnings like this, since the more Kconfig options
> you have for a module the higher the chance you'll leave a warning by mistake
> in some random kernel config.

I agree with Lyude here. However the whole HID tree is crippled with
those "if input_ff" and that would mean a bigger policy change.

So I would suggest keeping the Kconfig option for now, and if you
want, Vicki (or anybody else) maybe it's time to get rid of those
input_ff Kconfigs and make them slightly more evident for users.
Whether the first driver to use it selects input_ff or they all depend
on input_ff without the ability to disable it is entirely the
responsibility of the submitter :)

Cheers,
Benjamin

>
> If you end up deciding to leave the Kconfig in I'd at least update the commit
> message to mention explicitly you added it so people notice it even if they
> don't look at the diff (e.g. maintainers just merging reviewed patches).
>
> I have no hard opinion either way as long as we fix the compilation warning
> :). With the issues mentioned here addressed, this patch is:
>
> Reviewed-by: Lyude Paul <lyude@redhat.com>
>
> >       input_free_device(input);
> >       return ret;
> >  }
> > @@ -842,6 +895,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)) {
> > @@ -898,6 +952,7 @@ static int steam_probe(struct hid_device *hdev,
> >  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);
>
> --
> Cheers,
>  Lyude Paul (she/her)
>  Software Engineer at Red Hat
>
diff mbox series

Patch

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e2a5d30c8895..e9de0a2d3cd3 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 efd192d6c51a..788b5baaf145 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) {
@@ -541,6 +584,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 init_ff_fail;
+	}
+#endif
+
 	ret = input_register_device(input);
 	if (ret)
 		goto input_register_fail;
@@ -549,6 +601,7 @@  static int steam_input_register(struct steam_device *steam)
 	return 0;
 
 input_register_fail:
+init_ff_fail:
 	input_free_device(input);
 	return ret;
 }
@@ -842,6 +895,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)) {
@@ -898,6 +952,7 @@  static int steam_probe(struct hid_device *hdev,
 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);