From bde14b68c41f3cc5515ab7463e3669da8c841706 Mon Sep 17 00:00:00 2001
From: Eric Naim <dnaim@cachyos.org>
Date: Wed, 8 Jan 2025 12:17:54 +0700
Subject: [PATCH] handheld

Signed-off-by: Eric Naim <dnaim@cachyos.org>
---
 .../ABI/testing/sysfs-platform-asus-wmi       |   17 +
 drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h      |    2 -
 .../gpu/drm/amd/amdgpu/atombios_encoders.c    |   10 +-
 drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c        |    2 +-
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c |   38 +-
 .../amd/display/dc/hwss/dce110/dce110_hwseq.c |    9 +
 .../drm/amd/display/dc/link/link_validation.c |   11 +
 drivers/hid/Kconfig                           |    9 +
 drivers/hid/Makefile                          |    1 +
 drivers/hid/hid-asus-ally.c                   | 2251 +++++++++++++++++
 drivers/hid/hid-asus-ally.h                   |  398 +++
 drivers/hid/hid-asus.c                        |   21 +-
 drivers/hwmon/Kconfig                         |   11 +
 drivers/hwmon/Makefile                        |    1 +
 drivers/hwmon/steamdeck-hwmon.c               |  294 +++
 drivers/leds/Kconfig                          |    7 +
 drivers/leds/Makefile                         |    1 +
 drivers/leds/leds-steamdeck.c                 |   74 +
 drivers/mfd/Kconfig                           |   11 +
 drivers/mfd/Makefile                          |    2 +
 drivers/mfd/steamdeck.c                       |  147 ++
 drivers/net/wireless/ath/ath11k/core.c        |    2 +-
 drivers/platform/x86/Kconfig                  |   21 +
 drivers/platform/x86/Makefile                 |    1 +
 drivers/platform/x86/asus-armoury.c           | 1075 ++++++++
 drivers/platform/x86/asus-armoury.h           |  258 ++
 drivers/platform/x86/asus-wmi.c               |  270 +-
 drivers/video/backlight/backlight.c           |    2 +
 include/linux/platform_data/x86/asus-wmi.h    |   22 +
 include/sound/tas2781.h                       |    1 +
 sound/pci/hda/tas2781_hda_i2c.c               |   63 +-
 sound/soc/amd/acp/acp-mach.h                  |    2 +-
 sound/soc/codecs/max98388.c                   |   24 +-
 33 files changed, 4958 insertions(+), 100 deletions(-)
 create mode 100644 drivers/hid/hid-asus-ally.c
 create mode 100644 drivers/hid/hid-asus-ally.h
 create mode 100644 drivers/hwmon/steamdeck-hwmon.c
 create mode 100644 drivers/leds/leds-steamdeck.c
 create mode 100644 drivers/mfd/steamdeck.c
 create mode 100644 drivers/platform/x86/asus-armoury.c
 create mode 100644 drivers/platform/x86/asus-armoury.h

diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
index 28144371a0f1..765d50b0d9df 100644
--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
+++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
@@ -63,6 +63,7 @@ Date:		Aug 2022
 KernelVersion:	6.1
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Switch the GPU hardware MUX mode. Laptops with this feature can
 		can be toggled to boot with only the dGPU (discrete mode) or in
 		standard Optimus/Hybrid mode. On switch a reboot is required:
@@ -75,6 +76,7 @@ Date:		Aug 2022
 KernelVersion:	5.17
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Disable discrete GPU:
 			* 0 - Enable dGPU,
 			* 1 - Disable dGPU
@@ -84,6 +86,7 @@ Date:		Aug 2022
 KernelVersion:	5.17
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Enable the external GPU paired with ROG X-Flow laptops.
 		Toggling this setting will also trigger ACPI to disable the dGPU:
 
@@ -95,6 +98,7 @@ Date:		Aug 2022
 KernelVersion:	5.17
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Enable an LCD response-time boost to reduce or remove ghosting:
 			* 0 - Disable,
 			* 1 - Enable
@@ -104,6 +108,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Get the current charging mode being used:
 			* 1 - Barrel connected charger,
 			* 2 - USB-C charging
@@ -114,6 +119,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Show if the egpu (XG Mobile) is correctly connected:
 			* 0 - False,
 			* 1 - True
@@ -123,6 +129,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Change the mini-LED mode:
 			* 0 - Single-zone,
 			* 1 - Multi-zone
@@ -133,6 +140,7 @@ Date:		Apr 2024
 KernelVersion:	6.10
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		List the available mini-led modes.
 
 What:		/sys/devices/platform/<platform>/ppt_pl1_spl
@@ -140,6 +148,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD.
 		Shown on Intel+Nvidia or AMD+Nvidia based systems:
 
@@ -150,6 +159,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT,
 		on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems:
 
@@ -160,6 +170,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only:
 			* min=5, max=250
 
@@ -168,6 +179,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the APU SPPT limit. Shown on full AMD systems only:
 			* min=5, max=130
 
@@ -176,6 +188,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the platform SPPT limit. Shown on full AMD systems only:
 			* min=5, max=130
 
@@ -184,6 +197,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the dynamic boost limit of the Nvidia dGPU:
 			* min=5, max=25
 
@@ -192,6 +206,7 @@ Date:		Jun 2023
 KernelVersion:	6.5
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set the target temperature limit of the Nvidia dGPU:
 			* min=75, max=87
 
@@ -200,6 +215,7 @@ Date:		Apr 2024
 KernelVersion:	6.10
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set if the BIOS POST sound is played on boot.
 			* 0 - False,
 			* 1 - True
@@ -209,6 +225,7 @@ Date:		Apr 2024
 KernelVersion:	6.10
 Contact:	"Luke Jones" <luke@ljones.dev>
 Description:
+        DEPRECATED, WILL BE REMOVED SOON
 		Set if the MCU can go in to low-power mode on system sleep
 			* 0 - False,
 			* 1 - True
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
index 5e3faefc5510..09748a2c5aba 100644
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
@@ -435,8 +435,6 @@ struct amdgpu_mode_info {
 	struct drm_property *regamma_tf_property;
 };
 
