All of lore.kernel.org
 help / color / mirror / Atom feed
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 v8 08/12] HID: asus: add support for response curve
Date: Tue, 22 Sep 2026 23:02:01 +0000	[thread overview]
Message-ID: <20260922230205.6847-9-denis.benato@linux.dev> (raw)
In-Reply-To: <20260922230205.6847-1-denis.benato@linux.dev>

ROG ally devices allows configuring the response curve of both joysticks,
therefore add the ability of userspace to modify the response curve by
exposing relevant sysfs attributes.

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 | 298 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 295 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 7911f510466d..3d0c3530753c 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/ctype.h>
 #include <linux/device.h>
 #include <linux/dmi.h>
 #include <linux/hid.h>
@@ -178,6 +179,18 @@ struct asus_touchpad_info {
 	int report_size;
 };
 
+struct ally_joystick_resp_curve_param {
+	u8 move;
+	u8 resp;
+} __packed;
+
+struct ally_joystick_resp_curve {
+	struct ally_joystick_resp_curve_param entry_1;
+	struct ally_joystick_resp_curve_param entry_2;
+	struct ally_joystick_resp_curve_param entry_3;
+	struct ally_joystick_resp_curve_param entry_4;
+} __packed;
+
 struct ally_config {
 	/* Must be locked if the data is being changed */
 	struct mutex config_mutex;
@@ -210,6 +223,9 @@ struct ally_config {
 	/* Vibration settings */
 	u8 vibration_intensity_left;
 	u8 vibration_intensity_right;
+
+	struct ally_joystick_resp_curve left_curve;
+	struct ally_joystick_resp_curve right_curve;
 };
 
 /*
@@ -1924,6 +1940,236 @@ static struct device_attribute dev_attr_right_trigger_range_upper_limit =
 static struct device_attribute dev_attr_right_trigger_range_upper_limit_range =
 	__ATTR(range_upper_limit_range, 0444, right_trigger_range_upper_limit_range_show, NULL);
 
+enum ally_joystick_side {
+	JOYSTICK_LEFT = 0,
+	JOYSTICK_RIGHT,
+};
+
+/**
+ * ally_set_joystick_resp_curve() - Set joystick response curve parameters
+ * @ally: ally handheld structure
+ * @hdev: HID device
+ * @side: which joystick side (0=left, 1=right)
+ * @curve: response curve parameter structure
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int ally_set_joystick_resp_curve(struct ally_handheld *ally,
+					struct hid_device *hdev, enum ally_joystick_side side,
+					struct ally_joystick_resp_curve *curve)
+{
+	const u8 payload[] = { side,
+		curve->entry_1.move, curve->entry_1.resp,
+		curve->entry_2.move, curve->entry_2.resp,
+		curve->entry_3.move, curve->entry_3.resp,
+		curve->entry_4.move, curve->entry_4.resp
+	};
+	int ret;
+
+	u8 *buf __free(kfree) = ally_alloc_cmd(CMD_SET_RESP_CURVE, payload, sizeof(payload));
+	if (!buf)
+		return -ENOMEM;
+
+	ret = ally_gamepad_send_packet(ally, hdev, buf, ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/**
+ * ally_resp_curve_is_valid() - Check that a response curve is valid
+ * @curve: response curve parameter structure
+ *
+ * Return: true if the curve is valid, false otherwise
+ */
+static bool ally_resp_curve_is_valid(const struct ally_joystick_resp_curve *curve)
+{
+	return curve->entry_1.move < curve->entry_2.move &&
+	       curve->entry_2.move < curve->entry_3.move &&
+	       curve->entry_3.move < curve->entry_4.move;
+}
+
+static ssize_t response_curve_show(struct ally_config *cfg, bool is_left, char *buf)
+{
+	const struct ally_joystick_resp_curve *curve;
+
+	guard(mutex)(&cfg->config_mutex);
+
+	if (!cfg->resp_curve_support)
+		return -EOPNOTSUPP;
+
+	curve = is_left ? &cfg->left_curve : &cfg->right_curve;
+
+	return sysfs_emit(buf, "%u %u %u %u %u %u %u %u\n",
+			  curve->entry_1.move, curve->entry_1.resp,
+			  curve->entry_2.move, curve->entry_2.resp,
+			  curve->entry_3.move, curve->entry_3.resp,
+			  curve->entry_4.move, curve->entry_4.resp);
+}
+
+static ssize_t response_curve_store(struct device *dev, struct ally_handheld *ally,
+				    struct hid_device *hdev, struct ally_config *cfg,
+				    bool is_left, const char *buf, size_t count)
+{
+	struct ally_joystick_resp_curve curve;
+	struct ally_joystick_resp_curve *cached;
+	unsigned int v[8];
+	int used = 0;
+	int ret, i;
+
+	/*
+	 * Parse and validate the whole curve before taking the lock: a
+	 * write is all-or-nothing and a rejected one must leave the
+	 * recorded curve untouched.
+	 */
+	ret = sscanf(buf, "%u %u %u %u %u %u %u %u%n",
+		     &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7], &used);
+	if (ret != 8)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(v); i++) {
+		if (v[i] > 100)
+			return -EINVAL;
+	}
+
+	for (; buf[used]; used++) {
+		if (!isspace(buf[used]))
+			return -EINVAL;
+	}
+
+	curve.entry_1.move = v[0];
+	curve.entry_1.resp = v[1];
+	curve.entry_2.move = v[2];
+	curve.entry_2.resp = v[3];
+	curve.entry_3.move = v[4];
+	curve.entry_3.resp = v[5];
+	curve.entry_4.move = v[6];
+	curve.entry_4.resp = v[7];
+
+	if (!ally_resp_curve_is_valid(&curve))
+		return -EINVAL;
+
+	guard(mutex)(&cfg->config_mutex);
+
+	if (!cfg->resp_curve_support)
+		return -EOPNOTSUPP;
+
+	ret = ally_set_joystick_resp_curve(ally, hdev,
+					   is_left ? JOYSTICK_LEFT : JOYSTICK_RIGHT,
+					   &curve);
+	if (ret < 0) {
+		hid_err(hdev, "Failed to set joystick response curve: %d\n", ret);
+		return ret;
+	}
+
+	/* Only a curve the controller accepted becomes the recorded one. */
+	cached = is_left ? &cfg->left_curve : &cfg->right_curve;
+	*cached = curve;
+
+	sysfs_notify(&dev->kobj,
+		     is_left ? "left_joystick_axis" : "right_joystick_axis",
+		     "response_curve");
+
+	return count;
+}
+
+static ssize_t left_response_curve_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 *ally = drvdata->rog_ally;
+	struct ally_config *cfg;
+
+	if (!ally)
+		return -ENODEV;
+
+	cfg = ally_get_config(ally);
+	if (!cfg)
+		return -ENODEV;
+
+	return response_curve_show(cfg, true, buf);
+}
+
+static ssize_t left_response_curve_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 *ally = drvdata->rog_ally;
+	struct ally_config *cfg;
+
+	if (!ally)
+		return -ENODEV;
+
+	cfg = ally_get_config(ally);
+	if (!cfg)
+		return -ENODEV;
+
+	return response_curve_store(dev, ally, hdev, cfg, true, buf, count);
+}
+
+static ssize_t right_response_curve_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 *ally = drvdata->rog_ally;
+	struct ally_config *cfg;
+
+	if (!ally)
+		return -ENODEV;
+
+	cfg = ally_get_config(ally);
+	if (!cfg)
+		return -ENODEV;
+
+	return response_curve_show(cfg, false, buf);
+}
+
+static ssize_t right_response_curve_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 *ally = drvdata->rog_ally;
+	struct ally_config *cfg;
+
+	if (!ally)
+		return -ENODEV;
+
+	cfg = ally_get_config(ally);
+	if (!cfg)
+		return -ENODEV;
+
+	return response_curve_store(dev, ally, hdev, cfg, false, buf, count);
+}
+
+static ssize_t response_curve_index_show(struct device *dev,
+					 struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf,
+			  "move_1 resp_1 move_2 resp_2 move_3 resp_3 move_4 resp_4\n"
+			  "\n"
+			  "move_N: joystick deflection percentage at which curve point N\n"
+			  "is placed (0-100, each move_N must be greater than the\n"
+			  "previous one). resp_N: response percentage applied from\n"
+			  "point N on (0-100).\n");
+}
+
+static struct device_attribute dev_attr_left_response_curve =
+	__ATTR(response_curve, 0644, left_response_curve_show, left_response_curve_store);
+static struct device_attribute dev_attr_right_response_curve =
+	__ATTR(response_curve, 0644, right_response_curve_show, right_response_curve_store);
+static struct device_attribute dev_attr_left_response_curve_index =
+	__ATTR(response_curve_index, 0444, response_curve_index_show, NULL);
+static struct device_attribute dev_attr_right_response_curve_index =
+	__ATTR(response_curve_index, 0444, response_curve_index_show, NULL);
+
+
 static struct attribute *ally_config_attrs[] = {
 	&dev_attr_xbox_controller.attr,
 	NULL
@@ -1948,6 +2194,8 @@ static struct attribute *left_joystick_axis_attrs[] = {
 	&dev_attr_left_joystick_outer_threshold_range.attr,
 	&dev_attr_left_joystick_anti_deadzone.attr,
 	&dev_attr_left_joystick_anti_deadzone_range.attr,
+	&dev_attr_left_response_curve.attr,
+	&dev_attr_left_response_curve_index.attr,
 	NULL
 };
 
@@ -1958,6 +2206,8 @@ static struct attribute *right_joystick_axis_attrs[] = {
 	&dev_attr_right_joystick_outer_threshold_range.attr,
 	&dev_attr_right_joystick_anti_deadzone.attr,
 	&dev_attr_right_joystick_anti_deadzone_range.attr,
+	&dev_attr_right_response_curve.attr,
+	&dev_attr_right_response_curve_index.attr,
 	NULL
 };
 
@@ -2073,6 +2323,25 @@ static struct ally_config *ally_config_create(struct hid_device *hdev, struct al
 		cfg->right_trigger_max = 100;
 		cfg->vibration_intensity_left = 100;
 		cfg->vibration_intensity_right = 100;
+
+		/* Initialize default response curve values (linear) */
+		cfg->left_curve.entry_1.move = 0;
+		cfg->left_curve.entry_1.resp = 0;
+		cfg->left_curve.entry_2.move = 33;
+		cfg->left_curve.entry_2.resp = 33;
+		cfg->left_curve.entry_3.move = 66;
+		cfg->left_curve.entry_3.resp = 66;
+		cfg->left_curve.entry_4.move = 100;
+		cfg->left_curve.entry_4.resp = 100;
+
+		cfg->right_curve.entry_1.move = 0;
+		cfg->right_curve.entry_1.resp = 0;
+		cfg->right_curve.entry_2.move = 33;
+		cfg->right_curve.entry_2.resp = 33;
+		cfg->right_curve.entry_3.move = 66;
+		cfg->right_curve.entry_3.resp = 66;
+		cfg->right_curve.entry_4.move = 100;
+		cfg->right_curve.entry_4.resp = 100;
 	}
 
 	spin_lock_irqsave(&ally_data_lock, flags);
@@ -2090,10 +2359,11 @@ static struct ally_config *ally_config_create(struct hid_device *hdev, struct al
 	}
 
 	/*
-	 * Skip the calibration and anti-deadzone groups when none of the
-	 * features they expose is supported.
+	 * Skip the calibration, anti-deadzone and response curve groups when
+	 * none of the features they expose is supported.
 	 */
-	if (cfg->user_cal_support || cfg->anti_deadzone_support) {
+	if (cfg->user_cal_support || cfg->anti_deadzone_support ||
+	    cfg->resp_curve_support) {
 		int cal_i;
 
 		for (cal_i = 0; cal_i < ARRAY_SIZE(ally_cal_attr_groups);
@@ -2482,6 +2752,28 @@ static int hid_asus_ally_init(struct hid_device *hdev, struct ally_handheld *all
 					 ret);
 	}
 
+	if (cfg->resp_curve_support) {
+		/*
+		 * The MCU does not keep the response curve across a reset:
+		 * send the recorded curves again. A write to the sysfs
+		 * attribute validates and applies the curve atomically, so
+		 * the cache always holds one the controller accepted.
+		 */
+		ret = ally_set_joystick_resp_curve(ally, hdev, JOYSTICK_LEFT,
+						   &cfg->left_curve);
+		if (ret < 0)
+			hid_warn(hdev,
+				 "Failed to restore left response curve: %d\n",
+				 ret);
+
+		ret = ally_set_joystick_resp_curve(ally, hdev, JOYSTICK_RIGHT,
+						   &cfg->right_curve);
+		if (ret < 0)
+			hid_warn(hdev,
+				 "Failed to restore right response curve: %d\n",
+				 ret);
+	}
+
 	return 0;
 }
 
-- 
2.47.3


  parent reply	other threads:[~2026-09-22 23:02 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-22 23:01 [PATCH v8 00/12] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-09-22 23:01 ` [PATCH v8 01/12] HID: asus: reinitialize the device after exiting a sleep state Denis Benato
2026-09-22 23:01 ` [PATCH v8 02/12] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-09-22 23:01 ` [PATCH v8 03/12] HID: asus: add gamepad configuration Denis Benato
2026-09-22 23:01 ` [PATCH v8 04/12] HID: asus: add vibration strength configuration Denis Benato
2026-09-22 23:01 ` [PATCH v8 05/12] HID: asus: add joysticks inner and outer range configuration Denis Benato
2026-09-22 23:01 ` [PATCH v8 06/12] HID: asus: add triggers " Denis Benato
2026-09-22 23:02 ` [PATCH v8 07/12] HID: asus: add joysticks anti-deadzone configuration Denis Benato
2026-09-22 23:02 ` Denis Benato [this message]
2026-09-22 23:02 ` [PATCH v8 09/12] HID: asus: add support to force feedback Denis Benato
2026-09-22 23:15   ` sashiko-bot
2026-09-22 23:02 ` [PATCH v8 10/12] HID: asus: add support for gamepad mode Denis Benato
2026-09-22 23:02 ` [PATCH v8 11/12] HID: asus: add support for turbo buttons Denis Benato
2026-09-22 23:02 ` [PATCH v8 12/12] HID: asus: add support for btn remapping Denis Benato
2026-09-22 23:16   ` 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=20260922230205.6847-9-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.