linux-input.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Re: [PATCH] hid-asus-ally: Add full gamepad support
@ 2024-08-08 15:35 Antheas Kapenekakis
  2024-08-11  9:22 ` Luke Jones
  0 siblings, 1 reply; 13+ messages in thread
From: Antheas Kapenekakis @ 2024-08-08 15:35 UTC (permalink / raw)
  To: luke; +Cc: bentiss, jikos, linux-input, linux-kernel

Hi Luke,
I reviewed this patch and leave some of my thoughts below.

I am the creator of the Handheld Daemon (HHD) (https://github.com/hhd-dev/hhd)
app, which offers a lot of this functionality using a userspace implementation,
and have thousands of handheld users, a lot of whom are on both Allies,
so I believe I have some valuable feedback on this.

First for some background:
The original Ally used a composite USB device with the following components:
a vendor interface for Armoury Crate, an XInput gamepad device, and a Keyboard
device for things such as WASD and Mouse mode support.
Armoury Crate uses the vendor interface to control device mappings and how the
device should act, including by sending faux commands to it for gyro emulation
using the Bosch gyro built into the screen. As such, no drivers are required for
it in Windows, and the same can be said of Linux. Although without Armoury Crate
in either Windows or Linux, the controller does not behave properly.

In Ally X, Asus seems to have been told by Microsoft that the XInput driver is
being deprecated and they've had to move on to a different one.
For Asus, this meant doing a HID implementation which seems to be
similar to the Xbox Adaptive Controller, and using a custom windows
driver for it
in Windows. Therefore, unlike the original Ally, which did not require Windows
drivers to have its controller work, Ally X does.

It is a similar situation in Linux, where without a kernel driver, the
controller
of the Ally reads as a HID Gamepad with a wrong mapping, and does not have
vibration support.

The patch I am replying to does three things (which ideally should be separated
into different patches): provide an in-kernel equivalent for Armoury
Crate options
that will configure both Ally and Ally X, a HID driver for the Ally X that
restores vibration functionality and a sane mapping to the controllers, and
finally, a cross-interface "hack" which only works on the X and pulls the vendor
"Armoury" and "ROG Crate" buttons into the new HID gamepad driver and turns them
into "BTN_GUIDE" and "BTN_GUIDE+BTN_SOUTH" (which is recognized by
Steam to open the
side menu and by now defunct yuzu to switch from windowed to full screen).

Let me add notes for each one.

## Gamepad HID Driver
For the HID gamepad driver: we are in agreement that such a driver should exist
for the X. It makes basic functionality accessible to users without a userspace
tool and restores mapping and vibration.

Your implementation seems straightforward and I only have a couple of
notes for it:

First, you are hardcoding `desc.bEndpointAddress`, which is error
prone and could
break in the future with an MCU/EC update. You should instead use the Usage Page
and Usage of the device, which in this case are unique. Referencing HHD, for the
HID gamepad, the usage page is 0xFF31 and the usage is 0x0080. And perhaps there
is a gamepad driver already that could support Ally X with a simple quirk.

Following, you attempted to mimic an Xinput device, but your driver does not set
the ABS values of the device properly. For your convenience, here are the values
from the Legion Go, which uses an XInput gamepad:
```
   0x0000: ABS_X
     > [val 0, min -32768, max 32767, fuzz 16, flat 128, res 0]
   0x0001: ABS_Y
     > [val -1, min -32768, max 32767, fuzz 16, flat 128, res 0]
   0x0002: ABS_Z
     > [val 0, min 0, max 255, fuzz 0, flat 0, res 0]
   0x0003: ABS_RX
     > [val 0, min -32768, max 32767, fuzz 16, flat 128, res 0]
   0x0004: ABS_RY
     > [val 0, min -32768, max 32767, fuzz 16, flat 128, res 0]
   0x0005: ABS_RZ
     > [val 0, min 0, max 255, fuzz 0, flat 0, res 0]
   0x0010: ABS_HAT0X
     > [val 0, min -1, max 1, fuzz 0, flat 0, res 0]
   0x0011: ABS_HAT0Y
     > [val 0, min -1, max 1, fuzz 0, flat 0, res 0]
```
Specifically, your ABS_X, ABS_Y, ABS_RX, ABS_RY values are set from 0
to ABS_MAX, which is
a deviation from XInput and more similar to DInput. This means that if I added
support for your patchset to HHD which supports over 30 XInput
devices, I would have to
quirk it only for the Ally X. If you break HHD, the chance you break
userspace in
general (e.g., random proton game) is very high.

## Armoury Crate Functionality
Let me move over to armoury crate functionality, which you first started in
December as the XPAD patchset, and as such I have a certain familiarity with it.
I will not reiterate the `desc.bEndpointAddress` quirk.
The vendor interface is a HID device and it is the case both in Linux
and Windows
that configuration is usually done by userspace programs.
I recall multiple examples of this in Linux as well, e.g., Solaar for Logitech,
asusctl (your daemon) for Asus, libratbag for mice, and OpenRGB for
rgb which includes
the Ally. Can you justify the deviation here and the need for a kernel driver?

I am afraid that it might be unreliable, and applying kernel patches
is something
very difficult that most users can not do. Furthermore, given the user chooses
to install a userspace program to manage their controller (e.g., HHD),
you should
ensure this driver functionality can be disabled, from boot, so that it does not
interfere with userspace programs.

For example, one problem I recall that before we removed the XPAD patchset
from the distribution Bazzite, I noticed during reviewing logs that your patch
regularly failed the ready check on certain allies, and after trying
the ready check
myself as part of HHD, I had to pull it after 10 hours because users
came out of
the woodwork to report their controller misbehaved.
Of course, in those cases HHD handled initialization, so your patch failing did
not affect functionality.

I know that some basic initialization is required regardless for the
gamepad to work
without e.g., HHD. In my experience changing the gamepad mode to
Gamepad (from Mouse
mode) with a single HID command should suffice. Then, the rest of the settings
are initialized to manufacturer defaults.

## Xbox/Xbox + A Quirk
As mentioned above, the vendor device of the Ally reports the front manufacturer
buttons as HID commands from the special vendor device. This allows Armoury
crate to open in windows. Of course, in Linux there is no Armoury crate, so
these buttons are unused by default. Userspace programs can fix this
quirk and your previous kernel patches do as much, by exposing the buttons as
F15, F16, F17, F18.

A final component to this patchset is taking this vendor device and making it
accessible from the gamepad driver. This is uniquely possible on the
Ally X, because
the gamepad happens to be HID and located on the same USB interface, so you can,
with a bit of trickery, receive the front button commands from the
gamepad driver.
This allows you to quirk them, and here specifically for steam, to
Xbox and Xbox+A
("BTN_GUIDE" and "BTN_GUIDE+BTN_SOUTH"), which have similar functionality to
the Steamdeck's steam and three dot buttons.

In my opinion, that borders a bit on being too opinionated, and perhaps e.g.,
Asus would also share this opinion. Imagine if Asus tried to add some sort of
official support to linux, only to find out the Linux driver is remapping the
buttons and they have to work around that. Also, combining two HID devices is
a bit unorthodox, and may cause other issues (see `desc.bEndpointAddress`).
So perhaps Asus needs to be consulted here?

Further, I have some advice for your button implementation.
80ms is too short for steam and is a delay I have tried before. It
will fail in low
device TDPs, as Steam will begin to lag and miss the commands. For
your reference,
HHD uses 150ms for this and I have tried 80ms in the past.

In general, I am trying to move away from Xbox+A as it tends to add delay and
since I prefer to multiplex the right vendor button for double
presses, it starts
to become noticable. It also does not work if the user presses the
Nintendo layout
button in the Steam UI.

In addition, you are freezing the kernel driver to send those commands for 240ms
which is around 100 reports.
I would advise that you store that the user has pressed the vendor button and
then when you process the following HID reports, you set the Xbox
button as 1 for
300ms after the button is pressed and the A button to 1 after 150ms until 300ms.
It is fine to release both buttons at the same time.

## Other issues
These are my overall thoughts. Since I went through the effort of
posting to lkml,
I will also draw your attention to 2 other patches you upstreamed and
cause issues
for the original Ally and now Ally X.

The first one is allowing users to enable mcu_powersave in February.
Beforehand, it was force disabled by the kernel by your previous patch.
This feature remains broken when the device is at low TDPs and unplugged.
We force disable it now as part of the distribution Bazzite now.
Applies to both Ally and Ally X.

The second occasion was 3 weeks ago when you pushed the Ally X quirk
and converted
`ally_mcu_usb_switch` into a DMI table. It seems that a DMI table does
not work in
this codepath, and it is something that we had tried over a week
before you posted the
patch and had reverted it. If you had asked me I would have told you as much.
After this patch got cherry picked by CachyOS and ChimeraOS, it ended
up breaking
both Allies, which is not a favorable user experience.

Moving forward, I will try to be more attentive in the kernel conversation and,
e.g., upstream patches. And perhaps you can be a bit more thorough
testing before you push your patches up to the kernel ;).

Good luck with this patch series!

Of course, once you fix the notes I have above and/or your patchset is
integrated
to the kernel, I will make sure HHD is forward compatible with it, while being
backwards compatible with older kernels.

Best Regards,
Antheas

^ permalink raw reply	[flat|nested] 13+ messages in thread
* [PATCH] hid-asus-ally: Add full gamepad support
@ 2024-08-06  8:12 Luke D. Jones
  2024-08-06 21:34 ` kernel test robot
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Luke D. Jones @ 2024-08-06  8:12 UTC (permalink / raw)
  To: linux-input; +Cc: linux-kernel, bentiss, jikos, Luke D. Jones

This driver adds full support of the ASUS ROG Ally X gamepad and support
for the Ally 1 xpad configuration options.

- dinput is translated to XBox controller (Ally-X only)
- default mode has the QAM buttons mapped (Ally-X only)
  * left is XBox button
  * right is an XBox + A combo for steam QAM
- force feedback is supported (Ally-X only)
- LED brightness control (0-3)
- LED multicolor class support
- Support all configuration

The configuration options available are:

- Gamepad mode (game, wasd, mouse)
- Remapping each button, plus macro map (hold macro btn and press other)
- Joystrick and trigger deadzones
- Gamepad vibration intensity
- Leds (using multicolor class)
- Button turbo abilities (per button)
- Joystick repsonse curves
- Joystick anti-deadzones

The attribute path tree looks like this:

- `./sys/../<USB HID>/`
  - `joystick_left/
    - `deadzone`
    - `mapping` (mouse, wasd, custom)
    - `anti_deadzone`
    - `response_curve`
    - `calibration`
    - `calibration_reset`
  - `trigger_left/
    - `deadzone`
    - `response_curve`
    - `calibration`
    - `calibration_reset`
  - `gamepad_mode`
  - `button_mapping`
    - `A`
    - `B`
    - `dpad_left`
    - etc

No settings are applied until `apply_all` is written to. The exception is
for calibrations.

While there is calibration ability, it can be difficult to get correct
and is heavily device dependent, as such it is set when written and not
when `apply_all` is written to. On driver load the set calibrations are
retrieved - this may be what you've set in Linux, Windows, or factory
defaults.

This patch provides an excellent default experience for the Ally X
device while the Ally 1 is a little more limited due to Xinput being
used.

Signed-off-by: Luke D. Jones <luke@ljones.dev>
---
 drivers/hid/Kconfig         |    9 +
 drivers/hid/Makefile        |    1 +
 drivers/hid/hid-asus-ally.c | 2281 +++++++++++++++++++++++++++++++++++
 drivers/hid/hid-asus-ally.h |  544 +++++++++
 drivers/hid/hid-asus.c      |   20 +-
 5 files changed, 2853 insertions(+), 2 deletions(-)
 create mode 100644 drivers/hid/hid-asus-ally.c
 create mode 100644 drivers/hid/hid-asus-ally.h

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 08446c89eff6..ea0dbe9111c4 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -164,6 +164,15 @@ config HID_ASUS
 	- GL553V series
 	- GL753V series
 
+config HID_ASUS_ALLY
+    tristate "Asus Ally gamepad configuration support"
+    depends on USB_HID
+    depends on LEDS_CLASS
+    depends on LEDS_CLASS_MULTICOLOR
+    select POWER_SUPPLY
+    help
+    Support for configuring the Asus ROG Ally gamepad using attributes.
+
 config HID_AUREAL
 	tristate "Aureal"
 	help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e40f1ddebbb7..2b2aa8b6f828 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
 obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
 obj-$(CONFIG_HID_CREATIVE_SB0540)	+= hid-creative-sb0540.o
 obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
+obj-$(CONFIG_HID_ASUS_ALLY)	+= hid-asus-ally.o
 obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
 obj-$(CONFIG_HID_BELKIN)	+= hid-belkin.o
 obj-$(CONFIG_HID_BETOP_FF)	+= hid-betopff.o