-#define AMDGPU_MAX_BL_LEVEL 0xFF
-
 struct amdgpu_backlight_privdata {
 	struct amdgpu_encoder *encoder;
 	uint8_t negative;
diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c
index a51f3414b65d..bc0f9759c5c5 100644
--- a/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c
+++ b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c
@@ -39,6 +39,10 @@
 #include <linux/backlight.h>
 #include "bif/bif_4_1_d.h"
 
+
+/* Maximum backlight level. */
+#define AMDGPU_ATOM_MAX_BL_LEVEL 0xFF
+
 u8
 amdgpu_atombios_encoder_get_backlight_level_from_reg(struct amdgpu_device *adev)
 {
@@ -127,8 +131,8 @@ static u8 amdgpu_atombios_encoder_backlight_level(struct backlight_device *bd)
 	/* Convert brightness to hardware level */
 	if (bd->props.brightness < 0)
 		level = 0;
-	else if (bd->props.brightness > AMDGPU_MAX_BL_LEVEL)
-		level = AMDGPU_MAX_BL_LEVEL;
+	else if (bd->props.brightness > AMDGPU_ATOM_MAX_BL_LEVEL)
+		level = AMDGPU_ATOM_MAX_BL_LEVEL;
 	else
 		level = bd->props.brightness;
 
@@ -198,7 +202,7 @@ void amdgpu_atombios_encoder_init_backlight(struct amdgpu_encoder *amdgpu_encode
 	}
 
 	memset(&props, 0, sizeof(props));
-	props.max_brightness = AMDGPU_MAX_BL_LEVEL;
+	props.max_brightness = AMDGPU_ATOM_MAX_BL_LEVEL;
 	props.type = BACKLIGHT_RAW;
 	snprintf(bl_name, sizeof(bl_name),
 		 "amdgpu_bl%d", dev->primary->index);
diff --git a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c
index bc9b240a3488..6e65dfa224cc 100644
--- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c
+++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c
@@ -1288,7 +1288,7 @@ static int sdma_v5_2_sw_init(void *handle)
 	for (i = 0; i < adev->sdma.num_instances; i++) {
 		ring = &adev->sdma.instance[i].ring;
 		ring->ring_obj = NULL;
-		ring->use_doorbell = true;
+		ring->use_doorbell = false;
 		ring->me = i;
 
 		DRM_INFO("use_doorbell being set to: [%s]\n",
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index a80f8c9fc324..418a2644337a 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -162,6 +162,9 @@ MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB);
 /* Number of bytes in PSP footer for firmware. */
 #define PSP_FOOTER_BYTES 0x100
 
+/* Maximum backlight level. */
+#define AMDGPU_MAX_BL_LEVEL 0xFFFF
+
 /**
  * DOC: overview
  *
@@ -4519,7 +4522,7 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev)
 	return 0;
 }
 
-#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 12
+#define AMDGPU_DM_DEFAULT_MIN_BACKLIGHT 0
 #define AMDGPU_DM_DEFAULT_MAX_BACKLIGHT 255
 #define AMDGPU_DM_MIN_SPREAD ((AMDGPU_DM_DEFAULT_MAX_BACKLIGHT - AMDGPU_DM_DEFAULT_MIN_BACKLIGHT) / 2)
 #define AUX_BL_DEFAULT_TRANSITION_TIME_MS 50
@@ -4553,11 +4556,27 @@ static void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
 
 	if (caps.caps_valid) {
 		dm->backlight_caps[bl_idx].caps_valid = true;
+
+		printk(KERN_NOTICE"VLV Successfully queried backlight range over ACPI: %d %d\n",
+		       (int) caps.min_input_signal, (int) caps.max_input_signal);
+
+		if ( caps.min_input_signal != AMDGPU_DM_DEFAULT_MIN_BACKLIGHT ||
+			caps.max_input_signal != AMDGPU_DM_DEFAULT_MAX_BACKLIGHT )
+		{
+			caps.min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
+			caps.max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
+
+			printk(KERN_NOTICE"VLV OVERRIDE backlight range: %d %d\n",
+			       (int) caps.min_input_signal, (int) caps.max_input_signal);
+		}
+
 		if (caps.aux_support)
 			return;
 		dm->backlight_caps[bl_idx].min_input_signal = caps.min_input_signal;
 		dm->backlight_caps[bl_idx].max_input_signal = caps.max_input_signal;
 	} else {
+		printk(KERN_NOTICE"VLV ACPI does not provide backlight range, using defaults: %d %d\n",
+		       AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT);
 		dm->backlight_caps[bl_idx].min_input_signal =
 				AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
 		dm->backlight_caps[bl_idx].max_input_signal =
@@ -4567,6 +4586,9 @@ static void amdgpu_dm_update_backlight_caps(struct amdgpu_display_manager *dm,
 	if (dm->backlight_caps[bl_idx].aux_support)
 		return;
 
+	printk(KERN_NOTICE"VLV Kernel built without ACPI. using backlight range defaults: %d %d\n",
+	       AMDGPU_DM_DEFAULT_MIN_BACKLIGHT, AMDGPU_DM_DEFAULT_MAX_BACKLIGHT);
+
 	dm->backlight_caps[bl_idx].min_input_signal = AMDGPU_DM_DEFAULT_MIN_BACKLIGHT;
 	dm->backlight_caps[bl_idx].max_input_signal = AMDGPU_DM_DEFAULT_MAX_BACKLIGHT;
 #endif
@@ -4598,9 +4620,9 @@ static u32 convert_brightness_from_user(const struct amdgpu_dm_backlight_caps *c
 	if (!get_brightness_range(caps, &min, &max))
 		return brightness;
 
-	// Rescale 0..255 to min..max
-	return min + DIV_ROUND_CLOSEST((max - min) * brightness,
-				       AMDGPU_MAX_BL_LEVEL);
+	// Rescale 0..AMDGPU_MAX_BL_LEVEL to min..max
+	return min + DIV_ROUND_CLOSEST_ULL((u64)(max - min) * brightness,
+					   AMDGPU_MAX_BL_LEVEL);
 }
 
 static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *caps,
@@ -4613,9 +4635,9 @@ static u32 convert_brightness_to_user(const struct amdgpu_dm_backlight_caps *cap
 
 	if (brightness < min)
 		return 0;
-	// Rescale min..max to 0..255
-	return DIV_ROUND_CLOSEST(AMDGPU_MAX_BL_LEVEL * (brightness - min),
-				 max - min);
+	// Rescale min..max to 0..AMDGPU_MAX_BL_LEVEL
+	return DIV_ROUND_CLOSEST_ULL((u64)AMDGPU_MAX_BL_LEVEL * (brightness - min),
+				     max - min);
 }
 
 static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm,
@@ -8981,7 +9003,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state,
 	int planes_count = 0, vpos, hpos;
 	unsigned long flags;
 	u32 target_vblank, last_flip_vblank;
-	bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state);
+	bool vrr_active = true;//amdgpu_dm_crtc_vrr_active(acrtc_state);
 	bool cursor_update = false;
 	bool pflip_present = false;
 	bool dirty_rects_changed = false;
diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c
index 4fbed0298adf..e67435163766 100644
--- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c
+++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c
@@ -95,6 +95,8 @@
 #define FN(reg_name, field_name) \
 	hws->shifts->field_name, hws->masks->field_name
 
+static const uint8_t DP_SINK_BRANCH_DEV_NAME_KT50X0[] = "KT50X0!";
+
 struct dce110_hw_seq_reg_offsets {
 	uint32_t crtc;
 };
@@ -3241,6 +3243,13 @@ void dce110_enable_dp_link_output(
 			link->dc->res_pool->dp_clock_source;
 	const struct link_hwss *link_hwss = get_link_hwss(link, link_res);
 	unsigned int i;
+	if (link->ctx->dce_version == DCN_VERSION_3_01 &&
+	    link->dpcd_caps.sink_dev_id == DP_BRANCH_DEVICE_ID_0060AD &&
+	    memcmp(&link->dpcd_caps.branch_dev_name,
+		   DP_SINK_BRANCH_DEV_NAME_KT50X0,
+		   sizeof(link->dpcd_caps.branch_dev_name)) == 0) {
+		msleep(2000);
+	}
 
 	/*
 	 * Add the logic to extract BOTH power up and power down sequences
diff --git a/drivers/gpu/drm/amd/display/dc/link/link_validation.c b/drivers/gpu/drm/amd/display/dc/link/link_validation.c
index 60f15a9ba7a5..c3da48c213d4 100644
--- a/drivers/gpu/drm/amd/display/dc/link/link_validation.c
+++ b/drivers/gpu/drm/amd/display/dc/link/link_validation.c
@@ -35,6 +35,8 @@
 
 #define DC_LOGGER_INIT(logger)
 
+static const uint8_t DP_SINK_BRANCH_DEV_NAME_KT50X0[] = "KT50X0!";
+
 static uint32_t get_tmds_output_pixel_clock_100hz(const struct dc_crtc_timing *timing)
 {
 
@@ -276,6 +278,15 @@ static bool dp_validate_mode_timing(
 		timing->v_addressable == (uint32_t) 480)
 		return true;
 
+	if (link->ctx->dce_version == DCN_VERSION_3_01 &&
+	    link->dpcd_caps.sink_dev_id == DP_BRANCH_DEVICE_ID_0060AD &&
+	    memcmp(&link->dpcd_caps.branch_dev_name,
+		   DP_SINK_BRANCH_DEV_NAME_KT50X0,
+		   sizeof(link->dpcd_caps.branch_dev_name)) == 0) {
+		if (timing->pix_clk_100hz / 10 >= (uint32_t) 1200000)
+			return false; /* KT50X0 does not support Pxl clock >= 1200MHz */
+	}
+
 	link_setting = dp_get_verified_link_cap(link);
 
 	/* TODO: DYNAMIC_VALIDATION needs to be implemented */
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 6c8e9e004907..809d5f2684ff 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -185,6 +185,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 13d32f55e5d4..c4b93a15a4d5 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_HID_APPLETB_BL)	+= hid-appletb-bl.o
 obj-$(CONFIG_HID_APPLETB_KBD)	+= hid-appletb-kbd.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..d59316001f50
--- /dev/null
+++ b/drivers/hid/hid-asus-ally.c
@@ -0,0 +1,2251 @@
+// 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/compiler_attributes.h"
+#include "linux/device.h"
+#include "linux/pm.h"
+#include "linux/printk.h"
+#include "linux/slab.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 DEBUG
+
+#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
+
+#define ALLY_MIN_BIOS 319
+#define ALLY_X_MIN_BIOS 313
+
+#define BTN_DATA_LEN 11;
+#define BTN_CODE_BYTES_LEN 8
+
+static const u8 EC_INIT_STRING[] = { 0x5A, 'A', 'S', 'U', 'S', ' ', 'T', 'e','c', 'h', '.', 'I', 'n', 'c', '.', '\0' };
+static const u8 EC_MODE_LED_APPLY[] = { 0x5A, 0xB4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+static const u8 EC_MODE_LED_SET[] = { 0x5A, 0xB5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+static const u8 FORCE_FEEDBACK_OFF[] = { 0x0D, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
+
+static const struct hid_device_id rog_ally_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X) },
+	{}
+};
+
+struct btn_code_map {
+	u64 code;
+	const char *name;
+};
+
+static const struct btn_code_map ally_btn_codes[] = {
+	{ 0, "NONE" },
+	/* Gamepad button codes */
+	{ BTN_PAD_A, "PAD_A" },
+	{ BTN_PAD_B, "PAD_B" },
+	{ BTN_PAD_X, "PAD_X" },
+	{ BTN_PAD_Y, "PAD_Y" },
+	{ BTN_PAD_LB, "PAD_LB" },
+	{ BTN_PAD_RB, "PAD_RB" },
+	{ BTN_PAD_LS, "PAD_LS" },
+	{ BTN_PAD_RS, "PAD_RS" },
+	{ BTN_PAD_DPAD_UP, "PAD_DPAD_UP" },
+	{ BTN_PAD_DPAD_DOWN, "PAD_DPAD_DOWN" },
+	{ BTN_PAD_DPAD_LEFT, "PAD_DPAD_LEFT" },
+	{ BTN_PAD_DPAD_RIGHT, "PAD_DPAD_RIGHT" },
+	{ BTN_PAD_VIEW, "PAD_VIEW" },
+	{ BTN_PAD_MENU, "PAD_MENU" },
+	{ BTN_PAD_XBOX, "PAD_XBOX" },
+
+	/* Triggers mapped to keyboard codes */
+	{ BTN_KB_M2, "KB_M2" },
+	{ BTN_KB_M1, "KB_M1" },
+	{ BTN_KB_ESC, "KB_ESC" },
+	{ BTN_KB_F1, "KB_F1" },
+	{ BTN_KB_F2, "KB_F2" },
+	{ BTN_KB_F3, "KB_F3" },
+	{ BTN_KB_F4, "KB_F4" },
+	{ BTN_KB_F5, "KB_F5" },
+	{ BTN_KB_F6, "KB_F6" },
+	{ BTN_KB_F7, "KB_F7" },
+	{ BTN_KB_F8, "KB_F8" },
+	{ BTN_KB_F9, "KB_F9" },
+	{ BTN_KB_F10, "KB_F10" },
+	{ BTN_KB_F11, "KB_F11" },
+	{ BTN_KB_F12, "KB_F12" },
+	{ BTN_KB_F14, "KB_F14" },
+	{ BTN_KB_F15, "KB_F15" },
+	{ BTN_KB_BACKTICK, "KB_BACKTICK" },
+	{ BTN_KB_1, "KB_1" },
+	{ BTN_KB_2, "KB_2" },
+	{ BTN_KB_3, "KB_3" },
+	{ BTN_KB_4, "KB_4" },
+	{ BTN_KB_5, "KB_5" },
+	{ BTN_KB_6, "KB_6" },
+	{ BTN_KB_7, "KB_7" },
+	{ BTN_KB_8, "KB_8" },
+	{ BTN_KB_9, "KB_9" },
+	{ BTN_KB_0, "KB_0" },
+	{ BTN_KB_HYPHEN, "KB_HYPHEN" },
+	{ BTN_KB_EQUALS, "KB_EQUALS" },
+	{ BTN_KB_BACKSPACE, "KB_BACKSPACE" },
+	{ BTN_KB_TAB, "KB_TAB" },
+	{ BTN_KB_Q, "KB_Q" },
+	{ BTN_KB_W, "KB_W" },
+	{ BTN_KB_E, "KB_E" },
+	{ BTN_KB_R, "KB_R" },
+	{ BTN_KB_T, "KB_T" },
+	{ BTN_KB_Y, "KB_Y" },
+	{ BTN_KB_U, "KB_U" },
+	{ BTN_KB_O, "KB_O" },
+	{ BTN_KB_P, "KB_P" },
+	{ BTN_KB_LBRACKET, "KB_LBRACKET" },
+	{ BTN_KB_RBRACKET, "KB_RBRACKET" },
+	{ BTN_KB_BACKSLASH, "KB_BACKSLASH" },
+	{ BTN_KB_CAPS, "KB_CAPS" },
+	{ BTN_KB_A, "KB_A" },
+	{ BTN_KB_S, "KB_S" },
+	{ BTN_KB_D, "KB_D" },
+	{ BTN_KB_F, "KB_F" },
+	{ BTN_KB_G, "KB_G" },
+	{ BTN_KB_H, "KB_H" },
+	{ BTN_KB_J, "KB_J" },
+	{ BTN_KB_K, "KB_K" },
+	{ BTN_KB_L, "KB_L" },
+	{ BTN_KB_SEMI, "KB_SEMI" },
+	{ BTN_KB_QUOTE, "KB_QUOTE" },
+	{ BTN_KB_RET, "KB_RET" },
+	{ BTN_KB_LSHIFT, "KB_LSHIFT" },
+	{ BTN_KB_Z, "KB_Z" },
+	{ BTN_KB_X, "KB_X" },
+	{ BTN_KB_C, "KB_C" },
+	{ BTN_KB_V, "KB_V" },
+	{ BTN_KB_B, "KB_B" },
+	{ BTN_KB_N, "KB_N" },
+	{ BTN_KB_M, "KB_M" },
+	{ BTN_KB_COMMA, "KB_COMMA" },
+	{ BTN_KB_PERIOD, "KB_PERIOD" },
+	{ BTN_KB_RSHIFT, "KB_RSHIFT" },
+	{ BTN_KB_LCTL, "KB_LCTL" },
+	{ BTN_KB_META, "KB_META" },
+	{ BTN_KB_LALT, "KB_LALT" },
+	{ BTN_KB_SPACE, "KB_SPACE" },
+	{ BTN_KB_RALT, "KB_RALT" },
+	{ BTN_KB_MENU, "KB_MENU" },
+	{ BTN_KB_RCTL, "KB_RCTL" },
+	{ BTN_KB_PRNTSCN, "KB_PRNTSCN" },
+	{ BTN_KB_SCRLCK, "KB_SCRLCK" },
+	{ BTN_KB_PAUSE, "KB_PAUSE" },
+	{ BTN_KB_INS, "KB_INS" },
+	{ BTN_KB_HOME, "KB_HOME" },
+	{ BTN_KB_PGUP, "KB_PGUP" },
+	{ BTN_KB_DEL, "KB_DEL" },
+	{ BTN_KB_END, "KB_END" },
+	{ BTN_KB_PGDWN, "KB_PGDWN" },
+	{ BTN_KB_UP_ARROW, "KB_UP_ARROW" },
+	{ BTN_KB_DOWN_ARROW, "KB_DOWN_ARROW" },
+	{ BTN_KB_LEFT_ARROW, "KB_LEFT_ARROW" },
+	{ BTN_KB_RIGHT_ARROW, "KB_RIGHT_ARROW" },
+
+	/* Numpad mappings */
+	{ BTN_NUMPAD_LOCK, "NUMPAD_LOCK" },
+	{ BTN_NUMPAD_FWDSLASH, "NUMPAD_FWDSLASH" },
+	{ BTN_NUMPAD_ASTERISK, "NUMPAD_ASTERISK" },
+	{ BTN_NUMPAD_HYPHEN, "NUMPAD_HYPHEN" },
+	{ BTN_NUMPAD_0, "NUMPAD_0" },
+	{ BTN_NUMPAD_1, "NUMPAD_1" },
+	{ BTN_NUMPAD_2, "NUMPAD_2" },
+	{ BTN_NUMPAD_3, "NUMPAD_3" },
+	{ BTN_NUMPAD_4, "NUMPAD_4" },
+	{ BTN_NUMPAD_5, "NUMPAD_5" },
+	{ BTN_NUMPAD_6, "NUMPAD_6" },
+	{ BTN_NUMPAD_7, "NUMPAD_7" },
+	{ BTN_NUMPAD_8, "NUMPAD_8" },
+	{ BTN_NUMPAD_9, "NUMPAD_9" },
+	{ BTN_NUMPAD_PLUS, "NUMPAD_PLUS" },
+	{ BTN_NUMPAD_ENTER, "NUMPAD_ENTER" },
+	{ BTN_NUMPAD_PERIOD, "NUMPAD_PERIOD" },
+
+	/* Mouse mappings */
+	{ BTN_MOUSE_LCLICK, "MOUSE_LCLICK" },
+	{ BTN_MOUSE_RCLICK, "MOUSE_RCLICK" },
+	{ BTN_MOUSE_MCLICK, "MOUSE_MCLICK" },
+	{ BTN_MOUSE_WHEEL_UP, "MOUSE_WHEEL_UP" },
+	{ BTN_MOUSE_WHEEL_DOWN, "MOUSE_WHEEL_DOWN" },
+
+	/* Media mappings */
+	{ BTN_MEDIA_SCREENSHOT, "MEDIA_SCREENSHOT" },
+	{ BTN_MEDIA_SHOW_KEYBOARD, "MEDIA_SHOW_KEYBOARD" },
+	{ BTN_MEDIA_SHOW_DESKTOP, "MEDIA_SHOW_DESKTOP" },
+	{ BTN_MEDIA_START_RECORDING, "MEDIA_START_RECORDING" },
+	{ BTN_MEDIA_MIC_OFF, "MEDIA_MIC_OFF" },
+	{ BTN_MEDIA_VOL_DOWN, "MEDIA_VOL_DOWN" },
+	{ BTN_MEDIA_VOL_UP, "MEDIA_VOL_UP" },
+};
+static const size_t keymap_len = ARRAY_SIZE(ally_btn_codes);
+
+/* byte_array must be >= 8 in length */
+static void btn_code_to_byte_array(u64 keycode, u8 *byte_array)
+{
+	/* Convert the u64 to bytes[8] */
+	for (int i = 0; i < 8; ++i) {
+		byte_array[i] = (keycode >> (56 - 8 * i)) & 0xFF;
+	}
+}
+
+static u64 name_to_btn(const char *name)
+{
+	int len = strcspn(name, "\n");
+	for (size_t i = 0; i < keymap_len; ++i) {
+		if (strncmp(ally_btn_codes[i].name, name, len) == 0) {
+			return ally_btn_codes[i].code;
+		}
+	}
+	return -EINVAL;
+}
+
+static const char* btn_to_name(u64 key)
+{
+	for (size_t i = 0; i < keymap_len; ++i) {
+		if (ally_btn_codes[i].code == key) {
+			return ally_btn_codes[i].name;
+		}
+	}
+	return NULL;
+}
+
+struct btn_data {
+	u64 button;
+	u64 macro;
+	bool turbo;
+};
+
+struct btn_mapping {
+	struct btn_data btn_a;
+	struct btn_data btn_b;
+	struct btn_data btn_x;
+	struct btn_data btn_y;
+	struct btn_data btn_lb;
+	struct btn_data btn_rb;
+	struct btn_data btn_ls;
+	struct btn_data btn_rs;
+	struct btn_data btn_lt;
+	struct btn_data btn_rt;
+	struct btn_data dpad_up;
+	struct btn_data dpad_down;
+	struct btn_data dpad_left;
+	struct btn_data dpad_right;
+	struct btn_data btn_view;
+	struct btn_data btn_menu;
+	struct btn_data btn_m1;
+	struct btn_data btn_m2;
+};
+
+struct deadzone {
+	u8 inner;
+	u8 outer;
+};
+
+struct response_curve {
+	uint8_t move_pct_1;
+	uint8_t response_pct_1;
+	uint8_t move_pct_2;
+	uint8_t response_pct_2;
+	uint8_t move_pct_3;
+	uint8_t response_pct_3;
+	uint8_t move_pct_4;
+	uint8_t response_pct_4;
+} __packed;
+
+struct js_axis_calibrations {
+	uint16_t left_y_stable;
+	uint16_t left_y_min;
+	uint16_t left_y_max;
+	uint16_t left_x_stable;
+	uint16_t left_x_min;
+	uint16_t left_x_max;
+	uint16_t right_y_stable;
+	uint16_t right_y_min;
+	uint16_t right_y_max;
+	uint16_t right_x_stable;
+	uint16_t right_x_min;
+	uint16_t right_x_max;
+} __packed;
+
+struct tr_axis_calibrations {
+	uint16_t left_stable;
+	uint16_t left_max;
+	uint16_t right_stable;
+	uint16_t right_max;
+} __packed;
+
+/* 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: [mode]
+	 */
+	struct btn_mapping key_mapping[xpad_mode_mouse];
+	/*
+	 * index: left, right
+	 * max: 64
+	 */
+	u8 vibration_intensity[2];
+
+	/* deadzones */
+	struct deadzone ls_dz; // left stick
+	struct deadzone rs_dz; // right stick
+	struct deadzone lt_dz; // left trigger
+	struct deadzone rt_dz; // right trigger
+	/* anti-deadzones */
+	u8 ls_adz; // left stick
+	u8 rs_adz; // right stick
+	/* joystick response curves */
+	struct response_curve ls_rc;
+	struct response_curve rs_rc;
+
+	struct js_axis_calibrations js_cal;
+	struct tr_axis_calibrations tr_cal;
+};
+
+/* 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_dev {
+	struct hid_device *hdev;
+	struct led_classdev_mc led_rgb_dev;
+	struct work_struct work;
+	bool output_worker_initialized;
+	spinlock_t lock;
+
+	bool removed;
+	bool update_rgb;
+	uint8_t red[4];
+	uint8_t green[4];
+	uint8_t blue[4];
+};
+
+struct ally_rgb_data {
+	uint8_t brightness;
+	uint8_t red[4];
+	uint8_t green[4];
+	uint8_t blue[4];
+	bool initialized;
+};
+
+static struct ally_drvdata {
+	struct hid_device *hdev;
+	struct ally_x_device *ally_x;
+	struct ally_gamepad_cfg *gamepad_cfg;
+	struct ally_rgb_dev *led_rgb_dev;
+	struct ally_rgb_data led_rgb_data;
+} drvdata;
+
+static void reverse_bytes_in_pairs(u8 *buf, size_t size) {
+	uint16_t *word_ptr;
+	size_t i;
+
+	for (i = 0; i < size; i += 2) {
+		if (i + 1 < size) {
+			word_ptr = (uint16_t *)&buf[i];
+			*word_ptr = cpu_to_be16(*word_ptr);
+		}
+	}
+}
+
+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);
+}
+
+/**
+ * asus_dev_set_report - send set report request to device.
+ *
+ * @hdev: hid device
+ * @buf: in/out data to transfer
+ * @len: length of buf
+ *
+ * Return: count of data transferred, negative if error
+ *
+ * Same behavior as hid_hw_raw_request. Note that the input buffer is duplicated.
+ */
+static int asus_dev_set_report(struct hid_device *hdev, const u8 *buf, size_t len)
+{
+	unsigned char *dmabuf;
+	int ret;
+
+	dmabuf = kmemdup(buf, len, GFP_KERNEL);
+	if (!dmabuf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, buf[0], dmabuf, len, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(dmabuf);
+
+	return ret;
+}
+
+static u8 get_endpoint_address(struct hid_device *hdev)
+{
+	struct usb_interface *intf;
+	struct usb_host_endpoint *ep;
+
+	intf = to_usb_interface(hdev->dev.parent);
+
+	if (intf) {
+		ep = intf->cur_altsetting->endpoint;
+		if (ep) {
+			return ep->desc.bEndpointAddress;
+		}
+	}
+
+	return -ENODEV;
+}
+
+/**************************************************************************************************/
+/* ROG Ally gamepad configuration                                                                 */
+/**************************************************************************************************/
+
+/* This should be called before any attempts to set device functions */
+static int ally_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_dbg(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_dbg(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;
+}
+
+/* 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_apply_intensity(struct hid_device *hdev,
+					struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int 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[0];
+	hidbuf[5] = ally_cfg->vibration_intensity[1];
+
+	ret = ally_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[0],
+		ally_cfg->vibration_intensity[1]);
+}
+
+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[0] = left;
+	ally_cfg->vibration_intensity[1] = right;
+
+	ret = _gamepad_apply_intensity(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+ALLY_DEVICE_ATTR_RW(gamepad_vibration_intensity, vibration_intensity);
+
+/* ANALOGUE DEADZONES *****************************************************************************/
+static ssize_t _gamepad_apply_deadzones(struct hid_device *hdev,
+				       struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int ret;
+
+	ret = ally_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->ls_dz.inner;
+	hidbuf[5] = ally_cfg->ls_dz.outer;
+	hidbuf[6] = ally_cfg->rs_dz.inner;
+	hidbuf[7] = ally_cfg->rs_dz.outer;
+
+	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->lt_dz.inner;
+	hidbuf[5] = ally_cfg->lt_dz.outer;
+	hidbuf[6] = ally_cfg->rt_dz.inner;
+	hidbuf[7] = ally_cfg->rt_dz.outer;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	if (ret < 0)
+		goto end;
+
+end:
+	kfree(hidbuf);
+	return ret;
+}
+
+static void _gamepad_set_deadzones_default(struct ally_gamepad_cfg *ally_cfg)
+{
+	ally_cfg->ls_dz.inner = 0x00;
+	ally_cfg->ls_dz.outer = 0x64;
+	ally_cfg->rs_dz.inner = 0x00;
+	ally_cfg->rs_dz.outer = 0x64;
+	ally_cfg->lt_dz.inner = 0x00;
+	ally_cfg->lt_dz.outer = 0x64;
+	ally_cfg->rt_dz.inner = 0x00;
+	ally_cfg->rt_dz.outer = 0x64;
+}
+
+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_DEADZONES(axis_xy_left, ls_dz);
+ALLY_DEADZONES(axis_xy_right, rs_dz);
+ALLY_DEADZONES(axis_z_left, lt_dz);
+ALLY_DEADZONES(axis_z_right, rt_dz);
+
+/* ANTI-DEADZONES *********************************************************************************/
+static ssize_t _gamepad_apply_js_ADZ(struct hid_device *hdev,
+					     struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int 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->ls_adz;
+	hidbuf[5] = ally_cfg->rs_adz;
+
+	ret = ally_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 void _gamepad_set_anti_deadzones_default(struct ally_gamepad_cfg *ally_cfg)
+{
+	ally_cfg->ls_adz = 0x00;
+	ally_cfg->rs_adz = 0x00;
+}
+
+static ssize_t _gamepad_js_ADZ_store(struct device *dev, const char *buf, u8 *adz)
+{
+	int ret, val;
+
+	ret = kstrtoint(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (val < 0 || val > 32)
+		return -EINVAL;
+
+	*adz = val;
+
+	return ret;
+}
+
+static ssize_t axis_xy_left_anti_deadzone_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	return sysfs_emit(buf, "%d\n", ally_cfg->ls_adz);
+}
+
+static ssize_t axis_xy_left_anti_deadzone_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	int ret;
+
+	ret = _gamepad_js_ADZ_store(dev, buf, &ally_cfg->ls_adz);
+	if (ret)
+		return ret;
+
+	return count;
+}
+ALLY_DEVICE_ATTR_RW(axis_xy_left_anti_deadzone, anti_deadzone);
+
+static ssize_t axis_xy_right_anti_deadzone_show(struct device *dev,
+						struct device_attribute *attr,
+						char *buf)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+
+	return sysfs_emit(buf, "%d\n", ally_cfg->rs_adz);
+}
+
+static ssize_t axis_xy_right_anti_deadzone_store(struct device *dev,
+						struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;
+	int ret;
+
+	ret = _gamepad_js_ADZ_store(dev, buf, &ally_cfg->rs_adz);
+	if (ret)
+		return ret;
+
+	return count;
+}
+ALLY_DEVICE_ATTR_RW(axis_xy_right_anti_deadzone, anti_deadzone);
+
+/* JS RESPONSE CURVES *****************************************************************************/
+static void _gamepad_set_js_response_curves_default(struct ally_gamepad_cfg *ally_cfg)
+{
+	struct response_curve *js1_rc = &ally_cfg->ls_rc;
+	struct response_curve *js2_rc = &ally_cfg->rs_rc;
+	js1_rc->move_pct_1 = js2_rc->move_pct_1 = 0x16; // 25%
+	js1_rc->move_pct_2 = js2_rc->move_pct_2 = 0x32; // 50%
+	js1_rc->move_pct_3 = js2_rc->move_pct_3 = 0x48; // 75%
+	js1_rc->move_pct_4 = js2_rc->move_pct_4 = 0x64; // 100%
+	js1_rc->response_pct_1 = js2_rc->response_pct_1 = 0x16;
+	js1_rc->response_pct_2 = js2_rc->response_pct_2 = 0x32;
+	js1_rc->response_pct_3 = js2_rc->response_pct_3 = 0x48;
+	js1_rc->response_pct_4 = js2_rc->response_pct_4 = 0x64;
+}
+
+static ssize_t _gamepad_apply_response_curves(struct hid_device *hdev,
+					      struct ally_gamepad_cfg *ally_cfg)
+{
+	u8 *hidbuf;
+	int 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;
+	memcpy(&hidbuf[2], &ally_cfg->ls_rc, sizeof(ally_cfg->ls_rc));
+
+	ret = ally_gamepad_check_ready(hdev);
+	if (ret < 0)
+		goto report_fail;
+
+	hidbuf[4] = 0x02;
+	memcpy(&hidbuf[5], &ally_cfg->rs_rc, sizeof(ally_cfg->rs_rc));
+
+	ret = ally_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;
+}
+
+ALLY_JS_RC_POINT(axis_xy_left, move, 1);
+ALLY_JS_RC_POINT(axis_xy_left, move, 2);
+ALLY_JS_RC_POINT(axis_xy_left, move, 3);
+ALLY_JS_RC_POINT(axis_xy_left, move, 4);
+ALLY_JS_RC_POINT(axis_xy_left, response, 1);
+ALLY_JS_RC_POINT(axis_xy_left, response, 2);
+ALLY_JS_RC_POINT(axis_xy_left, response, 3);
+ALLY_JS_RC_POINT(axis_xy_left, response, 4);
+
+ALLY_JS_RC_POINT(axis_xy_right, move, 1);
+ALLY_JS_RC_POINT(axis_xy_right, move, 2);
+ALLY_JS_RC_POINT(axis_xy_right, move, 3);
+ALLY_JS_RC_POINT(axis_xy_right, move, 4);
+ALLY_JS_RC_POINT(axis_xy_right, response, 1);
+ALLY_JS_RC_POINT(axis_xy_right, response, 2);
+ALLY_JS_RC_POINT(axis_xy_right, response, 3);
+ALLY_JS_RC_POINT(axis_xy_right, response, 4);
+
+/* 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 */
+			reverse_bytes_in_pairs(&hidbuf[6], sizeof(struct js_axis_calibrations));
+			ally_cfg->js_cal = *(struct js_axis_calibrations *)&hidbuf[6];
+			print_hex_dump(KERN_INFO, "HID Buffer JS: ", DUMP_PREFIX_OFFSET, 16, 1, hidbuf, 32, true);
+			struct js_axis_calibrations *cal = &drvdata.gamepad_cfg->js_cal;
+			pr_err("LS_CAL: X: %d, Min: %d, Max: %d", cal->left_x_stable, cal->left_x_min, cal->left_x_max);
+			pr_err("LS_CAL: Y: %d, Min: %d, Max: %d", cal->left_y_stable, cal->left_y_min, cal->left_y_max);
+			pr_err("RS_CAL: X: %d, Min: %d, Max: %d", cal->right_x_stable, cal->right_x_min, cal->right_x_max);
+			pr_err("RS_CAL: Y: %d, Min: %d, Max: %d", cal->right_y_stable, cal->right_y_min, cal->right_y_max);
+		} else {
+			/* Trigger calibration */
+			reverse_bytes_in_pairs(&hidbuf[6], sizeof(struct tr_axis_calibrations));
+			ally_cfg->tr_cal = *(struct tr_axis_calibrations *)&hidbuf[6];
+			print_hex_dump(KERN_INFO, "HID Buffer TR: ", DUMP_PREFIX_OFFSET, 16, 1, hidbuf, 32, true);
+		}
+	}
+
+cleanup:
+	kfree(hidbuf);
+	return ret;
+}
+
+static struct attribute *axis_xy_left_attrs[] = {
+	&dev_attr_axis_xy_left_anti_deadzone.attr,
+	&dev_attr_axis_xy_left_deadzone.attr,
+	&dev_attr_axis_xyz_deadzone_index.attr,
+	&dev_attr_axis_xy_left_move_1.attr,
+	&dev_attr_axis_xy_left_move_2.attr,
+	&dev_attr_axis_xy_left_move_3.attr,
+	&dev_attr_axis_xy_left_move_4.attr,
+	&dev_attr_axis_xy_left_response_1.attr,
+	&dev_attr_axis_xy_left_response_2.attr,
+	&dev_attr_axis_xy_left_response_3.attr,
+	&dev_attr_axis_xy_left_response_4.attr,
+	NULL
+};
+static const struct attribute_group axis_xy_left_attr_group = {
+	.name = "axis_xy_left",
+	.attrs = axis_xy_left_attrs,
+};
+
+static struct attribute *axis_xy_right_attrs[] = {
+	&dev_attr_axis_xy_right_anti_deadzone.attr,
+	&dev_attr_axis_xy_right_deadzone.attr,
+	&dev_attr_axis_xyz_deadzone_index.attr,
+	&dev_attr_axis_xy_right_move_1.attr,
+	&dev_attr_axis_xy_right_move_2.attr,
+	&dev_attr_axis_xy_right_move_3.attr,
+	&dev_attr_axis_xy_right_move_4.attr,
+	&dev_attr_axis_xy_right_response_1.attr,
+	&dev_attr_axis_xy_right_response_2.attr,
+	&dev_attr_axis_xy_right_response_3.attr,
+	&dev_attr_axis_xy_right_response_4.attr,
+	NULL
+};
+static const struct attribute_group axis_xy_right_attr_group = {
+	.name = "axis_xy_right",
+	.attrs = axis_xy_right_attrs,
+};
+
+static struct attribute *axis_z_left_attrs[] = {
+	&dev_attr_axis_z_left_deadzone.attr,
+	&dev_attr_axis_xyz_deadzone_index.attr,
+	NULL,
+};
+static const struct attribute_group axis_z_left_attr_group = {
+	.name = "axis_z_left",
+	.attrs = axis_z_left_attrs,
+};
+
+static struct attribute *axis_z_right_attrs[] = {
+	&dev_attr_axis_z_right_deadzone.attr,
+	&dev_attr_axis_xyz_deadzone_index.attr,
+	NULL,
+};
+static const struct attribute_group axis_z_right_attr_group = {
+	.name = "axis_z_right",
+	.attrs = axis_z_right_attrs,
+};
+
+/* A HID packet conatins mappings for two buttons: btn1, btn1_macro, btn2, btn2_macro */
+static void _btn_pair_to_hid_pkt(struct ally_gamepad_cfg *ally_cfg,
+				enum btn_pair_index pair,
+				struct btn_data *btn1, struct btn_data *btn2,
+				u8 *out, int out_len)
+{
+	int start = 5;
+
+	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;
+
+	btn_code_to_byte_array(btn1->button, &out[start]);
+	start += BTN_DATA_LEN;
+	btn_code_to_byte_array(btn1->macro, &out[start]);
+	start += BTN_DATA_LEN;
+	btn_code_to_byte_array(btn2->button, &out[start]);
+	start += BTN_DATA_LEN;
+	btn_code_to_byte_array(btn2->macro, &out[start]);
+	//print_hex_dump(KERN_DEBUG, "byte_array: ", DUMP_PREFIX_OFFSET, 64, 1, out, 64, false);
+}
+
+/* Apply the mapping pair to the device */
+static int _gamepad_apply_btn_pair(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg,
+				 enum btn_pair_index btn_pair)
+{
+	u8 mode = ally_cfg->mode - 1;
+	struct btn_data *btn1, *btn2;
+	u8 *hidbuf;
+	int ret;
+
+	ret = ally_gamepad_check_ready(hdev);
+	if (ret < 0)
+		return ret;
+
+	hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL);
+	if (!hidbuf)
+		return -ENOMEM;
+
+	switch (btn_pair) {
+	case btn_pair_dpad_u_d:
+		btn1 = &ally_cfg->key_mapping[mode].dpad_up;
+		btn2 = &ally_cfg->key_mapping[mode].dpad_down;
+		break;
+	case btn_pair_dpad_l_r:
+		btn1 = &ally_cfg->key_mapping[mode].dpad_left;
+		btn2 = &ally_cfg->key_mapping[mode].dpad_right;
+		break;
+	case btn_pair_ls_rs:
+		btn1 = &ally_cfg->key_mapping[mode].btn_ls;
+		btn2 = &ally_cfg->key_mapping[mode].btn_rs;
+		break;
+	case btn_pair_lb_rb:
+		btn1 = &ally_cfg->key_mapping[mode].btn_lb;
+		btn2 = &ally_cfg->key_mapping[mode].btn_rb;
+		break;
+	case btn_pair_lt_rt:
+		btn1 = &ally_cfg->key_mapping[mode].btn_lt;
+		btn2 = &ally_cfg->key_mapping[mode].btn_rt;
+		break;
+	case btn_pair_a_b:
+		btn1 = &ally_cfg->key_mapping[mode].btn_a;
+		btn2 = &ally_cfg->key_mapping[mode].btn_b;
+		break;
+	case btn_pair_x_y:
+		btn1 = &ally_cfg->key_mapping[mode].btn_x;
+		btn2 = &ally_cfg->key_mapping[mode].btn_y;
+		break;
+	case btn_pair_view_menu:
+		btn1 = &ally_cfg->key_mapping[mode].btn_view;
+		btn2 = &ally_cfg->key_mapping[mode].btn_menu;
+		break;
+	case btn_pair_m1_m2:
+		btn1 = &ally_cfg->key_mapping[mode].btn_m1;
+		btn2 = &ally_cfg->key_mapping[mode].btn_m2;
+		break;
+	default:
+		break;
+	}
+
+	_btn_pair_to_hid_pkt(ally_cfg, btn_pair, btn1, btn2, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+
+	kfree(hidbuf);
+
+	return ret;
+}
+
+static int _gamepad_apply_turbo(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg)
+{
+	struct btn_mapping *map = &ally_cfg->key_mapping[ally_cfg->mode - 1];
+	u8 *hidbuf;
+	int 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;
+
+	hidbuf[4] = map->dpad_up.turbo;
+	hidbuf[6] = map->dpad_down.turbo;
+	hidbuf[8] = map->dpad_left.turbo;
+	hidbuf[10] = map->dpad_right.turbo;
+
+	hidbuf[12] = map->btn_ls.turbo;
+	hidbuf[14] = map->btn_rs.turbo;
+	hidbuf[16] = map->btn_lb.turbo;
+	hidbuf[18] = map->btn_rb.turbo;
+
+	hidbuf[20] = map->btn_a.turbo;
+	hidbuf[22] = map->btn_b.turbo;
+	hidbuf[24] = map->btn_x.turbo;
+	hidbuf[26] = map->btn_y.turbo;
+
+	hidbuf[28] = map->btn_lt.turbo;
+	hidbuf[30] = map->btn_rt.turbo;
+
+	ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE);
+
+	kfree(hidbuf);
+
+	return ret;
+}
+
+static ssize_t _gamepad_apply_all(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg)
+{
+	int ret;
+
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_dpad_u_d);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_dpad_l_r);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_ls_rs);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_lb_rb);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_a_b);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_x_y);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_view_menu);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_m1_m2);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_lt_rt);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_turbo(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_deadzones(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	ret = _gamepad_apply_js_ADZ(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+	ret =_gamepad_apply_response_curves(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static ssize_t gamepad_apply_all_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_apply_all(hdev, ally_cfg);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+ALLY_DEVICE_ATTR_WO(gamepad_apply_all, apply_all);
+
+/* button map attributes, regular and macro*/
+ALLY_BTN_MAPPING(m1, btn_m1);
+ALLY_BTN_MAPPING(m2, btn_m2);
+ALLY_BTN_MAPPING(view, btn_view);
+ALLY_BTN_MAPPING(menu, btn_menu);
+ALLY_TURBO_BTN_MAPPING(a, btn_a);
+ALLY_TURBO_BTN_MAPPING(b, btn_b);
+ALLY_TURBO_BTN_MAPPING(x, btn_x);
+ALLY_TURBO_BTN_MAPPING(y, btn_y);
+ALLY_TURBO_BTN_MAPPING(lb, btn_lb);
+ALLY_TURBO_BTN_MAPPING(rb, btn_rb);
+ALLY_TURBO_BTN_MAPPING(ls, btn_ls);
+ALLY_TURBO_BTN_MAPPING(rs, btn_rs);
+ALLY_TURBO_BTN_MAPPING(lt, btn_lt);
+ALLY_TURBO_BTN_MAPPING(rt, btn_rt);
+ALLY_TURBO_BTN_MAPPING(dpad_u, dpad_up);
+ALLY_TURBO_BTN_MAPPING(dpad_d, dpad_down);
+ALLY_TURBO_BTN_MAPPING(dpad_l, dpad_left);
+ALLY_TURBO_BTN_MAPPING(dpad_r, dpad_right);
+
+static void _gamepad_set_xpad_default(struct ally_gamepad_cfg *ally_cfg)
+{
+	struct btn_mapping *map = &ally_cfg->key_mapping[ally_cfg->mode - 1];
+	map->btn_m1.button = BTN_KB_M1;
+	map->btn_m2.button = BTN_KB_M2;
+	map->btn_a.button = BTN_PAD_A;
+	map->btn_b.button = BTN_PAD_B;
+	map->btn_x.button = BTN_PAD_X;
+	map->btn_y.button = BTN_PAD_Y;
+	map->btn_lb.button = BTN_PAD_LB;
+	map->btn_rb.button = BTN_PAD_RB;
+	map->btn_lt.button = BTN_PAD_LT;
+	map->btn_rt.button = BTN_PAD_RT;
+	map->btn_ls.button = BTN_PAD_LS;
+	map->btn_rs.button = BTN_PAD_RS;
+	map->dpad_up.button = BTN_PAD_DPAD_UP;
+	map->dpad_down.button = BTN_PAD_DPAD_DOWN;
+	map->dpad_left.button = BTN_PAD_DPAD_LEFT;
+	map->dpad_right.button = BTN_PAD_DPAD_RIGHT;
+	map->btn_view.button = BTN_PAD_VIEW;
+	map->btn_menu.button = BTN_PAD_MENU;
+}
+
+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_set_xpad_default(ally_cfg);
+		break;
+	default:
+		_gamepad_set_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;
+
+	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 = ally_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_apply_all(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);
+
+/* ROOT LEVEL ATTRS *******************************************************************************/
+static struct attribute *gamepad_device_attrs[] = {
+	&dev_attr_btn_mapping_reset.attr,
+	&dev_attr_gamepad_mode.attr,
+	&dev_attr_gamepad_apply_all.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,
+};
+
+static const struct attribute_group *gamepad_device_attr_groups[] = {
+	&ally_controller_attr_group,
+	&axis_xy_left_attr_group,
+	&axis_xy_right_attr_group,
+	&axis_z_left_attr_group,
+	&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_lt_attr_group,
+	&btn_mapping_rt_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 struct ally_gamepad_cfg *ally_gamepad_cfg_create(struct hid_device *hdev)
+{
+	struct ally_gamepad_cfg *ally_cfg;
+	struct input_dev *input_dev;
+	int err;
+
+	ally_cfg = devm_kzalloc(&hdev->dev, sizeof(*ally_cfg), GFP_KERNEL);
+	if (!ally_cfg)
+		return ERR_PTR(-ENOMEM);
+	ally_cfg->hdev = hdev;
+	// Allocate memory for each mode's `btn_mapping`
+	ally_cfg->mode = xpad_mode_game;
+
+	input_dev = devm_input_allocate_device(&hdev->dev);
+	if (!input_dev) {
+		err = -ENOMEM;
+		goto free_ally_cfg;
+	}
+
+	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->input = input_dev;
+
+	/* ignore all errors for this as they are related to USB HID I/O */
+	_gamepad_set_xpad_default(ally_cfg);
+	ally_cfg->key_mapping[ally_cfg->mode - 1].btn_m1.button = BTN_KB_M1;
+	ally_cfg->key_mapping[ally_cfg->mode - 1].btn_m2.button = BTN_KB_M2;
+	_gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_m1_m2);
+	gamepad_get_calibration(hdev);
+
+	ally_cfg->vibration_intensity[0] = 0x64;
+	ally_cfg->vibration_intensity[1] = 0x64;
+	_gamepad_set_deadzones_default(ally_cfg);
+	_gamepad_set_anti_deadzones_default(ally_cfg);
+	_gamepad_set_js_response_curves_default(ally_cfg);
+
+	drvdata.gamepad_cfg = ally_cfg; // Must asign before attr group setup
+	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 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 LED control                                                                           */
+/**************************************************************************************************/
+static void ally_rgb_schedule_work(struct ally_rgb_dev *led)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&led->lock, flags);
+	if (!led->removed)
+		schedule_work(&led->work);
+	spin_unlock_irqrestore(&led->lock, flags);
+}
+
+/*
+ * The RGB still has the basic 0-3 level brightness. Since the multicolour
+ * brightness is being used in place, set this to max
+ */
+static int ally_rgb_set_bright_base_max(struct hid_device *hdev)
+{
+	u8 buf[] = { FEATURE_KBD_LED_REPORT_ID1, 0xba, 0xc5, 0xc4, 0x02 };
+
+	return asus_dev_set_report(hdev, buf, sizeof(buf));
+}
+
+static void ally_rgb_do_work(struct work_struct *work)
+{
+	struct ally_rgb_dev *led = container_of(work, struct ally_rgb_dev, work);
+	int ret;
+	unsigned long flags;
+
+	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[5 + i * 3] = drvdata.led_rgb_dev->green[i];
+		buf[6 + i * 3] = drvdata.led_rgb_dev->blue[i];
+		buf[4 + i * 3] = drvdata.led_rgb_dev->red[i];
+	}
+	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_rgb_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+	struct ally_rgb_dev *led = container_of(mc_cdev, struct ally_rgb_dev, led_rgb_dev);
+	int intensity, bright;
+	unsigned long flags;
+
+	led_mc_calc_color_components(mc_cdev, brightness);
+	spin_lock_irqsave(&led->lock, flags);
+	led->update_rgb = true;
+	bright = mc_cdev->led_cdev.brightness;
+	for (int i = 0; i < 4; i++) {
+		intensity = mc_cdev->subled_info[i].intensity;
+		drvdata.led_rgb_dev->red[i] = (((intensity >> 16) & 0xFF) * bright) / 255;
+		drvdata.led_rgb_dev->green[i] = (((intensity >> 8) & 0xFF) * bright) / 255;
+		drvdata.led_rgb_dev->blue[i] = ((intensity & 0xFF) * bright) / 255;
+	}
+	spin_unlock_irqrestore(&led->lock, flags);
+	drvdata.led_rgb_data.initialized = true;
+
+	ally_rgb_schedule_work(led);
+}
+
+static int ally_rgb_set_static_from_multi(struct hid_device *hdev)
+{
+	u8 buf[17] = {FEATURE_KBD_LED_REPORT_ID1, 0xb3};
+	int ret;
+
+	/*
+	 * Set single zone single colour based on the first LED of EC software mode.
+	 * buf[2] = zone, buf[3] = mode
+	 */
+	buf[4] = drvdata.led_rgb_data.red[0];
+	buf[5] = drvdata.led_rgb_data.green[0];
+	buf[6] = drvdata.led_rgb_data.blue[0];
+
+	ret = asus_dev_set_report(hdev, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	ret = asus_dev_set_report(hdev, EC_MODE_LED_APPLY, sizeof(EC_MODE_LED_APPLY));
+	if (ret < 0)
+		return ret;
+
+	return asus_dev_set_report(hdev, EC_MODE_LED_SET, sizeof(EC_MODE_LED_SET));
+}
+
+/*
+ * Store the RGB values for restoring on resume, and set the static mode to the first LED colour
+*/
+static void ally_rgb_store_settings(void)
+{
+	int arr_size = sizeof(drvdata.led_rgb_data.red);
+
+	struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev;
+
+	drvdata.led_rgb_data.brightness = led_rgb->led_rgb_dev.led_cdev.brightness;
+
+	memcpy(drvdata.led_rgb_data.red, led_rgb->red, arr_size);
+	memcpy(drvdata.led_rgb_data.green, led_rgb->green, arr_size);
+	memcpy(drvdata.led_rgb_data.blue, led_rgb->blue, arr_size);
+
+	ally_rgb_set_static_from_multi(led_rgb->hdev);
+}
+
+static void ally_rgb_restore_settings(struct ally_rgb_dev *led_rgb, struct led_classdev *led_cdev,
+				      struct mc_subled *mc_led_info)
+{
+	int arr_size = sizeof(drvdata.led_rgb_data.red);
+
+	memcpy(led_rgb->red, drvdata.led_rgb_data.red, arr_size);
+	memcpy(led_rgb->green, drvdata.led_rgb_data.green, arr_size);
+	memcpy(led_rgb->blue, drvdata.led_rgb_data.blue, arr_size);
+	for (int i = 0; i < 4; i++) {
+		mc_led_info[i].intensity = (drvdata.led_rgb_data.red[i] << 16) |
+					   (drvdata.led_rgb_data.green[i] << 8) |
+					   drvdata.led_rgb_data.blue[i];
+	}
+	led_cdev->brightness = drvdata.led_rgb_data.brightness;
+}
+
+/* Set LEDs. Call after any setup. */
+static void ally_rgb_resume(void)
+{
+	struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev;
+	struct led_classdev *led_cdev;
+	struct mc_subled *mc_led_info;
+
+	if (!led_rgb)
+		return;
+
+	led_cdev = &led_rgb->led_rgb_dev.led_cdev;
+	mc_led_info = led_rgb->led_rgb_dev.subled_info;
+
+	if (drvdata.led_rgb_data.initialized) {
+		ally_rgb_restore_settings(led_rgb, led_cdev, mc_led_info);
+		led_rgb->update_rgb = true;
+		ally_rgb_schedule_work(led_rgb);
+		ally_rgb_set_bright_base_max(led_rgb->hdev);
+	}
+}
+
+static int ally_rgb_register(struct hid_device *hdev, struct ally_rgb_dev *led_rgb)
+{
+	struct mc_subled *mc_led_info;
+	struct led_classdev *led_cdev;
+
+	mc_led_info =
+		devm_kmalloc_array(&hdev->dev, 12, sizeof(*mc_led_info), GFP_KERNEL | __GFP_ZERO);
+	if (!mc_led_info)
+		return -ENOMEM;
+
+	mc_led_info[0].color_index = LED_COLOR_ID_RGB;
+	mc_led_info[1].color_index = LED_COLOR_ID_RGB;
+	mc_led_info[2].color_index = LED_COLOR_ID_RGB;
+	mc_led_info[3].color_index = LED_COLOR_ID_RGB;
+
+	led_rgb->led_rgb_dev.subled_info = mc_led_info;
+	led_rgb->led_rgb_dev.num_colors = 4;
+
+	led_cdev = &led_rgb->led_rgb_dev.led_cdev;
+	led_cdev->brightness = 128;
+	led_cdev->name = "ally:rgb:joystick_rings";
+	led_cdev->max_brightness = 255;
+	led_cdev->brightness_set = ally_rgb_set;
+
+	if (drvdata.led_rgb_data.initialized) {
+		ally_rgb_restore_settings(led_rgb, led_cdev, mc_led_info);
+	}
+
+	return devm_led_classdev_multicolor_register(&hdev->dev, &led_rgb->led_rgb_dev);
+}
+
+static struct ally_rgb_dev *ally_rgb_create(struct hid_device *hdev)
+{
+	struct ally_rgb_dev *led_rgb;
+	int ret;
+
+	led_rgb = devm_kzalloc(&hdev->dev, sizeof(struct ally_rgb_dev), GFP_KERNEL);
+	if (!led_rgb)
+		return ERR_PTR(-ENOMEM);
+
+	ret = ally_rgb_register(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->removed = false;
+
+	INIT_WORK(&led_rgb->work, ally_rgb_do_work);
+	led_rgb->output_worker_initialized = true;
+	spin_lock_init(&led_rgb->lock);
+
+	ally_rgb_set_bright_base_max(hdev);
+
+	/* Not marked as initialized unless ally_rgb_set() is called */
+	if (drvdata.led_rgb_data.initialized) {
+		msleep(1500);
+		led_rgb->update_rgb = true;
+		ally_rgb_schedule_work(led_rgb);
+	}
+
+	return led_rgb;
+}
+
+static void ally_rgb_remove(struct hid_device *hdev)
+{
+	struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev;
+	unsigned long flags;
+	int ep;
+
+	ep = get_endpoint_address(hdev);
+	if (ep != ALLY_CFG_INTF_IN_ADDRESS)
+		return;
+
+	if (!drvdata.led_rgb_dev || led_rgb->removed)
+		return;
+
+	spin_lock_irqsave(&led_rgb->lock, flags);
+	led_rgb->removed = true;
+	led_rgb->output_worker_initialized = false;
+	spin_unlock_irqrestore(&led_rgb->lock, flags);
+	cancel_work_sync(&led_rgb->work);
+	devm_led_classdev_multicolor_unregister(&hdev->dev, &led_rgb->led_rgb_dev);
+
+	hid_info(hdev, "Removed Ally RGB interface");
+}
+
+/**************************************************************************************************/
+/* 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) ||
+		    (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;
+}
+
+/*
+ * Very simple parse. We don't care about any other part of the string except the version section.
+ * Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
+ */
+static int mcu_parse_version_string(const u8 *response, size_t response_size)
+{
+	int dot_count = 0;
+	size_t i;
+
+	// Look for the second '.' to identify the start of the version
+	for (i = 0; i < response_size; i++) {
+		if (response[i] == '.') {
+			dot_count++;
+			if (dot_count == 2) {
+				int version =
+					simple_strtol((const char *)&response[i + 1], NULL, 10);
+				return (version >= 0) ? version : -EINVAL;
+			}
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int mcu_request_version(struct hid_device *hdev)
+{
+	const u8 request[] = { 0x5a, 0x05, 0x03, 0x31, 0x00, 0x20 };
+	size_t response_size = FEATURE_ROG_ALLY_REPORT_SIZE;
+	u8 *response;
+	int ret;
+
+	response = kzalloc(response_size, GFP_KERNEL);
+	if (!response) {
+		kfree(request);
+		return -ENOMEM;
+	}
+
+	ret = asus_dev_set_report(hdev, request, sizeof(request));
+	if (ret < 0)
+		goto error;
+
+	ret = asus_dev_get_report(hdev, response, response_size);
+	if (ret < 0)
+		goto error;
+
+	ret = mcu_parse_version_string(response, response_size);
+	if (ret < 0)
+		goto error;
+
+	goto cleanup;
+
+error:
+	hid_err(hdev, "Failed to get MCU version: %d\n", ret);
+cleanup:
+	kfree(response);
+
+	return ret;
+}
+
+static void mcu_maybe_warn_version(struct hid_device *hdev, int idProduct)
+{
+	int min_version, version;
+
+	min_version = 0;
+	version = mcu_request_version(hdev);
+	if (version) {
+		switch (idProduct) {
+		case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY:
+			min_version = ALLY_MIN_BIOS;
+			break;
+		case USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X:
+			min_version = ALLY_X_MIN_BIOS;
+			break;
+		}
+	}
+
+	hid_info(hdev, "Ally device MCU version: %d\n", version);
+	if (version <= min_version) {
+		hid_warn(hdev,
+			 "The MCU version must be %d or greater\n"
+			 "Please update your MCU with official ASUS firmware release "
+			 "which has bug fixes to make the Linux experience better\n",
+			 min_version);
+	}
+}
+
+static int ally_hid_init(struct hid_device *hdev)
+{
+	int ret;
+
+	ret = asus_dev_set_report(hdev, EC_INIT_STRING, sizeof(EC_INIT_STRING));
+	if (ret < 0) {
+		hid_err(hdev, "Ally failed to send init command: %d\n", ret);
+		return ret;
+	}
+
+	ret = asus_dev_set_report(hdev, FORCE_FEEDBACK_OFF, sizeof(FORCE_FEEDBACK_OFF));
+	if (ret < 0)
+		hid_err(hdev, "Ally failed to send init command: %d\n", ret);
+
+	return ret;
+}
+
+static int ally_hid_probe(struct hid_device *hdev, const struct hid_device_id *_id)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct usb_device *udev = interface_to_usbdev(intf);
+	u16 idProduct = le16_to_cpu(udev->descriptor.idProduct);
+	int ret, ep;
+
+	ep = get_endpoint_address(hdev);
+	if (ep < 0)
+		return ep;
+
+	if (ep != ALLY_CFG_INTF_IN_ADDRESS &&
+	    ep != 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_hid_init(hdev);
+	if (ret < 0)
+		return ret;
+
+	drvdata.hdev = hdev;
+	hid_set_drvdata(hdev, &drvdata);
+
+	/* This should almost always exist */
+	if (ep == ALLY_CFG_INTF_IN_ADDRESS) {
+		mcu_maybe_warn_version(hdev, idProduct);
+
+		drvdata.led_rgb_dev = ally_rgb_create(hdev);
+		if (IS_ERR(drvdata.led_rgb_dev))
+			hid_err(hdev, "Failed to create Ally gamepad LEDs.\n");
+		else
+			hid_info(hdev, "Created Ally RGB LED controls.\n");
+
+		drvdata.gamepad_cfg = ally_gamepad_cfg_create(hdev);
+		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_dev) && IS_ERR(drvdata.gamepad_cfg))
+			goto err_close;
+	}
+
+	/* May or may not exist */
+	if (ep == 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_hid_remove(struct hid_device *hdev)
+{
+	if (drvdata.led_rgb_dev)
+		ally_rgb_remove(hdev);
+
+	if (drvdata.ally_x)
+		ally_x_remove(hdev);
+
+	if (drvdata.gamepad_cfg)
+		ally_cfg_remove(hdev);
+
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static int ally_hid_resume(struct hid_device *hdev)
+{
+	ally_rgb_resume();
+
+	return 0;
+}
+
+static int ally_hid_reset_resume(struct hid_device *hdev)
+{
+	int ep = get_endpoint_address(hdev);
+	if (ep != ALLY_CFG_INTF_IN_ADDRESS)
+		return 0;
+
+	ally_hid_init(hdev);
+	ally_rgb_resume();
+
+	return 0;
+}
+
+static int ally_pm_thaw(struct device *dev)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+
+	return ally_hid_reset_resume(hdev);
+}
+
+static int ally_pm_suspend(struct device *dev)
+{
+	if (drvdata.led_rgb_dev) {
+		ally_rgb_store_settings();
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops ally_pm_ops = {
+	.thaw = ally_pm_thaw,
+	.suspend = ally_pm_suspend,
+	.poweroff = ally_pm_suspend,
+};
+
+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_hid_probe,
+			 .remove = ally_hid_remove,
+			 .raw_event = ally_raw_event,
+			 /* HID is the better place for resume functions, not pm_ops */
+			 .resume = ally_hid_resume,
+			 .reset_resume = ally_hid_reset_resume,
+			 .driver = {
+				 .pm = &ally_pm_ops,
+			 } };
+
+static int __init rog_ally_init(void)
+{
+	return hid_register_driver(&rog_ally_cfg);
+}
+
+static void __exit rog_ally_exit(void)
+{
+	hid_unregister_driver(&rog_ally_cfg);
+}
+
+module_init(rog_ally_init);
+module_exit(rog_ally_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..c83817589082
--- /dev/null
+++ b/drivers/hid/hid-asus-ally.h
@@ -0,0 +1,398 @@
+/* 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>
+
+/*
+ * 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_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_turbo = 0x20,
+	xpad_cmd_len_response_curve = 0x09,
+	xpad_cmd_len_adz = 0x02,
+};
+
+/* Values correspond to the actual HID byte value required */
+enum btn_pair_index {
+	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,
+};
+
+#define BTN_PAD_A             0x0101000000000000
+#define BTN_PAD_B             0x0102000000000000
+#define BTN_PAD_X             0x0103000000000000
+#define BTN_PAD_Y             0x0104000000000000
+#define BTN_PAD_LB            0x0105000000000000
+#define BTN_PAD_RB            0x0106000000000000
+#define BTN_PAD_LS            0x0107000000000000
+#define BTN_PAD_RS            0x0108000000000000
+#define BTN_PAD_DPAD_UP       0x0109000000000000
+#define BTN_PAD_DPAD_DOWN     0x010A000000000000
+#define BTN_PAD_DPAD_LEFT     0x010B000000000000
+#define BTN_PAD_DPAD_RIGHT    0x010C000000000000
+#define BTN_PAD_LT            0x010D000000000000
+#define BTN_PAD_RT            0x010E000000000000
+#define BTN_PAD_VIEW          0x0111000000000000
+#define BTN_PAD_MENU          0x0112000000000000
+#define BTN_PAD_XBOX          0x0113000000000000
+
+#define BTN_KB_M2             0x02008E0000000000
+#define BTN_KB_M1             0x02008F0000000000
+#define BTN_KB_ESC            0x0200760000000000
+#define BTN_KB_F1             0x0200500000000000
+#define BTN_KB_F2             0x0200600000000000
+#define BTN_KB_F3             0x0200400000000000
+#define BTN_KB_F4             0x02000C0000000000
+#define BTN_KB_F5             0x0200030000000000
+#define BTN_KB_F6             0x02000B0000000000
+#define BTN_KB_F7             0x0200800000000000
+#define BTN_KB_F8             0x02000A0000000000
+#define BTN_KB_F9             0x0200010000000000
+#define BTN_KB_F10            0x0200090000000000
+#define BTN_KB_F11            0x0200780000000000
+#define BTN_KB_F12            0x0200070000000000
+#define BTN_KB_F14            0x0200180000000000
+#define BTN_KB_F15            0x0200100000000000
+#define BTN_KB_BACKTICK       0x02000E0000000000
+#define BTN_KB_1              0x0200160000000000
+#define BTN_KB_2              0x02001E0000000000
+#define BTN_KB_3              0x0200260000000000
+#define BTN_KB_4              0x0200250000000000
+#define BTN_KB_5              0x02002E0000000000
+#define BTN_KB_6              0x0200360000000000
+#define BTN_KB_7              0x02003D0000000000
+#define BTN_KB_8              0x02003E0000000000
+#define BTN_KB_9              0x0200460000000000
+#define BTN_KB_0              0x0200450000000000
+#define BTN_KB_HYPHEN         0x02004E0000000000
+#define BTN_KB_EQUALS         0x0200550000000000
+#define BTN_KB_BACKSPACE      0x0200660000000000
+#define BTN_KB_TAB            0x02000D0000000000
+#define BTN_KB_Q              0x0200150000000000
+#define BTN_KB_W              0x02001D0000000000
+#define BTN_KB_E              0x0200240000000000
+#define BTN_KB_R              0x02002D0000000000
+#define BTN_KB_T              0x02002C0000000000
+#define BTN_KB_Y              0x0200350000000000
+#define BTN_KB_U              0x02003C0000000000
+#define BTN_KB_O              0x0200440000000000
+#define BTN_KB_P              0x02004D0000000000
+#define BTN_KB_LBRACKET       0x0200540000000000
+#define BTN_KB_RBRACKET       0x02005B0000000000
+#define BTN_KB_BACKSLASH      0x02005D0000000000
+#define BTN_KB_CAPS           0x0200580000000000
+#define BTN_KB_A              0x02001C0000000000
+#define BTN_KB_S              0x02001B0000000000
+#define BTN_KB_D              0x0200230000000000
+#define BTN_KB_F              0x02002B0000000000
+#define BTN_KB_G              0x0200340000000000
+#define BTN_KB_H              0x0200330000000000
+#define BTN_KB_J              0x02003B0000000000
+#define BTN_KB_K              0x0200420000000000
+#define BTN_KB_L              0x02004B0000000000
+#define BTN_KB_SEMI           0x02004C0000000000
+#define BTN_KB_QUOTE          0x0200520000000000
+#define BTN_KB_RET            0x02005A0000000000
+#define BTN_KB_LSHIFT         0x0200880000000000
+#define BTN_KB_Z              0x02001A0000000000
+#define BTN_KB_X              0x0200220000000000
+#define BTN_KB_C              0x0200210000000000
+#define BTN_KB_V              0x02002A0000000000
+#define BTN_KB_B              0x0200320000000000
+#define BTN_KB_N              0x0200310000000000
+#define BTN_KB_M              0x02003A0000000000
+#define BTN_KB_COMMA          0x0200410000000000
+#define BTN_KB_PERIOD         0x0200490000000000
+#define BTN_KB_RSHIFT         0x0200890000000000
+#define BTN_KB_LCTL           0x02008C0000000000
+#define BTN_KB_META           0x0200820000000000
+#define BTN_KB_LALT           0x02008A0000000000
+#define BTN_KB_SPACE          0x0200290000000000
+#define BTN_KB_RALT           0x02008B0000000000
+#define BTN_KB_MENU           0x0200840000000000
+#define BTN_KB_RCTL           0x02008D0000000000
+#define BTN_KB_PRNTSCN        0x0200C30000000000
+#define BTN_KB_SCRLCK         0x02007E0000000000
+#define BTN_KB_PAUSE          0x0200910000000000
+#define BTN_KB_INS            0x0200C20000000000
+#define BTN_KB_HOME           0x0200940000000000
+#define BTN_KB_PGUP           0x0200960000000000
+#define BTN_KB_DEL            0x0200C00000000000
+#define BTN_KB_END            0x0200950000000000
+#define BTN_KB_PGDWN          0x0200970000000000
+#define BTN_KB_UP_ARROW       0x0200980000000000
+#define BTN_KB_DOWN_ARROW     0x0200990000000000
+#define BTN_KB_LEFT_ARROW     0x0200910000000000
+#define BTN_KB_RIGHT_ARROW    0x02009B0000000000
+
+#define BTN_NUMPAD_LOCK       0x0200770000000000
+#define BTN_NUMPAD_FWDSLASH   0x0200900000000000
+#define BTN_NUMPAD_ASTERISK   0x02007C0000000000
+#define BTN_NUMPAD_HYPHEN     0x02007B0000000000
+#define BTN_NUMPAD_0          0x0200700000000000
+#define BTN_NUMPAD_1          0x0200690000000000
+#define BTN_NUMPAD_2          0x0200720000000000
+#define BTN_NUMPAD_3          0x02007A0000000000
+#define BTN_NUMPAD_4          0x02006B0000000000
+#define BTN_NUMPAD_5          0x0200730000000000
+#define BTN_NUMPAD_6          0x0200740000000000
+#define BTN_NUMPAD_7          0x02006C0000000000
+#define BTN_NUMPAD_8          0x0200750000000000
+#define BTN_NUMPAD_9          0x02007D0000000000
+#define BTN_NUMPAD_PLUS       0x0200790000000000
+#define BTN_NUMPAD_ENTER      0x0200810000000000
+#define BTN_NUMPAD_PERIOD     0x0200710000000000
+
+#define BTN_MOUSE_LCLICK      0x0300000001000000
+#define BTN_MOUSE_RCLICK      0x0300000002000000
+#define BTN_MOUSE_MCLICK      0x0300000003000000
+#define BTN_MOUSE_WHEEL_UP    0x0300000004000000
+#define BTN_MOUSE_WHEEL_DOWN  0x0300000005000000
+
+#define BTN_MEDIA_SCREENSHOT      0x0500001600000000
+#define BTN_MEDIA_SHOW_KEYBOARD   0x0500001900000000
+#define BTN_MEDIA_SHOW_DESKTOP    0x0500001C00000000
+#define BTN_MEDIA_START_RECORDING 0x0500001E00000000
+#define BTN_MEDIA_MIC_OFF         0x0500000100000000
+#define BTN_MEDIA_VOL_DOWN        0x0500000200000000
+#define BTN_MEDIA_VOL_UP          0x0500000300000000
+
+#define ALLY_DEVICE_ATTR_WO(_name, _sysfs_name)    \
+	struct device_attribute dev_attr_##_name = \
+		__ATTR(_sysfs_name, 0200, NULL, _name##_store)
+
+/* 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)
+
+/* button specific macros */
+#define ALLY_BTN_SHOW(_fname, _btn_name, _secondary)                           \
+	static ssize_t _fname##_show(struct device *dev,                       \
+				     struct device_attribute *attr, char *buf) \
+	{                                                                      \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;       \
+		struct btn_data *btn;                                          \
+		const char* name;                                              \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name;   \
+		name = btn_to_name(_secondary ? btn->macro : btn->button);     \
+		return sysfs_emit(buf, "%s\n", name);                          \
+	}
+
+#define ALLY_BTN_STORE(_fname, _btn_name, _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;       \
+		struct btn_data *btn;                                          \
+		u64 code;                                                      \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name;   \
+		code = name_to_btn(buf);                                       \
+		if (_secondary)                                                \
+			btn->macro = code;                                     \
+		else                                                           \
+			btn->button = code;                                    \
+		return count;                                                  \
+	}
+
+#define ALLY_TURBO_SHOW(_fname, _btn_name)                                     \
+	static ssize_t _fname##_show(struct device *dev,                       \
+				     struct device_attribute *attr, char *buf) \
+	{                                                                      \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;       \
+		struct btn_data *btn;                                          \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name;   \
+		return sysfs_emit(buf, "%d\n", btn->turbo);                    \
+	}
+
+#define ALLY_TURBO_STORE(_fname, _btn_name)                                    \
+	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;       \
+		struct btn_data *btn;                                          \
+		bool turbo;                                                    \
+		int ret; \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name;   \
+		ret = kstrtobool(buf, &turbo);                                 \
+		if (ret)                                                       \
+			return ret;                                            \
+		btn->turbo = turbo;                                            \
+		return count;                                                  \
+	}
+
+#define ALLY_DEADZONE_SHOW(_fname, _axis_name)                                 \
+	static ssize_t _fname##_show(struct device *dev,                       \
+				     struct device_attribute *attr, char *buf) \
+	{                                                                      \
+		struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg;       \
+		struct deadzone *dz;                                           \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		dz = &ally_cfg->_axis_name;                                    \
+		return sysfs_emit(buf, "%d %d\n", dz->inner, dz->outer);       \
+	}
+
+#define ALLY_DEADZONE_STORE(_fname, _axis_name)                                \
+	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;       \
+		struct hid_device *hdev = to_hid_device(dev);                  \
+		u32 inner, outer;                                              \
+		if (!drvdata.gamepad_cfg)                                      \
+			return -ENODEV;                                        \
+		if (sscanf(buf, "%d %d", &inner, &outer) != 2)                 \
+			return -EINVAL;                                        \
+		if (inner > 64 || outer > 64 || inner > outer)                 \
+			return -EINVAL;                                        \
+		ally_cfg->_axis_name.inner = inner;                            \
+		ally_cfg->_axis_name.outer = outer;                            \
+		_gamepad_apply_deadzones(hdev, ally_cfg);                      \
+		return count;                                                  \
+	}
+
+#define ALLY_DEADZONES(_fname, _mname)                                    \
+	ALLY_DEADZONE_SHOW(_fname##_deadzone, _mname);                    \
+	ALLY_DEADZONE_STORE(_fname##_deadzone, _mname);                   \
+	ALLY_DEVICE_ATTR_RW(_fname##_deadzone, deadzone)
+
+/* response curve macros */
+#define ALLY_RESP_CURVE_SHOW(_fname, _mname)                             \
+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, "%d\n", ally_cfg->ls_rc._mname);  \
+	}
+
+#define ALLY_RESP_CURVE_STORE(_fname, _mname)                            \
+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; \
+		int ret, val;                                            \
+		if (!drvdata.gamepad_cfg)                                \
+			return -ENODEV;                                  \
+		ret = kstrtoint(buf, 0, &val);                           \
+		if (ret)                                                 \
+			return ret;                                      \
+		if (val < 0 || val > 100)                                \
+			return -EINVAL;                                  \
+		ally_cfg->ls_rc._mname = val;                            \
+		return count;                                            \
+	}
+
+/* _point_n must start at 1 */
+#define ALLY_JS_RC_POINT(_fname, _mname, _num)                                 \
+	ALLY_RESP_CURVE_SHOW(_fname##_##_mname##_##_num, _mname##_pct_##_num); \
+	ALLY_RESP_CURVE_STORE(_fname##_##_mname##_##_num, _mname##_pct_##_num); \
+	ALLY_DEVICE_ATTR_RW(_fname##_##_mname##_##_num, curve_##_mname##_pct_##_num)
+
+#define ALLY_BTN_ATTRS_GROUP(_name, _fname)                               \
+	static struct attribute *_fname##_attrs[] = {                     \
+		&dev_attr_##_fname.attr,                                  \
+		&dev_attr_##_fname##_macro.attr,                          \
+	};                                                                \
+	static const struct attribute_group _fname##_attr_group = {       \
+		.name = __stringify(_name),                               \
+		.attrs = _fname##_attrs,                                  \
+	}
+
+#define _ALLY_BTN_REMAP(_fname, _btn_name)                              \
+	ALLY_BTN_SHOW(btn_mapping_##_fname##_remap, _btn_name, false);  \
+	ALLY_BTN_STORE(btn_mapping_##_fname##_remap, _btn_name, false); \
+	ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_remap, remap);
+
+#define _ALLY_BTN_MACRO(_fname, _btn_name)                             \
+	ALLY_BTN_SHOW(btn_mapping_##_fname##_macro, _btn_name, true);  \
+	ALLY_BTN_STORE(btn_mapping_##_fname##_macro, _btn_name, true); \
+	ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_macro, macro_remap);
+
+#define ALLY_BTN_MAPPING(_fname, _btn_name)                                       \
+	_ALLY_BTN_REMAP(_fname, _btn_name)                                        \
+	_ALLY_BTN_MACRO(_fname, _btn_name)                                        \
+	static struct attribute *_fname##_attrs[] = {                             \
+		&dev_attr_btn_mapping_##_fname##_remap.attr,                      \
+		&dev_attr_btn_mapping_##_fname##_macro.attr,                      \
+		NULL,                                                             \
+	};                                                                        \
+	static const struct attribute_group btn_mapping_##_fname##_attr_group = { \
+		.name = __stringify(btn_##_fname),                                \
+		.attrs = _fname##_attrs,                                          \
+	}
+
+#define ALLY_TURBO_BTN_MAPPING(_fname, _btn_name)                                 \
+	_ALLY_BTN_REMAP(_fname, _btn_name)                                        \
+	_ALLY_BTN_MACRO(_fname, _btn_name)                                        \
+	ALLY_TURBO_SHOW(btn_mapping_##_fname##_turbo, _btn_name);                 \
+	ALLY_TURBO_STORE(btn_mapping_##_fname##_turbo, _btn_name);                \
+	ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_turbo, turbo);                 \
+	static struct attribute *_fname##_turbo_attrs[] = {                       \
+		&dev_attr_btn_mapping_##_fname##_remap.attr,                      \
+		&dev_attr_btn_mapping_##_fname##_macro.attr,                      \
+		&dev_attr_btn_mapping_##_fname##_turbo.attr,                      \
+		NULL,                                                             \
+	};                                                                        \
+	static const struct attribute_group btn_mapping_##_fname##_attr_group = { \
+		.name = __stringify(btn_##_fname),                                \
+		.attrs = _fname##_turbo_attrs,                                    \
+	}
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index a4b47319ad8e..7c5269cd4d76 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 },
@@ -1301,4 +1317,5 @@ static struct hid_driver asus_driver = {
 };
 module_hid_driver(asus_driver);
 
+MODULE_IMPORT_NS(ASUS_WMI);
 MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 08a3c863f80a..2e9b739d9ad6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2053,6 +2053,17 @@ config SENSORS_SCH5636
 	  This driver can also be built as a module. If so, the module
 	  will be called sch5636.
 
+config SENSORS_STEAMDECK
+	tristate "Steam Deck EC sensors"
+	depends on MFD_STEAMDECK
+	help
+	  If you say yes here you get support for the hardware
+	  monitoring features exposed by EC firmware on Steam Deck
+	  devices
+
+	  This driver can also be built as a module. If so, the module
+	  will be called steamdeck-hwmon.
+
 config SENSORS_STTS751
 	tristate "ST Microelectronics STTS751"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 9554d2fdcf7b..21111114e3dc 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -207,6 +207,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
 obj-$(CONFIG_SENSORS_SMSC47M1)	+= smsc47m1.o
 obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
 obj-$(CONFIG_SENSORS_SPARX5)	+= sparx5-temp.o
+obj-$(CONFIG_SENSORS_STEAMDECK) += steamdeck-hwmon.o
 obj-$(CONFIG_SENSORS_SPD5118)	+= spd5118.o
 obj-$(CONFIG_SENSORS_STTS751)	+= stts751.o
 obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o
diff --git a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c
new file mode 100644
index 000000000000..9d0a5471b181
--- /dev/null
+++ b/drivers/hwmon/steamdeck-hwmon.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Steam Deck EC sensors driver
+ *
+ * Copyright (C) 2021-2022 Valve Corporation
+ */
+
+#include <linux/acpi.h>
+#include <linux/hwmon.h>
+#include <linux/platform_device.h>
+
+#define STEAMDECK_HWMON_NAME	"steamdeck-hwmon"
+
+struct steamdeck_hwmon {
+	struct acpi_device *adev;
+};
+
+static long
+steamdeck_hwmon_get(struct steamdeck_hwmon *sd, const char *method)
+{
+	unsigned long long val;
+	if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle,
+					       (char *)method, NULL, &val)))
+		return -EIO;
+
+	return val;
+}
+
+static int
+steamdeck_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+		     u32 attr, int channel, long *out)
+{
+	struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
+
+	switch (type) {
+	case hwmon_curr:
+		if (attr != hwmon_curr_input)
+			return -EOPNOTSUPP;
+
+		*out = steamdeck_hwmon_get(sd, "PDAM");
+		if (*out < 0)
+			return *out;
+		break;
+	case hwmon_in:
+		if (attr != hwmon_in_input)
+			return -EOPNOTSUPP;
+
+		*out = steamdeck_hwmon_get(sd, "PDVL");
+		if (*out < 0)
+			return *out;
+		break;
+	case hwmon_temp:
+		if (attr != hwmon_temp_input)
+			return -EOPNOTSUPP;
+
+		*out = steamdeck_hwmon_get(sd, "BATT");
+		if (*out < 0)
+			return *out;
+		/*
+		 * Assuming BATT returns deg C we need to mutiply it
+		 * by 1000 to convert to mC
+		 */
+		*out *= 1000;
+		break;
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			*out = steamdeck_hwmon_get(sd, "FANR");
+			if (*out < 0)
+				return *out;
+			break;
+		case hwmon_fan_target:
+			*out = steamdeck_hwmon_get(sd, "FSSR");
+			if (*out < 0)
+				return *out;
+			break;
+		case hwmon_fan_fault:
+			*out = steamdeck_hwmon_get(sd, "FANC");
+			if (*out < 0)
+				return *out;
+			/*
+			 * FANC (Fan check):
+			 * 0: Abnormal
+			 * 1: Normal
+			 */
+			*out = !*out;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int
+steamdeck_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
+			    u32 attr, int channel, const char **str)
+{
+	switch (type) {
+		/*
+		 * These two aren't, strictly speaking, measured. EC
+		 * firmware just reports what PD negotiation resulted
+		 * in.
+		 */
+	case hwmon_curr:
+		*str = "PD Contract Current";
+		break;
+	case hwmon_in:
+		*str = "PD Contract Voltage";
+		break;
+	case hwmon_temp:
+		*str = "Battery Temp";
+		break;
+	case hwmon_fan:
+		*str = "System Fan";
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int
+steamdeck_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+		      u32 attr, int channel, long val)
+{
+	struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
+
+	if (type != hwmon_fan ||
+	    attr != hwmon_fan_target)
+		return -EOPNOTSUPP;
+
+	val = clamp_val(val, 0, 7300);
+
+	if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
+						    "FANS", val)))
+		return -EIO;
+
+	return 0;
+}
+
+static umode_t
+steamdeck_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
+			   u32 attr, int channel)
+{
+	if (type == hwmon_fan &&
+	    attr == hwmon_fan_target)
+		return 0644;
+
+	return 0444;
+}
+
+static const struct hwmon_channel_info *steamdeck_hwmon_info[] = {
+	HWMON_CHANNEL_INFO(in,
+			   HWMON_I_INPUT | HWMON_I_LABEL),
+	HWMON_CHANNEL_INFO(curr,
+			   HWMON_C_INPUT | HWMON_C_LABEL),
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT | HWMON_T_LABEL),
+	HWMON_CHANNEL_INFO(fan,
+			   HWMON_F_INPUT | HWMON_F_LABEL |
+			   HWMON_F_TARGET | HWMON_F_FAULT),
+	NULL
+};
+
+static const struct hwmon_ops steamdeck_hwmon_ops = {
+	.is_visible = steamdeck_hwmon_is_visible,
+	.read = steamdeck_hwmon_read,
+	.read_string = steamdeck_hwmon_read_string,
+	.write = steamdeck_hwmon_write,
+};
+
+static const struct hwmon_chip_info steamdeck_hwmon_chip_info = {
+	.ops = &steamdeck_hwmon_ops,
+	.info = steamdeck_hwmon_info,
+};
+
+
+static ssize_t
+steamdeck_hwmon_simple_store(struct device *dev, const char *buf, size_t count,
+			     const char *method,
+			     unsigned long upper_limit)
+{
+	struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
+	unsigned long value;
+
+	if (kstrtoul(buf, 10, &value) || value >= upper_limit)
+		return -EINVAL;
+
+	if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
+						    (char *)method, value)))
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t
+steamdeck_hwmon_simple_show(struct device *dev, char *buf,
+			    const char *method)
+{
+	struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
+	unsigned long value;
+
+	value = steamdeck_hwmon_get(sd, method);
+	if (value < 0)
+		return value;
+
+	return sprintf(buf, "%ld\n", value);
+}
+
+#define STEAMDECK_HWMON_ATTR_RW(_name, _set_method, _get_method,	\
+				_upper_limit)				\
+	static ssize_t _name##_show(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		return steamdeck_hwmon_simple_show(dev, buf,		\
+						   _get_method);	\
+	}								\
+	static ssize_t _name##_store(struct device *dev,		\
+				     struct device_attribute *attr,	\
+				     const char *buf, size_t count)	\
+	{								\
+		return steamdeck_hwmon_simple_store(dev, buf, count,	\
+						    _set_method,	\
+						    _upper_limit);	\
+	}								\
+	static DEVICE_ATTR_RW(_name)
+
+STEAMDECK_HWMON_ATTR_RW(max_battery_charge_level, "FCBL", "SFBL", 101);
+STEAMDECK_HWMON_ATTR_RW(max_battery_charge_rate,  "CHGR", "GCHR", 101);
+
+static struct attribute *steamdeck_hwmon_attributes[] = {
+	&dev_attr_max_battery_charge_level.attr,
+	&dev_attr_max_battery_charge_rate.attr,
+	NULL
+};
+
+static const struct attribute_group steamdeck_hwmon_group = {
+	.attrs = steamdeck_hwmon_attributes,
+};
+
+static const struct attribute_group *steamdeck_hwmon_groups[] = {
+	&steamdeck_hwmon_group,
+	NULL
+};
+
+static int steamdeck_hwmon_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct steamdeck_hwmon *sd;
+	struct device *hwmon;
+
+	sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
+	if (!sd)
+		return -ENOMEM;
+
+	sd->adev = ACPI_COMPANION(dev->parent);
+	hwmon = devm_hwmon_device_register_with_info(dev,
+						     "steamdeck_hwmon",
+						     sd,
+						     &steamdeck_hwmon_chip_info,
+						     steamdeck_hwmon_groups);
+	if (IS_ERR(hwmon)) {
+		dev_err(dev, "Failed to register HWMON device");
+		return PTR_ERR(hwmon);
+	}
+
+	return 0;
+}
+
+static const struct platform_device_id steamdeck_hwmon_id_table[] = {
+	{ .name = STEAMDECK_HWMON_NAME },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, steamdeck_hwmon_id_table);
+
+static struct platform_driver steamdeck_hwmon_driver = {
+	.probe = steamdeck_hwmon_probe,
+	.driver = {
+		.name = STEAMDECK_HWMON_NAME,
+	},
+	.id_table = steamdeck_hwmon_id_table,
+};
+module_platform_driver(steamdeck_hwmon_driver);
+
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("Steam Deck EC sensors driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index b784bb74a837..fb3d45acfd9d 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -959,6 +959,13 @@ config LEDS_ACER_A500
 	  This option enables support for the Power Button LED of
 	  Acer Iconia Tab A500.
 
+config LEDS_STEAMDECK
+	tristate "LED support for Steam Deck"
+	depends on LEDS_CLASS && MFD_STEAMDECK
+	help
+	  This option enabled support for the status LED (next to the
+	  power button) on Steam Deck
+
 source "drivers/leds/blink/Kconfig"
 
 comment "Flash and Torch LED drivers"
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 18afbb5a23ee..b0ff19d14419 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -81,6 +81,7 @@ obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
+obj-$(CONFIG_LEDS_STEAMDECK)		+= leds-steamdeck.o
 obj-$(CONFIG_LEDS_SUN50I_A100)		+= leds-sun50i-a100.o
 obj-$(CONFIG_LEDS_SUNFIRE)		+= leds-sunfire.o
 obj-$(CONFIG_LEDS_SYSCON)		+= leds-syscon.o
diff --git a/drivers/leds/leds-steamdeck.c b/drivers/leds/leds-steamdeck.c
new file mode 100644
index 000000000000..56d31d2dd099
--- /dev/null
+++ b/drivers/leds/leds-steamdeck.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Steam Deck EC MFD LED cell driver
+ *
+ * Copyright (C) 2021-2022 Valve Corporation
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+
+struct steamdeck_led {
+	struct acpi_device *adev;
+	struct led_classdev cdev;
+};
+
+static int steamdeck_leds_brightness_set(struct led_classdev *cdev,
+					 enum led_brightness value)
+{
+	struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led,
+						cdev);
+
+	if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
+						    "CHBV", value)))
+		return -EIO;
+
+	return 0;
+}
+
+static int steamdeck_leds_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct steamdeck_led *sd;
+	int ret;
+
+	sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
+	if (!sd)
+		return -ENOMEM;
+
+	sd->adev = ACPI_COMPANION(dev->parent);
+
+	sd->cdev.name = "status:white";
+	sd->cdev.brightness_set_blocking = steamdeck_leds_brightness_set;
+	sd->cdev.max_brightness = 100;
+
+	ret = devm_led_classdev_register(dev, &sd->cdev);
+	if (ret) {
+		dev_err(dev, "Failed to register LEDs device: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct platform_device_id steamdeck_leds_id_table[] = {
+	{ .name = "steamdeck-leds" },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, steamdeck_leds_id_table);
+
+static struct platform_driver steamdeck_leds_driver = {
+	.probe = steamdeck_leds_probe,
+	.driver = {
+		.name = "steamdeck-leds",
+	},
+	.id_table = steamdeck_leds_id_table,
+};
+module_platform_driver(steamdeck_leds_driver);
+
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("Steam Deck LEDs driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f9325bcce1b9..8e7c9b64b077 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2402,5 +2402,16 @@ config MFD_RSMU_SPI
 	  Additional drivers must be enabled in order to use the functionality
 	  of the device.
 
+config MFD_STEAMDECK
+	tristate "Valve Steam Deck"
+	select MFD_CORE
+	depends on ACPI
+	depends on X86_64 || COMPILE_TEST
+	help
+	  This driver registers various MFD cells that expose aspects
+	  of Steam Deck specific ACPI functionality.
+
+	  Say N here, unless you are running on Steam Deck hardware.
+
 endmenu
 endif
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 2a9f91e81af8..8950b482f267 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -289,3 +289,5 @@ obj-$(CONFIG_MFD_ATC260X_I2C)	+= atc260x-i2c.o
 
 obj-$(CONFIG_MFD_RSMU_I2C)	+= rsmu_i2c.o rsmu_core.o
 obj-$(CONFIG_MFD_RSMU_SPI)	+= rsmu_spi.o rsmu_core.o
+
+obj-$(CONFIG_MFD_STEAMDECK)	+= steamdeck.o
diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c
new file mode 100644
index 000000000000..a60fa7db9141
--- /dev/null
+++ b/drivers/mfd/steamdeck.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Steam Deck EC MFD core driver
+ *
+ * Copyright (C) 2021-2022 Valve Corporation
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+
+#define STEAMDECK_STA_OK			\
+	(ACPI_STA_DEVICE_ENABLED |		\
+	 ACPI_STA_DEVICE_PRESENT |		\
+	 ACPI_STA_DEVICE_FUNCTIONING)
+
+struct steamdeck {
+	struct acpi_device *adev;
+	struct device *dev;
+};
+
+#define STEAMDECK_ATTR_RO(_name, _method)				\
+	static ssize_t _name##_show(struct device *dev,			\
+				    struct device_attribute *attr,	\
+				    char *buf)				\
+	{								\
+		struct steamdeck *sd = dev_get_drvdata(dev);		\
+		unsigned long long val;					\
+									\
+		if (ACPI_FAILURE(acpi_evaluate_integer(			\
+					 sd->adev->handle,		\
+					 _method, NULL, &val)))		\
+			return -EIO;					\
+									\
+		return sysfs_emit(buf, "%llu\n", val);			\
+	}								\
+	static DEVICE_ATTR_RO(_name)
+
+STEAMDECK_ATTR_RO(firmware_version, "PDFW");
+STEAMDECK_ATTR_RO(board_id, "BOID");
+
+static ssize_t controller_board_power_store(struct device *dev,
+					    struct device_attribute *attr,
+					    const char *buf, size_t count)
+{
+	struct steamdeck *sd = dev_get_drvdata(dev);
+	bool enabled;
+	ssize_t ret = kstrtobool(buf, &enabled);
+
+	if (ret)
+		return ret;
+
+	if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
+						    "SCBP", enabled)))
+		return -EIO;
+
+	return count;
+}
+static DEVICE_ATTR_WO(controller_board_power);
+
+static struct attribute *steamdeck_attrs[] = {
+	&dev_attr_firmware_version.attr,
+	&dev_attr_board_id.attr,
+	&dev_attr_controller_board_power.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(steamdeck);
+
+static const struct mfd_cell steamdeck_cells[] = {
+	{ .name = "steamdeck-hwmon"  },
+	{ .name = "steamdeck-leds"   },
+	{ .name = "steamdeck-extcon" },
+};
+
+static void steamdeck_remove_sysfs_groups(void *data)
+{
+	struct steamdeck *sd = data;
+
+	sysfs_remove_groups(&sd->dev->kobj, steamdeck_groups);
+}
+
+static int steamdeck_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	unsigned long long sta;
+	struct steamdeck *sd;
+	acpi_status status;
+	int ret;
+
+	sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
+	if (!sd)
+		return -ENOMEM;
+	sd->adev = ACPI_COMPANION(dev);
+	sd->dev = dev;
+	platform_set_drvdata(pdev, sd);
+
+	status = acpi_evaluate_integer(sd->adev->handle, "_STA",
+				       NULL, &sta);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "Status check failed (0x%x)\n", status);
+		return -EINVAL;
+	}
+
+	if ((sta & STEAMDECK_STA_OK) != STEAMDECK_STA_OK) {
+		dev_err(dev, "Device is not ready\n");
+		return -EINVAL;
+	}
+
+	ret = sysfs_create_groups(&dev->kobj, steamdeck_groups);
+	if (ret) {
+		dev_err(dev, "Failed to create sysfs group\n");
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(dev, steamdeck_remove_sysfs_groups,
+				       sd);
+	if (ret) {
+		dev_err(dev, "Failed to register devres action\n");
+		return ret;
+	}
+
+	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+				    steamdeck_cells, ARRAY_SIZE(steamdeck_cells),
+				    NULL, 0, NULL);
+}
+
+static const struct acpi_device_id steamdeck_device_ids[] = {
+	{ "VLV0100", 0 },
+	{ "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, steamdeck_device_ids);
+
+static struct platform_driver steamdeck_driver = {
+	.probe = steamdeck_probe,
+	.driver = {
+		.name = "steamdeck",
+		.acpi_match_table = steamdeck_device_ids,
+	},
+};
+module_platform_driver(steamdeck_driver);
+
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("Steam Deck EC MFD core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index ccf4ad35fdc3..ee935c23ca6a 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -724,7 +724,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
 		.name = "qca2066 hw2.1",
 		.hw_rev = ATH11K_HW_QCA2066_HW21,
 		.fw = {
-			.dir = "QCA2066/hw2.1",
+			.dir = "QCA206X/hw2.1",
 			.board_size = 256 * 1024,
 			.cal_offset = 128 * 1024,
 		},
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 3875abba5a79..d0fc68d93f48 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -265,6 +265,18 @@ config ASUS_WIRELESS
 	  If you choose to compile this driver as a module the module will be
 	  called asus-wireless.
 
+config ASUS_ARMOURY
+	tristate "ASUS Armoury driver"
+	depends on ASUS_WMI
+	select FW_ATTR_CLASS
+	help
+	  Say Y here if you have a WMI aware Asus machine and would like to use the
+	  firmware_attributes API to control various settings typically exposed in
+	  the ASUS Armoury Crate application available on Windows.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called asus-armoury.
+
 config ASUS_WMI
 	tristate "ASUS WMI Driver"
 	depends on ACPI_WMI
@@ -287,6 +299,15 @@ config ASUS_WMI
 	  To compile this driver as a module, choose M here: the module will
 	  be called asus-wmi.
 
+config ASUS_WMI_DEPRECATED_ATTRS
+	bool "BIOS option support in WMI platform (DEPRECATED)"
+	depends on ASUS_WMI
+	default y
+	help
+	  Say Y to expose the configurable BIOS options through the asus-wmi
+	  driver. This can be used with or without the asus-armoury driver which
+	  has the same attributes, but more, and better features.
+
 config ASUS_NB_WMI
 	tristate "Asus Notebook WMI Driver"
 	depends on ASUS_WMI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e1b142947067..fe3e7e7dede8 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_APPLE_GMUX)	+= apple-gmux.o
 # ASUS
 obj-$(CONFIG_ASUS_LAPTOP)	+= asus-laptop.o
 obj-$(CONFIG_ASUS_WIRELESS)	+= asus-wireless.o
+obj-$(CONFIG_ASUS_ARMOURY)	+= asus-armoury.o
 obj-$(CONFIG_ASUS_WMI)		+= asus-wmi.o
 obj-$(CONFIG_ASUS_NB_WMI)	+= asus-nb-wmi.o
 obj-$(CONFIG_ASUS_TF103C_DOCK)	+= asus-tf103c-dock.o
diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c
new file mode 100644
index 000000000000..fb4ae804521d
--- /dev/null
+++ b/drivers/platform/x86/asus-armoury.c
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Asus Armoury (WMI) attributes driver. This driver uses the fw_attributes
+ * class to expose the various WMI functions that many gaming and some
+ * non-gaming ASUS laptops have available.
+ * These typically don't fit anywhere else in the sysfs such as under LED class,
+ * hwmon or other, and are set in Windows using the ASUS Armoury Crate tool.
+ *
+ * Copyright(C) 2024 Luke Jones <luke@ljones.dev>
+ */
+
+#include "linux/cleanup.h"
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/x86/asus-wmi.h>
+#include <linux/types.h>
+#include <linux/acpi.h>
+
+#include "asus-armoury.h"
+#include "firmware_attributes_class.h"
+
+#define DEBUG
+
+#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C"
+
+#define ASUS_MINI_LED_MODE_MASK   0x03
+/* Standard modes for devices with only on/off */
+#define ASUS_MINI_LED_OFF         0x00
+#define ASUS_MINI_LED_ON          0x01
+/* Like "on" but the effect is more vibrant or brighter */
+#define ASUS_MINI_LED_STRONG_MODE 0x02
+/* New modes for devices with 3 mini-led mode types */
+#define ASUS_MINI_LED_2024_WEAK   0x00
+#define ASUS_MINI_LED_2024_STRONG 0x01
+#define ASUS_MINI_LED_2024_OFF    0x02
+
+#define ASUS_POWER_CORE_MASK GENMASK(15, 8)
+#define ASUS_PERF_CORE_MASK GENMASK(7, 0)
+
+enum cpu_core_type {
+	CPU_CORE_PERF = 0,
+	CPU_CORE_POWER,
+};
+
+enum cpu_core_value {
+	CPU_CORE_DEFAULT = 0,
+	CPU_CORE_MIN,
+	CPU_CORE_MAX,
+	CPU_CORE_CURRENT,
+};
+
+#define CPU_PERF_CORE_COUNT_MIN 4
+#define CPU_POWR_CORE_COUNT_MIN 0
+
+/* Default limits for tunables available on ASUS ROG laptops */
+#define NVIDIA_BOOST_MIN      5
+#define NVIDIA_BOOST_MAX      25
+#define NVIDIA_TEMP_MIN       75
+#define NVIDIA_TEMP_MAX       87
+#define NVIDIA_POWER_MIN      0
+#define NVIDIA_POWER_MAX      70
+#define NVIDIA_POWER_DEFAULT  70
+#define PPT_CPU_LIMIT_MIN     5
+#define PPT_CPU_LIMIT_MAX     150
+#define PPT_CPU_LIMIT_DEFAULT 80
+#define PPT_PLATFORM_MIN      5
+#define PPT_PLATFORM_MAX      100
+#define PPT_PLATFORM_DEFAULT  80
+
+/* Tunables provided by ASUS for gaming laptops */
+struct rog_tunables {
+	u32 cpu_default;
+	u32 cpu_min;
+	u32 cpu_max;
+
+	u32 platform_default;
+	u32 platform_min;
+	u32 platform_max;
+
+	u32 ppt_pl1_spl; // cpu
+	u32 ppt_pl2_sppt; // cpu
+	u32 ppt_apu_sppt; // plat
+	u32 ppt_platform_sppt; // plat
+	u32 ppt_fppt; // cpu
+
+	u32 nv_boost_default;
+	u32 nv_boost_min;
+	u32 nv_boost_max;
+	u32 nv_dynamic_boost;
+
+	u32 nv_temp_default;
+	u32 nv_temp_min;
+	u32 nv_temp_max;
+	u32 nv_temp_target;
+
+	u32 dgpu_tgp_default;
+	u32 dgpu_tgp_min;
+	u32 dgpu_tgp_max;
+	u32 dgpu_tgp;
+
+	u32 cur_perf_cores;
+	u32 min_perf_cores;
+	u32 max_perf_cores;
+	u32 cur_power_cores;
+	u32 min_power_cores;
+	u32 max_power_cores;
+};
+
+static const struct class *fw_attr_class;
+
+struct asus_armoury_priv {
+	struct device *fw_attr_dev;
+	struct kset *fw_attr_kset;
+
+	struct rog_tunables *rog_tunables;
+	u32 mini_led_dev_id;
+	u32 gpu_mux_dev_id;
+
+	struct mutex mutex;
+};
+
+static struct asus_armoury_priv asus_armoury = {
+	.mutex = __MUTEX_INITIALIZER(asus_armoury.mutex)
+};
+
+struct fw_attrs_group {
+	bool pending_reboot;
+};
+
+static struct fw_attrs_group fw_attrs = {
+	.pending_reboot = false,
+};
+
+struct asus_attr_group {
+	const struct attribute_group *attr_group;
+	u32 wmi_devid;
+};
+
+static bool asus_wmi_is_present(u32 dev_id)
+{
+	u32 retval;
+	int status;
+
+	status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, &retval);
+	pr_debug("%s called (0x%08x), retval: 0x%08x\n", __func__, dev_id, retval);
+
+	return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
+}
+
+static void asus_set_reboot_and_signal_event(void)
+{
+	fw_attrs.pending_reboot = true;
+	kobject_uevent(&asus_armoury.fw_attr_dev->kobj, KOBJ_CHANGE);
+}
+
+static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%d\n", fw_attrs.pending_reboot);
+}
+
+static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot);
+
+static bool asus_bios_requires_reboot(struct kobj_attribute *attr)
+{
+	return !strcmp(attr->attr.name, "gpu_mux_mode") ||
+	       !strcmp(attr->attr.name, "cores_performance") ||
+	       !strcmp(attr->attr.name, "cores_efficiency") ||
+	       !strcmp(attr->attr.name, "panel_hd_mode");
+}
+
+static int armoury_wmi_set_devstate(struct kobj_attribute *attr, u32 value, u32 wmi_dev)
+{
+	u32 result;
+	int err;
+
+	guard(mutex)(&asus_armoury.mutex);
+	err = asus_wmi_set_devstate(wmi_dev, value, &result);
+	if (err) {
+		pr_err("Failed to set %s: %d\n", attr->attr.name, err);
+		return err;
+	}
+	/*
+	 * !1 is usually considered a fail by ASUS, but some WMI methods do use > 1
+	 * to return a status code or similar.
+	 */
+	if (result < 1) {
+		pr_err("Failed to set %s: (result): 0x%x\n", attr->attr.name, result);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+/**
+ * attr_int_store() - Send an int to wmi method, checks if within min/max exclusive.
+ * @kobj: Pointer to the driver object.
+ * @attr: Pointer to the attribute calling this function.
+ * @buf: The buffer to read from, this is parsed to `int` type.
+ * @count: Required by sysfs attribute macros, pass in from the callee attr.
+ * @min: Minimum accepted value. Below this returns -EINVAL.
+ * @max: Maximum accepted value. Above this returns -EINVAL.
+ * @store_value: Pointer to where the parsed value should be stored.
+ * @wmi_dev: The WMI function ID to use.
+ *
+ * This function is intended to be generic so it can be called from any "_store"
+ * attribute which works only with integers. The integer to be sent to the WMI method
+ * is range checked and an error returned if out of range.
+ *
+ * If the value is valid and WMI is success, then the sysfs attribute is notified
+ * and if asus_bios_requires_reboot() is true then reboot attribute is also notified.
+ *
+ * Returns: Either count, or an error.
+ */
+static ssize_t attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
+			      size_t count, u32 min, u32 max, u32 *store_value, u32 wmi_dev)
+{
+	u32 value;
+	int err;
+
+	err = kstrtouint(buf, 10, &value);
+	if (err)
+		return err;
+
+	if (value < min || value > max)
+		return -EINVAL;
+
+	err = armoury_wmi_set_devstate(attr, value, wmi_dev);
+	if (err)
+		return err;
+
+	if (store_value != NULL)
+		*store_value = value;
+	sysfs_notify(kobj, NULL, attr->attr.name);
+
+	if (asus_bios_requires_reboot(attr))
+		asus_set_reboot_and_signal_event();
+
+	return count;
+}
+
+/* Mini-LED mode **************************************************************/
+static ssize_t mini_led_mode_current_value_show(struct kobject *kobj,
+						struct kobj_attribute *attr, char *buf)
+{
+	u32 value;
+	int err;
+
+	err = asus_wmi_get_devstate_dsts(asus_armoury.mini_led_dev_id, &value);
+	if (err)
+		return err;
+
+	value &= ASUS_MINI_LED_MODE_MASK;
+
+	/*
+	 * Remap the mode values to match previous generation mini-LED. The last gen
+	 * WMI 0 == off, while on this version WMI 2 == off (flipped).
+	 */
+	if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
+		switch (value) {
+		case ASUS_MINI_LED_2024_WEAK:
+			value = ASUS_MINI_LED_ON;
+			break;
+		case ASUS_MINI_LED_2024_STRONG:
+			value = ASUS_MINI_LED_STRONG_MODE;
+			break;
+		case ASUS_MINI_LED_2024_OFF:
+			value = ASUS_MINI_LED_OFF;
+			break;
+		}
+	}
+
+	return sysfs_emit(buf, "%u\n", value);
+}
+
+static ssize_t mini_led_mode_current_value_store(struct kobject *kobj,
+						 struct kobj_attribute *attr,
+						const char *buf, size_t count)
+{
+	u32 mode;
+	int err;
+
+	err = kstrtou32(buf, 10, &mode);
+	if (err)
+		return err;
+
+	if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE &&
+	    mode > ASUS_MINI_LED_ON)
+		return -EINVAL;
+	if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2 &&
+	    mode > ASUS_MINI_LED_STRONG_MODE)
+		return -EINVAL;
+
+	/*
+	 * Remap the mode values so expected behaviour is the same as the last
+	 * generation of mini-LED with 0 == off, 1 == on.
+	 */
+	if (asus_armoury.mini_led_dev_id == ASUS_WMI_DEVID_MINI_LED_MODE2) {
+		switch (mode) {
+		case ASUS_MINI_LED_OFF:
+			mode = ASUS_MINI_LED_2024_OFF;
+			break;
+		case ASUS_MINI_LED_ON:
+			mode = ASUS_MINI_LED_2024_WEAK;
+			break;
+		case ASUS_MINI_LED_STRONG_MODE:
+			mode = ASUS_MINI_LED_2024_STRONG;
+			break;
+		}
+	}
+
+	err = armoury_wmi_set_devstate(attr, mode, asus_armoury.mini_led_dev_id);
+	if (err)
+		return err;
+
+	sysfs_notify(kobj, NULL, attr->attr.name);
+
+	return count;
+}
+
+static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj,
+						  struct kobj_attribute *attr, char *buf)
+{
+	switch (asus_armoury.mini_led_dev_id) {
+	case ASUS_WMI_DEVID_MINI_LED_MODE:
+		return sysfs_emit(buf, "0;1\n");
+	case ASUS_WMI_DEVID_MINI_LED_MODE2:
+		return sysfs_emit(buf, "0;1;2\n");
+	}
+
+	return sysfs_emit(buf, "0\n");
+}
+
+ATTR_GROUP_ENUM_CUSTOM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode");
+
+static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj,
+						struct kobj_attribute *attr, const char *buf,
+						size_t count)
+{
+	int result, err;
+	u32 optimus;
+
+	err = kstrtou32(buf, 10, &optimus);
+	if (err)
+		return err;
+
+	if (optimus > 1)
+		return -EINVAL;
+
+	if (asus_wmi_is_present(ASUS_WMI_DEVID_DGPU)) {
+		err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_DGPU, &result);
+		if (err)
+			return err;
+		if (result && !optimus) {
+			err = -ENODEV;
+			pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %02X %02X %d\n",
+				result, optimus, err);
+			return err;
+		}
+	}
+
+	if (asus_wmi_is_present(ASUS_WMI_DEVID_EGPU)) {
+		err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_EGPU, &result);
+		if (err)
+			return err;
+		if (result && !optimus) {
+			err = -ENODEV;
+			pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n",
+				err);
+			return err;
+		}
+	}
+
+	err = armoury_wmi_set_devstate(attr, optimus, asus_armoury.gpu_mux_dev_id);
+	if (err)
+		return err;
+
+	sysfs_notify(kobj, NULL, attr->attr.name);
+	asus_set_reboot_and_signal_event();
+
+	return count;
+}
+WMI_SHOW_INT(gpu_mux_mode_current_value, "%d\n", asus_armoury.gpu_mux_dev_id);
+ATTR_GROUP_BOOL_CUSTOM(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode");
+
+/*
+ * A user may be required to store the value twice, typical store first, then
+ * rescan PCI bus to activate power, then store a second time to save correctly.
+ */
+static ssize_t dgpu_disable_current_value_store(struct kobject *kobj,
+						struct kobj_attribute *attr, const char *buf,
+						size_t count)
+{
+	int result, err;
+	u32 disable;
+
+	err = kstrtou32(buf, 10, &disable);
+	if (err)
+		return err;
+
+	if (disable > 1)
+		return -EINVAL;
+
+	if (asus_armoury.gpu_mux_dev_id) {
+		err = asus_wmi_get_devstate_dsts(asus_armoury.gpu_mux_dev_id, &result);
+		if (err)
+			return err;
+		if (!result && disable) {
+			err = -ENODEV;
+			pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err);
+			return err;
+		}
+		// TODO: handle a > 1 result, shouold do a PCI rescan and run again
+	}
+
+	err = armoury_wmi_set_devstate(attr, disable, ASUS_WMI_DEVID_DGPU);
+	if (err)
+		return err;
+
+	sysfs_notify(kobj, NULL, attr->attr.name);
+
+	return count;
+}
+WMI_SHOW_INT(dgpu_disable_current_value, "%d\n", ASUS_WMI_DEVID_DGPU);
+ATTR_GROUP_BOOL_CUSTOM(dgpu_disable, "dgpu_disable", "Disable the dGPU");
+
+/* The ACPI call to enable the eGPU also disables the internal dGPU */
+static ssize_t egpu_enable_current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t count)
+{
+	int result, err;
+	u32 enable;
+
+	err = kstrtou32(buf, 10, &enable);
+	if (err)
+		return err;
+
+	if (enable > 1)
+		return -EINVAL;
+
+	err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_EGPU_CONNECTED, &result);
+	if (err) {
+		pr_warn("Failed to get eGPU connection status: %d\n", err);
+		return err;
+	}
+
+	if (asus_armoury.gpu_mux_dev_id) {
+		err = asus_wmi_get_devstate_dsts(asus_armoury.gpu_mux_dev_id, &result);
+		if (err) {
+			pr_warn("Failed to get GPU MUX status: %d\n", result);
+			return result;
+		}
+		if (!result && enable) {
+			err = -ENODEV;
+			pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err);
+			return err;
+		}
+	}
+
+	err = armoury_wmi_set_devstate(attr, enable, ASUS_WMI_DEVID_EGPU);
+	if (err)
+		return err;
+
+	sysfs_notify(kobj, NULL, attr->attr.name);
+
+	return count;
+}
+WMI_SHOW_INT(egpu_enable_current_value, "%d\n", ASUS_WMI_DEVID_EGPU);
+ATTR_GROUP_BOOL_CUSTOM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)");
+
+/* Device memory available to APU */
+
+static ssize_t apu_mem_current_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+					  char *buf)
+{
+	int err;
+	u32 mem;
+
+	err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_APU_MEM, &mem);
+	if (err)
+		return err;
+
+	switch (mem) {
+	case 0x100:
+		mem = 0;
+		break;
+	case 0x102:
+		mem = 1;
+		break;
+	case 0x103:
+		mem = 2;
+		break;
+	case 0x104:
+		mem = 3;
+		break;
+	case 0x105:
+		mem = 4;
+		break;
+	case 0x106:
+		/* This is out of order and looks wrong but is correct */
+		mem = 8;
+		break;
+	case 0x107:
+		mem = 5;
+		break;
+	case 0x108:
+		mem = 6;
+		break;
+	case 0x109:
+		mem = 7;
+		break;
+	default:
+		mem = 4;
+		break;
+	}
+
+	return sysfs_emit(buf, "%u\n", mem);
+}
+
+static ssize_t apu_mem_current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					   const char *buf, size_t count)
+{
+	int result, err;
+	u32 requested, mem;
+
+	result = kstrtou32(buf, 10, &requested);
+	if (result)
+		return result;
+
+	switch (requested) {
+	case 0:
+		mem = 0x000;
+		break;
+	case 1:
+		mem = 0x102;
+		break;
+	case 2:
+		mem = 0x103;
+		break;
+	case 3:
+		mem = 0x104;
+		break;
+	case 4:
+		mem = 0x105;
+		break;
+	case 5:
+		mem = 0x107;
+		break;
+	case 6:
+		mem = 0x108;
+		break;
+	case 7:
+		mem = 0x109;
+		break;
+	case 8:
+		/* This is out of order and looks wrong but is correct */
+		mem = 0x106;
+		break;
+	default:
+		return -EIO;
+	}
+
+	err = asus_wmi_set_devstate(ASUS_WMI_DEVID_APU_MEM, mem, &result);
+	if (err) {
+		pr_warn("Failed to set apu_mem: %d\n", err);
+		return err;
+	}
+
+	pr_info("APU memory changed to %uGB, reboot required\n", requested);
+	sysfs_notify(kobj, NULL, attr->attr.name);
+
+	asus_set_reboot_and_signal_event();
+
+	return count;
+}
+
+static ssize_t apu_mem_possible_values_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf)
+{
+	return sysfs_emit(buf, "0;1;2;3;4;5;6;7;8\n");
+}
+ATTR_GROUP_ENUM_CUSTOM(apu_mem, "apu_mem", "Set available system RAM (in GB) for the APU to use");
+
+static int init_max_cpu_cores(void)
+{
+	u32 cores;
+	int err;
+
+	err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES_MAX, &cores);
+	if (err)
+		return err;
+
+	cores &= ~ASUS_WMI_DSTS_PRESENCE_BIT;
+	asus_armoury.rog_tunables->max_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores);
+	asus_armoury.rog_tunables->max_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores);
+
+	err = asus_wmi_get_devstate_dsts(ASUS_WMI_DEVID_CORES, &cores);
+	if (err) {
+		pr_err("Could not get CPU core count: error %d", err);
+		return err;
+	}
+
+	asus_armoury.rog_tunables->cur_perf_cores = FIELD_GET(ASUS_PERF_CORE_MASK, cores);
+	asus_armoury.rog_tunables->cur_power_cores = FIELD_GET(ASUS_POWER_CORE_MASK, cores);
+
+	asus_armoury.rog_tunables->min_perf_cores = CPU_PERF_CORE_COUNT_MIN;
+	asus_armoury.rog_tunables->min_power_cores = CPU_POWR_CORE_COUNT_MIN;
+
+	return 0;
+}
+
+static ssize_t cores_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf,
+				enum cpu_core_type core_type, enum cpu_core_value core_value)
+{
+	u32 cores;
+
+	switch (core_value) {
+	case CPU_CORE_DEFAULT:
+	case CPU_CORE_MAX:
+		if (core_type == CPU_CORE_PERF)
+			return sysfs_emit(buf, "%d\n",
+					  asus_armoury.rog_tunables->max_perf_cores);
+		else
+			return sysfs_emit(buf, "%d\n",
+					  asus_armoury.rog_tunables->max_power_cores);
+	case CPU_CORE_MIN:
+		if (core_type == CPU_CORE_PERF)
+			return sysfs_emit(buf, "%d\n",
+					  asus_armoury.rog_tunables->min_perf_cores);
+		else
+			return sysfs_emit(buf, "%d\n",
+					  asus_armoury.rog_tunables->min_power_cores);
+	default:
+		break;
+	}
+
+	if (core_type == CPU_CORE_PERF)
+		cores = asus_armoury.rog_tunables->cur_perf_cores;
+	else
+		cores = asus_armoury.rog_tunables->cur_power_cores;
+
+	return sysfs_emit(buf, "%d\n", cores);
+}
+
+static ssize_t cores_current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					 const char *buf, enum cpu_core_type core_type)
+{
+	u32 new_cores, perf_cores, power_cores, out_val, min, max;
+	int result, err;
+
+	result = kstrtou32(buf, 10, &new_cores);
+	if (result)
+		return result;
+
+	if (core_type == CPU_CORE_PERF) {
+		perf_cores = new_cores;
+		power_cores = out_val = asus_armoury.rog_tunables->cur_power_cores;
+		min = asus_armoury.rog_tunables->min_perf_cores;
+		max = asus_armoury.rog_tunables->max_perf_cores;
+	} else {
+		perf_cores = asus_armoury.rog_tunables->cur_perf_cores;
+		power_cores = out_val = new_cores;
+		min = asus_armoury.rog_tunables->min_power_cores;
+		max = asus_armoury.rog_tunables->max_power_cores;
+	}
+
+	if (new_cores < min || new_cores > max)
+		return -EINVAL;
+
+	out_val = 0;
+	out_val |= FIELD_PREP(ASUS_PERF_CORE_MASK, perf_cores);
+	out_val |= FIELD_PREP(ASUS_POWER_CORE_MASK, power_cores);
+
+	mutex_lock(&asus_armoury.mutex);
+	err = asus_wmi_set_devstate(ASUS_WMI_DEVID_CORES, out_val, &result);
+	mutex_unlock(&asus_armoury.mutex);
+
+	if (err) {
+		pr_warn("Failed to set CPU core count: %d\n", err);
+		return err;
+	}
+
+	if (result > 1) {
+		pr_warn("Failed to set CPU core count (result): 0x%x\n", result);
+		return -EIO;
+	}
+
+	pr_info("CPU core count changed, reboot required\n");
+	sysfs_notify(kobj, NULL, attr->attr.name);
+	asus_set_reboot_and_signal_event();
+
+	return 0;
+}
+
+static ssize_t cores_performance_min_value_show(struct kobject *kobj,
+						struct kobj_attribute *attr, char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_MIN);
+}
+
+static ssize_t cores_performance_max_value_show(struct kobject *kobj,
+						struct kobj_attribute *attr, char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_MAX);
+}
+
+static ssize_t cores_performance_default_value_show(struct kobject *kobj,
+						    struct kobj_attribute *attr, char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_DEFAULT);
+}
+
+static ssize_t cores_performance_current_value_show(struct kobject *kobj,
+						    struct kobj_attribute *attr, char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_PERF, CPU_CORE_CURRENT);
+}
+
+static ssize_t cores_performance_current_value_store(struct kobject *kobj,
+						     struct kobj_attribute *attr,
+						     const char *buf, size_t count)
+{
+	int err;
+
+	err = cores_current_value_store(kobj, attr, buf, CPU_CORE_PERF);
+	if (err)
+		return err;
+
+	return count;
+}
+ATTR_GROUP_CORES_RW(cores_performance, "cores_performance",
+		    "Set the max available performance cores");
+
+static ssize_t cores_efficiency_min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+					       char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_MIN);
+}
+
+static ssize_t cores_efficiency_max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+					       char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_MAX);
+}
+
+static ssize_t cores_efficiency_default_value_show(struct kobject *kobj,
+						   struct kobj_attribute *attr, char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_DEFAULT);
+}
+
+static ssize_t cores_efficiency_current_value_show(struct kobject *kobj,
+						   struct kobj_attribute *attr, char *buf)
+{
+	return cores_value_show(kobj, attr, buf, CPU_CORE_POWER, CPU_CORE_CURRENT);
+}
+
+static ssize_t cores_efficiency_current_value_store(struct kobject *kobj,
+						    struct kobj_attribute *attr, const char *buf,
+						    size_t count)
+{
+	int err;
+
+	err = cores_current_value_store(kobj, attr, buf, CPU_CORE_POWER);
+	if (err)
+		return err;
+
+	return count;
+}
+ATTR_GROUP_CORES_RW(cores_efficiency, "cores_efficiency",
+		    "Set the max available efficiency cores");
+
+/* Simple attribute creation */
+ATTR_GROUP_ROG_TUNABLE(ppt_pl1_spl, "ppt_pl1_spl", ASUS_WMI_DEVID_PPT_PL1_SPL, cpu_default,
+		       cpu_min, cpu_max, 1, "Set the CPU slow package limit");
+ATTR_GROUP_ROG_TUNABLE(ppt_pl2_sppt, "ppt_pl2_sppt", ASUS_WMI_DEVID_PPT_PL2_SPPT, cpu_default,
+		       cpu_min, cpu_max, 1, "Set the CPU fast package limit");
+ATTR_GROUP_ROG_TUNABLE(ppt_apu_sppt, "ppt_apu_sppt", ASUS_WMI_DEVID_PPT_APU_SPPT,
+		       platform_default, platform_min, platform_max, 1,
+		       "Set the CPU slow package limit");
+ATTR_GROUP_ROG_TUNABLE(ppt_platform_sppt, "ppt_platform_sppt", ASUS_WMI_DEVID_PPT_PLAT_SPPT,
+		       platform_default, platform_min, platform_max, 1,
+		       "Set the CPU slow package limit");
+ATTR_GROUP_ROG_TUNABLE(ppt_fppt, "ppt_fppt", ASUS_WMI_DEVID_PPT_FPPT, cpu_default, cpu_min,
+		       cpu_max, 1, "Set the CPU slow package limit");
+ATTR_GROUP_ROG_TUNABLE(nv_dynamic_boost, "nv_dynamic_boost", ASUS_WMI_DEVID_NV_DYN_BOOST,
+		       nv_boost_default, nv_boost_min, nv_boost_max, 1,
+		       "Set the Nvidia dynamic boost limit");
+ATTR_GROUP_ROG_TUNABLE(nv_temp_target, "nv_temp_target", ASUS_WMI_DEVID_NV_THERM_TARGET,
+		       nv_temp_default, nv_boost_min, nv_temp_max, 1,
+		       "Set the Nvidia max thermal limit");
+ATTR_GROUP_ROG_TUNABLE(dgpu_tgp, "dgpu_tgp", ASUS_WMI_DEVID_DGPU_SET_TGP, dgpu_tgp_default,
+		       dgpu_tgp_min, dgpu_tgp_max, 1,
+		       "Set the additional TGP on top of the base TGP");
+
+ATTR_GROUP_INT_VALUE_ONLY_RO(dgpu_base_tgp, "dgpu_base_tgp", ASUS_WMI_DEVID_DGPU_BASE_TGP,
+			     "Read the base TGP value");
+
+ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2",
+		       "Show the current mode of charging");
+
+ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND,
+		   "Set the boot POST sound");
+ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE,
+		   "Set MCU powersaving mode");
+ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD,
+		   "Set the panel refresh overdrive");
+ATTR_GROUP_BOOL_RW(panel_hd_mode, "panel_hd_mode", ASUS_WMI_DEVID_PANEL_HD,
+		   "Set the panel HD mode to UHD<0> or FHD<1>");
+ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED,
+		   "Show the eGPU connection status");
+
+/* If an attribute does not require any special case handling add it here */
+static const struct asus_attr_group armoury_attr_groups[] = {
+	{ &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED },
+	{ &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU },
+	{ &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU },
+
+	{ &ppt_pl1_spl_attr_group, ASUS_WMI_DEVID_PPT_PL1_SPL },
+	{ &ppt_pl2_sppt_attr_group, ASUS_WMI_DEVID_PPT_PL2_SPPT },
+	{ &ppt_apu_sppt_attr_group, ASUS_WMI_DEVID_PPT_APU_SPPT },
+	{ &ppt_platform_sppt_attr_group, ASUS_WMI_DEVID_PPT_PLAT_SPPT },
+	{ &ppt_fppt_attr_group, ASUS_WMI_DEVID_PPT_FPPT },
+	{ &nv_dynamic_boost_attr_group, ASUS_WMI_DEVID_NV_DYN_BOOST },
+	{ &nv_temp_target_attr_group, ASUS_WMI_DEVID_NV_THERM_TARGET },
+	{ &dgpu_base_tgp_attr_group, ASUS_WMI_DEVID_DGPU_BASE_TGP },
+	{ &dgpu_tgp_attr_group, ASUS_WMI_DEVID_DGPU_SET_TGP },
+	{ &apu_mem_attr_group, ASUS_WMI_DEVID_APU_MEM },
+	{ &cores_efficiency_attr_group, ASUS_WMI_DEVID_CORES_MAX },
+	{ &cores_performance_attr_group, ASUS_WMI_DEVID_CORES_MAX },
+
+	{ &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE },
+	{ &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND },
+	{ &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE },
+	{ &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD },
+	{ &panel_hd_mode_attr_group, ASUS_WMI_DEVID_PANEL_HD },
+};
+
+static int asus_fw_attr_add(void)
+{
+	int err, i;
+
+	err = fw_attributes_class_get(&fw_attr_class);
+	if (err)
+		return err;
+
+	asus_armoury.fw_attr_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
+						NULL, "%s", DRIVER_NAME);
+	if (IS_ERR(asus_armoury.fw_attr_dev)) {
+		err = PTR_ERR(asus_armoury.fw_attr_dev);
+		goto fail_class_get;
+	}
+
+	asus_armoury.fw_attr_kset = kset_create_and_add("attributes", NULL,
+						&asus_armoury.fw_attr_dev->kobj);
+	if (!asus_armoury.fw_attr_kset) {
+		err = -ENOMEM;
+		goto err_destroy_classdev;
+	}
+
+	err = sysfs_create_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
+	if (err) {
+		pr_err("Failed to create sysfs level attributes\n");
+		goto err_destroy_kset;
+	}
+
+	asus_armoury.mini_led_dev_id = 0;
+	if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE)) {
+		asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
+	} else if (asus_wmi_is_present(ASUS_WMI_DEVID_MINI_LED_MODE2)) {
+		asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2;
+	}
+
+	if (asus_armoury.mini_led_dev_id) {
+		err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
+		if (err) {
+			pr_err("Failed to create sysfs-group for mini_led\n");
+			goto err_remove_file;
+		}
+	}
+
+	asus_armoury.gpu_mux_dev_id = 0;
+	if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX)) {
+		asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX;
+	} else if (asus_wmi_is_present(ASUS_WMI_DEVID_GPU_MUX_VIVO)) {
+		asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO;
+	}
+
+	if (asus_armoury.gpu_mux_dev_id) {
+		err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
+		if (err) {
+			pr_err("Failed to create sysfs-group for gpu_mux\n");
+			goto err_remove_mini_led_group;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) {
+		if (!asus_wmi_is_present(armoury_attr_groups[i].wmi_devid))
+			continue;
+
+		err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj,
+					 armoury_attr_groups[i].attr_group);
+		if (err) {
+			pr_err("Failed to create sysfs-group for %s\n",
+			       armoury_attr_groups[i].attr_group->name);
+			goto err_remove_groups;
+		}
+	}
+
+	return 0;
+
+err_remove_groups:
+	while (--i >= 0) {
+		if (asus_wmi_is_present(armoury_attr_groups[i].wmi_devid))
+			sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, armoury_attr_groups[i].attr_group);
+	}
+	sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group);
+err_remove_mini_led_group:
+	sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group);
+err_remove_file:
+	sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
+err_destroy_kset:
+	kset_unregister(asus_armoury.fw_attr_kset);
+err_destroy_classdev:
+	device_destroy(fw_attr_class, MKDEV(0, 0));
+fail_class_get:
+	fw_attributes_class_put();
+	return err;
+}
+
+/* Init / exit ****************************************************************/
+
+/* Set up the min/max and defaults for ROG tunables */
+static void init_rog_tunables(struct rog_tunables *rog)
+{
+	u32 platform_default = PPT_PLATFORM_DEFAULT;
+	u32 cpu_default = PPT_CPU_LIMIT_DEFAULT;
+	u32 platform_max = PPT_PLATFORM_MAX;
+	u32 max_boost = NVIDIA_BOOST_MAX;
+	u32 cpu_max = PPT_CPU_LIMIT_MAX;
+	const char *product;
+
+	/*
+	 * ASUS product_name contains everything required, e.g,
+	 * "ROG Flow X16 GV601VV_GV601VV_00185149B".
+	 * The bulk of these defaults are gained from users reporting what
+	 * ASUS Armoury Crate in Windows provides them.
+	 * This should be turned in to a table eventually.
+	 */
+	product = dmi_get_system_info(DMI_PRODUCT_NAME);
+
+	if (strstr(product, "GA402R")) {
+		cpu_default = 125;
+	} else if (strstr(product, "13QY")) {
+		cpu_max = 250;
+	} else if (strstr(product, "X13")) {
+		cpu_max = 75;
+		cpu_default = 50;
+	} else if (strstr(product, "RC71") || strstr(product, "RC72")) {
+		cpu_max = 50;
+		cpu_default = 30;
+	} else if (strstr(product, "G814") || strstr(product, "G614") ||
+		   strstr(product, "G834") || strstr(product, "G634")) {
+		cpu_max = 175;
+	} else if (strstr(product, "GA402X") || strstr(product, "GA403") ||
+		   strstr(product, "FA507N") || strstr(product, "FA507X") ||
+		   strstr(product, "FA707N") || strstr(product, "FA707X")) {
+		cpu_max = 90;
+	} else {
+		pr_notice("Using default CPU limits. Please report if these are not correct.\n");
+	}
+
+	if (strstr(product, "GZ301ZE"))
+		max_boost = 5;
+	else if (strstr(product, "FX507ZC4"))
+		max_boost = 15;
+	else if (strstr(product, "GU605"))
+		max_boost = 20;
+
+	/* ensure defaults for tunables */
+	rog->cpu_default = cpu_default;
+	rog->cpu_min = PPT_CPU_LIMIT_MIN;
+	rog->cpu_max = cpu_max;
+
+	rog->platform_default = platform_default;
+	rog->platform_max = PPT_PLATFORM_MIN;
+	rog->platform_max = platform_max;
+
+	rog->ppt_pl1_spl = cpu_default;
+	rog->ppt_pl2_sppt = cpu_default;
+	rog->ppt_apu_sppt = cpu_default;
+
+	rog->ppt_platform_sppt = platform_default;
+	rog->ppt_fppt = cpu_default;
+
+	rog->nv_boost_default = NVIDIA_BOOST_MAX;
+	rog->nv_boost_min = NVIDIA_BOOST_MIN;
+	rog->nv_boost_max = max_boost;
+	rog->nv_dynamic_boost = NVIDIA_BOOST_MIN;
+
+	rog->nv_temp_default = NVIDIA_TEMP_MAX;
+	rog->nv_temp_min = NVIDIA_TEMP_MIN;
+	rog->nv_temp_max = NVIDIA_TEMP_MAX;
+	rog->nv_temp_target = NVIDIA_TEMP_MIN;
+
+	rog->dgpu_tgp_default = NVIDIA_POWER_DEFAULT;
+	rog->dgpu_tgp_min = NVIDIA_POWER_MIN;
+	rog->dgpu_tgp_max = NVIDIA_POWER_MAX;
+	rog->dgpu_tgp = NVIDIA_POWER_MAX;
+}
+
+static int __init asus_fw_init(void)
+{
+	char *wmi_uid;
+	int err;
+
+	wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID);
+	if (!wmi_uid)
+		return -ENODEV;
+
+	/*
+	 * if equal to "ASUSWMI" then it's DCTS that can't be used for this
+	 * driver, DSTS is required.
+	 */
+	if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI))
+		return -ENODEV;
+
+	asus_armoury.rog_tunables = kzalloc(sizeof(struct rog_tunables), GFP_KERNEL);
+	if (!asus_armoury.rog_tunables)
+		return -ENOMEM;
+
+	init_rog_tunables(asus_armoury.rog_tunables);
+	if (asus_wmi_is_present(ASUS_WMI_DEVID_CORES_MAX)) {
+		err = init_max_cpu_cores();
+		if (err) {
+			kfree(asus_armoury.rog_tunables);
+			pr_err("Could not initialise CPU core control %d\n", err);
+			return err;
+		}
+	}
+
+	err = asus_fw_attr_add();
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static void __exit asus_fw_exit(void)
+{
+	mutex_lock(&asus_armoury.mutex);
+
+	sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr);
+	kset_unregister(asus_armoury.fw_attr_kset);
+	device_destroy(fw_attr_class, MKDEV(0, 0));
+	fw_attributes_class_put();
+
+	mutex_unlock(&asus_armoury.mutex);
+}
+
+module_init(asus_fw_init);
+module_exit(asus_fw_exit);
+
+MODULE_IMPORT_NS(ASUS_WMI);
+MODULE_AUTHOR("Luke Jones <luke@ljones.dev>");
+MODULE_DESCRIPTION("ASUS BIOS Configuration Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("wmi:" ASUS_NB_WMI_EVENT_GUID);
diff --git a/drivers/platform/x86/asus-armoury.h b/drivers/platform/x86/asus-armoury.h
new file mode 100644
index 000000000000..2620708d3994
--- /dev/null
+++ b/drivers/platform/x86/asus-armoury.h
@@ -0,0 +1,258 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Definitions for kernel modules using asus-armoury driver
+ *
+ *  Copyright (c) 2024 Luke Jones <luke@ljones.dev>
+ */
+
+#ifndef _ASUS_ARMOURY_H_
+#define _ASUS_ARMOURY_H_
+
+#include <linux/types.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "asus-armoury"
+
+static ssize_t attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr,
+			      const char *buf, size_t count, u32 min, u32 max,
+			      u32 *store_value, u32 wmi_dev);
+
+static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+			     char *buf)
+{
+	return sysfs_emit(buf, "integer\n");
+}
+
+static ssize_t enum_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+			      char *buf)
+{
+	return sysfs_emit(buf, "enumeration\n");
+}
+
+#define __ASUS_ATTR_RO(_func, _name)                                  \
+	{                                                             \
+		.attr = { .name = __stringify(_name), .mode = 0444 }, \
+		.show = _func##_##_name##_show,                       \
+	}
+
+#define __ASUS_ATTR_RO_AS(_name, _show)                               \
+	{                                                             \
+		.attr = { .name = __stringify(_name), .mode = 0444 }, \
+		.show = _show,                                        \
+	}
+
+#define __ASUS_ATTR_RW(_func, _name) \
+	__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
+
+#define __WMI_STORE_INT(_attr, _min, _max, _wmi)                          \
+	static ssize_t _attr##_store(struct kobject *kobj,                \
+				     struct kobj_attribute *attr,         \
+				     const char *buf, size_t count)       \
+	{                                                                 \
+		return attr_uint_store(kobj, attr, buf, count, _min, _max, \
+				      NULL, _wmi);                        \
+	}
+
+#define WMI_SHOW_INT(_attr, _fmt, _wmi)                                     \
+	static ssize_t _attr##_show(struct kobject *kobj,                   \
+				    struct kobj_attribute *attr, char *buf) \
+	{                                                                   \
+		u32 result;                                                 \
+		int err;                                                    \
+		                                                            \
+		err = asus_wmi_get_devstate_dsts(_wmi, &result);            \
+		if (err)                                                    \
+			return err;                                         \
+		return sysfs_emit(buf, _fmt,                                \
+				  result & ~ASUS_WMI_DSTS_PRESENCE_BIT);    \
+	}
+
+/* Create functions and attributes for use in other macros or on their own */
+
+#define __ATTR_CURRENT_INT_RO(_attr, _wmi)                          \
+	WMI_SHOW_INT(_attr##_current_value, "%d\n", _wmi);          \
+	static struct kobj_attribute attr_##_attr##_current_value = \
+		__ASUS_ATTR_RO(_attr, current_value)
+
+#define __ATTR_CURRENT_INT_RW(_attr, _minv, _maxv, _wmi)            \
+	__WMI_STORE_INT(_attr##_current_value, _minv, _maxv, _wmi); \
+	WMI_SHOW_INT(_attr##_current_value, "%d\n", _wmi);          \
+	static struct kobj_attribute attr_##_attr##_current_value = \
+		__ASUS_ATTR_RW(_attr, current_value)
+
+/* Shows a formatted static variable */
+#define __ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                         \
+	static ssize_t _attrname##_##_prop##_show(                            \
+		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+	{                                                                     \
+		return sysfs_emit(buf, _fmt, _val);                           \
+	}                                                                     \
+	static struct kobj_attribute attr_##_attrname##_##_prop =             \
+		__ASUS_ATTR_RO(_attrname, _prop)
+
+/* Requires current_value_show */
+#define __ATTR_GROUP_INT_VALUE_ONLY(_attrname, _fsname, _dispname)     \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);   \
+	static struct kobj_attribute attr_##_attrname##_type =         \
+		__ASUS_ATTR_RO_AS(type, int_type_show);                \
+	static struct attribute *_attrname##_attrs[] = {               \
+		&attr_##_attrname##_current_value.attr,                \
+		&attr_##_attrname##_display_name.attr,                 \
+		&attr_##_attrname##_type.attr, NULL                    \
+	};                                                             \
+	static const struct attribute_group _attrname##_attr_group = { \
+		.name = _fsname, .attrs = _attrname##_attrs            \
+	}
+
+/* Boolean style enumeration, base macro. Requires adding show/store */
+#define __ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _dispname)     \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);    \
+	__ATTR_SHOW_FMT(possible_values, _attrname, "%s\n", _possible); \
+	static struct kobj_attribute attr_##_attrname##_type =          \
+		__ASUS_ATTR_RO_AS(type, enum_type_show);                \
+	static struct attribute *_attrname##_attrs[] = {                \
+		&attr_##_attrname##_current_value.attr,                 \
+		&attr_##_attrname##_display_name.attr,                  \
+		&attr_##_attrname##_possible_values.attr,               \
+		&attr_##_attrname##_type.attr,                          \
+		NULL                                                    \
+	};                                                              \
+	static const struct attribute_group _attrname##_attr_group = {  \
+		.name = _fsname, .attrs = _attrname##_attrs             \
+	}
+
+#define ATTR_GROUP_INT_VALUE_ONLY_RO(_attrname, _fsname, _wmi, _dispname) \
+	__ATTR_CURRENT_INT_RO(_attrname, _wmi);                           \
+	__ATTR_GROUP_INT_VALUE_ONLY(_attrname, _fsname, _dispname)
+
+#define ATTR_GROUP_BOOL_RO(_attrname, _fsname, _wmi, _dispname) \
+	__ATTR_CURRENT_INT_RO(_attrname, _wmi);                 \
+	__ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname)
+
+#define ATTR_GROUP_BOOL_RW(_attrname, _fsname, _wmi, _dispname) \
+	__ATTR_CURRENT_INT_RW(_attrname, 0, 1, _wmi);           \
+	__ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname)
+
+/*
+ * Requires <name>_current_value_show(), <name>_current_value_show()
+ */
+#define ATTR_GROUP_BOOL_CUSTOM(_attrname, _fsname, _dispname)           \
+	static struct kobj_attribute attr_##_attrname##_current_value = \
+		__ASUS_ATTR_RW(_attrname, current_value);               \
+	__ATTR_GROUP_ENUM(_attrname, _fsname, "0;1", _dispname)
+
+#define ATTR_GROUP_ENUM_INT_RO(_attrname, _fsname, _wmi, _possible, _dispname) \
+	__ATTR_CURRENT_INT_RO(_attrname, _wmi);                                \
+	__ATTR_GROUP_ENUM(_attrname, _fsname, _possible, _dispname)
+
+/*
+ * Requires <name>_current_value_show(), <name>_current_value_show()
+ * and <name>_possible_values_show()
+ */
+#define ATTR_GROUP_ENUM_CUSTOM(_attrname, _fsname, _dispname)             \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);      \
+	static struct kobj_attribute attr_##_attrname##_current_value =   \
+		__ASUS_ATTR_RW(_attrname, current_value);                 \
+	static struct kobj_attribute attr_##_attrname##_possible_values = \
+		__ASUS_ATTR_RO(_attrname, possible_values);               \
+	static struct kobj_attribute attr_##_attrname##_type =            \
+		__ASUS_ATTR_RO_AS(type, enum_type_show);                  \
+	static struct attribute *_attrname##_attrs[] = {                  \
+		&attr_##_attrname##_current_value.attr,                   \
+		&attr_##_attrname##_display_name.attr,                    \
+		&attr_##_attrname##_possible_values.attr,                 \
+		&attr_##_attrname##_type.attr,                            \
+		NULL                                                      \
+	};                                                                \
+	static const struct attribute_group _attrname##_attr_group = {    \
+		.name = _fsname, .attrs = _attrname##_attrs               \
+	}
+
+/* CPU core attributes need a little different in setup */
+#define ATTR_GROUP_CORES_RW(_attrname, _fsname, _dispname)              \
+	__ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", 1);        \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);    \
+	static struct kobj_attribute attr_##_attrname##_current_value = \
+		__ASUS_ATTR_RW(_attrname, current_value);               \
+	static struct kobj_attribute attr_##_attrname##_default_value = \
+		__ASUS_ATTR_RO(_attrname, default_value);               \
+	static struct kobj_attribute attr_##_attrname##_min_value =     \
+		__ASUS_ATTR_RO(_attrname, min_value);                   \
+	static struct kobj_attribute attr_##_attrname##_max_value =     \
+		__ASUS_ATTR_RO(_attrname, max_value);                   \
+	static struct kobj_attribute attr_##_attrname##_type =          \
+		__ASUS_ATTR_RO_AS(type, int_type_show);                 \
+	static struct attribute *_attrname##_attrs[] = {                \
+		&attr_##_attrname##_current_value.attr,                 \
+		&attr_##_attrname##_default_value.attr,                 \
+		&attr_##_attrname##_min_value.attr,                     \
+		&attr_##_attrname##_max_value.attr,                     \
+		&attr_##_attrname##_scalar_increment.attr,              \
+		&attr_##_attrname##_display_name.attr,                  \
+		&attr_##_attrname##_type.attr,                          \
+		NULL                                                    \
+	};                                                              \
+	static const struct attribute_group _attrname##_attr_group = {  \
+		.name = _fsname, .attrs = _attrname##_attrs             \
+	}
+
+/*
+ * ROG PPT attributes need a little different in setup as they
+ * require rog_tunables members.
+ */
+
+#define __ROG_TUNABLE_RW(_attr, _min, _max, _wmi)                             \
+	static ssize_t _attr##_current_value_store(                           \
+		struct kobject *kobj, struct kobj_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		return attr_uint_store(kobj, attr, buf, count,                 \
+				      asus_armoury.rog_tunables->_min,        \
+				      asus_armoury.rog_tunables->_max,        \
+				      &asus_armoury.rog_tunables->_attr,      \
+				      _wmi);                                  \
+	}                                                                     \
+	static ssize_t _attr##_current_value_show(                            \
+		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+	{                                                                     \
+		return sysfs_emit(buf, "%u\n",                                \
+				  asus_armoury.rog_tunables->_attr);          \
+	}                                                                     \
+	static struct kobj_attribute attr_##_attr##_current_value =           \
+		__ASUS_ATTR_RW(_attr, current_value)
+
+#define __ROG_TUNABLE_SHOW(_prop, _attrname, _val)                            \
+	static ssize_t _attrname##_##_prop##_show(                            \
+		struct kobject *kobj, struct kobj_attribute *attr, char *buf) \
+	{                                                                     \
+		return sysfs_emit(buf, "%d\n",                                \
+				  asus_armoury.rog_tunables->_val);           \
+	}                                                                     \
+	static struct kobj_attribute attr_##_attrname##_##_prop =             \
+		__ASUS_ATTR_RO(_attrname, _prop)
+
+#define ATTR_GROUP_ROG_TUNABLE(_attrname, _fsname, _wmi, _default, _min, _max, \
+			       _incstep, _dispname)                            \
+	__ROG_TUNABLE_SHOW(default_value, _attrname, _default);                \
+	__ROG_TUNABLE_RW(_attrname, _min, _max, _wmi);                         \
+	__ROG_TUNABLE_SHOW(min_value, _attrname, _min);                        \
+	__ROG_TUNABLE_SHOW(max_value, _attrname, _max);                        \
+	__ATTR_SHOW_FMT(scalar_increment, _attrname, "%d\n", _incstep);        \
+	__ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname);           \
+	static struct kobj_attribute attr_##_attrname##_type =                 \
+		__ASUS_ATTR_RO_AS(type, int_type_show);                        \
+	static struct attribute *_attrname##_attrs[] = {                       \
+		&attr_##_attrname##_current_value.attr,                        \
+		&attr_##_attrname##_default_value.attr,                        \
+		&attr_##_attrname##_min_value.attr,                            \
+		&attr_##_attrname##_max_value.attr,                            \
+		&attr_##_attrname##_scalar_increment.attr,                     \
+		&attr_##_attrname##_display_name.attr,                         \
+		&attr_##_attrname##_type.attr,                                 \
+		NULL                                                           \
+	};                                                                     \
+	static const struct attribute_group _attrname##_attr_group = {         \
+		.name = _fsname, .attrs = _attrname##_attrs                    \
+	}
+
+#endif /* _ASUS_BIOSCFG_H_ */
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 1101e5b2488e..d4981e08f44f 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -55,8 +55,6 @@ module_param(fnlock_default, bool, 0444);
 #define to_asus_wmi_driver(pdrv)					\
 	(container_of((pdrv), struct asus_wmi_driver, platform_driver))
 
