From: Denis Benato <denis.benato@linux.dev>
To: linux-kernel@vger.kernel.org
Cc: platform-driver-x86@vger.kernel.org, linux-input@vger.kernel.org,
kernel@lists.opengamingcollective.org,
"Benjamin Tissoires" <bentiss@kernel.org>,
"Jiri Kosina" <jikos@kernel.org>,
"Luke D . Jones" <luke@ljones.dev>,
"Mateusz Schwartz" <matthew.schwartz@linux.dev>,
"Denis Benato" <benato.denis96@gmail.com>,
"Jonathan LoBue" <jlobue10@gmail.com>,
"Khamunetri Clark" <khamunetriclark@gmail.com>,
"Derek J. Clark" <derekjohn.clark@gmail.com>,
"Joshua Leivenzon" <hacker1024@users.sourceforge.net>,
"Nathan Chancellor" <nathan@kernel.org>,
"Rebecca Mara Müller" <rebecca.mara@posteo.de>,
"Ivan Levchenko" <me@livan.pro>,
"Corentin Chary" <corentin.chary@gmail.com>,
"Hans de Goede" <hansg@kernel.org>,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Denis Benato" <denis.benato@linux.dev>
Subject: [PATCH v12 09/17] HID: asus: add support to force feedback
Date: Sat, 31 Oct 2026 17:41:22 +0000 [thread overview]
Message-ID: <20261031174130.208240-10-denis.benato@linux.dev> (raw)
In-Reply-To: <20261031174130.208240-1-denis.benato@linux.dev>
Unlike ROG ally the X version and following ones uses DInput protocol
and the force feedback needs to be implemented as its protocol is
vendor-specific, therefore add support for FF_RUMBLE with magnitude
scaling on a work-queue based approach to avoid using possibly
sleeping calls in atomic context.
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Khamunetri Clark <khamunetriclark@gmail.com>
Co-developed-by: Luke Jones <luke@ljones.dev>
Signed-off-by: Luke Jones <luke@ljones.dev>
---
drivers/hid/Kconfig | 1 +
drivers/hid/hid-asus.c | 143 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 144 insertions(+)
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 2481bc01c435..dd411f6eff4f 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -189,6 +189,7 @@ config HID_ASUS
depends on USB_HID
depends on LEDS_CLASS
depends on ASUS_WMI || ASUS_WMI=n
+ select INPUT_FF_MEMLESS
select POWER_SUPPLY
help
Support for Asus notebook built-in keyboard and touchpad via i2c, and
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 783c78160277..3ade3dbda72e 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -22,6 +22,7 @@
#include <linux/acpi.h>
#include <linux/cleanup.h>
+#include <linux/compiler.h>
#include <linux/compiler_attributes.h>
#include <linux/container_of.h>
#include <linux/ctype.h>
@@ -30,6 +31,7 @@
#include <linux/hid.h>
#include <linux/jiffies.h>
#include <linux/kstrtox.h>
+#include <linux/limits.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -210,6 +212,23 @@ struct asus_touchpad_info {
int report_size;
};
+/* Output report 0x0D on the gamepad interface. */
+struct ally_ff_data {
+ u8 enable;
+ u8 magnitude_left;
+ u8 magnitude_right;
+ u8 magnitude_strong;
+ u8 magnitude_weak;
+ u8 pulse_sustain_10ms;
+ u8 pulse_release_10ms;
+ u8 loop_count;
+} __packed;
+
+struct ally_ff_report {
+ u8 report_id;
+ struct ally_ff_data ff;
+} __packed;
+
struct ally_intf {
struct hid_device *hdev;
/* HID-core evdev of this interface. Reports for it land here. */
@@ -219,6 +238,14 @@ struct ally_intf {
u8 cad_state;
unsigned long cad_last_event_time;
struct delayed_work resume_work;
+ struct {
+ /* Guards live, update, and packet. */
+ spinlock_t lock;
+ bool live;
+ bool update;
+ struct ally_ff_report packet;
+ struct work_struct work;
+ } ff;
};
struct asus_drvdata {
@@ -325,10 +352,15 @@ enum ally_command_codes {
CMD_SET_ANTI_DEADZONE = 0x18,
};
+/* Rumble magnitudes use the hardware's 0..100 intensity range. */
+#define ALLY_FF_MAX_INTENSITY 100
+
static const u8 ally_force_feedback_off[] = {
0x0D, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB
};
+static_assert(sizeof(struct ally_ff_report) == sizeof(ally_force_feedback_off));
+
/*
* Each Ally interface owns its own ally_intf. A report is delivered on the
* evdev of the interface that received it. Nothing here points at another
@@ -2184,6 +2216,84 @@ static struct input_dev *ally_x_alloc_input_dev(struct hid_device *hdev,
return input_dev;
}
+static int ally_ff_output(struct hid_device *hdev, const void *buf, size_t len)
+{
+ u8 *dmabuf __free(kfree) = kmemdup(buf, len, GFP_KERNEL);
+
+ if (!dmabuf)
+ return -ENOMEM;
+
+ return hid_hw_output_report(hdev, dmabuf, len);
+}
+
+static int ally_ff_off(struct hid_device *hdev)
+{
+ return ally_ff_output(hdev, ally_force_feedback_off,
+ sizeof(ally_force_feedback_off));
+}
+
+static void ally_ff_work_fn(struct work_struct *work)
+{
+ struct ally_intf *intf = container_of(work, struct ally_intf, ff.work);
+ struct ally_ff_report report;
+ bool update = false;
+ int ret;
+
+ scoped_guard(spinlock_irqsave, &intf->ff.lock) {
+ if (!intf->ff.update)
+ break;
+
+ report = intf->ff.packet;
+ intf->ff.update = false;
+ update = true;
+ }
+
+ if (!update)
+ return;
+
+ ret = ally_ff_output(intf->hdev, &report, sizeof(report));
+ if (ret < 0)
+ hid_err(intf->hdev, "Failed to send force-feedback: %d\n", ret);
+}
+
+static int ally_ff_play(struct input_dev *idev, void *data, struct ff_effect *effect)
+{
+ struct ally_intf *intf = input_get_drvdata(idev);
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ guard(spinlock_irqsave)(&intf->ff.lock);
+
+ if (!intf->ff.live)
+ return 0;
+
+ intf->ff.packet.ff.magnitude_strong =
+ effect->u.rumble.strong_magnitude * ALLY_FF_MAX_INTENSITY / U16_MAX;
+ intf->ff.packet.ff.magnitude_weak =
+ effect->u.rumble.weak_magnitude * ALLY_FF_MAX_INTENSITY / U16_MAX;
+ intf->ff.update = true;
+ schedule_work(&intf->ff.work);
+ return 0;
+}
+
+static void ally_ff_quiesce(struct ally_intf *intf)
+{
+ int ret;
+
+ if (!intf || !intf->gamepad)
+ return;
+
+ scoped_guard(spinlock_irqsave, &intf->ff.lock)
+ intf->ff.live = false;
+
+ cancel_work_sync(&intf->ff.work);
+
+ ret = ally_ff_off(intf->hdev);
+ if (ret < 0)
+ hid_warn(intf->hdev, "Failed to stop force-feedback\n");
+}
+
static int ally_x_setup_input(struct hid_device *hdev, struct ally_intf *intf)
{
struct input_dev *input = ally_x_alloc_input_dev(hdev, intf);
@@ -2212,8 +2322,24 @@ static int ally_x_setup_input(struct hid_device *hdev, struct ally_intf *intf)
input_set_capability(input, EV_KEY, BTN_THUMBL);
input_set_capability(input, EV_KEY, BTN_THUMBR);
+ spin_lock_init(&intf->ff.lock);
+ INIT_WORK(&intf->ff.work, ally_ff_work_fn);
+ memcpy(&intf->ff.packet, ally_force_feedback_off, sizeof(intf->ff.packet));
+ intf->ff.update = false;
+ intf->ff.live = false;
+
+ input_set_capability(input, EV_FF, FF_RUMBLE);
+ ret = input_ff_create_memless(input, NULL, ally_ff_play);
+ if (ret) {
+ hid_err(hdev, "Failed to create force-feedback: %d\n", ret);
+ input_free_device(input);
+ return ret;
+ }
+
+ intf->ff.live = true;
ret = input_register_device(input);
if (ret) {
+ intf->ff.live = false;
hid_err(hdev, "Failed to register Ally X gamepad device: %d\n", ret);
input_free_device(input);
return ret;
@@ -2404,6 +2530,14 @@ static struct ally_intf *hid_asus_ally_probe(struct hid_device *hdev)
hid_err(hdev, "Failed to create Ally X gamepad device.\n");
return ERR_PTR(ret);
}
+
+ /*
+ * This is the interface that owns the rumble output report.
+ * Failure to silence it is not fatal.
+ */
+ ret = ally_ff_off(hdev);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to disable force-feedback: %d\n", ret);
break;
case HID_ALLY_INTF_KEYBOARD_IN:
break;
@@ -2426,6 +2560,13 @@ static int hid_asus_ally_reset_resume(struct hid_device *hdev, struct ally_intf
return 0;
ep = ally_get_endpoint_address(hdev);
+ if (ep == HID_ALLY_X_INTF_IN) {
+ ret = ally_ff_off(hdev);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to disable force-feedback: %d\n", ret);
+ return 0;
+ }
+
if (ep != HID_ALLY_INTF_CFG_IN)
return 0;
@@ -3914,6 +4055,7 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
ally_quiesce(drvdata->ally);
ally_sysfs_remove(hdev);
+ ally_ff_quiesce(drvdata->ally);
}
asus_worker_stop(drvdata->worker);
@@ -3937,6 +4079,7 @@ static void asus_remove(struct hid_device *hdev)
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
ally_quiesce(drvdata->ally);
ally_sysfs_remove(hdev);
+ ally_ff_quiesce(drvdata->ally);
}
asus_worker_stop(drvdata->worker);
--
2.47.3
next prev parent reply other threads:[~2026-10-31 17:42 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 17:41 [PATCH v12 00/17] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-10-31 17:41 ` [PATCH v12 01/17] HID: asus: size feature reports from the HID report descriptor Denis Benato
2026-10-31 18:04 ` sashiko-bot
2026-10-31 18:08 ` Denis Benato
2026-10-31 18:30 ` Rebecca Mara Müller
2026-10-31 17:41 ` [PATCH v12 02/17] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-10-31 18:11 ` sashiko-bot
2026-10-31 17:41 ` [PATCH v12 03/17] HID: asus: add gamepad configuration Denis Benato
2026-10-31 17:59 ` sashiko-bot
2026-10-31 17:41 ` [PATCH v12 04/17] HID: asus: add vibration strength configuration Denis Benato
2026-10-31 17:41 ` [PATCH v12 05/17] HID: asus: add joysticks inner and outer range configuration Denis Benato
2026-10-31 17:57 ` sashiko-bot
2026-10-31 17:41 ` [PATCH v12 06/17] HID: asus: add triggers " Denis Benato
2026-10-31 17:54 ` sashiko-bot
2026-10-31 17:41 ` [PATCH v12 07/17] HID: asus: add joysticks anti-deadzone configuration Denis Benato
2026-10-31 17:51 ` sashiko-bot
2026-10-31 17:41 ` [PATCH v12 08/17] HID: asus: add support for response curve Denis Benato
2026-10-31 17:41 ` Denis Benato [this message]
2026-10-31 17:41 ` [PATCH v12 10/17] HID: asus: add support for gamepad mode Denis Benato
2026-10-31 17:41 ` [PATCH v12 11/17] HID: asus: add support for turbo buttons Denis Benato
2026-10-31 17:54 ` sashiko-bot
2026-10-31 17:41 ` [PATCH v12 12/17] HID: asus: add support for btn remapping Denis Benato
2026-10-31 18:00 ` sashiko-bot
2026-10-31 19:06 ` Denis Benato
2026-10-31 17:41 ` [PATCH v12 13/17] HID: asus: identify XG Mobile stations and bind the LED to the reported model Denis Benato
2026-10-31 17:41 ` [PATCH v12 14/17] HID: asus: expose XG Mobile identity and firmware information Denis Benato
2026-10-31 17:41 ` [PATCH v12 15/17] HID: asus: document ROG XG Mobile sysfs attributes Denis Benato
2026-10-31 17:41 ` [PATCH v12 16/17] platform/x86: asus-armoury: pick the eGPU activation from the reported GPU Denis Benato
2026-10-31 17:41 ` [PATCH v12 17/17] MAINTAINERS: add maintainer for the ASUS HID driver Denis Benato
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261031174130.208240-10-denis.benato@linux.dev \
--to=denis.benato@linux.dev \
--cc=benato.denis96@gmail.com \
--cc=bentiss@kernel.org \
--cc=corentin.chary@gmail.com \
--cc=derekjohn.clark@gmail.com \
--cc=hacker1024@users.sourceforge.net \
--cc=hansg@kernel.org \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=jikos@kernel.org \
--cc=jlobue10@gmail.com \
--cc=kernel@lists.opengamingcollective.org \
--cc=khamunetriclark@gmail.com \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luke@ljones.dev \
--cc=matthew.schwartz@linux.dev \
--cc=me@livan.pro \
--cc=nathan@kernel.org \
--cc=platform-driver-x86@vger.kernel.org \
--cc=rebecca.mara@posteo.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.