[PATCH v12 05/17] HID: asus: add joysticks inner and outer range configuration
From: Denis Benato
Date: Sat Oct 31 2026 - 13:44:07 EST
ROG Ally devices supports configuring joysticks inner and outer range:
add sysfs attributes to allow userspace modifying the sensitivity
of those controllers.
Signed-off-by: Denis Benato <denis.benato@xxxxxxxxx>
Co-developed-by: Luke Jones <luke@xxxxxxxxxx>
Signed-off-by: Luke Jones <luke@xxxxxxxxxx>
---
drivers/hid/hid-asus.c | 354 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 348 insertions(+), 6 deletions(-)
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index fe2008f9d51d..50b0e6040595 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -98,6 +98,9 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define ASUS_HID_INPUT_KBD_REPORT_ID 0x01
+#define ASUS_ALLY_OUTER_THRESHOLD_MIN 70
+#define ASUS_ALLY_OUTER_THRESHOLD_MAX 100
+
/* Spurious HID codes sent by QUIRK_ROG_NKEY_KEYBOARD devices */
#define ASUS_SPURIOUS_CODE_0XEA 0xea
#define ASUS_SPURIOUS_CODE_0XEC 0xec
@@ -767,6 +770,11 @@ static struct ally_prefs {
u8 vibration_intensity_left;
u8 vibration_intensity_right;
+
+ u8 left_deadzone;
+ u8 left_outer_threshold;
+ u8 right_deadzone;
+ u8 right_outer_threshold;
} ally_prefs = {
.lock = __MUTEX_INITIALIZER(ally_prefs.lock),
};
@@ -826,7 +834,6 @@ static int ally_detect_capabilities(struct hid_device *hdev)
return 0;
}
-/* Caller holds ally_prefs.lock. The packet is sent on @hdev. */
static int ally_set_xbox_controller(struct hid_device *hdev, bool enabled)
{
const u8 payload[] = { enabled ?
@@ -1042,6 +1049,304 @@ static struct attribute *ally_right_vibration_attrs[] = {
NULL
};
+static int ally_set_joystick_thresholds(struct hid_device *hdev, u8 left_it,
+ u8 left_ot, u8 right_it, u8 right_ot)
+{
+ const u8 payload[] = { left_it, left_ot, right_it, right_ot };
+ size_t buf_len;
+ int ret;
+
+ u8 *buf __free(kfree) = ally_alloc_cmd(hdev, CMD_SET_JOYSTICK_DEADZONE,
+ payload, sizeof(payload),
+ &buf_len);
+
+ if (!buf)
+ return -ENOMEM;
+
+ ret = ally_gamepad_send_packet(hdev, buf, buf_len);
+ if (ret < 0) {
+ hid_err(hdev, "Failed to set joystick ranges: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void ally_joystick_defaults(void)
+{
+ guard(mutex)(&ally_prefs.lock);
+
+ if (ally_prefs.initialized)
+ return;
+
+ ally_prefs.left_deadzone = 10;
+ ally_prefs.left_outer_threshold = 90;
+ ally_prefs.right_deadzone = 10;
+ ally_prefs.right_outer_threshold = 90;
+}
+
+static ssize_t left_joystick_inner_threshold_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ guard(mutex)(&ally_prefs.lock);
+
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", ally_prefs.left_deadzone);
+}
+
+static ssize_t left_joystick_inner_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u8 value;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value > 50)
+ return -EINVAL;
+
+ scoped_guard(mutex, &ally_prefs.lock) {
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(hdev, value,
+ ally_prefs.left_outer_threshold,
+ ally_prefs.right_deadzone,
+ ally_prefs.right_outer_threshold);
+ if (ret)
+ return ret;
+
+ ally_prefs.left_deadzone = value;
+ }
+
+ sysfs_notify(&dev->kobj, "left_joystick_axis", "inner_threshold");
+
+ return count;
+}
+
+static ssize_t left_joystick_inner_threshold_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "0 50\n");
+}
+
+static ssize_t left_joystick_outer_threshold_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ guard(mutex)(&ally_prefs.lock);
+
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", ally_prefs.left_outer_threshold);
+}
+
+static ssize_t left_joystick_outer_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u8 value;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value < 70 || value > 100)
+ return -EINVAL;
+
+ scoped_guard(mutex, &ally_prefs.lock) {
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(hdev, ally_prefs.left_deadzone,
+ value, ally_prefs.right_deadzone,
+ ally_prefs.right_outer_threshold);
+ if (ret)
+ return ret;
+
+ ally_prefs.left_outer_threshold = value;
+ }
+
+ sysfs_notify(&dev->kobj, "left_joystick_axis", "outer_threshold");
+
+ return count;
+}
+
+static ssize_t left_joystick_outer_threshold_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "70 100\n");
+}
+
+static ssize_t right_joystick_inner_threshold_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ guard(mutex)(&ally_prefs.lock);
+
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", ally_prefs.right_deadzone);
+}
+
+static ssize_t right_joystick_inner_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u8 value;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value > 50)
+ return -EINVAL;
+
+ scoped_guard(mutex, &ally_prefs.lock) {
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(hdev, ally_prefs.left_deadzone,
+ ally_prefs.left_outer_threshold,
+ value,
+ ally_prefs.right_outer_threshold);
+ if (ret)
+ return ret;
+
+ ally_prefs.right_deadzone = value;
+ }
+
+ sysfs_notify(&dev->kobj, "right_joystick_axis", "inner_threshold");
+
+ return count;
+}
+
+static ssize_t right_joystick_inner_threshold_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "0 50\n");
+}
+
+static ssize_t right_joystick_outer_threshold_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ guard(mutex)(&ally_prefs.lock);
+
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", ally_prefs.right_outer_threshold);
+}
+
+static ssize_t right_joystick_outer_threshold_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ u8 value;
+ int ret;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value < ASUS_ALLY_OUTER_THRESHOLD_MIN || value > ASUS_ALLY_OUTER_THRESHOLD_MAX)
+ return -EINVAL;
+
+ scoped_guard(mutex, &ally_prefs.lock) {
+ if (!ally_prefs.user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(hdev, ally_prefs.left_deadzone,
+ ally_prefs.left_outer_threshold,
+ ally_prefs.right_deadzone, value);
+ if (ret)
+ return ret;
+
+ ally_prefs.right_outer_threshold = value;
+ }
+
+ sysfs_notify(&dev->kobj, "right_joystick_axis", "outer_threshold");
+
+ return count;
+}
+
+static ssize_t right_joystick_outer_threshold_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%u %u\n", ASUS_ALLY_OUTER_THRESHOLD_MIN,
+ ASUS_ALLY_OUTER_THRESHOLD_MAX);
+}
+
+static struct device_attribute dev_attr_left_joystick_inner_threshold =
+ __ATTR(inner_threshold, 0644, left_joystick_inner_threshold_show,
+ left_joystick_inner_threshold_store);
+
+static struct device_attribute dev_attr_left_joystick_inner_threshold_range =
+ __ATTR(inner_threshold_range, 0444, left_joystick_inner_threshold_range_show, NULL);
+
+static struct device_attribute dev_attr_left_joystick_outer_threshold =
+ __ATTR(outer_threshold, 0644, left_joystick_outer_threshold_show,
+ left_joystick_outer_threshold_store);
+
+static struct device_attribute dev_attr_left_joystick_outer_threshold_range =
+ __ATTR(outer_threshold_range, 0444, left_joystick_outer_threshold_range_show, NULL);
+
+static struct device_attribute dev_attr_right_joystick_inner_threshold =
+ __ATTR(inner_threshold, 0644, right_joystick_inner_threshold_show,
+ right_joystick_inner_threshold_store);
+
+static struct device_attribute dev_attr_right_joystick_inner_threshold_range =
+ __ATTR(inner_threshold_range, 0444, right_joystick_inner_threshold_range_show, NULL);
+
+static struct device_attribute dev_attr_right_joystick_outer_threshold =
+ __ATTR(outer_threshold, 0644, right_joystick_outer_threshold_show,
+ right_joystick_outer_threshold_store);
+
+static struct device_attribute dev_attr_right_joystick_outer_threshold_range =
+ __ATTR(outer_threshold_range, 0444, right_joystick_outer_threshold_range_show, NULL);
+
+static struct attribute *left_joystick_axis_attrs[] = {
+ &dev_attr_left_joystick_inner_threshold.attr,
+ &dev_attr_left_joystick_outer_threshold.attr,
+ &dev_attr_left_joystick_inner_threshold_range.attr,
+ &dev_attr_left_joystick_outer_threshold_range.attr,
+ NULL
+};
+
+static struct attribute *right_joystick_axis_attrs[] = {
+ &dev_attr_right_joystick_inner_threshold.attr,
+ &dev_attr_right_joystick_outer_threshold.attr,
+ &dev_attr_right_joystick_inner_threshold_range.attr,
+ &dev_attr_right_joystick_outer_threshold_range.attr,
+ NULL
+};
+
+/*
+ * Registered only when the device can configure these parameters. The
+ * callbacks still reject a missing capability.
+ */
+static const struct attribute_group ally_left_joystick_axis_group = {
+ .name = "left_joystick_axis",
+ .attrs = left_joystick_axis_attrs,
+};
+
+static const struct attribute_group ally_right_joystick_axis_group = {
+ .name = "right_joystick_axis",
+ .attrs = right_joystick_axis_attrs,
+};
+
+static const struct attribute_group *const ally_cal_attr_groups[] = {
+ &ally_left_joystick_axis_group,
+ &ally_right_joystick_axis_group,
+};
+
static struct attribute *ally_config_attrs[] = {
&dev_attr_xbox_controller.attr,
NULL
@@ -1070,6 +1375,7 @@ static int ally_prefs_setup(struct hid_device *hdev)
return ret;
ally_vibration_defaults();
+ ally_joystick_defaults();
guard(mutex)(&ally_prefs.lock);
@@ -1093,6 +1399,27 @@ static int ally_prefs_setup(struct hid_device *hdev)
}
}
+ /* Skip the calibration groups when the capability is missing. */
+ if (ally_prefs.user_cal_support) {
+ int cal_i;
+
+ for (cal_i = 0; cal_i < ARRAY_SIZE(ally_cal_attr_groups); cal_i++) {
+ ret = sysfs_create_group(&hdev->dev.kobj,
+ ally_cal_attr_groups[cal_i]);
+ if (ret) {
+ hid_err(hdev, "Failed to create Ally sysfs group '%s': %d\n",
+ ally_cal_attr_groups[cal_i]->name, ret);
+ while (cal_i--)
+ sysfs_remove_group(&hdev->dev.kobj,
+ ally_cal_attr_groups[cal_i]);
+ for (i = 0; i < ARRAY_SIZE(ally_attr_groups); i++)
+ sysfs_remove_group(&hdev->dev.kobj,
+ &ally_attr_groups[i]);
+ return ret;
+ }
+ }
+ }
+
ally_prefs.sysfs_live = true;
return 0;
}
@@ -1104,15 +1431,21 @@ static void ally_sysfs_remove(struct hid_device *hdev)
if (ally_get_endpoint_address(hdev) != HID_ALLY_INTF_CFG_IN)
return;
- scoped_guard(mutex, &ally_prefs.lock) {
- if (!ally_prefs.sysfs_live)
- return;
+ guard(mutex)(&ally_prefs.lock);
- ally_prefs.sysfs_live = false;
- }
+ if (!ally_prefs.sysfs_live)
+ return;
+
+ ally_prefs.sysfs_live = false;
for (i = 0; i < ARRAY_SIZE(ally_attr_groups); i++)
sysfs_remove_group(&hdev->dev.kobj, &ally_attr_groups[i]);
+
+ if (ally_prefs.user_cal_support || ally_prefs.anti_deadzone_support ||
+ ally_prefs.resp_curve_support) {
+ for (i = 0; i < ARRAY_SIZE(ally_cal_attr_groups); i++)
+ sysfs_remove_group(&hdev->dev.kobj, ally_cal_attr_groups[i]);
+ }
}
struct ally_x_input_report {
@@ -1317,6 +1650,15 @@ static int hid_asus_ally_init(struct hid_device *hdev)
hid_warn(hdev, "Failed to restore vibration intensity: %d\n", ret);
}
+ if (ally_prefs.initialized && ally_prefs.user_cal_support) {
+ ret = ally_set_joystick_thresholds(hdev, ally_prefs.left_deadzone,
+ ally_prefs.left_outer_threshold,
+ ally_prefs.right_deadzone,
+ ally_prefs.right_outer_threshold);
+ if (ret < 0)
+ hid_warn(hdev, "Failed to restore joystick thresholds: %d\n", ret);
+ }
+
return 0;
}
--
2.47.3