-#define ASUS_WMI_MGMT_GUID	"97845ED0-4E6D-11DE-8A39-0800200C9A66"
-
 #define NOTIFY_BRNUP_MIN		0x11
 #define NOTIFY_BRNUP_MAX		0x1f
 #define NOTIFY_BRNDOWN_MIN		0x20
@@ -105,8 +103,6 @@ module_param(fnlock_default, bool, 0444);
 #define USB_INTEL_XUSB2PR		0xD0
 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI	0x9c31
 
-#define ASUS_ACPI_UID_ASUSWMI		"ASUSWMI"
-
 #define WMI_EVENT_MASK			0xFFFF
 
 #define FAN_CURVE_POINTS		8
@@ -142,16 +138,20 @@ module_param(fnlock_default, bool, 0444);
 #define ASUS_MINI_LED_2024_STRONG	0x01
 #define ASUS_MINI_LED_2024_OFF		0x02
 
-/* Controls the power state of the USB0 hub on ROG Ally which input is on */
 #define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE"
-/* 300ms so far seems to produce a reliable result on AC and battery */
-#define ASUS_USB0_PWR_EC0_CSEE_WAIT 1500
+/*
+ * The period required to wait after screen off/on/s2idle.check in MS.
+ * Time here greatly impacts the wake behaviour. Used in suspend/wake.
+ */
+#define ASUS_USB0_PWR_EC0_CSEE_WAIT	600
+#define ASUS_USB0_PWR_EC0_CSEE_OFF	0xB7
+#define ASUS_USB0_PWR_EC0_CSEE_ON	0xB8
 
 static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
 
 static int throttle_thermal_policy_write(struct asus_wmi *);
 