diff --git a/drivers/hid/hid-asus-ally.c b/drivers/hid/hid-asus-ally.c
new file mode 100644
index 000000000000..9e8007fc3c19
--- /dev/null
+++ b/drivers/hid/hid-asus-ally.c
@@ -0,0 +1,2281 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID driver for Asus ROG laptops and Ally
+ *
+ *  Copyright (c) 2023 Luke Jones <luke@ljones.dev>
+ */
+
+#include "linux/delay.h"
+#include "linux/device.h"
+#include "linux/err.h"
+#include "linux/input-event-codes.h"
+#include "linux/kstrtox.h"
+#include "linux/printk.h"
+#include "linux/slab.h"
+#include "linux/stddef.h"
+#include "linux/sysfs.h"
+#include <linux/hid.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+
+#include "hid-ids.h"
+#include "hid-asus-ally.h"
+
+#define READY_MAX_TRIES 3
+#define FEATURE_REPORT_ID 0x0d
+#define FEATURE_ROG_ALLY_REPORT_ID 0x5a
+#define FEATURE_ROG_ALLY_CODE_PAGE 0xD1
+#define FEATURE_ROG_ALLY_REPORT_SIZE 64
+#define ALLY_X_INPUT_REPORT_USB 0x0B
+#define ALLY_X_INPUT_REPORT_USB_SIZE 16
+
+#define ALLY_CFG_INTF_IN_ADDRESS 0x83
+#define ALLY_CFG_INTF_OUT_ADDRESS 0x04
+#define ALLY_X_INTERFACE_ADDRESS 0x87
+
+#define FEATURE_KBD_LED_REPORT_ID1 0x5d
+#define FEATURE_KBD_LED_REPORT_ID2 0x5e
+
+enum ROG_ALLY_TYPE {
+	ROG_ALLY_TYPE,
+	ROG_ALLY_TYPE_X,
+};
+
+static const struct hid_device_id rog_ally_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
+	  .driver_data = ROG_ALLY_TYPE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
+	  .driver_data = ROG_ALLY_TYPE_X },
+	{}
+};
+
+struct KeyCode {
+	const char *label;
+	u8 code;
+};
+
+static const struct KeyCode gamepad_codes[] = {
+	{ "PAD_A", 0x01 },	   { "PAD_B", 0x02 },	      { "PAD_X", 0x03 },
+	{ "PAD_Y", 0x04 },	   { "PAD_LB", 0x05 },	      { "PAD_RB", 0x06 },
+	{ "PAD_LS", 0x07 },	   { "PAD_RS", 0x08 },	      { "PAD_DPAD_UP", 0x09 },
+	{ "PAD_DPAD_DOWN", 0x0a }, { "PAD_DPAD_LEFT", 0x0b }, { "PAD_DPAD_RIGHT", 0x0c },
+	{ "PAD_VIEW", 0x11 },	   { "PAD_MENU", 0x12 },      { "PAD_XBOX", 0x13 }
+};
+
+static const struct KeyCode keyboard_codes[] = { { "KB_M1", 0x8f },
+						 { "KB_M2", 0x8e },
+						 { "KB_ESC", 0x76 },
+						 { "KB_F1", 0x50 },
+						 { "KB_F2", 0x60 },
+						 { "KB_F3", 0x40 },
+						 { "KB_F4", 0x0c },
+						 { "KB_F5", 0x03 },
+						 { "KB_F6", 0x0b },
+						 { "KB_F7", 0x80 },
+						 { "KB_F8", 0x0a },
+						 { "KB_F9", 0x01 },
+						 { "KB_F10", 0x09 },
+						 { "KB_F11", 0x78 },
+						 { "KB_F12", 0x07 },
+						 { "KB_F14", 0x10 },
+						 { "KB_F15", 0x18 },
+						 { "KB_BACKTICK", 0x0e },
+						 { "KB_1", 0x16 },
+						 { "KB_2", 0x1e },
+						 { "KB_3", 0x26 },
+						 { "KB_4", 0x25 },
+						 { "KB_5", 0x2e },
+						 { "KB_6", 0x36 },
+						 { "KB_7", 0x3d },
+						 { "KB_8", 0x3e },
+						 { "KB_9", 0x46 },
+						 { "KB_0", 0x45 },
+						 { "KB_HYPHEN", 0x4e },
+						 { "KB_EQUALS", 0x55 },
+						 { "KB_BACKSPACE", 0x66 },
+						 { "KB_TAB", 0x0d },
+						 { "KB_Q", 0x15 },
+						 { "KB_W", 0x1d },
+						 { "KB_E", 0x24 },
+						 { "KB_R", 0x2d },
+						 { "KB_T", 0x2d },
+						 { "KB_Y", 0x35 },
+						 { "KB_U", 0x3c },
+						 { "KB_I", 0x43 },
+						 { "KB_O", 0x44 },
+						 { "KB_P", 0x4d },
+						 { "KB_LBRACKET", 0x54 },
+						 { "KB_RBRACKET", 0x5b },
+						 { "KB_BACKSLASH", 0x5d },
+						 { "KB_CAPS", 0x58 },
+						 { "KB_A", 0x1c },
+						 { "KB_S", 0x1b },
+						 { "KB_D", 0x23 },
+						 { "KB_F", 0x2b },
+						 { "KB_G", 0x34 },
+						 { "KB_H", 0x33 },
+						 { "KB_J", 0x3b },
+						 { "KB_K", 0x42 },
+						 { "KB_L", 0x4b },
+						 { "KB_SEMI", 0x4c },
+						 { "KB_QUOTE", 0x52 },
+						 { "KB_RET", 0x5a },
+						 { "KB_LSHIFT", 0x88 },
+						 { "KB_Z", 0x1a },
+						 { "KB_X", 0x22 },
+						 { "KB_C", 0x21 },
+						 { "KB_V", 0x2a },
+						 { "KB_B", 0x32 },
+						 { "KB_N", 0x31 },
+						 { "KB_M", 0x3a },
+						 { "KB_COMMA", 0x41 },
+						 { "KB_PERIOD", 0x49 },
+						 { "KB_FWDSLASH", 0x4a },
+						 { "KB_RSHIFT", 0x89 },
+						 { "KB_LCTL", 0x8c },
+						 { "KB_META", 0x82 },
+						 { "KB_LALT", 0xba },
+						 { "KB_SPACE", 0x29 },
+						 { "KB_RALT", 0x8b },
+						 { "KB_MENU", 0x84 },
+						 { "KB_RCTL", 0x8d },
+						 { "KB_PRNTSCN", 0xc3 },
+						 { "KB_SCRLCK", 0x7e },
+						 { "KB_PAUSE", 0x91 },
+						 { "KB_INS", 0xc2 },
+						 { "KB_HOME", 0x94 },
+						 { "KB_PGUP", 0x96 },
+						 { "KB_DEL", 0xc0 },
+						 { "KB_END", 0x95 },
+						 { "KB_PGDWN", 0x97 },
+						 { "KB_UP_ARROW", 0x99 },
+						 { "KB_DOWN_ARROW", 0x98 },
+						 { "KB_LEFT_ARROW", 0x91 },
+						 { "KB_RIGHT_ARROW", 0x9b },
+						 { "NUMPAD_LOCK", 0x77 },
+						 { "NUMPAD_FWDSLASH", 0x90 },
+						 { "NUMPAD_ASTERISK", 0x7c },
+						 { "NUMPAD_HYPHEN", 0x7b },
+						 { "NUMPAD_0", 0x70 },
+						 { "NUMPAD_1", 0x69 },
+						 { "NUMPAD_2", 0x72 },
+						 { "NUMPAD_3", 0x7a },
+						 { "NUMPAD_4", 0x6b },
+						 { "NUMPAD_5", 0x73 },
+						 { "NUMPAD_6", 0x74 },
+						 { "NUMPAD_7", 0x6c },
+						 { "NUMPAD_8", 0x75 },
+						 { "NUMPAD_9", 0x7d },
+						 { "NUMPAD_PLUS", 0x79 },
+						 { "NUMPAD_ENTER", 0x81 },
+						 { "NUMPAD_PERIOD", 0x71 } };
+
+static const struct KeyCode mouse_codes[] = { { "MOUSE_LCLICK", 0x01 },
+					      { "MOUSE_RCLICK", 0x02 },
+					      { "MOUSE_MCLICK", 0x03 },
+					      { "MOUSE_WHEEL_UP", 0x04 },
+					      { "MOUSE_WHEEL_DOWN", 0x05 } };
+
+static const struct KeyCode media_codes[] = {
+	{ "MEDIA_SCREENSHOT", 0x16 },	{ "MEDIA_SHOW_KEYBOARD", 0x19 },
+	{ "MEDIA_SHOW_DESKTOP", 0x1c }, { "MEDIA_START_RECORDING", 0x1e },
+	{ "MEDIA_MIC_OFF", 0x01 },	{ "MEDIA_VOL_DOWN", 0x02 },
+	{ "MEDIA_VOL_UP", 0x03 }
+};
+
+/* The hatswitch outputs integers, we use them to index this X|Y pair */
+static const int hat_values[][2] = {
+	{ 0, 0 }, { 0, -1 }, { 1, -1 }, { 1, 0 },   { 1, 1 },
+	{ 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 },
+};
+
+/* rumble packet structure */
+struct 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 ff_report {
+	u8 report_id;
+	struct ff_data ff;
+} __packed;
+
+struct ally_x_input_report {
+	uint16_t x, y;
+	uint16_t rx, ry;
+	uint16_t z, rz;
+	uint8_t buttons[4];
+} __packed;
+
+struct ally_x_device {
+	struct input_dev *input;
+	struct hid_device *hdev;
+	spinlock_t lock;
+
+	struct ff_report *ff_packet;
+	struct work_struct output_worker;
+	bool output_worker_initialized;
+	/* Prevent multiple queued event due to the enforced delay in worker */
+	bool update_qam_btn;
+	/* Set if the QAM and AC buttons emit Xbox and Xbox+A */
+	bool qam_btns_steam_mode;
+	bool update_ff;
+};
+
+struct ally_rgb_leds {
+	struct hid_device *hdev;
+	/* Need two dev here to enable the 3 step brightness */
+	struct led_classdev led_bright_dev;
+	struct led_classdev_mc led_rgb_dev;
+	struct work_struct work;
+	spinlock_t lock;
+
+	bool removed;
+
+	/* Update the main brightness 0-2 using a single raw write */
+	bool update_bright;
+	unsigned int brightness;
+
+	/* Update the RGB only to keep write efficient */
+	bool update_rgb;
+	uint8_t gamepad_red;
+	uint8_t gamepad_green;
+	uint8_t gamepad_blue;
+};
+
+/* ROG Ally has many settings related to the gamepad, all using the same n-key endpoint */
+struct ally_gamepad_cfg {
+	struct hid_device *hdev;
+	struct input_dev *input;
+
+	enum xpad_mode mode;
+	/*
+	 * index: [joysticks/triggers][left(2 bytes), right(2 bytes)]
+	 * joysticks: 2 bytes: inner, outer
+	 * triggers: 2 bytes: lower, upper
+	 * min/max: 0-64
+	 */
+	u8 deadzones[xpad_mode_mouse][2][4];
+	/*
+	 * index: left, right
+	 * max: 64
+	 */
+	u8 vibration_intensity[xpad_mode_mouse][2];
+	/*
+	 * index: [joysticks][2 byte stepping per point]
+	 * - 4 points of 2 bytes each
+	 * - byte 0 of pair = stick move %
+	 * - byte 1 of pair = stick response %
+	 * - min/max: 1-63
+	 */
+	bool supports_response_curves;
+	u8 response_curve[xpad_mode_mouse][2][8];
+	/*
+	 * left = byte 0, right = byte 1
+	 */
+	bool supports_anti_deadzones;
+	u8 anti_deadzones[xpad_mode_mouse][2];
+	/*
+	 * index: [mode][phys pair][b1, b1 secondary, b2, b2 secondary, blocks of 11]
+	 */
+	u8 key_mapping[xpad_mode_mouse][btn_pair_lt_rt][MAPPING_BLOCK_LEN];
+	/*
+	 * index: [mode][button index]
+	 */
+	u8 turbo_btns[xpad_mode_mouse][TURBO_BLOCK_LEN];
+	/*
+	 * index: [joystick side][Y-stable, Y-min, Y-max, X-stable, X-min, X-max]
+	 */
+	u32 js_calibrations[2][6];
+	/*
+	 * index: [trigger side][stable, max]
+	 */
+	u32 tr_calibrations[2][2];
+};
+
+static struct ally_drvdata {
+	struct hid_device *hdev;
+	struct ally_x_device *ally_x;
+	struct ally_gamepad_cfg *gamepad_cfg;
+	struct ally_rgb_leds *led_rgb;
+} drvdata;
+
+static int asus_dev_get_report(struct hid_device *hdev, u8 *out_buf, size_t out_buf_size)
+{
+	return hid_hw_raw_request(hdev, FEATURE_REPORT_ID, out_buf, out_buf_size,
+				  HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+}
+
+static int asus_dev_set_report(struct hid_device *hdev, const u8 *buf, size_t buf_size)
+{
+	unsigned char *dmabuf;
+	int ret;
+
+	dmabuf = kmemdup(buf, buf_size, GFP_KERNEL);
+	if (!dmabuf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, buf[0], dmabuf, buf_size, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(dmabuf);
+
+	return ret;
+}
+
+/**************************************************************************************************/
+/* ROG Ally gamepad i/o and force-feedback                                                        */
+/**************************************************************************************************/
+static int ally_x_raw_event(struct ally_x_device *ally_x, struct hid_report *report, u8 *data,
+			    int size)
+{
+	struct ally_x_input_report *in_report;
+	unsigned long flags;
+	u8 byte;
+
+	if (data[0] == 0x0B) {
+		in_report = (struct ally_x_input_report *)&data[1];
+
+		input_report_abs(ally_x->input, ABS_X, in_report->x);
+		input_report_abs(ally_x->input, ABS_Y, in_report->y);
+		input_report_abs(ally_x->input, ABS_RX, in_report->rx);
+		input_report_abs(ally_x->input, ABS_RY, in_report->ry);
+		input_report_abs(ally_x->input, ABS_Z, in_report->z);
+		input_report_abs(ally_x->input, ABS_RZ, in_report->rz);
+
+		byte = in_report->buttons[0];
+		input_report_key(ally_x->input, BTN_A, byte & BIT(0));
+		input_report_key(ally_x->input, BTN_B, byte & BIT(1));
+		input_report_key(ally_x->input, BTN_X, byte & BIT(2));
+		input_report_key(ally_x->input, BTN_Y, byte & BIT(3));
+		input_report_key(ally_x->input, BTN_TL, byte & BIT(4));
+		input_report_key(ally_x->input, BTN_TR, byte & BIT(5));
+		input_report_key(ally_x->input, BTN_SELECT, byte & BIT(6));
+		input_report_key(ally_x->input, BTN_START, byte & BIT(7));
+
+		byte = in_report->buttons[1];
+		input_report_key(ally_x->input, BTN_THUMBL, byte & BIT(0));
+		input_report_key(ally_x->input, BTN_THUMBR, byte & BIT(1));
+		input_report_key(ally_x->input, BTN_MODE, byte & BIT(2));
+
+		byte = in_report->buttons[2];
+		input_report_abs(ally_x->input, ABS_HAT0X, hat_values[byte][0]);
+		input_report_abs(ally_x->input, ABS_HAT0Y, hat_values[byte][1]);
+	}
+	/*
+	 * The MCU used on Ally provides many devices: gamepad, keyboord, mouse, other.
+	 * The AC and QAM buttons route through another interface making it difficult to
+	 * use the events unless we grab those and use them here. Only works for Ally X.
+	 */
+	else if (data[0] == 0x5A) {
+		if (ally_x->qam_btns_steam_mode) {
+			spin_lock_irqsave(&ally_x->lock, flags);
+			if (data[1] == 0x38 && !ally_x->update_qam_btn) {
+				ally_x->update_qam_btn = true;
+				if (ally_x->output_worker_initialized)
+					schedule_work(&ally_x->output_worker);
+			}
+			spin_unlock_irqrestore(&ally_x->lock, flags);
+			/* Left/XBox button. Long press does ctrl+alt+del which we can't catch */
+			input_report_key(ally_x->input, BTN_MODE, data[1] == 0xA6);
+		} else {
+			input_report_key(ally_x->input, KEY_F16, data[1] == 0xA6);
+			input_report_key(ally_x->input, KEY_PROG1, data[1] == 0x38);
+		}
+		/* QAM long press */
+		input_report_key(ally_x->input, KEY_F17, data[1] == 0xA7);
+		/* QAM long press released */
+		input_report_key(ally_x->input, KEY_F18, data[1] == 0xA8);
+	}
+
+	input_sync(ally_x->input);
+
+	return 0;
+}
+
+static struct input_dev *ally_x_alloc_input_dev(struct hid_device *hdev,
+						const char *name_suffix)
+{
+	struct input_dev *input_dev;
+
+	input_dev = devm_input_allocate_device(&hdev->dev);
+	if (!input_dev)
+		return ERR_PTR(-ENOMEM);
+
+	input_dev->id.bustype = hdev->bus;
+	input_dev->id.vendor = hdev->vendor;
+	input_dev->id.product = hdev->product;
+	input_dev->id.version = hdev->version;
+	input_dev->uniq = hdev->uniq;
+	input_dev->name = "ASUS ROG Ally X Gamepad";
+
+	input_set_drvdata(input_dev, hdev);
+
+	return input_dev;
+}
+
+static int ally_x_play_effect(struct input_dev *idev, void *data, struct ff_effect *effect)
+{
+	struct ally_x_device *ally_x = drvdata.ally_x;
+	unsigned long flags;
+
+	if (effect->type != FF_RUMBLE)
+		return 0;
+
+	spin_lock_irqsave(&ally_x->lock, flags);
+	ally_x->ff_packet->ff.magnitude_strong = effect->u.rumble.strong_magnitude / 512;
+	ally_x->ff_packet->ff.magnitude_weak = effect->u.rumble.weak_magnitude / 512;
+	ally_x->update_ff = true;
+	spin_unlock_irqrestore(&ally_x->lock, flags);
+
+	if (ally_x->output_worker_initialized)
+		schedule_work(&ally_x->output_worker);
+
+	return 0;
+}
+
+static void ally_x_work(struct work_struct *work)
+{
+	struct ally_x_device *ally_x = container_of(work, struct ally_x_device, output_worker);
+	struct ff_report *ff_report = NULL;
+	bool update_qam = false;
+	bool update_ff = false;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ally_x->lock, flags);
+	update_ff = ally_x->update_ff;
+	if (ally_x->update_ff) {
+		ff_report = kmemdup(ally_x->ff_packet, sizeof(*ally_x->ff_packet), GFP_KERNEL);
+		ally_x->update_ff = false;
+	}
+	update_qam = ally_x->update_qam_btn;
+	spin_unlock_irqrestore(&ally_x->lock, flags);
+
+	if (update_ff && ff_report) {
+		ff_report->ff.magnitude_left = ff_report->ff.magnitude_strong;
+		ff_report->ff.magnitude_right = ff_report->ff.magnitude_weak;
+		asus_dev_set_report(ally_x->hdev, (u8 *)ff_report, sizeof(*ff_report));
+	}
+	kfree(ff_report);
+
+	if (update_qam) {
+		/*
+		 * The sleeps here are required to allow steam to register the button combo.
+		 */
+		usleep_range(1000, 2000);
+		input_report_key(ally_x->input, BTN_MODE, 1);
+		input_sync(ally_x->input);
+
+		msleep(80);
+		input_report_key(ally_x->input, BTN_A, 1);
+		input_sync(ally_x->input);
+
+		msleep(80);
+		input_report_key(ally_x->input, BTN_A, 0);
+		input_sync(ally_x->input);
+
+		msleep(80);
+		input_report_key(ally_x->input, BTN_MODE, 0);
+		input_sync(ally_x->input);
+
+		spin_lock_irqsave(&ally_x->lock, flags);
+		ally_x->update_qam_btn = false;
+		spin_unlock_irqrestore(&ally_x->lock, flags);
+	}
+}
+
+static struct input_dev *ally_x_setup_input(struct hid_device *hdev)
+{
+	int ret, abs_min = 0, js_abs_max = 65535, tr_abs_max = 1023;
+	struct input_dev *input;
+
+	input = ally_x_alloc_input_dev(hdev, NULL);
+	if (IS_ERR(input))
+		return ERR_CAST(input);
+
+	input_set_abs_params(input, ABS_X, abs_min, js_abs_max, 0, 0);
+	input_set_abs_params(input, ABS_Y, abs_min, js_abs_max, 0, 0);
+	input_set_abs_params(input, ABS_RX, abs_min, js_abs_max, 0, 0);
+	input_set_abs_params(input, ABS_RY, abs_min, js_abs_max, 0, 0);
+	input_set_abs_params(input, ABS_Z, abs_min, tr_abs_max, 0, 0);
+	input_set_abs_params(input, ABS_RZ, abs_min, tr_abs_max, 0, 0);
+	input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+	input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+	input_set_capability(input, EV_KEY, BTN_A);
+	input_set_capability(input, EV_KEY, BTN_B);
+	input_set_capability(input, EV_KEY, BTN_X);
+	input_set_capability(input, EV_KEY, BTN_Y);
+	input_set_capability(input, EV_KEY, BTN_TL);
+	input_set_capability(input, EV_KEY, BTN_TR);
+	input_set_capability(input, EV_KEY, BTN_SELECT);
+	input_set_capability(input, EV_KEY, BTN_START);
+	input_set_capability(input, EV_KEY, BTN_MODE);
+	input_set_capability(input, EV_KEY, BTN_THUMBL);
+	input_set_capability(input, EV_KEY, BTN_THUMBR);
+
+	input_set_capability(input, EV_KEY, KEY_PROG1);
+	input_set_capability(input, EV_KEY, KEY_F16);
+	input_set_capability(input, EV_KEY, KEY_F17);
+	input_set_capability(input, EV_KEY, KEY_F18);
+
+	input_set_capability(input, EV_FF, FF_RUMBLE);
+	input_ff_create_memless(input, NULL, ally_x_play_effect);
+
+	ret = input_register_device(input);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return input;
+}
+
+static ssize_t ally_x_qam_mode_show(struct device *dev, struct device_attribute *attr,
+				    char *buf)
+{
+	struct ally_x_device *ally_x = drvdata.ally_x;
+
+	return sysfs_emit(buf, "%d\n", ally_x->qam_btns_steam_mode);
+}
+
+static ssize_t ally_x_qam_mode_store(struct device *dev, struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct ally_x_device *ally_x = drvdata.ally_x;
+	bool val;
+	int ret;
+
+	ret = kstrtobool(buf, &val);
+	if (ret < 0)
+		return ret;
+
+	ally_x->qam_btns_steam_mode = val;
+
+	return count;
+}
+ALLY_DEVICE_ATTR_RW(ally_x_qam_mode, qam_mode);
+
+static struct ally_x_device *ally_x_create(struct hid_device *hdev)
+{
+	uint8_t max_output_report_size;
+	struct ally_x_device *ally_x;
+	struct ff_report *report;
+	int ret;
+
+	ally_x = devm_kzalloc(&hdev->dev, sizeof(*ally_x), GFP_KERNEL);
+	if (!ally_x)
+		return ERR_PTR(-ENOMEM);
+
+	ally_x->hdev = hdev;
+	INIT_WORK(&ally_x->output_worker, ally_x_work);
+	spin_lock_init(&ally_x->lock);
+	ally_x->output_worker_initialized = true;
+	ally_x->qam_btns_steam_mode =
+		true; /* Always default to steam mode, it can be changed by userspace attr */
+
+	max_output_report_size = sizeof(struct ally_x_input_report);
+	report = devm_kzalloc(&hdev->dev, sizeof(*report), GFP_KERNEL);
+	if (!report) {
+		ret = -ENOMEM;
+		goto free_ally_x;
+	}
+
+	/* None of these bytes will change for the FF command for now */
+	report->report_id = 0x0D;
+	report->ff.enable = 0x0F; /* Enable all by default */
+	report->ff.pulse_sustain_10ms = 0xFF; /* Duration */
+	report->ff.pulse_release_10ms = 0x00; /* Start Delay */
+	report->ff.loop_count = 0xEB; /* Loop Count */
+	ally_x->ff_packet = report;
+
+	ally_x->input = ally_x_setup_input(hdev);
+	if (IS_ERR(ally_x->input)) {
+		ret = PTR_ERR(ally_x->input);
+		goto free_ff_packet;
+	}
+
+	if (sysfs_create_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr)) {
+		ret = -ENODEV;
+		goto unregister_input;
+	}
+
+	ally_x->update_ff = true;
+	if (ally_x->output_worker_initialized)
+		schedule_work(&ally_x->output_worker);
+
+	hid_info(hdev, "Registered Ally X controller using %s\n",
+		 dev_name(&ally_x->input->dev));
+	return ally_x;
+
+unregister_input:
+	input_unregister_device(ally_x->input);
+free_ff_packet:
+	kfree(ally_x->ff_packet);
+free_ally_x:
+	kfree(ally_x);
+	return ERR_PTR(ret);
+}
+
+static void ally_x_remove(struct hid_device *hdev)
+{
+	struct ally_x_device *ally_x = drvdata.ally_x;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ally_x->lock, flags);
+	ally_x->output_worker_initialized = false;
+	spin_unlock_irqrestore(&ally_x->lock, flags);
+	cancel_work_sync(&ally_x->output_worker);
+	sysfs_remove_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr);
+}
+
+/**************************************************************************************************/
+/* ROG Ally configuration                                                                         */
+/**************************************************************************************************/
+static int __gamepad_write_all_to_mcu(struct hid_device *hdev,
+				      struct ally_gamepad_cfg *ally_cfg);
+
+static int process_key_code(const struct KeyCode *codes, int code_count, const char *buf_copy,
+			    u8 *out, int out_idx)
+{
+	for (int i = 0; i < code_count; i++) {
+		if (strcmp(buf_copy, codes[i].label) == 0) {
+			out[out_idx] = codes[i].code;
+			return 0; // Found
+		}
+	}
+	return -EINVAL; // Not found
+}
+
+static int __string_to_key_code(const char *buf, u8 *out, int out_len)
+{
+	char buf_copy[32];
+	u8 *save_buf;
+
+	if (out_len != BTN_CODE_LEN)
+		return -EINVAL;
+
+	save_buf = kzalloc(out_len, GFP_KERNEL);
+	if (!save_buf)
+		return -ENOMEM;
+	memcpy(save_buf, out, out_len);
+	memset(out, 0, out_len); /* always clear before adjusting */
+
+	strscpy(buf_copy, buf);
+	buf_copy[strcspn(buf_copy, "\n")] = 0;
+
+	/* Gamepad group */
+	out[0] = 0x01;
+	if (process_key_code(gamepad_codes, ARRAY_SIZE(gamepad_codes), buf_copy, out, 1) == 0)
+		goto success;
+
+	/* Keyboard group */
+	out[0] = 0x02;
+	if (process_key_code(keyboard_codes, ARRAY_SIZE(keyboard_codes), buf_copy, out, 2) == 0)
+		goto success;
+
+	/* Mouse group */
+	out[0] = 0x03;
+	if (process_key_code(mouse_codes, ARRAY_SIZE(mouse_codes), buf_copy, out, 4) == 0)
+		goto success;
+
+	/* Media group */
+	out[0] = 0x05;
+	if (process_key_code(media_codes, ARRAY_SIZE(media_codes), buf_copy, out, 3) == 0)
+		goto success;
+
+	/* Restore bytes if invalid input */
+	memcpy(out, save_buf, out_len);
+	kfree(save_buf);
+	return -EINVAL;
+
+success:
+	kfree(save_buf);
+	return 0;
+}
+
+static const char *key_code_to_string(const struct KeyCode *codes, int code_count, u8 code)
+{
+	for (int i = 0; i < code_count; i++) {
+		if (codes[i].code == code)
+			return codes[i].label;
+	}
+	return "";
+}
+
+static u8 *__get_btn_block(struct ally_gamepad_cfg *ally_cfg, enum btn_pair pair,
+			   enum btn_pair_side side, bool secondary)
+{
+	int offs;
+
+	offs = side ? MAPPING_BLOCK_LEN / 2 : 0;
+	offs = secondary ? offs + BTN_CODE_LEN : offs;
+	return ally_cfg->key_mapping[ally_cfg->mode - 1][pair - 1] + offs;
+}
+
+static const char *__btn_map_to_string(struct ally_gamepad_cfg *ally_cfg, enum btn_pair pair,
+				       enum btn_pair_side side, bool secondary)
+{
+	u8 *out_arg = __get_btn_block(ally_cfg, pair, side, secondary);
+
+	switch (out_arg[0]) {
+	case 0x01: // Gamepad buttons
+		return key_code_to_string(gamepad_codes, ARRAY_SIZE(gamepad_codes), out_arg[1]);
+	case 0x02: // Keyboard keys
+		return key_code_to_string(keyboard_codes, ARRAY_SIZE(keyboard_codes),
+					  out_arg[2]);
+	case 0x03: // Mouse buttons
+		return key_code_to_string(mouse_codes, ARRAY_SIZE(mouse_codes), out_arg[4]);
+	case 0x05: // Media controls
+		return key_code_to_string(media_codes, ARRAY_SIZE(media_codes), out_arg[3]);
+	default:
+		return "";
+	}
+}
+
+/* ASUS ROG Ally device specific attributes */
+
+/* This should be called before any attempts to set device functions */
+static int __gamepad_check_ready(struct hid_device *hdev)
+{
+	int ret, count;
+	u8 *hidbuf;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	ret = 0;
+	for (count = 0; count < READY_MAX_TRIES; count++) {
+		hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+		hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+		hidbuf[2] = xpad_cmd_check_ready;
+		hidbuf[3] = 01;
+		ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+		if (ret < 0)
+			hid_warn(hdev, "ROG Ally check failed set report: %d\n", ret);
+
+		hidbuf[0] = hidbuf[1] = hidbuf[2] = hidbuf[3] = 0;
+		ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+		if (ret < 0)
+			hid_warn(hdev, "ROG Ally check failed get report: %d\n", ret);
+
+		ret = hidbuf[2] == xpad_cmd_check_ready;
+		if (ret)
+			break;
+		usleep_range(
+			1000,
+			2000); /* don't spam the entire loop in less than USB response time */
+	}
+
+	if (count == READY_MAX_TRIES)
+		hid_warn(hdev, "ROG Ally never responded with a ready\n");
+
+	kfree(hidbuf);
+	return ret;
+}
+
+/* BUTTON REMAPPING *******************************************************************************/
+static void __btn_pair_to_pkt(struct ally_gamepad_cfg *ally_cfg, enum btn_pair pair, u8 *out,
+			      int out_len)
+{
+	out[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	out[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	out[2] = xpad_cmd_set_mapping;
+	out[3] = pair;
+	out[4] = xpad_cmd_len_mapping;
+	memcpy(&out[5], &ally_cfg->key_mapping[ally_cfg->mode - 1][pair - 1],
+	       MAPPING_BLOCK_LEN);
+}
+
+/* Store the button setting in driver data. Does not apply to device until __gamepad_set_mapping */
+static int __gamepad_mapping_store(struct ally_gamepad_cfg *ally_cfg, const char *buf,
+				   enum btn_pair pair, int side, bool secondary)
+{
+	u8 *out_arg;
+
+	out_arg = __get_btn_block(ally_cfg, pair, side, secondary);
+
+	return __string_to_key_code(buf, out_arg, BTN_CODE_LEN);
+}
+
+/* Apply the mapping pair to the device */
+static int __gamepad_set_mapping(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg,
+				 enum btn_pair pair)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	__btn_pair_to_pkt(ally_cfg, pair, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+
+	kfree(hidbuf);
+
+	return ret;
+}
+
+static ssize_t btn_mapping_apply_store(struct device *dev, struct device_attribute *attr,
+				       const char *buf, size_t count)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	struct hid_device *hdev = to_hid_device(dev);
+	int ret;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	ret = __gamepad_write_all_to_mcu(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+ALLY_DEVICE_ATTR_WO(btn_mapping_apply, apply_all);
+
+/* BUTTON TURBO ***********************************************************************************/
+static int __btn_turbo_index(enum btn_pair pair, int side)
+{
+	return (pair - 1) * (2 * TURBO_BLOCK_STEP) + (side * TURBO_BLOCK_STEP);
+};
+
+static int __gamepad_turbo_show(struct device *dev, enum btn_pair pair, int side)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	return ally_cfg->turbo_btns[ally_cfg->mode - 1][__btn_turbo_index(pair, side)];
+};
+
+static int __gamepad_turbo_store(struct device *dev, const char *buf, enum btn_pair pair,
+				 int side)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	int ret, val;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret)
+		return ret;
+	if (val < 0 || val > 16)
+		return -EINVAL;
+
+	ally_cfg->turbo_btns[ally_cfg->mode - 1][__btn_turbo_index(pair, side)] = val;
+
+	return 0;
+};
+
+/* button map attributes, regular and macro*/
+ALLY_BTN_MAPPING(m2, btn_pair_m1_m2, btn_pair_side_left);
+ALLY_BTN_MAPPING(m1, btn_pair_m1_m2, btn_pair_side_right);
+ALLY_BTN_MAPPING(a, btn_pair_a_b, btn_pair_side_left);
+ALLY_BTN_MAPPING(b, btn_pair_a_b, btn_pair_side_right);
+ALLY_BTN_MAPPING(x, btn_pair_x_y, btn_pair_side_left);
+ALLY_BTN_MAPPING(y, btn_pair_x_y, btn_pair_side_right);
+ALLY_BTN_MAPPING(lb, btn_pair_lb_rb, btn_pair_side_left);
+ALLY_BTN_MAPPING(rb, btn_pair_lb_rb, btn_pair_side_right);
+ALLY_BTN_MAPPING(ls, btn_pair_ls_rs, btn_pair_side_left);
+ALLY_BTN_MAPPING(rs, btn_pair_ls_rs, btn_pair_side_right);
+ALLY_BTN_MAPPING(lt, btn_pair_lt_rt, btn_pair_side_left);
+ALLY_BTN_MAPPING(rt, btn_pair_lt_rt, btn_pair_side_right);
+ALLY_BTN_MAPPING(dpad_u, btn_pair_dpad_u_d, btn_pair_side_left);
+ALLY_BTN_MAPPING(dpad_d, btn_pair_dpad_u_d, btn_pair_side_right);
+ALLY_BTN_MAPPING(dpad_l, btn_pair_dpad_l_r, btn_pair_side_left);
+ALLY_BTN_MAPPING(dpad_r, btn_pair_dpad_l_r, btn_pair_side_right);
+ALLY_BTN_MAPPING(view, btn_pair_view_menu, btn_pair_side_left);
+ALLY_BTN_MAPPING(menu, btn_pair_view_menu, btn_pair_side_right);
+
+static void __gamepad_mapping_xpad_default(struct ally_gamepad_cfg *ally_cfg)
+{
+	memcpy(&ally_cfg->key_mapping[0][0], &XPAD_DEF1, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][1], &XPAD_DEF2, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][2], &XPAD_DEF3, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][3], &XPAD_DEF4, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][4], &XPAD_DEF5, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][5], &XPAD_DEF6, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][6], &XPAD_DEF7, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][7], &XPAD_DEF8, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[0][8], &XPAD_DEF9, MAPPING_BLOCK_LEN);
+}
+
+static void __gamepad_mapping_wasd_default(struct ally_gamepad_cfg *ally_cfg)
+{
+	memcpy(&ally_cfg->key_mapping[1][0], &WASD_DEF1, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][1], &WASD_DEF2, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][2], &WASD_DEF3, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][3], &WASD_DEF4, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][4], &WASD_DEF5, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][5], &WASD_DEF6, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][6], &WASD_DEF7, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][7], &WASD_DEF8, MAPPING_BLOCK_LEN);
+	memcpy(&ally_cfg->key_mapping[1][8], &WASD_DEF9, MAPPING_BLOCK_LEN);
+}
+
+static ssize_t btn_mapping_reset_store(struct device *dev, struct device_attribute *attr,
+				       const char *buf, size_t count)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	switch (ally_cfg->mode) {
+	case xpad_mode_game:
+		__gamepad_mapping_xpad_default(ally_cfg);
+		break;
+	case xpad_mode_wasd:
+		__gamepad_mapping_wasd_default(ally_cfg);
+		break;
+	default:
+		__gamepad_mapping_xpad_default(ally_cfg);
+		break;
+	}
+
+	return count;
+}
+
+ALLY_DEVICE_ATTR_WO(btn_mapping_reset, reset_btn_mapping);
+
+/* GAMEPAD MODE ***********************************************************************************/
+static ssize_t __gamepad_set_mode(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg,
+				  int val)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_mode;
+	hidbuf[3] = xpad_cmd_len_mode;
+	hidbuf[4] = val;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		goto report_fail;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto report_fail;
+
+	ret = __gamepad_write_all_to_mcu(hdev, ally_cfg);
+	if (ret < 0)
+		goto report_fail;
+
+report_fail:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t gamepad_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	return sysfs_emit(buf, "%d\n", ally_cfg->mode);
+}
+
+static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	int ret, val;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val < xpad_mode_game || val > xpad_mode_mouse)
+		return -EINVAL;
+
+	ally_cfg->mode = val;
+
+	ret = __gamepad_set_mode(hdev, ally_cfg, val);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+DEVICE_ATTR_RW(gamepad_mode);
+
+/* VIBRATION INTENSITY ****************************************************************************/
+static ssize_t gamepad_vibration_intensity_index_show(struct device *dev,
+						      struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "left right\n");
+}
+
+ALLY_DEVICE_ATTR_RO(gamepad_vibration_intensity_index, vibration_intensity_index);
+
+static ssize_t __gamepad_write_vibe_intensity_to_mcu(struct hid_device *hdev,
+						     struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_vibe_intensity;
+	hidbuf[3] = xpad_cmd_len_vibe_intensity;
+	hidbuf[4] = ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_left];
+	hidbuf[5] = ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_right];
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		goto report_fail;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto report_fail;
+
+report_fail:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t gamepad_vibration_intensity_show(struct device *dev,
+						struct device_attribute *attr, char *buf)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	return sysfs_emit(
+		buf, "%d %d\n",
+		ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_left],
+		ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_right]);
+}
+
+static ssize_t gamepad_vibration_intensity_store(struct device *dev,
+						 struct device_attribute *attr, const char *buf,
+						 size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	u32 left, right;
+	int ret;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	if (sscanf(buf, "%d %d", &left, &right) != 2)
+		return -EINVAL;
+
+	if (left > 64 || right > 64)
+		return -EINVAL;
+
+	ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_left] = left;
+	ally_cfg->vibration_intensity[ally_cfg->mode - 1][btn_pair_side_right] = right;
+
+	ret = __gamepad_write_vibe_intensity_to_mcu(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+ALLY_DEVICE_ATTR_RW(gamepad_vibration_intensity, vibration_intensity);
+
+/* ROOT LEVEL ATTRS *******************************************************************************/
+static struct attribute *gamepad_device_attrs[] = {
+	&dev_attr_gamepad_mode.attr,
+	&dev_attr_btn_mapping_reset.attr,
+	&dev_attr_btn_mapping_apply.attr,
+	&dev_attr_gamepad_vibration_intensity.attr,
+	&dev_attr_gamepad_vibration_intensity_index.attr,
+	NULL
+};
+
+static const struct attribute_group ally_controller_attr_group = {
+	.attrs = gamepad_device_attrs,
+};
+
+/* ANALOGUE DEADZONES *****************************************************************************/
+static ssize_t __gamepad_set_deadzones(struct hid_device *hdev,
+				       struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_js_dz;
+	hidbuf[3] = xpad_cmd_len_deadzone;
+	hidbuf[4] = ally_cfg->deadzones[ally_cfg->mode - 1][0][0];
+	hidbuf[5] = ally_cfg->deadzones[ally_cfg->mode - 1][0][1];
+	hidbuf[6] = ally_cfg->deadzones[ally_cfg->mode - 1][0][2];
+	hidbuf[7] = ally_cfg->deadzones[ally_cfg->mode - 1][0][3];
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto end;
+
+	hidbuf[2] = xpad_cmd_set_tr_dz;
+	hidbuf[4] = ally_cfg->deadzones[ally_cfg->mode - 1][1][0];
+	hidbuf[5] = ally_cfg->deadzones[ally_cfg->mode - 1][1][1];
+	hidbuf[6] = ally_cfg->deadzones[ally_cfg->mode - 1][1][2];
+	hidbuf[7] = ally_cfg->deadzones[ally_cfg->mode - 1][1][3];
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto end;
+
+end:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t __gamepad_store_deadzones(struct ally_gamepad_cfg *ally_cfg, enum xpad_axis axis,
+					 const char *buf)
+{
+	int cmd, side, is_tr;
+	u32 inner, outer;
+
+	if (sscanf(buf, "%d %d", &inner, &outer) != 2)
+		return -EINVAL;
+
+	if (inner > 64 || outer > 64 || inner > outer)
+		return -EINVAL;
+
+	is_tr = axis > xpad_axis_xy_right;
+	side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 2 : 0;
+	cmd = is_tr ? xpad_cmd_set_js_dz : xpad_cmd_set_tr_dz;
+
+	ally_cfg->deadzones[ally_cfg->mode - 1][is_tr][side] = inner;
+	ally_cfg->deadzones[ally_cfg->mode - 1][is_tr][side + 1] = outer;
+
+	return 0;
+}
+
+static ssize_t axis_xyz_deadzone_index_show(struct device *dev, struct device_attribute *attr,
+					    char *buf)
+{
+	return sysfs_emit(buf, "inner outer\n");
+}
+
+ALLY_DEVICE_ATTR_RO(axis_xyz_deadzone_index, deadzone_index);
+
+ALLY_AXIS_DEADZONE(xpad_axis_xy_left, deadzone);
+ALLY_AXIS_DEADZONE(xpad_axis_xy_right, deadzone);
+ALLY_AXIS_DEADZONE(xpad_axis_z_left, deadzone);
+ALLY_AXIS_DEADZONE(xpad_axis_z_right, deadzone);
+
+/* ANTI-DEADZONES *********************************************************************************/
+static ssize_t __gamepad_write_js_ADZ_to_mcu(struct hid_device *hdev,
+					     struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_adz;
+	hidbuf[3] = xpad_cmd_len_adz;
+	hidbuf[4] = ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_left];
+	hidbuf[5] = ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_right];
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		goto report_fail;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto report_fail;
+
+report_fail:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t __gamepad_js_ADZ_store(struct device *dev, const char *buf,
+				      enum btn_pair_side side)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	int ret, val;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val < 0 || val > 32)
+		return -EINVAL;
+
+	ally_cfg->anti_deadzones[ally_cfg->mode - 1][side] = val;
+
+	return ret;
+}
+
+static ssize_t xpad_axis_xy_left_ADZ_show(struct device *dev, struct device_attribute *attr,
+					  char *buf)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	return sysfs_emit(buf, "%d\n",
+			  ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_left]);
+}
+
+static ssize_t xpad_axis_xy_left_ADZ_store(struct device *dev, struct device_attribute *attr,
+					   const char *buf, size_t count)
+{
+	int ret = __gamepad_js_ADZ_store(dev, buf, btn_pair_side_left);
+
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+ALLY_DEVICE_ATTR_RW(xpad_axis_xy_left_ADZ, anti_deadzone);
+
+static ssize_t xpad_axis_xy_right_ADZ_show(struct device *dev, struct device_attribute *attr,
+					   char *buf)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	return sysfs_emit(buf, "%d\n",
+			  ally_cfg->anti_deadzones[ally_cfg->mode - 1][btn_pair_side_right]);
+}
+
+static ssize_t xpad_axis_xy_right_ADZ_store(struct device *dev, struct device_attribute *attr,
+					    const char *buf, size_t count)
+{
+	int ret = __gamepad_js_ADZ_store(dev, buf, btn_pair_side_right);
+
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+ALLY_DEVICE_ATTR_RW(xpad_axis_xy_right_ADZ, anti_deadzone);
+
+/* JS RESPONSE CURVES *****************************************************************************/
+static ssize_t rc_point_index_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "move response\n");
+}
+
+ALLY_DEVICE_ATTR_RO(rc_point_index, rc_point_index);
+
+static ssize_t __gamepad_write_response_curves_to_mcu(struct hid_device *hdev,
+						      struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_response_curve;
+	hidbuf[3] = xpad_cmd_len_response_curve;
+	hidbuf[4] = 0x01;
+	memcpy(&hidbuf[5], &ally_cfg->response_curve[ally_cfg->mode - 1][btn_pair_side_left],
+	       8);
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		goto report_fail;
+
+	hidbuf[4] = 0x02;
+	memcpy(&hidbuf[5], &ally_cfg->response_curve[ally_cfg->mode - 1][btn_pair_side_right],
+	       8);
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		goto report_fail;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto report_fail;
+
+report_fail:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t __gamepad_store_response_curve(struct device *dev, const char *buf,
+					      enum btn_pair_side side, int point)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	u32 move, response;
+	int idx;
+
+	idx = (point - 1) * 2;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	if (sscanf(buf, "%d %d", &move, &response) != 2)
+		return -EINVAL;
+
+	if (move > 64 || response > 64)
+		return -EINVAL;
+
+	ally_cfg->response_curve[ally_cfg->mode - 1][side][idx] = move;
+	ally_cfg->response_curve[ally_cfg->mode - 1][side][idx + 1] = response;
+
+	return 0;
+}
+
+ALLY_JS_RC_POINT(left, 1, rc_point_);
+ALLY_JS_RC_POINT(left, 2, rc_point_);
+ALLY_JS_RC_POINT(left, 3, rc_point_);
+ALLY_JS_RC_POINT(left, 4, rc_point_);
+
+ALLY_JS_RC_POINT(right, 1, rc_point_);
+ALLY_JS_RC_POINT(right, 2, rc_point_);
+ALLY_JS_RC_POINT(right, 3, rc_point_);
+ALLY_JS_RC_POINT(right, 4, rc_point_);
+
+/* CALIBRATIONS ***********************************************************************************/
+static int __gamepad_get_calibration(struct hid_device *hdev)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	u8 *hidbuf;
+	int ret, i;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	for (i = 0; i < 2; i++) {
+		hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+		hidbuf[1] = 0xD0;
+		hidbuf[2] = 0x03;
+		hidbuf[3] = i + 1; // 0x01 JS, 0x02 TR
+		hidbuf[4] = 0x20;
+
+		ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+		if (ret < 0) {
+			hid_warn(hdev, "ROG Ally check failed set report: %d\n", ret);
+			goto cleanup;
+		}
+
+		memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE);
+		ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+		if (ret < 0 || hidbuf[5] != 1) {
+			hid_warn(hdev, "ROG Ally check failed get report: %d\n", ret);
+			goto cleanup;
+		}
+
+		if (i == 0) {
+			/* Joystick calibration */
+			/* [left][index] is Y: stable, min, max. X: stable, min, max */
+			ally_cfg->js_calibrations[0][3] = (hidbuf[6] << 8) | hidbuf[7];
+			ally_cfg->js_calibrations[0][4] = (hidbuf[8] << 8) | hidbuf[9];
+			ally_cfg->js_calibrations[0][5] = (hidbuf[10] << 8) | hidbuf[11];
+			ally_cfg->js_calibrations[0][0] = (hidbuf[12] << 8) | hidbuf[13];
+			ally_cfg->js_calibrations[0][1] = (hidbuf[14] << 8) | hidbuf[15];
+			ally_cfg->js_calibrations[0][2] = (hidbuf[16] << 8) | hidbuf[17];
+			/* [right][index] is Y: stable, min, max. X: stable, min, max */
+			ally_cfg->js_calibrations[1][0] = (hidbuf[24] << 8) | hidbuf[25];
+			ally_cfg->js_calibrations[1][1] = (hidbuf[26] << 8) | hidbuf[27];
+			ally_cfg->js_calibrations[1][2] = (hidbuf[28] << 8) | hidbuf[29];
+			ally_cfg->js_calibrations[1][3] = (hidbuf[18] << 8) | hidbuf[19];
+			ally_cfg->js_calibrations[1][4] = (hidbuf[20] << 8) | hidbuf[21];
+			ally_cfg->js_calibrations[1][5] = (hidbuf[22] << 8) | hidbuf[23];
+		} else {
+			/* Trigger calibration */
+			/* [left/right][stable/max] */
+			ally_cfg->tr_calibrations[0][0] = (hidbuf[6] << 8) | hidbuf[7];
+			ally_cfg->tr_calibrations[0][1] = (hidbuf[8] << 8) | hidbuf[9];
+			ally_cfg->tr_calibrations[1][0] = (hidbuf[10] << 8) | hidbuf[11];
+			ally_cfg->tr_calibrations[1][1] = (hidbuf[12] << 8) | hidbuf[13];
+		}
+	}
+
+cleanup:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t __gamepad_write_cal_to_mcu(struct device *dev, enum xpad_axis axis)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	u8 *c, side, pkt_len, data_len;
+	int ret, cal, checksum = 0;
+	u8 *hidbuf;
+	int *head;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 1 : 0;
+	pkt_len = axis > xpad_axis_xy_right ? 0x06 : 0x0E;
+	data_len = axis > xpad_axis_xy_right ? 2 : 6;
+	head = axis > xpad_axis_xy_right ? ally_cfg->tr_calibrations[side] :
+					   ally_cfg->js_calibrations[side];
+
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_calibration;
+	hidbuf[3] = pkt_len;
+	hidbuf[4] = 0x01; /* second command (write calibration) */
+	hidbuf[5] = axis;
+	c = &hidbuf[6]; /* pointer to data start */
+
+	for (size_t i = 0; i < data_len; i++) {
+		cal = head[i];
+		*c = (u8)((cal & 0xff00) >> 8);
+		checksum += *c;
+		c += 1;
+		*c = (u8)(cal & 0xff);
+		checksum += *c;
+		c += 1;
+	}
+
+	hidbuf[6 + data_len * 2] = checksum;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto report_fail;
+
+	memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE);
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_calibration;
+	hidbuf[3] = xpad_cmd_len_calibration3;
+	hidbuf[4] = 0x03; /* second command (apply the calibration that was written) */
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto report_fail;
+
+report_fail:
+	kfree(hidbuf);
+	return ret;
+}
+
+static ssize_t __gamepad_cal_store(struct device *dev, const char *buf, enum xpad_axis axis)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	u32 x_stable, x_min, x_max, y_stable, y_min, y_max, side;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	if (axis == xpad_axis_xy_left || axis == xpad_axis_xy_right) {
+		if (sscanf(buf, "%d %d %d %d %d %d", &x_stable, &x_min, &x_max, &y_stable,
+			   &y_min, &y_max) != 6)
+			return -EINVAL;
+
+		side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 1 : 0;
+		/* stored in reverse order for easy copy to packet */
+		ally_cfg->js_calibrations[side][0] = y_stable;
+		ally_cfg->js_calibrations[side][1] = y_min;
+		ally_cfg->js_calibrations[side][2] = y_max;
+		ally_cfg->js_calibrations[side][3] = x_stable;
+		ally_cfg->js_calibrations[side][4] = x_min;
+		ally_cfg->js_calibrations[side][5] = x_max;
+
+		return __gamepad_write_cal_to_mcu(dev, axis);
+	}
+	if (sscanf(buf, "%d %d", &x_stable, &x_max) != 2)
+		return -EINVAL;
+
+	side = axis == xpad_axis_xy_right || axis == xpad_axis_z_right ? 1 : 0;
+	/* stored in reverse order for easy copy to packet */
+	ally_cfg->tr_calibrations[side][0] = x_stable;
+	ally_cfg->tr_calibrations[side][1] = x_max;
+
+	return __gamepad_write_cal_to_mcu(dev, axis);
+}
+
+static ssize_t __gamepad_cal_show(struct device *dev, char *buf, enum xpad_axis axis)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	int side = (axis == xpad_axis_xy_right || axis == xpad_axis_z_right) ? 1 : 0;
+
+	if (!drvdata.gamepad_cfg)
+		return -ENODEV;
+
+	if (axis == xpad_axis_xy_left || axis == xpad_axis_xy_right) {
+		return sysfs_emit(
+			buf, "%d %d %d %d %d %d\n", ally_cfg->js_calibrations[side][3],
+			ally_cfg->js_calibrations[side][4], ally_cfg->js_calibrations[side][5],
+			ally_cfg->js_calibrations[side][0], ally_cfg->js_calibrations[side][1],
+			ally_cfg->js_calibrations[side][2]);
+	}
+
+	return sysfs_emit(buf, "%d %d\n", ally_cfg->tr_calibrations[side][0],
+			  ally_cfg->tr_calibrations[side][1]);
+}
+
+ALLY_CAL_ATTR(xpad_axis_xy_left_cal, xpad_axis_xy_left, calibration);
+ALLY_CAL_ATTR(xpad_axis_xy_right_cal, xpad_axis_xy_right, calibration);
+ALLY_CAL_ATTR(xpad_axis_z_left_cal, xpad_axis_z_left, calibration);
+ALLY_CAL_ATTR(xpad_axis_z_right_cal, xpad_axis_z_right, calibration);
+
+static ssize_t xpad_axis_xy_cal_index_show(struct device *dev, struct device_attribute *attr,
+					   char *buf)
+{
+	return sysfs_emit(buf, "x_stable x_min x_max y_stable y_min y_max\n");
+}
+
+ALLY_DEVICE_ATTR_RO(xpad_axis_xy_cal_index, calibration_index);
+
+static ssize_t xpad_axis_z_cal_index_show(struct device *dev, struct device_attribute *attr,
+					  char *buf)
+{
+	return sysfs_emit(buf, "z_stable z_max\n");
+}
+
+ALLY_DEVICE_ATTR_RO(xpad_axis_z_cal_index, calibration_index);
+
+static ssize_t __gamepad_cal_reset(struct device *dev, const char *buf, enum xpad_axis axis)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	/* Write the reset value, then apply it */
+	for (u8 cmd = 0x02; cmd <= 0x03; cmd++) {
+		memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE);
+		hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+		hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+		hidbuf[2] = xpad_cmd_set_calibration;
+		hidbuf[3] = (cmd == 0x02) ? xpad_cmd_len_calibration2 :
+					    xpad_cmd_len_calibration3;
+		hidbuf[4] = cmd;
+		hidbuf[5] = axis;
+
+		ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+		if (ret < 0)
+			break;
+	}
+
+	__gamepad_get_calibration(hdev);
+
+	kfree(hidbuf);
+	return ret;
+}
+
+ALLY_CAL_RESET_ATTR(xpad_axis_xy_left_cal_reset, xpad_axis_xy_left, calibration_reset);
+ALLY_CAL_RESET_ATTR(xpad_axis_xy_right_cal_reset, xpad_axis_xy_right, calibration_reset);
+ALLY_CAL_RESET_ATTR(xpad_axis_z_left_cal_reset, xpad_axis_z_left, calibration_reset);
+ALLY_CAL_RESET_ATTR(xpad_axis_z_right_cal_reset, xpad_axis_z_right, calibration_reset);
+
+static struct attribute *gamepad_axis_xy_left_attrs[] = {
+	&dev_attr_xpad_axis_xy_left_deadzone.attr,
+	&dev_attr_axis_xyz_deadzone_index.attr,
+	&dev_attr_xpad_axis_xy_left_ADZ.attr,
+	&dev_attr_xpad_axis_xy_left_cal_reset.attr,
+	&dev_attr_xpad_axis_xy_left_cal.attr,
+	&dev_attr_xpad_axis_xy_cal_index.attr,
+	&dev_attr_rc_point_left_1.attr,
+	&dev_attr_rc_point_left_2.attr,
+	&dev_attr_rc_point_left_3.attr,
+	&dev_attr_rc_point_left_4.attr,
+	&dev_attr_rc_point_index.attr,
+	NULL
+};
+static const struct attribute_group ally_controller_axis_xy_left_attr_group = {
+	.name = "axis_xy_left",
+	.attrs = gamepad_axis_xy_left_attrs,
+};
+
+static struct attribute *gamepad_axis_xy_right_attrs[] = {
+	&dev_attr_xpad_axis_xy_right_deadzone.attr,
+	&dev_attr_axis_xyz_deadzone_index.attr,
+	&dev_attr_xpad_axis_xy_right_ADZ.attr,
+	&dev_attr_xpad_axis_xy_right_cal_reset.attr,
+	&dev_attr_xpad_axis_xy_right_cal.attr,
+	&dev_attr_xpad_axis_xy_cal_index.attr,
+	&dev_attr_rc_point_right_1.attr,
+	&dev_attr_rc_point_right_2.attr,
+	&dev_attr_rc_point_right_3.attr,
+	&dev_attr_rc_point_right_4.attr,
+	&dev_attr_rc_point_index.attr,
+	NULL
+};
+static const struct attribute_group ally_controller_axis_xy_right_attr_group = {
+	.name = "axis_xy_right",
+	.attrs = gamepad_axis_xy_right_attrs,
+};
+
+static struct attribute *gamepad_axis_z_left_attrs[] = {
+	&dev_attr_xpad_axis_z_left_deadzone.attr,  &dev_attr_axis_xyz_deadzone_index.attr,
+	&dev_attr_xpad_axis_z_left_cal.attr,	   &dev_attr_xpad_axis_z_cal_index.attr,
+	&dev_attr_xpad_axis_z_left_cal_reset.attr, NULL
+};
+static const struct attribute_group ally_controller_axis_z_left_attr_group = {
+	.name = "axis_z_left",
+	.attrs = gamepad_axis_z_left_attrs,
+};
+
+static struct attribute *gamepad_axis_z_right_attrs[] = {
+	&dev_attr_xpad_axis_z_right_deadzone.attr,  &dev_attr_axis_xyz_deadzone_index.attr,
+	&dev_attr_xpad_axis_z_right_cal.attr,	    &dev_attr_xpad_axis_z_cal_index.attr,
+	&dev_attr_xpad_axis_z_right_cal_reset.attr, NULL
+};
+static const struct attribute_group ally_controller_axis_z_right_attr_group = {
+	.name = "axis_z_right",
+	.attrs = gamepad_axis_z_right_attrs,
+};
+
+static const struct attribute_group *gamepad_device_attr_groups[] = {
+	&ally_controller_attr_group,
+	&ally_controller_axis_xy_left_attr_group,
+	&ally_controller_axis_xy_right_attr_group,
+	&ally_controller_axis_z_left_attr_group,
+	&ally_controller_axis_z_right_attr_group,
+	&btn_mapping_m1_attr_group,
+	&btn_mapping_m2_attr_group,
+	&btn_mapping_a_attr_group,
+	&btn_mapping_b_attr_group,
+	&btn_mapping_x_attr_group,
+	&btn_mapping_y_attr_group,
+	&btn_mapping_lb_attr_group,
+	&btn_mapping_rb_attr_group,
+	&btn_mapping_ls_attr_group,
+	&btn_mapping_rs_attr_group,
+	&btn_mapping_dpad_u_attr_group,
+	&btn_mapping_dpad_d_attr_group,
+	&btn_mapping_dpad_l_attr_group,
+	&btn_mapping_dpad_r_attr_group,
+	&btn_mapping_view_attr_group,
+	&btn_mapping_menu_attr_group,
+	NULL
+};
+
+static int __gamepad_write_all_to_mcu(struct hid_device *hdev,
+				      struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_dpad_u_d);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_dpad_l_r);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_ls_rs);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_lb_rb);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_a_b);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_x_y);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_view_menu);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_set_mapping(hdev, ally_cfg, btn_pair_m1_m2);
+	if (ret < 0)
+		return ret;
+	__gamepad_set_mapping(hdev, ally_cfg, btn_pair_lt_rt);
+	if (ret < 0)
+		return ret;
+	__gamepad_set_deadzones(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	__gamepad_write_js_ADZ_to_mcu(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	__gamepad_write_vibe_intensity_to_mcu(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	__gamepad_write_response_curves_to_mcu(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	ret = __gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	/* set turbo */
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+	hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID;
+	hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE;
+	hidbuf[2] = xpad_cmd_set_turbo;
+	hidbuf[3] = xpad_cmd_len_turbo;
+	memcpy(&hidbuf[4], ally_cfg->turbo_btns[ally_cfg->mode - 1], TURBO_BLOCK_LEN);
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+
+	kfree(hidbuf);
+	return ret;
+}
+
+static struct ally_gamepad_cfg *ally_gamepad_cfg_create(struct hid_device *hdev)
+{
+	struct ally_gamepad_cfg *ally_cfg;
+	struct input_dev *input_dev;
+	int i, err;
+
+	ally_cfg = devm_kzalloc(&hdev->dev, sizeof(*ally_cfg), GFP_KERNEL);
+	if (!ally_cfg)
+		return ERR_PTR(-ENOMEM);
+	ally_cfg->hdev = hdev;
+
+	input_dev = devm_input_allocate_device(&hdev->dev);
+	if (!input_dev) {
+		err = -ENOMEM;
+		goto free_ally_cfg;
+	}
+	ally_cfg->input = input_dev;
+
+	input_dev->id.bustype = hdev->bus;
+	input_dev->id.vendor = hdev->vendor;
+	input_dev->id.product = hdev->product;
+	input_dev->id.version = hdev->version;
+	input_dev->uniq = hdev->uniq;
+	input_dev->name = "ASUS ROG Ally Config";
+	input_set_capability(input_dev, EV_KEY, KEY_PROG1);
+	input_set_capability(input_dev, EV_KEY, KEY_F16);
+	input_set_capability(input_dev, EV_KEY, KEY_F17);
+	input_set_capability(input_dev, EV_KEY, KEY_F18);
+
+	input_set_drvdata(input_dev, hdev);
+
+	err = input_register_device(input_dev);
+	if (err)
+		goto free_input_dev;
+
+	ally_cfg->mode = xpad_mode_game;
+	for (i = 0; i < xpad_mode_mouse; i++) {
+		ally_cfg->deadzones[i][0][1] = 64;
+		ally_cfg->deadzones[i][0][3] = 64;
+		ally_cfg->deadzones[i][1][1] = 64;
+		ally_cfg->deadzones[i][1][3] = 64;
+		ally_cfg->response_curve[i][0][0] = 0x14;
+		ally_cfg->response_curve[i][0][1] = 0x14;
+		ally_cfg->response_curve[i][0][2] = 0x28;
+		ally_cfg->response_curve[i][0][3] = 0x28;
+		ally_cfg->response_curve[i][0][4] = 0x3c;
+		ally_cfg->response_curve[i][0][5] = 0x3c;
+		ally_cfg->response_curve[i][0][6] = 0x63;
+		ally_cfg->response_curve[i][0][7] = 0x63;
+		ally_cfg->response_curve[i][1][0] = 0x14;
+		ally_cfg->response_curve[i][1][1] = 0x14;
+		ally_cfg->response_curve[i][1][2] = 0x28;
+		ally_cfg->response_curve[i][1][3] = 0x28;
+		ally_cfg->response_curve[i][1][4] = 0x3c;
+		ally_cfg->response_curve[i][1][5] = 0x3c;
+		ally_cfg->response_curve[i][1][6] = 0x63;
+		ally_cfg->response_curve[i][1][7] = 0x63;
+		ally_cfg->vibration_intensity[i][0] = 64;
+		ally_cfg->vibration_intensity[i][1] = 64;
+	}
+	drvdata.gamepad_cfg = ally_cfg;
+
+	/* ignore all errors for this as they are related to USB HID I/O */
+	__gamepad_mapping_xpad_default(ally_cfg);
+	__gamepad_mapping_wasd_default(ally_cfg);
+	/* these calls will never error so ignore the return */
+	__gamepad_mapping_store(ally_cfg, KB_M2, btn_pair_m1_m2, btn_pair_side_left, false);
+	__gamepad_mapping_store(ally_cfg, KB_M1, btn_pair_m1_m2, btn_pair_side_right, false);
+	__gamepad_set_mode(hdev, ally_cfg, xpad_mode_game);
+	__gamepad_set_mapping(hdev, ally_cfg, btn_pair_m1_m2);
+	/* ensure we have data for users to start from */
+	__gamepad_get_calibration(hdev);
+
+	if (sysfs_create_groups(&hdev->dev.kobj, gamepad_device_attr_groups)) {
+		err = -ENODEV;
+		goto unregister_input_dev;
+	}
+
+	return ally_cfg;
+
+unregister_input_dev:
+	input_unregister_device(input_dev);
+	ally_cfg->input = NULL; // Prevent double free when kfree(ally_cfg) happens
+
+free_input_dev:
+	devm_kfree(&hdev->dev, input_dev);
+
+free_ally_cfg:
+	devm_kfree(&hdev->dev, ally_cfg);
+	return ERR_PTR(err);
+}
+
+static void ally_cfg_remove(struct hid_device *hdev)
+{
+	__gamepad_set_mode(hdev, drvdata.gamepad_cfg, xpad_mode_mouse);
+	sysfs_remove_groups(&hdev->dev.kobj, gamepad_device_attr_groups);
+}
+
+/**************************************************************************************************/
+/* ROG Ally LED control                                                                           */
+/**************************************************************************************************/
+static void ally_schedule_work(struct ally_rgb_leds *led)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	if (!led->removed)
+		schedule_work(&led->work);
+	spin_unlock_irqrestore(&led->lock, flags);
+	pr_warn("5");
+}
+
+static void ally_led_do_brightness(struct work_struct *work)
+{
+	struct ally_rgb_leds *led = container_of(work, struct ally_rgb_leds, work);
+	u8 buf[] = { FEATURE_ROG_ALLY_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	if (!led->update_bright) {
+		spin_unlock_irqrestore(&led->lock, flags);
+		return;
+	}
+	led->update_bright = false;
+	buf[4] = led->brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	if (asus_dev_set_report(led->hdev, buf, sizeof(buf)) < 0)
+		hid_err(led->hdev, "Ally failed to set backlight\n");
+}
+
+static void ally_led_do_rgb(struct work_struct *work)
+{
+	struct ally_rgb_leds *led = container_of(work, struct ally_rgb_leds, work);
+	unsigned long flags;
+	int ret;
+
+	u8 buf[16] = { [0] = FEATURE_ROG_ALLY_REPORT_ID,
+		       [1] = FEATURE_ROG_ALLY_CODE_PAGE,
+		       [2] = xpad_cmd_set_leds,
+		       [3] = xpad_cmd_len_leds };
+
+	spin_lock_irqsave(&led->lock, flags);
+	if (!led->update_rgb) {
+		spin_unlock_irqrestore(&led->lock, flags);
+		return;
+	}
+	for (int i = 0; i < 4; i++) {
+		buf[4 + i * 3] = led->gamepad_red;
+		buf[5 + i * 3] = led->gamepad_green;
+		buf[6 + i * 3] = led->gamepad_blue;
+	}
+	led->update_rgb = false;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	ret = asus_dev_set_report(led->hdev, buf, sizeof(buf));
+	if (ret < 0)
+		hid_err(led->hdev, "Ally failed to set gamepad backlight: %d\n", ret);
+}
+
+static void ally_led_work(struct work_struct *work)
+{
+	ally_led_do_brightness(work);
+	ally_led_do_rgb(work);
+}
+
+static void ally_backlight_set(struct led_classdev *led_cdev, enum led_brightness brightness)
+{
+	struct ally_rgb_leds *led =
+		container_of(led_cdev, struct ally_rgb_leds, led_bright_dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	led->update_bright = true;
+	led->brightness = brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	ally_schedule_work(led);
+}
+
+static enum led_brightness ally_backlight_get(struct led_classdev *led_cdev)
+{
+	struct ally_rgb_leds *led =
+		container_of(led_cdev, struct ally_rgb_leds, led_bright_dev);
+	enum led_brightness brightness;
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	brightness = led->brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	return brightness;
+}
+
+static void ally_set_rgb_brightness(struct led_classdev *cdev, enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+	struct ally_rgb_leds *led = container_of(mc_cdev, struct ally_rgb_leds, led_rgb_dev);
+	unsigned long flags;
+
+	led_mc_calc_color_components(mc_cdev, brightness);
+	spin_lock_irqsave(&led->lock, flags);
+	led->update_rgb = true;
+	led->gamepad_red = mc_cdev->subled_info[0].brightness;
+	led->gamepad_green = mc_cdev->subled_info[1].brightness;
+	led->gamepad_blue = mc_cdev->subled_info[2].brightness;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	ally_schedule_work(led);
+}
+
+static int ally_gamepad_register_brightness(struct hid_device *hdev,
+					    struct ally_rgb_leds *led_rgb)
+{
+	struct led_classdev *led_cdev;
+
+	led_cdev = &led_rgb->led_bright_dev;
+	led_cdev->name = "ally:kbd_backlight"; /* Let a desktop control it also */
+	led_cdev->max_brightness = 3;
+	led_cdev->brightness_set = ally_backlight_set;
+	led_cdev->brightness_get = ally_backlight_get;
+
+	return devm_led_classdev_register(&hdev->dev, &led_rgb->led_bright_dev);
+}
+
+static int ally_gamepad_register_rgb_leds(struct hid_device *hdev,
+					  struct ally_rgb_leds *led_rgb)
+{
+	struct mc_subled *mc_led_info;
+	struct led_classdev *led_cdev;
+
+	mc_led_info = devm_kmalloc_array(&hdev->dev, 3, sizeof(*mc_led_info),
+					 GFP_KERNEL | __GFP_ZERO);
+	if (!mc_led_info)
+		return -ENOMEM;
+
+	mc_led_info[0].color_index = LED_COLOR_ID_RED;
+	mc_led_info[1].color_index = LED_COLOR_ID_GREEN;
+	mc_led_info[2].color_index = LED_COLOR_ID_BLUE;
+
+	led_rgb->led_rgb_dev.subled_info = mc_led_info;
+	led_rgb->led_rgb_dev.num_colors = 3;
+
+	led_cdev = &led_rgb->led_rgb_dev.led_cdev;
+	led_cdev->name = "ally:rgb:gamepad";
+	led_cdev->brightness = 128;
+	led_cdev->max_brightness = 255;
+	led_cdev->brightness_set = ally_set_rgb_brightness;
+
+	return devm_led_classdev_multicolor_register(&hdev->dev, &led_rgb->led_rgb_dev);
+}
+
+static struct ally_rgb_leds *ally_gamepad_rgb_create(struct hid_device *hdev)
+{
+	struct ally_rgb_leds *led_rgb;
+	int ret;
+
+	led_rgb = devm_kzalloc(&hdev->dev, sizeof(struct ally_rgb_leds), GFP_KERNEL);
+	if (!led_rgb)
+		return ERR_PTR(-ENOMEM);
+
+	ret = ally_gamepad_register_rgb_leds(hdev, led_rgb);
+	if (ret < 0) {
+		cancel_work_sync(&led_rgb->work);
+		devm_kfree(&hdev->dev, led_rgb);
+		return ERR_PTR(ret);
+	}
+
+	ret = ally_gamepad_register_brightness(hdev, led_rgb);
+	if (ret < 0) {
+		cancel_work_sync(&led_rgb->work);
+		devm_kfree(&hdev->dev, led_rgb);
+		return ERR_PTR(ret);
+	}
+
+	led_rgb->hdev = hdev;
+	led_rgb->brightness = 3;
+	led_rgb->removed = false;
+
+	INIT_WORK(&led_rgb->work, ally_led_work);
+	spin_lock_init(&led_rgb->lock);
+
+	return led_rgb;
+}
+
+static void ally_rgb_remove(struct hid_device *hdev)
+{
+	struct ally_rgb_leds *led_rgb = drvdata.led_rgb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&led_rgb->lock, flags);
+	led_rgb->removed = true;
+	spin_unlock_irqrestore(&led_rgb->lock, flags);
+	cancel_work_sync(&led_rgb->work);
+}
+
+/**************************************************************************************************/
+/* ROG Ally driver init                                                                           */
+/**************************************************************************************************/
+static int ally_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data,
+			  int size)
+{
+	struct ally_gamepad_cfg *cfg = drvdata.gamepad_cfg;
+	struct ally_x_device *ally_x = drvdata.ally_x;
+
+	if (ally_x) {
+		if ((hdev->bus == BUS_USB && report->id == ALLY_X_INPUT_REPORT_USB &&
+		     size == ALLY_X_INPUT_REPORT_USB_SIZE) ||
+		    (cfg && data[0] == 0x5A)) {
+			ally_x_raw_event(ally_x, report, data, size);
+		} else {
+			return -1;
+		}
+	}
+	if (cfg && !ally_x) {
+		input_report_key(cfg->input, KEY_PROG1, data[1] == 0x38);
+		input_report_key(cfg->input, KEY_F16, data[1] == 0xA6);
+		input_report_key(cfg->input, KEY_F17, data[1] == 0xA7);
+		input_report_key(cfg->input, KEY_F18, data[1] == 0xA8);
+		input_sync(cfg->input);
+	}
+
+	return 0;
+}
+
+static int ally_gamepad_init(struct hid_device *hdev, u8 report_id)
+{
+	const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54, 0x65,
+			   0x63,      0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
+	int ret;
+
+	ret = asus_dev_set_report(hdev, buf, sizeof(buf));
+	if (ret < 0)
+		hid_err(hdev, "Ally failed to send init command: %d\n", ret);
+
+	return ret;
+}
+
+static int ally_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct usb_host_endpoint *ep = intf->cur_altsetting->endpoint;
+	int ret;
+
+	if (ep->desc.bEndpointAddress != ALLY_CFG_INTF_IN_ADDRESS &&
+	    ep->desc.bEndpointAddress != ALLY_X_INTERFACE_ADDRESS)
+		return -ENODEV;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "Parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret) {
+		hid_err(hdev, "Failed to start HID device\n");
+		return ret;
+	}
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_err(hdev, "Failed to open HID device\n");
+		goto err_stop;
+	}
+
+	/* Initialize MCU even before alloc */
+	ret = ally_gamepad_init(hdev, FEATURE_REPORT_ID);
+	if (ret < 0)
+		return ret;
+
+	ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
+	if (ret < 0)
+		return ret;
+
+	ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
+	if (ret < 0)
+		return ret;
+
+	drvdata.hdev = hdev;
+	hid_set_drvdata(hdev, &drvdata);
+
+	/* This should almost always exist */
+	if (ep->desc.bEndpointAddress == ALLY_CFG_INTF_IN_ADDRESS) {
+		drvdata.led_rgb = ally_gamepad_rgb_create(hdev);
+		if (IS_ERR(drvdata.led_rgb))
+			hid_err(hdev, "Failed to create Ally gamepad LEDs.\n");
+		else
+			hid_info(hdev, "Created Ally RGB LED controls.\n");
+
+		ally_gamepad_cfg_create(hdev); // assigns self
+		if (IS_ERR(drvdata.gamepad_cfg))
+			hid_err(hdev, "Failed to create Ally gamepad attributes.\n");
+		else
+			hid_info(hdev, "Created Ally gamepad attributes.\n");
+
+		if (IS_ERR(drvdata.led_rgb) && IS_ERR(drvdata.gamepad_cfg))
+			goto err_close;
+	}
+
+	/* May or may not exist */
+	if (ep->desc.bEndpointAddress == ALLY_X_INTERFACE_ADDRESS) {
+		drvdata.ally_x = ally_x_create(hdev);
+		if (IS_ERR(drvdata.ally_x)) {
+			hid_err(hdev, "Failed to create Ally X gamepad.\n");
+			drvdata.ally_x = NULL;
+			goto err_close;
+		}
+		hid_info(hdev, "Created Ally X controller.\n");
+
+		// Not required since we send this inputs ep through the gamepad input dev
+		if (drvdata.gamepad_cfg && drvdata.gamepad_cfg->input) {
+			input_unregister_device(drvdata.gamepad_cfg->input);
+			hid_info(hdev, "Ally X removed unrequired input dev.\n");
+		}
+	}
+
+	return 0;
+
+err_close:
+	hid_hw_close(hdev);
+err_stop:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void ally_remove(struct hid_device *hdev)
+{
+	if (drvdata.ally_x)
+		ally_x_remove(hdev);
+	if (drvdata.led_rgb)
+		ally_rgb_remove(hdev);
+	if (drvdata.gamepad_cfg)
+		ally_cfg_remove(hdev);
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static int ally_resume(struct hid_device *hdev)
+{
+	int ret;
+
+	ret = ally_gamepad_init(hdev, FEATURE_REPORT_ID);
+	if (ret < 0)
+		return ret;
+
+	ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
+	if (ret < 0)
+		return ret;
+
+	ret = ally_gamepad_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
+	if (ret < 0)
+		return ret;
+
+	if (drvdata.ally_x && drvdata.ally_x->output_worker_initialized)
+		schedule_work(&drvdata.ally_x->output_worker);
+
+	return ret;
+}
+
+MODULE_DEVICE_TABLE(hid, rog_ally_devices);
+
+static struct hid_driver rog_ally_cfg = {
+	.name = "asus_rog_ally",
+	.id_table = rog_ally_devices,
+	.probe = ally_probe,
+	.remove = ally_remove,
+	.raw_event = ally_raw_event,
+	.resume = ally_resume,
+};
+
+static int __init rog_ally_cfg_init(void)
+{
+	return hid_register_driver(&rog_ally_cfg);
+}
+
+static void __exit rog_ally_cfg_exit(void)
+{
+	hid_unregister_driver(&rog_ally_cfg);
+}
+
+module_init(rog_ally_cfg_init);
+module_exit(rog_ally_cfg_exit);
+
+MODULE_AUTHOR("Luke D. Jones");
+MODULE_DESCRIPTION("HID Driver for ASUS ROG Ally gamepad configuration.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-asus-ally.h b/drivers/hid/hid-asus-ally.h
new file mode 100644
index 000000000000..252d9f126e32
--- /dev/null
+++ b/drivers/hid/hid-asus-ally.h
@@ -0,0 +1,544 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ *  HID driver for Asus ROG laptops and Ally
+ *
+ *  Copyright (c) 2023 Luke Jones <luke@ljones.dev>
+ */
+
+#include <linux/hid.h>
+#include <linux/types.h>
+
+#define ALLY_X_INTERFACE_ADDRESS 0x87
+
+#define BTN_CODE_LEN 11
+#define MAPPING_BLOCK_LEN 44
+
+#define TURBO_BLOCK_LEN 32
+#define TURBO_BLOCK_STEP 2
+
+#define PAD_A "pad_a"
+#define PAD_B "pad_b"
+#define PAD_X "pad_x"
+#define PAD_Y "pad_y"
+#define PAD_LB "pad_lb"
+#define PAD_RB "pad_rb"
+#define PAD_LS "pad_ls"
+#define PAD_RS "pad_rs"
+#define PAD_DPAD_UP "pad_dpad_up"
+#define PAD_DPAD_DOWN "pad_dpad_down"
+#define PAD_DPAD_LEFT "pad_dpad_left"
+#define PAD_DPAD_RIGHT "pad_dpad_right"
+#define PAD_VIEW "pad_view"
+#define PAD_MENU "pad_menu"
+#define PAD_XBOX "pad_xbox"
+
+#define KB_M1 "kb_m1"
+#define KB_M2 "kb_m2"
+#define KB_ESC "kb_esc"
+#define KB_F1 "kb_f1"
+#define KB_F2 "kb_f2"
+#define KB_F3 "kb_f3"
+#define KB_F4 "kb_f4"
+#define KB_F5 "kb_f5"
+#define KB_F6 "kb_f6"
+#define KB_F7 "kb_f7"
+#define KB_F8 "kb_f8"
+#define KB_F9 "kb_f9"
+#define KB_F10 "kb_f10"
+#define KB_F11 "kb_f11"
+#define KB_F12 "kb_f12"
+#define KB_F14 "kb_f14"
+#define KB_F15 "kb_f15"
+
+#define KB_BACKTICK "kb_backtick"
+#define KB_1 "kb_1"
+#define KB_2 "kb_2"
+#define KB_3 "kb_3"
+#define KB_4 "kb_4"
+#define KB_5 "kb_5"
+#define KB_6 "kb_6"
+#define KB_7 "kb_7"
+#define KB_8 "kb_8"
+#define KB_9 "kb_9"
+#define KB_0 "kb_0"
+#define KB_HYPHEN "kb_hyphen"
+#define KB_EQUALS "kb_equals"
+#define KB_BACKSPACE "kb_backspace"
+
+#define KB_TAB "kb_tab"
+#define KB_Q "kb_q"
+#define KB_W "kb_w"
+#define KB_E "kb_e"
+#define KB_R "kb_r"
+#define KB_T "kb_t"
+#define KB_Y "kb_y"
+#define KB_U "kb_u"
+#define KB_I "kb_i"
+#define KB_O "kb_o"
+#define KB_P "kb_p"
+#define KB_LBRACKET "kb_lbracket"
+#define KB_RBRACKET "kb_rbracket"
+#define KB_BACKSLASH "kb_bkslash"
+
+#define KB_CAPS "kb_caps"
+#define KB_A "kb_a"
+#define KB_S "kb_s"
+#define KB_D "kb_d"
+#define KB_F "kb_f"
+#define KB_G "kb_g"
+#define KB_H "kb_h"
+#define KB_J "kb_j"
+#define KB_K "kb_k"
+#define KB_L "kb_l"
+#define KB_SEMI "kb_semicolon"
+#define KB_QUOTE "kb_quote"
+#define KB_RET "kb_enter"
+
+#define KB_LSHIFT "kb_lshift"
+#define KB_Z "kb_z"
+#define KB_X "kb_x"
+#define KB_C "kb_c"
+#define KB_V "kb_v"
+#define KB_B "kb_b"
+#define KB_N "kb_n"
+#define KB_M "kb_m"
+#define KB_COMMA "kb_comma"
+#define KB_PERIOD "kb_period"
+#define KB_FWDSLASH "kb_fwdslash"
+#define KB_RSHIFT "kb_rshift"
+
+#define KB_LCTL "kb_lctl"
+#define KB_META "kb_meta"
+#define KB_LALT "kb_lalt"
+#define KB_SPACE "kb_space"
+#define KB_RALT "kb_ralt"
+#define KB_MENU "kb_menu"
+#define KB_RCTL "kb_rctl"
+
+#define KB_PRNTSCN "kb_prntscn"
+#define KB_SCRLCK "kb_scrlck"
+#define KB_PAUSE "kb_pause"
+#define KB_INS "kb_ins"
+#define KB_HOME "kb_home"
+#define KB_PGUP "kb_pgup"
+#define KB_DEL "kb_del"
+#define KB_END "kb_end"
+#define KB_PGDWN "kb_pgdwn"
+
+#define KB_UP_ARROW "kb_up_arrow"
+#define KB_DOWN_ARROW "kb_down_arrow"
+#define KB_LEFT_ARROW "kb_left_arrow"
+#define KB_RIGHT_ARROW "kb_right_arrow"
+
+#define NUMPAD_LOCK "numpad_lock"
+#define NUMPAD_FWDSLASH "numpad_fwdslash"
+#define NUMPAD_ASTERISK "numpad_asterisk"
+#define NUMPAD_HYPHEN "numpad_hyphen"
+#define NUMPAD_0 "numpad_0"
+#define NUMPAD_1 "numpad_1"
+#define NUMPAD_2 "numpad_2"
+#define NUMPAD_3 "numpad_3"
+#define NUMPAD_4 "numpad_4"
+#define NUMPAD_5 "numpad_5"
+#define NUMPAD_6 "numpad_6"
+#define NUMPAD_7 "numpad_7"
+#define NUMPAD_8 "numpad_8"
+#define NUMPAD_9 "numpad_9"
+#define NUMPAD_PLUS "numpad_plus"
+#define NUMPAD_ENTER "numpad_enter"
+#define NUMPAD_PERIOD "numpad_."
+
+#define MOUSE_LCLICK "rat_lclick"
+#define MOUSE_RCLICK "rat_rclick"
+#define MOUSE_MCLICK "rat_mclick"
+#define MOUSE_WHEEL_UP "rat_wheel_up"
+#define MOUSE_WHEEL_DOWN "rat_wheel_down"
+
+#define MEDIA_SCREENSHOT "media_screenshot"
+#define MEDIA_SHOW_KEYBOARD "media_show_keyboard"
+#define MEDIA_SHOW_DESKTOP "media_show_desktop"
+#define MEDIA_START_RECORDING "media_start_recording"
+#define MEDIA_MIC_OFF "media_mic_off"
+#define MEDIA_VOL_DOWN "media_vol_down"
+#define MEDIA_VOL_UP "media_vol_up"
+
+/* required so we can have nested attributes with same name but different functions */
+#define ALLY_DEVICE_ATTR_RW(_name, _sysfs_name)    \
+	struct device_attribute dev_attr_##_name = \
+		__ATTR(_sysfs_name, 0644, _name##_show, _name##_store)
+
+#define ALLY_DEVICE_ATTR_RO(_name, _sysfs_name)    \
+	struct device_attribute dev_attr_##_name = \
+		__ATTR(_sysfs_name, 0444, _name##_show, NULL)
+
+#define ALLY_DEVICE_ATTR_WO(_name, _sysfs_name)    \
+	struct device_attribute dev_attr_##_name = \
+		__ATTR(_sysfs_name, 0200, NULL, _name##_store)
+
+/* response curve macros */
+#define ALLY_RESP_CURVE_SHOW(_name, _point_n)                                 \
+	static ssize_t _name##_show(struct device *dev,                       \
+				    struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;      \
+		int idx = (_point_n - 1) * 2;                                 \
+		if (!drvdata.gamepad_cfg)                                     \
+			return -ENODEV;                                       \
+		return sysfs_emit(                                            \
+			buf, "%d %d\n",                                       \
+			ally_cfg->response_curve[ally_cfg->mode]              \
+						[btn_pair_side_left][idx],    \
+			ally_cfg->response_curve[ally_cfg->mode]              \
+						[btn_pair_side_right]         \
+						[idx + 1]);                   \
+	}
+
+#define ALLY_RESP_CURVE_STORE(_name, _side, _point_n)               \
+	static ssize_t _name##_store(struct device *dev,            \
+				     struct device_attribute *attr, \
+				     const char *buf, size_t count) \
+	{                                                           \
+		int ret = __gamepad_store_response_curve(           \
+			dev, buf, btn_pair_side_##_side, _point_n); \
+		if (ret < 0)                                        \
+			return ret;                                 \
+		return count;                                       \
+	}
+
+/* _point_n must start at 1 */
+#define ALLY_JS_RC_POINT(_side, _point_n, _sysfs_label)                        \
+	ALLY_RESP_CURVE_SHOW(rc_point_##_side##_##_point_n, _point_n);         \
+	ALLY_RESP_CURVE_STORE(rc_point_##_side##_##_point_n, _side, _point_n); \
+	ALLY_DEVICE_ATTR_RW(rc_point_##_side##_##_point_n,                     \
+			    _sysfs_label##_point_n)
+
+/* deadzone macros */
+#define ALLY_AXIS_DEADZONE_SHOW(_axis)                                         \
+	static ssize_t _axis##_deadzone_show(                                  \
+		struct device *dev, struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;       \
+		int side, is_tr;                                               \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		is_tr = _axis > xpad_axis_xy_right;                            \
+		side = _axis == xpad_axis_xy_right ||                          \
+				       _axis == xpad_axis_z_right ?            \
+			       2 :                                             \
+			       0;                                              \
+		return sysfs_emit(                                             \
+			buf, "%d %d\n",                                        \
+			ally_cfg->deadzones[ally_cfg->mode][is_tr][side],      \
+			ally_cfg->deadzones[ally_cfg->mode][is_tr][side + 1]); \
+	}
+
+#define ALLY_AXIS_DEADZONE_STORE(_axis)                                      \
+	static ssize_t _axis##_deadzone_store(struct device *dev,            \
+					      struct device_attribute *attr, \
+					      const char *buf, size_t count) \
+	{                                                                    \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;     \
+		if (!drvdata.gamepad_cfg)                                    \
+			return -ENODEV;                                      \
+		int ret = __gamepad_store_deadzones(ally_cfg, _axis, buf);   \
+		if (ret < 0)                                                 \
+			return ret;                                          \
+		return count;                                                \
+	}
+
+#define ALLY_AXIS_DEADZONE(_axis, _sysfs_label) \
+	ALLY_AXIS_DEADZONE_SHOW(_axis);         \
+	ALLY_AXIS_DEADZONE_STORE(_axis);        \
+	ALLY_DEVICE_ATTR_RW(_axis##_deadzone, _sysfs_label)
+
+/* button specific macros */
+#define ALLY_BTN_SHOW(_fname, _pair, _side, _secondary)                        \
+	static ssize_t _fname##_show(struct device *dev,                       \
+				     struct device_attribute *attr, char *buf) \
+	{                                                                      \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;       \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		return sysfs_emit(buf, "%s\n",                                 \
+				  __btn_map_to_string(ally_cfg, _pair, _side,  \
+						      _secondary));            \
+	}
+
+#define ALLY_BTN_STORE(_fname, _pair, _side, _secondary)                       \
+	static ssize_t _fname##_store(struct device *dev,                      \
+				      struct device_attribute *attr,           \
+				      const char *buf, size_t count)           \
+	{                                                                      \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;       \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		int ret = __gamepad_mapping_store(ally_cfg, buf, _pair, _side, \
+						  _secondary);                 \
+		if (ret < 0)                                                   \
+			return ret;                                            \
+		return count;                                                  \
+	}
+
+#define ALLY_BTN_TURBO_SHOW(_fname, _pair, _side)                             \
+	static ssize_t _fname##_turbo_show(                                   \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return sysfs_emit(buf, "%d\n",                                \
+				  __gamepad_turbo_show(dev, _pair, _side));   \
+	}
+
+#define ALLY_BTN_TURBO_STORE(_fname, _pair, _side)                         \
+	static ssize_t _fname##_turbo_store(struct device *dev,            \
+					    struct device_attribute *attr, \
+					    const char *buf, size_t count) \
+	{                                                                  \
+		int ret = __gamepad_turbo_store(dev, buf, _pair, _side);   \
+		if (ret < 0)                                               \
+			return ret;                                        \
+		return count;                                              \
+	}
+
+#define ALLY_BTN_ATTRS_GROUP(_name, _fname)                               \
+	static struct attribute *_fname##_attrs[] = {                     \
+		&dev_attr_##_fname.attr, &dev_attr_##_fname##_macro.attr, \
+		&dev_attr_##_fname##_turbo.attr, NULL                     \
+	};                                                                \
+	static const struct attribute_group _fname##_attr_group = {       \
+		.name = __stringify(_name),                               \
+		.attrs = _fname##_attrs,                                  \
+	}
+
+#define ALLY_BTN_MAPPING(_fname, _pair, _side)                            \
+	ALLY_BTN_SHOW(btn_mapping_##_fname, _pair, _side, false);         \
+	ALLY_BTN_STORE(btn_mapping_##_fname, _pair, _side, false);        \
+	ALLY_BTN_SHOW(btn_mapping_##_fname##_macro, _pair, _side, true);  \
+	ALLY_BTN_STORE(btn_mapping_##_fname##_macro, _pair, _side, true); \
+	ALLY_BTN_TURBO_SHOW(btn_mapping_##_fname, _pair, _side);          \
+	ALLY_BTN_TURBO_STORE(btn_mapping_##_fname, _pair, _side);         \
+	ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname, remap);                 \
+	ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_macro, macro_remap);   \
+	ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_turbo, turbo);         \
+	ALLY_BTN_ATTRS_GROUP(btn_##_fname, btn_mapping_##_fname)
+
+/* calibration macros */
+#define ALLY_CAL_STORE(_fname, _axis)                                \
+	static ssize_t _fname##_store(struct device *dev,            \
+				      struct device_attribute *attr, \
+				      const char *buf, size_t count) \
+	{                                                            \
+		int ret = __gamepad_cal_store(dev, buf, _axis);      \
+		if (ret < 0)                                         \
+			return ret;                                  \
+		return count;                                        \
+	}
+
+#define ALLY_CAL_SHOW(_fname, _axis)                                           \
+	static ssize_t _fname##_show(struct device *dev,                       \
+				     struct device_attribute *attr, char *buf) \
+	{                                                                      \
+		return __gamepad_cal_show(dev, buf, _axis);                    \
+	}
+
+#define ALLY_CAL_ATTR(_fname, _axis, _sysfs_label) \
+	ALLY_CAL_STORE(_fname, _axis);             \
+	ALLY_CAL_SHOW(_fname, _axis);              \
+	ALLY_DEVICE_ATTR_RW(_fname, _sysfs_label)
+
+#define ALLY_CAL_RESET_STORE(_fname, _axis)                          \
+	static ssize_t _fname##_store(struct device *dev,            \
+				      struct device_attribute *attr, \
+				      const char *buf, size_t count) \
+	{                                                            \
+		int ret = __gamepad_cal_reset(dev, buf, _axis);      \
+		if (ret < 0)                                         \
+			return ret;                                  \
+		return count;                                        \
+	}
+
+#define ALLY_CAL_RESET_ATTR(_fname, _axis, _sysfs_label) \
+	ALLY_CAL_RESET_STORE(_fname, _axis);             \
+	ALLY_DEVICE_ATTR_WO(_fname, _sysfs_label)
+
+/*
+ * The following blocks of packets exist to make setting a default boot config
+ * easier. They were directly captured from setting the gamepad up.
+ */
+
+/* Default blocks for the xpad mode */
+static const u8 XPAD_DEF1[MAPPING_BLOCK_LEN] = {
+	0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8c, 0x88, 0x76, 0x00, 0x00
+};
+static const u8 XPAD_DEF2[MAPPING_BLOCK_LEN] = {
+	0x01, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00,
+	0x01, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0d, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF3[MAPPING_BLOCK_LEN] = {
+	0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF4[MAPPING_BLOCK_LEN] = {
+	0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF5[MAPPING_BLOCK_LEN] = {
+	0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF6[MAPPING_BLOCK_LEN] = {
+	0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4d, 0x00, 0x00, 0x00,
+	0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF7[MAPPING_BLOCK_LEN] = {
+	0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF8[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 XPAD_DEF9[MAPPING_BLOCK_LEN] = {
+	0x01, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* default blocks for the wasd mode */
+static const u8 WASD_DEF1[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8c, 0x88, 0x76, 0x00, 0x00
+};
+static const u8 WASD_DEF2[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x9a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0d, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF3[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF4[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF5[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF6[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4d, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF7[MAPPING_BLOCK_LEN] = {
+	0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF8[MAPPING_BLOCK_LEN] = {
+	0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 WASD_DEF9[MAPPING_BLOCK_LEN] = {
+	0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x88, 0x0d, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/*
+ * the xpad_mode is used inside the mode setting packet and is used
+ * for indexing (xpad_mode - 1)
+ */
+enum xpad_mode {
+	xpad_mode_game = 0x01,
+	xpad_mode_wasd = 0x02,
+	xpad_mode_mouse = 0x03,
+};
+
+/* the xpad_cmd determines which feature is set or queried */
+enum xpad_cmd {
+	xpad_cmd_set_mode = 0x01,
+	xpad_cmd_set_mapping = 0x02,
+	xpad_cmd_set_js_dz = 0x04, /* deadzones */
+	xpad_cmd_set_tr_dz = 0x05, /* deadzones */
+	xpad_cmd_set_vibe_intensity = 0x06,
+	xpad_cmd_set_leds = 0x08,
+	xpad_cmd_check_ready = 0x0A,
+	xpad_cmd_set_calibration = 0x0D,
+	xpad_cmd_set_turbo = 0x0F,
+	xpad_cmd_set_response_curve = 0x13,
+	xpad_cmd_set_adz = 0x18,
+};
+
+/* the xpad_cmd determines which feature is set or queried */
+enum xpad_cmd_len {
+	xpad_cmd_len_mode = 0x01,
+	xpad_cmd_len_mapping = 0x2c,
+	xpad_cmd_len_deadzone = 0x04,
+	xpad_cmd_len_vibe_intensity = 0x02,
+	xpad_cmd_len_leds = 0x0C,
+	xpad_cmd_len_calibration2 = 0x01,
+	xpad_cmd_len_calibration3 = 0x01,
+	xpad_cmd_len_turbo = 0x20,
+	xpad_cmd_len_response_curve = 0x09,
+	xpad_cmd_len_adz = 0x02,
+};
+
+/*
+ * the xpad_mode is used in various set and query HID packets and is
+ * used for indexing (xpad_axis - 1)
+ */
+enum xpad_axis {
+	xpad_axis_xy_left = 0x01,
+	xpad_axis_xy_right = 0x02,
+	xpad_axis_z_left = 0x03,
+	xpad_axis_z_right = 0x04,
+};
+
+enum btn_pair {
+	btn_pair_dpad_u_d = 0x01,
+	btn_pair_dpad_l_r = 0x02,
+	btn_pair_ls_rs = 0x03,
+	btn_pair_lb_rb = 0x04,
+	btn_pair_a_b = 0x05,
+	btn_pair_x_y = 0x06,
+	btn_pair_view_menu = 0x07,
+	btn_pair_m1_m2 = 0x08,
+	btn_pair_lt_rt = 0x09,
+};
+
+enum btn_pair_side {
+	btn_pair_side_left = 0x00,
+	btn_pair_side_right = 0x01,
+};
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 659cf9c96e26..528c210f9e8c 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -52,6 +52,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define FEATURE_KBD_LED_REPORT_ID1 0x5d
 #define FEATURE_KBD_LED_REPORT_ID2 0x5e
 
+#define ALLY_CFG_INTF_IN_ADDRESS 0x83
+#define ALLY_CFG_INTF_OUT_ADDRESS 0x04
+#define ALLY_X_INTERFACE_ADDRESS 0x87
+
 #define SUPPORT_KBD_BACKLIGHT BIT(0)
 
 #define MAX_TOUCH_MAJOR 8
@@ -84,6 +88,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_MEDION_E1239T		BIT(10)
 #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ROG_ALLY_XPAD		BIT(13)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -1003,6 +1008,17 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
 
 	drvdata->quirks = id->driver_data;
 
+	/* Ignore these endpoints as they will be used by other drivers */
+	if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
+		struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+		struct usb_host_endpoint *ep = intf->cur_altsetting->endpoint;
+
+		if (ep->desc.bEndpointAddress == ALLY_X_INTERFACE_ADDRESS ||
+			ep->desc.bEndpointAddress == ALLY_CFG_INTF_IN_ADDRESS ||
+			ep->desc.bEndpointAddress == ALLY_CFG_INTF_OUT_ADDRESS)
+			return -ENODEV;
+	}
+
 	/*
 	 * T90CHI's keyboard dock returns same ID values as T100CHI's dock.
 	 * Thus, identify T90CHI dock with product name string.
@@ -1254,10 +1270,10 @@ static const struct hid_device_id asus_devices[] = {
 	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD},
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X),
-	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
+	  QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_ALLY_XPAD },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
 	    USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
 	  QUIRK_ROG_CLAYMORE_II_KEYBOARD },
-- 
2.45.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2024-08-12 20:14 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-08-08 15:35 [PATCH] hid-asus-ally: Add full gamepad support Antheas Kapenekakis
2024-08-11  9:22 ` Luke Jones
2024-08-11 16:10   ` Antheas Kapenekakis
2024-08-11 20:20     ` Denis Benato
2024-08-11 23:14     ` Derek J. Clark
2024-08-12  8:31       ` Antheas Kapenekakis
2024-08-12 12:49         ` Denis Benato
2024-08-12 20:12         ` Re: " Derek J. Clark
  -- strict thread matches above, loose matches on Subject: below --
2024-08-06  8:12 Luke D. Jones
2024-08-06 21:34 ` kernel test robot
2024-08-06 22:20   ` Luke Jones
2024-08-07  9:50 ` kernel test robot
2024-08-07 11:43 ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).