From: Denis Benato <denis.benato@linux.dev>
To: linux-kernel@vger.kernel.org
Cc: linux-input@vger.kernel.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>,
Denis Benato <denis.benato@linux.dev>
Subject: [PATCH v5 06/13] HID: asus: add joysticks inner and outer range configuration
Date: Fri, 4 Sep 2026 14:58:37 +0000 [thread overview]
Message-ID: <20260904145845.184887-7-denis.benato@linux.dev> (raw)
In-Reply-To: <20260904145845.184887-1-denis.benato@linux.dev>
ROG Ally devices supports configuring joysticks inner and outer range:
add sysfs attributes to allow userspace modifying the sensitivity
of those controllers.
Assisted-by: opencode:glm-5.2
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Luke Jones <luke@ljones.dev>
---
drivers/hid/hid-asus.c | 426 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 426 insertions(+)
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 659956ebaefc..8b9ab5f7931c 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -991,6 +991,351 @@ static struct device_attribute dev_attr_right_vibration_intensity =
static struct device_attribute dev_attr_right_vibration_intensity_range =
__ATTR(intensity_range, 0444, right_vibration_intensity_range_show, NULL);
+/**
+ * ally_set_joystick_thresholds() - Generic function to set joystick ranges
+ * @ally: ally handheld structure
+ * @hdev: HID device
+ * @cfg: ally config
+ * @left_it: inner threshold (deadzone) of the left stick (0-50)
+ * @left_ot: outer threshold of the left stick (70-100)
+ * @right_it: inner threshold (deadzone) of the right stick (0-50)
+ * @right_ot: outer threshold of the right stick (70-100)
+ *
+ * This function sends the command to set both inner and outer threshold
+ * for the left and right joysticks.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int ally_set_joystick_thresholds(struct ally_handheld *ally,
+ struct hid_device *hdev, struct ally_config *cfg,
+ u8 left_it, u8 left_ot, u8 right_it, u8 right_ot)
+{
+ const u8 payload[] = { left_it, left_ot, right_it, right_ot };
+ int ret;
+
+ u8 *buf __free(kfree) = ally_alloc_cmd(CMD_SET_JOYSTICK_DEADZONE, payload, sizeof(payload));
+ if (!buf)
+ return -ENOMEM;
+
+ ret = ally_gamepad_send_packet(ally, hdev, buf, ROG_ALLY_REPORT_SIZE);
+ if (ret < 0) {
+ hid_err(hdev, "Failed to set joystick ranges: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static ssize_t left_joystick_inner_threshold_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", cfg->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);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+ u8 value;
+ int ret;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value > 50)
+ return -EINVAL;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(ally, hdev, cfg,
+ value,
+ cfg->left_outer_threshold,
+ cfg->right_deadzone,
+ cfg->right_outer_threshold);
+ if (ret)
+ return ret;
+
+ cfg->left_deadzone = value;
+
+ 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)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", cfg->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);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+ u8 value;
+ int ret;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value < 70 || value > 100)
+ return -EINVAL;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(ally, hdev, cfg,
+ cfg->left_deadzone,
+ value,
+ cfg->right_deadzone,
+ cfg->right_outer_threshold);
+ if (ret)
+ return ret;
+
+ cfg->left_outer_threshold = value;
+
+ 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)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", cfg->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);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+ u8 value;
+ int ret;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value > 50)
+ return -EINVAL;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(ally, hdev, cfg,
+ cfg->left_deadzone,
+ cfg->left_outer_threshold,
+ value,
+ cfg->right_outer_threshold);
+ if (ret)
+ return ret;
+
+ cfg->right_deadzone = value;
+
+ 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)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ return sysfs_emit(buf, "%u\n", cfg->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);
+ struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct ally_handheld *const ally = drvdata->rog_ally;
+ struct ally_config *cfg;
+ u8 value;
+ int ret;
+
+ if (!ally)
+ return -ENODEV;
+
+ cfg = ally_get_config(ally);
+ if (!cfg)
+ return -ENODEV;
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret || value < 70 || value > 100)
+ return -EINVAL;
+
+ guard(mutex)(&cfg->config_mutex);
+
+ if (!cfg->user_cal_support)
+ return -EOPNOTSUPP;
+
+ ret = ally_set_joystick_thresholds(ally, hdev, cfg,
+ cfg->left_deadzone,
+ cfg->left_outer_threshold,
+ cfg->right_deadzone,
+ value);
+ if (ret)
+ return ret;
+
+ cfg->right_outer_threshold = value;
+
+ return count;
+}
+
+static ssize_t right_joystick_outer_threshold_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "70 100\n");
+}
+
+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 *ally_config_attrs[] = {
&dev_attr_xbox_controller.attr,
NULL
@@ -1008,6 +1353,22 @@ static struct attribute *ally_right_vibration_attrs[] = {
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
+};
+
static const struct attribute_group ally_attr_groups[] = {
{
.attrs = ally_config_attrs,
@@ -1022,6 +1383,28 @@ static const struct attribute_group ally_attr_groups[] = {
},
};
+/*
+ * The joystick and trigger range calibration attributes are tied to the
+ * user-calibration capability: their sysfs groups are registered only
+ * when the device supports configuring those parameters, and the show
+ * and store callbacks reject accesses with -EOPNOTSUPP regardless, in
+ * case the groups are registered for another supported feature.
+ */
+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,
+};
+
/**
* ally_config_create() - Initialize configuration and create sysfs entries
* @hdev: HID device
@@ -1076,6 +1459,25 @@ static struct ally_config *ally_config_create(struct hid_device *hdev, struct al
}
}
+ /* Skip the calibration groups when the capability is missing. */
+ if (cfg->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 < 0) {
+ hid_err(hdev, "Failed to create sysfs group '%s': %d\n",
+ ally_cal_attr_groups[cal_i]->name, ret);
+ while (cal_i-- > 0)
+ sysfs_remove_group(&hdev->dev.kobj,
+ ally_cal_attr_groups[cal_i]);
+ goto ally_config_create_sysfs_err;
+ }
+ }
+ }
+
/* So far the only hardware this is supported is the Ally 1 */
if (cfg->xbox_controller_support) {
ret = ally_set_xbox_controller(hdev, ally, cfg, true);
@@ -1110,6 +1512,18 @@ static void ally_config_remove(struct hid_device *hdev, struct ally_config *cfg)
for (i = 0; i < ARRAY_SIZE(ally_attr_groups); i++)
sysfs_remove_group(&hdev->dev.kobj, &ally_attr_groups[i]);
+
+ /*
+ * The calibration groups are removed with the same gating used to
+ * create them, so a device without those capabilities does not
+ * trigger a "not found" warning.
+ */
+ if (cfg->user_cal_support || cfg->anti_deadzone_support ||
+ cfg->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]);
+ }
}
/**
@@ -1410,6 +1824,18 @@ static int hid_asus_ally_init(struct hid_device *hdev, struct ally_handheld *all
hid_warn(hdev, "Failed to restore vibration intensity: %d\n",
ret);
+ if (cfg->user_cal_support) {
+ ret = ally_set_joystick_thresholds(ally, hdev, cfg,
+ cfg->left_deadzone,
+ cfg->left_outer_threshold,
+ cfg->right_deadzone,
+ cfg->right_outer_threshold);
+ if (ret < 0)
+ hid_warn(hdev,
+ "Failed to restore joystick thresholds: %d\n",
+ ret);
+ }
+
return 0;
}
--
2.47.3
next prev parent reply other threads:[~2026-09-04 15:01 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-04 14:58 [PATCH v5 00/13] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-09-04 14:58 ` [PATCH v5 01/13] HID: asus: do not send keyboard init reports to touchpads Denis Benato
2026-09-04 14:58 ` [PATCH v5 02/13] HID: asus: reinitialize the device after exiting a sleep state Denis Benato
2026-09-04 14:58 ` [PATCH v5 03/13] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-09-04 14:58 ` [PATCH v5 04/13] HID: asus: add gamepad configuration Denis Benato
2026-09-04 15:21 ` sashiko-bot
2026-09-04 14:58 ` [PATCH v5 05/13] HID: asus: add vibration strength configuration Denis Benato
2026-09-04 15:39 ` sashiko-bot
2026-09-04 14:58 ` Denis Benato [this message]
2026-09-04 15:25 ` [PATCH v5 06/13] HID: asus: add joysticks inner and outer range configuration sashiko-bot
2026-09-04 14:58 ` [PATCH v5 07/13] HID: asus: add triggers " Denis Benato
2026-09-04 14:58 ` [PATCH v5 08/13] HID: asus: add joysticks anti-deadzone configuration Denis Benato
2026-09-04 14:58 ` [PATCH v5 09/13] HID: asus: add support for response curve Denis Benato
2026-09-04 15:33 ` sashiko-bot
2026-09-04 14:58 ` [PATCH v5 10/13] HID: asus: add support to force feedback Denis Benato
2026-09-04 14:58 ` [PATCH v5 11/13] HID: asus: add support for gamepad mode Denis Benato
2026-09-04 14:58 ` [PATCH v5 12/13] HID: asus: add support for turbo buttons Denis Benato
2026-09-04 16:02 ` sashiko-bot
2026-09-04 14:58 ` [PATCH v5 13/13] HID: asus: add support for btn remapping Denis Benato
2026-09-04 15:46 ` sashiko-bot
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=20260904145845.184887-7-denis.benato@linux.dev \
--to=denis.benato@linux.dev \
--cc=benato.denis96@gmail.com \
--cc=bentiss@kernel.org \
--cc=derekjohn.clark@gmail.com \
--cc=jikos@kernel.org \
--cc=jlobue10@gmail.com \
--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 \
/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.