-static const struct dmi_system_id asus_ally_mcu_quirk[] = {
+static const struct dmi_system_id asus_rog_ally_device[] = {
 	{
 		.matches = {
 			DMI_MATCH(DMI_BOARD_NAME, "RC71L"),
@@ -274,9 +274,6 @@ struct asus_wmi {
 	u32 tablet_switch_dev_id;
 	bool tablet_switch_inverted;
 
-	/* The ROG Ally device requires the MCU USB device be disconnected before suspend */
-	bool ally_mcu_usb_switch;
-
 	enum fan_type fan_type;
 	enum fan_type gpu_fan_type;
 	enum fan_type mid_fan_type;
@@ -289,11 +286,12 @@ struct asus_wmi {
 	u8 fan_boost_mode_mask;
 	u8 fan_boost_mode;
 
+
+	/* Tunables provided by ASUS for gaming laptops */
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 	bool egpu_enable_available;
 	bool dgpu_disable_available;
 	u32 gpu_mux_dev;
-
-	/* Tunables provided by ASUS for gaming laptops */
 	u32 ppt_pl2_sppt;
 	u32 ppt_pl1_spl;
 	u32 ppt_apu_sppt;
@@ -301,6 +299,9 @@ struct asus_wmi {
 	u32 ppt_fppt;
 	u32 nv_dynamic_boost;
 	u32 nv_temp_target;
+	bool panel_overdrive_available;
+	u32 mini_led_dev_id;
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 	u32 kbd_rgb_dev;
 	bool kbd_rgb_state_available;
@@ -319,9 +320,6 @@ struct asus_wmi {
 	// The RSOC controls the maximum charging percentage.
 	bool battery_rsoc_available;
 
-	bool panel_overdrive_available;
-	u32 mini_led_dev_id;
-
 	struct hotplug_slot hotplug_slot;
 	struct mutex hotplug_lock;
 	struct mutex wmi_lock;
@@ -335,6 +333,17 @@ struct asus_wmi {
 	struct asus_wmi_driver *driver;
 };
 
+static bool ally_mcu_usb_plug;
+
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
+static void asus_wmi_show_deprecated(void)
+{
+	pr_notice_once("Accessing attributes through /sys/bus/platform/asus_wmi "
+		"is deprecated and will be removed in a future release. Please "
+		"switch over to /sys/class/firmware_attributes.\n");
+}
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
+
 /* WMI ************************************************************************/
 
 static int asus_wmi_evaluate_method3(u32 method_id,
@@ -385,7 +394,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval)
 {
 	return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval);
 }
-EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method);
+EXPORT_SYMBOL_NS_GPL(asus_wmi_evaluate_method, ASUS_WMI);
 
 static int asus_wmi_evaluate_method5(u32 method_id,
 		u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4, u32 *retval)
@@ -549,12 +558,50 @@ static int asus_wmi_get_devstate(struct asus_wmi *asus, u32 dev_id, u32 *retval)
 	return 0;
 }
 
-static int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param,
-				 u32 *retval)
+/**
+ * asus_wmi_get_devstate_dsts() - Get the WMI function state.
+ * @dev_id: The WMI method ID to call.
+ * @retval: A pointer to where to store the value returned from WMI.
+ *
+ * On success the return value is 0, and the retval is a valid value returned
+ * by the successful WMI function call otherwise an error is returned if the
+ * call failed, or if the WMI method ID is unsupported.
+ */
+int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval)
+{
+	int err;
+
+	err = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, retval);
+	if (err)
+		return err;
+
+	if (*retval == ASUS_WMI_UNSUPPORTED_METHOD)
+		return -ENODEV;
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(asus_wmi_get_devstate_dsts, ASUS_WMI);
+
+/**
+ * asus_wmi_set_devstate() - Set the WMI function state.
+ * @dev_id: The WMI function to call.
+ * @ctrl_param: The argument to be used for this WMI function.
+ * @retval: A pointer to where to store the value returned from WMI.
+ *
+ * The returned WMI function state if not checked here for error as
+ * asus_wmi_set_devstate() is not called unless first paired with a call to
+ * asus_wmi_get_devstate_dsts() to check that the WMI function is supported.
+ *
+ * On success the return value is 0, and the retval is a valid value returned
+ * by the successful WMI function call. An error value is returned only if the
+ * WMI function failed.
+ */
+int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval)
 {
 	return asus_wmi_evaluate_method(ASUS_WMI_METHODID_DEVS, dev_id,
 					ctrl_param, retval);
 }
+EXPORT_SYMBOL_NS_GPL(asus_wmi_set_devstate, ASUS_WMI);
 
 /* Helper for special devices with magic return codes */
 static int asus_wmi_get_devstate_bits(struct asus_wmi *asus,
@@ -687,6 +734,7 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
 }
 
 /* Charging mode, 1=Barrel, 2=USB ******************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t charge_mode_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -697,12 +745,16 @@ static ssize_t charge_mode_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", value & 0xff);
 }
 
 static DEVICE_ATTR_RO(charge_mode);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* dGPU ********************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t dgpu_disable_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -713,6 +765,8 @@ static ssize_t dgpu_disable_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
@@ -766,8 +820,10 @@ static ssize_t dgpu_disable_store(struct device *dev,
 	return count;
 }
 static DEVICE_ATTR_RW(dgpu_disable);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* eGPU ********************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t egpu_enable_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -778,6 +834,8 @@ static ssize_t egpu_enable_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
@@ -834,8 +892,10 @@ static ssize_t egpu_enable_store(struct device *dev,
 	return count;
 }
 static DEVICE_ATTR_RW(egpu_enable);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Is eGPU connected? *********************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t egpu_connected_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -846,12 +906,16 @@ static ssize_t egpu_connected_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
 static DEVICE_ATTR_RO(egpu_connected);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* gpu mux switch *************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t gpu_mux_mode_show(struct device *dev,
 				 struct device_attribute *attr, char *buf)
 {
@@ -862,6 +926,8 @@ static ssize_t gpu_mux_mode_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
@@ -920,6 +986,7 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
 	return count;
 }
 static DEVICE_ATTR_RW(gpu_mux_mode);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* TUF Laptop Keyboard RGB Modes **********************************************/
 static ssize_t kbd_rgb_mode_store(struct device *dev,
@@ -1043,6 +1110,7 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = {
 };
 
 /* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t ppt_pl2_sppt_store(struct device *dev,
 				    struct device_attribute *attr,
 				    const char *buf, size_t count)
@@ -1081,6 +1149,8 @@ static ssize_t ppt_pl2_sppt_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->ppt_pl2_sppt);
 }
 static DEVICE_ATTR_RW(ppt_pl2_sppt);
@@ -1123,6 +1193,8 @@ static ssize_t ppt_pl1_spl_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->ppt_pl1_spl);
 }
 static DEVICE_ATTR_RW(ppt_pl1_spl);
@@ -1166,6 +1238,8 @@ static ssize_t ppt_fppt_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->ppt_fppt);
 }
 static DEVICE_ATTR_RW(ppt_fppt);
@@ -1209,6 +1283,8 @@ static ssize_t ppt_apu_sppt_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->ppt_apu_sppt);
 }
 static DEVICE_ATTR_RW(ppt_apu_sppt);
@@ -1252,6 +1328,8 @@ static ssize_t ppt_platform_sppt_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->ppt_platform_sppt);
 }
 static DEVICE_ATTR_RW(ppt_platform_sppt);
@@ -1295,6 +1373,8 @@ static ssize_t nv_dynamic_boost_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->nv_dynamic_boost);
 }
 static DEVICE_ATTR_RW(nv_dynamic_boost);
@@ -1338,11 +1418,15 @@ static ssize_t nv_temp_target_show(struct device *dev,
 {
 	struct asus_wmi *asus = dev_get_drvdata(dev);
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%u\n", asus->nv_temp_target);
 }
 static DEVICE_ATTR_RW(nv_temp_target);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Ally MCU Powersave ********************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t mcu_powersave_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -1353,6 +1437,8 @@ static ssize_t mcu_powersave_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
@@ -1388,6 +1474,7 @@ static ssize_t mcu_powersave_store(struct device *dev,
 	return count;
 }
 static DEVICE_ATTR_RW(mcu_powersave);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Battery ********************************************************************/
 
@@ -2261,6 +2348,7 @@ static int asus_wmi_rfkill_init(struct asus_wmi *asus)
 }
 
 /* Panel Overdrive ************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t panel_od_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -2271,6 +2359,8 @@ static ssize_t panel_od_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
@@ -2307,9 +2397,10 @@ static ssize_t panel_od_store(struct device *dev,
 	return count;
 }
 static DEVICE_ATTR_RW(panel_od);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Bootup sound ***************************************************************/
-
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t boot_sound_show(struct device *dev,
 			     struct device_attribute *attr, char *buf)
 {
@@ -2320,6 +2411,8 @@ static ssize_t boot_sound_show(struct device *dev,
 	if (result < 0)
 		return result;
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", result);
 }
 
@@ -2355,8 +2448,10 @@ static ssize_t boot_sound_store(struct device *dev,
 	return count;
 }
 static DEVICE_ATTR_RW(boot_sound);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Mini-LED mode **************************************************************/
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t mini_led_mode_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -2387,6 +2482,8 @@ static ssize_t mini_led_mode_show(struct device *dev,
 		}
 	}
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "%d\n", value);
 }
 
@@ -2457,10 +2554,13 @@ static ssize_t available_mini_led_mode_show(struct device *dev,
 		return sysfs_emit(buf, "0 1 2\n");
 	}
 
+	asus_wmi_show_deprecated();
+
 	return sysfs_emit(buf, "0\n");
 }
 
 static DEVICE_ATTR_RO(available_mini_led_mode);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Quirks *********************************************************************/
 
@@ -3770,6 +3870,7 @@ static int throttle_thermal_policy_switch_next(struct asus_wmi *asus)
 	return 0;
 }
 
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 static ssize_t throttle_thermal_policy_show(struct device *dev,
 				   struct device_attribute *attr, char *buf)
 {
@@ -3813,6 +3914,7 @@ static ssize_t throttle_thermal_policy_store(struct device *dev,
  * Throttle thermal policy: 0 - default, 1 - overboost, 2 - silent
  */
 static DEVICE_ATTR_RW(throttle_thermal_policy);
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 /* Platform profile ***********************************************************/
 static int asus_wmi_platform_profile_get(struct platform_profile_handler *pprof,
@@ -4410,27 +4512,29 @@ static struct attribute *platform_attributes[] = {
 	&dev_attr_camera.attr,
 	&dev_attr_cardr.attr,
 	&dev_attr_touchpad.attr,
-	&dev_attr_charge_mode.attr,
-	&dev_attr_egpu_enable.attr,
-	&dev_attr_egpu_connected.attr,
-	&dev_attr_dgpu_disable.attr,
-	&dev_attr_gpu_mux_mode.attr,
 	&dev_attr_lid_resume.attr,
 	&dev_attr_als_enable.attr,
 	&dev_attr_fan_boost_mode.attr,
-	&dev_attr_throttle_thermal_policy.attr,
-	&dev_attr_ppt_pl2_sppt.attr,
-	&dev_attr_ppt_pl1_spl.attr,
-	&dev_attr_ppt_fppt.attr,
-	&dev_attr_ppt_apu_sppt.attr,
-	&dev_attr_ppt_platform_sppt.attr,
-	&dev_attr_nv_dynamic_boost.attr,
-	&dev_attr_nv_temp_target.attr,
-	&dev_attr_mcu_powersave.attr,
-	&dev_attr_boot_sound.attr,
-	&dev_attr_panel_od.attr,
-	&dev_attr_mini_led_mode.attr,
-	&dev_attr_available_mini_led_mode.attr,
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
+		&dev_attr_charge_mode.attr,
+		&dev_attr_egpu_enable.attr,
+		&dev_attr_egpu_connected.attr,
+		&dev_attr_dgpu_disable.attr,
+		&dev_attr_gpu_mux_mode.attr,
+		&dev_attr_ppt_pl2_sppt.attr,
+		&dev_attr_ppt_pl1_spl.attr,
+		&dev_attr_ppt_fppt.attr,
+		&dev_attr_ppt_apu_sppt.attr,
+		&dev_attr_ppt_platform_sppt.attr,
+		&dev_attr_nv_dynamic_boost.attr,
+		&dev_attr_nv_temp_target.attr,
+		&dev_attr_mcu_powersave.attr,
+		&dev_attr_boot_sound.attr,
+		&dev_attr_panel_od.attr,
+		&dev_attr_mini_led_mode.attr,
+		&dev_attr_available_mini_led_mode.attr,
+		&dev_attr_throttle_thermal_policy.attr,
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 	NULL
 };
 
@@ -4452,7 +4556,11 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
 		devid = ASUS_WMI_DEVID_LID_RESUME;
 	else if (attr == &dev_attr_als_enable.attr)
 		devid = ASUS_WMI_DEVID_ALS_ENABLE;
-	else if (attr == &dev_attr_charge_mode.attr)
+	else if (attr == &dev_attr_fan_boost_mode.attr)
+		ok = asus->fan_boost_mode_available;
+
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
+	if (attr == &dev_attr_charge_mode.attr)
 		devid = ASUS_WMI_DEVID_CHARGE_MODE;
 	else if (attr == &dev_attr_egpu_enable.attr)
 		ok = asus->egpu_enable_available;
@@ -4490,6 +4598,7 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
 		ok = asus->mini_led_dev_id != 0;
 	else if (attr == &dev_attr_available_mini_led_mode.attr)
 		ok = asus->mini_led_dev_id != 0;
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 	if (devid != -1) {
 		ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
@@ -4729,7 +4838,19 @@ static int asus_wmi_add(struct platform_device *pdev)
 	if (err)
 		goto fail_platform;
 
+	ally_mcu_usb_plug = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE)
+				&& dmi_check_system(asus_rog_ally_device);
+	if (ally_mcu_usb_plug) {
+		/*
+		 * These steps ensure the device is in a valid good state, this is
+		 * especially important for the Ally 1 after a reboot.
+		 */
+		acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, ASUS_USB0_PWR_EC0_CSEE_ON);
+		msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
+	}
+
 	/* ensure defaults for tunables */
+#if IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS)
 	asus->ppt_pl2_sppt = 5;
 	asus->ppt_pl1_spl = 5;
 	asus->ppt_apu_sppt = 5;
@@ -4741,8 +4862,6 @@ static int asus_wmi_add(struct platform_device *pdev)
 	asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
 	asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
 	asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
-	asus->ally_mcu_usb_switch = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE)
-						&& dmi_check_system(asus_ally_mcu_quirk);
 
 	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE))
 		asus->mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE;
@@ -4753,23 +4872,24 @@ static int asus_wmi_add(struct platform_device *pdev)
 		asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX;
 	else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX_VIVO))
 		asus->gpu_mux_dev = ASUS_WMI_DEVID_GPU_MUX_VIVO;
-
-	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE))
-		asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE;
-	else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2))
-		asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2;
+#endif /* IS_ENABLED(CONFIG_ASUS_WMI_DEPRECATED_ATTRS) */
 
 	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY))
 		asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY;
 	else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO))
 		asus->throttle_thermal_policy_dev = ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO;
 
+	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE))
+		asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE;
+	else if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE2))
+		asus->kbd_rgb_dev = ASUS_WMI_DEVID_TUF_RGB_MODE2;
+
 	err = fan_boost_mode_check_present(asus);
 	if (err)
 		goto fail_fan_boost_mode;
 
 	err = platform_profile_setup(asus);
-	if (err)
+	if (err && err != -EEXIST)
 		goto fail_platform_profile_setup;
 
 	err = asus_wmi_sysfs_init(asus->platform_device);
@@ -4933,34 +5053,6 @@ static int asus_hotk_resume(struct device *device)
 	return 0;
 }
 
-static int asus_hotk_resume_early(struct device *device)
-{
-	struct asus_wmi *asus = dev_get_drvdata(device);
-
-	if (asus->ally_mcu_usb_switch) {
-		/* sleep required to prevent USB0 being yanked then reappearing rapidly */
-		if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8)))
-			dev_err(device, "ROG Ally MCU failed to connect USB dev\n");
-		else
-			msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
-	}
-	return 0;
-}
-
-static int asus_hotk_prepare(struct device *device)
-{
-	struct asus_wmi *asus = dev_get_drvdata(device);
-
-	if (asus->ally_mcu_usb_switch) {
-		/* sleep required to ensure USB0 is disabled before sleep continues */
-		if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7)))
-			dev_err(device, "ROG Ally MCU failed to disconnect USB dev\n");
-		else
-			msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
-	}
-	return 0;
-}
-
 static int asus_hotk_restore(struct device *device)
 {
 	struct asus_wmi *asus = dev_get_drvdata(device);
@@ -5001,11 +5093,32 @@ static int asus_hotk_restore(struct device *device)
 	return 0;
 }
 
+static void asus_ally_s2idle_restore(void)
+{
+	if (ally_mcu_usb_plug) {
+		acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, ASUS_USB0_PWR_EC0_CSEE_ON);
+		msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
+	}
+}
+
+static int asus_hotk_prepare(struct device *device)
+{
+	if (ally_mcu_usb_plug) {
+		acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, ASUS_USB0_PWR_EC0_CSEE_OFF);
+		msleep(ASUS_USB0_PWR_EC0_CSEE_WAIT);
+	}
+	return 0;
+}
+
+/* Use only for Ally devices due to the wake_on_ac */
+static struct acpi_s2idle_dev_ops asus_ally_s2idle_dev_ops = {
+	.restore = asus_ally_s2idle_restore,
+};
+
 static const struct dev_pm_ops asus_pm_ops = {
 	.thaw = asus_hotk_thaw,
 	.restore = asus_hotk_restore,
 	.resume = asus_hotk_resume,
-	.resume_early = asus_hotk_resume_early,
 	.prepare = asus_hotk_prepare,
 };
 
@@ -5033,6 +5146,10 @@ static int asus_wmi_probe(struct platform_device *pdev)
 			return ret;
 	}
 
+	ret = acpi_register_lps0_dev(&asus_ally_s2idle_dev_ops);
+	if (ret)
+		pr_warn("failed to register LPS0 sleep handler in asus-wmi\n");
+
 	return asus_wmi_add(pdev);
 }
 
@@ -5065,6 +5182,7 @@ EXPORT_SYMBOL_GPL(asus_wmi_register_driver);
 
 void asus_wmi_unregister_driver(struct asus_wmi_driver *driver)
 {
+	acpi_unregister_lps0_dev(&asus_ally_s2idle_dev_ops);
 	platform_device_unregister(driver->platform_device);
 	platform_driver_unregister(&driver->platform_driver);
 	used = false;
diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
index a82934694d05..b9daf4469143 100644
--- a/drivers/video/backlight/backlight.c
+++ b/drivers/video/backlight/backlight.c
@@ -162,6 +162,7 @@ static inline void backlight_unregister_fb(struct backlight_device *bd)
 static void backlight_generate_event(struct backlight_device *bd,
 				     enum backlight_update_reason reason)
 {
+#if 0 // We don't want to generate udev events for brightness changes on Steam Deck, as some games like Celeste will re-enumerate controller devices in response to this event.
 	char *envp[2];
 
 	switch (reason) {
@@ -177,6 +178,7 @@ static void backlight_generate_event(struct backlight_device *bd,
 	}
 	envp[1] = NULL;
 	kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp);
+#endif // 0
 	sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness");
 }
 
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index 365e119bebaa..cc21e4272460 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -6,6 +6,9 @@
 #include <linux/types.h>
 #include <linux/dmi.h>
 
+#define ASUS_WMI_MGMT_GUID	"97845ED0-4E6D-11DE-8A39-0800200C9A66"
+#define ASUS_ACPI_UID_ASUSWMI	"ASUSWMI"
+
 /* WMI Methods */
 #define ASUS_WMI_METHODID_SPEC	        0x43455053 /* BIOS SPECification */
 #define ASUS_WMI_METHODID_SFBD		0x44424653 /* Set First Boot Device */
@@ -73,6 +76,7 @@
 #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO 0x00110019
 
 /* Misc */
+#define ASUS_WMI_DEVID_PANEL_HD		0x0005001C
 #define ASUS_WMI_DEVID_PANEL_OD		0x00050019
 #define ASUS_WMI_DEVID_CAMERA		0x00060013
 #define ASUS_WMI_DEVID_LID_FLIP		0x00060062
@@ -133,6 +137,14 @@
 /* dgpu on/off */
 #define ASUS_WMI_DEVID_DGPU		0x00090020
 
+/* Intel E-core and P-core configuration in a format 0x0[E]0[P] */
+#define ASUS_WMI_DEVID_CORES		0x001200D2
+ /* Maximum Intel E-core and P-core availability */
+#define ASUS_WMI_DEVID_CORES_MAX	0x001200D3
+#define ASUS_WMI_DEVID_DGPU_BASE_TGP	0x00120099
+#define ASUS_WMI_DEVID_DGPU_SET_TGP	0x00120098
+#define ASUS_WMI_DEVID_APU_MEM		0x000600C1
+
 /* gpu mux switch, 0 = dGPU, 1 = Optimus */
 #define ASUS_WMI_DEVID_GPU_MUX		0x00090016
 #define ASUS_WMI_DEVID_GPU_MUX_VIVO	0x00090026
@@ -158,8 +170,18 @@
 #define ASUS_WMI_DSTS_LIGHTBAR_MASK	0x0000000F
 
 #if IS_REACHABLE(CONFIG_ASUS_WMI)
+int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval);
+int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval);
 int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval);
 #else
+static inline int asus_wmi_get_devstate_dsts(u32 dev_id, u32 *retval)
+{
+	return -ENODEV;
+}
+static inline int asus_wmi_set_devstate(u32 dev_id, u32 ctrl_param, u32 *retval)
+{
+	return -ENODEV;
+}
 static inline int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1,
 					   u32 *retval)
 {
diff --git a/include/sound/tas2781.h b/include/sound/tas2781.h
index 8cd6da0480b7..72d2060904f6 100644
--- a/include/sound/tas2781.h
+++ b/include/sound/tas2781.h
@@ -156,6 +156,7 @@ struct tasdevice_priv {
 	struct tasdevice_rca rcabin;
 	struct calidata cali_data;
 	struct tasdevice_fw *fmw;
+	struct gpio_desc *speaker_id;
 	struct gpio_desc *reset;
 	struct mutex codec_lock;
 	struct regmap *regmap;
diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c
index 370d847517f9..45cfb5a6f309 100644
--- a/sound/pci/hda/tas2781_hda_i2c.c
+++ b/sound/pci/hda/tas2781_hda_i2c.c
@@ -16,6 +16,7 @@
 #include <linux/i2c.h>
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
+#include <linux/pci_ids.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 #include <sound/hda_codec.h>
@@ -110,10 +111,20 @@ static int tas2781_get_i2c_res(struct acpi_resource *ares, void *data)
 	return 1;
 }
 
+static const struct acpi_gpio_params speakerid_gpios = { 0, 0, false };
+
+static const struct acpi_gpio_mapping tas2781_speaker_id_gpios[] = {
+	{ "speakerid-gpios", &speakerid_gpios, 1 },
+	{ }
+};
+
 static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid)
 {
 	struct acpi_device *adev;
+	struct device *physdev;
 	LIST_HEAD(resources);
+	const char *sub;
+	uint32_t subid;
 	int ret;
 
 	adev = acpi_dev_get_first_match_dev(hid, NULL, -1);
@@ -123,18 +134,45 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid)
 		return -ENODEV;
 	}
 
+	physdev = get_device(acpi_get_first_physical_node(adev));
 	ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p);
-	if (ret < 0)
+	if (ret < 0) {
+		dev_err(p->dev, "Failed to get ACPI resource.\n");
 		goto err;
+	}
+	sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev));
+	if (IS_ERR(sub)) {
+		dev_err(p->dev, "Failed to get SUBSYS ID.\n");
+		goto err;
+	}
+	/* Speaker id was needed for ASUS projects. */
+	ret = kstrtou32(sub, 16, &subid);
+	if (!ret && upper_16_bits(subid) == PCI_VENDOR_ID_ASUSTEK) {
+		ret = devm_acpi_dev_add_driver_gpios(p->dev,
+			tas2781_speaker_id_gpios);
+		if (ret < 0)
+			dev_err(p->dev, "Failed to add driver gpio %d.\n",
+				ret);
+		p->speaker_id = devm_gpiod_get(p->dev, "speakerid", GPIOD_IN);
+		if (IS_ERR(p->speaker_id)) {
+			dev_err(p->dev, "Failed to get Speaker id.\n");
+			ret = PTR_ERR(p->speaker_id);
+			goto err;
+		}
+	} else {
+		p->speaker_id = NULL;
+	}
 
 	acpi_dev_free_resource_list(&resources);
 	strscpy(p->dev_name, hid, sizeof(p->dev_name));
+	put_device(physdev);
 	acpi_dev_put(adev);
 
 	return 0;
 
 err:
 	dev_err(p->dev, "read acpi error, ret: %d\n", ret);
+	put_device(physdev);
 	acpi_dev_put(adev);
 
 	return ret;
@@ -615,7 +653,7 @@ static void tasdev_fw_ready(const struct firmware *fmw, void *context)
 	struct tasdevice_priv *tas_priv = context;
 	struct tas2781_hda *tas_hda = dev_get_drvdata(tas_priv->dev);
 	struct hda_codec *codec = tas_priv->codec;
-	int i, ret;
+	int i, ret, spk_id;
 
 	pm_runtime_get_sync(tas_priv->dev);
 	mutex_lock(&tas_priv->codec_lock);
@@ -648,8 +686,25 @@ static void tasdev_fw_ready(const struct firmware *fmw, void *context)
 	tasdevice_dsp_remove(tas_priv);
 
 	tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING;
-	scnprintf(tas_priv->coef_binaryname, 64, "TAS2XXX%04X.bin",
-		codec->core.subsystem_id & 0xffff);
+	if (tas_priv->speaker_id != NULL) {
+		// Speaker id need to be checked for ASUS only.
+		spk_id = gpiod_get_value(tas_priv->speaker_id);
+		if (spk_id < 0) {
+			// Speaker id is not valid, use default.
+			dev_dbg(tas_priv->dev, "Wrong spk_id = %d\n", spk_id);
+			spk_id = 0;
+		}
+		snprintf(tas_priv->coef_binaryname,
+			  sizeof(tas_priv->coef_binaryname),
+			  "TAS2XXX%04X%d.bin",
+			  lower_16_bits(codec->core.subsystem_id),
+			  spk_id);
+	} else {
+		snprintf(tas_priv->coef_binaryname,
+			  sizeof(tas_priv->coef_binaryname),
+			  "TAS2XXX%04X.bin",
+			  lower_16_bits(codec->core.subsystem_id));
+	}
 	ret = tasdevice_dsp_parser(tas_priv);
 	if (ret) {
 		dev_err(tas_priv->dev, "dspfw load %s error\n",
diff --git a/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h
index 93d9e3886b7e..81e7c12ebf3f 100644
--- a/sound/soc/amd/acp/acp-mach.h
+++ b/sound/soc/amd/acp/acp-mach.h
@@ -27,8 +27,8 @@
 enum be_id {
 	HEADSET_BE_ID = 0,
 	AMP_BE_ID,
-	DMIC_BE_ID,
 	BT_BE_ID,
+	DMIC_BE_ID,
 };
 
 enum cpu_endpoints {
diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c
index 99986090b4a6..e259be44ce8f 100644
--- a/sound/soc/codecs/max98388.c
+++ b/sound/soc/codecs/max98388.c
@@ -390,27 +390,43 @@ static void max98388_reset(struct max98388_priv *max98388, struct device *dev)
 {
 	int ret, reg, count;
 
+
 	/* Software Reset */
 	ret = regmap_update_bits(max98388->regmap,
 				 MAX98388_R2000_SW_RESET,
 				 MAX98388_SOFT_RESET,
 				 MAX98388_SOFT_RESET);
-	if (ret)
+
+	if (ret) {
 		dev_err(dev, "Reset command failed. (ret:%d)\n", ret);
+		goto exit;
+	}
+
 
 	count = 0;
 	while (count < 3) {
 		usleep_range(10000, 11000);
+
 		/* Software Reset Verification */
 		ret = regmap_read(max98388->regmap,
 				  MAX98388_R22FF_REV_ID, &reg);
+
 		if (!ret) {
 			dev_info(dev, "Reset completed (retry:%d)\n", count);
-			return;
+			goto exit;
 		}
 		count++;
 	}
+
 	dev_err(dev, "Reset failed. (ret:%d)\n", ret);
+
+
+exit:
+	regcache_cache_only(max98388->regmap, true);
+	ret = regmap_update_bits(max98388->regmap,
+				 MAX98388_R2000_SW_RESET,
+				 MAX98388_SOFT_RESET, 0);
+	regcache_cache_only(max98388->regmap, false);
 }
 
 static int max98388_probe(struct snd_soc_component *component)
@@ -419,6 +435,7 @@ static int max98388_probe(struct snd_soc_component *component)
 
 	/* Software Reset */
 	max98388_reset(max98388, component->dev);
+	usleep_range(400, 1000);
 
 	/* General channel source configuration */
 	regmap_write(max98388->regmap,
@@ -812,6 +829,7 @@ static bool max98388_readable_register(struct device *dev,
 	case MAX98388_R210E_AUTO_RESTART:
 	case MAX98388_R210F_GLOBAL_EN:
 	case MAX98388_R22FF_REV_ID:
+	case MAX98388_R2000_SW_RESET:
 		return true;
 	default:
 		return false;
@@ -824,6 +842,7 @@ static bool max98388_volatile_reg(struct device *dev, unsigned int reg)
 	case MAX98388_R2001_INT_RAW1 ... MAX98388_R2005_INT_STATE2:
 	case MAX98388_R210F_GLOBAL_EN:
 	case MAX98388_R22FF_REV_ID:
+	case MAX98388_R2000_SW_RESET:
 		return true;
 	default:
 		return false;
@@ -867,6 +886,7 @@ static int max98388_resume(struct device *dev)
 
 	regcache_cache_only(max98388->regmap, false);
 	max98388_reset(max98388, dev);
+	usleep_range(400, 1000);
 	regcache_sync(max98388->regmap);
 
 	return 0;
-- 
2.47.1

