From 091bab55ece63fd59b5fc3f8ed774dae899e6a17 Mon Sep 17 00:00:00 2001
From: Peter Jung <admin@ptr1337.dev>
Date: Sun, 20 Apr 2025 19:41:12 +0200
Subject: [PATCH] handheld

Signed-off-by: Peter Jung <admin@ptr1337.dev>
---
 .../wmi/devices/msi-wmi-platform.rst          |   26 +
 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 +
 .../gpu/drm/drm_panel_orientation_quirks.c    |   44 +-
 drivers/hid/hid-ids.h                         |    1 +
 drivers/hid/hid-input.c                       |    2 +
 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                  |    3 +
 drivers/platform/x86/msi-wmi-platform.c       | 1188 ++++++++++++++++-
 drivers/video/backlight/backlight.c           |    2 +
 sound/soc/amd/acp/acp-mach.h                  |    2 +-
 sound/soc/codecs/max98388.c                   |   24 +-
 25 files changed, 1839 insertions(+), 75 deletions(-)
 create mode 100644 drivers/hwmon/steamdeck-hwmon.c
 create mode 100644 drivers/leds/leds-steamdeck.c
 create mode 100644 drivers/mfd/steamdeck.c

diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
index 31a136942892..ac5f1c72da90 100644
--- a/Documentation/wmi/devices/msi-wmi-platform.rst
+++ b/Documentation/wmi/devices/msi-wmi-platform.rst
@@ -165,6 +165,32 @@ The fan RPM readings can be calculated with the following formula:
 
 If the fan speed reading is zero, then the fan RPM is zero too.
 
+The subfeature ``0x01`` is used to retrieve the fan speed table for the CPU fan. The output
+data contains the fan speed table and two bytes with unknown data. The fan speed table
+consists of six 8-bit entries, each containing a fan speed value in percent.
+
+The subfeature ``0x02`` is used tho retrieve the same data for the GPU fan.
+
+WMI method Set_Fan()
+--------------------
+
+The fan speed tables can be accessed using subfeature ``0x01`` (CPU fan) and subfeature ``0x02``
+(GPU fan). The input data has the same format as the output data of the ``Get_Fan`` WMI method.
+
+WMI method Get_AP()
+-------------------
+
+The current fan mode can be accessed using subfeature ``0x01``. The output data contains a flag
+byte and two bytes of unknown data. If the 7th bit inside the flag byte is cleared then all fans
+are operating in automatic mode, otherwise the fans operate based on the fan speed tables
+accessible thru the ``Get_Fan``/``Set_Fan`` WMI methods.
+
+WMI method Set_AP()
+-------------------
+
+The current fan mode can be changed using subfeature ``0x01``. The input data has the same format
+as the output data of the ``Get_AP`` WMI method.
+
 WMI method Get_WMI()
 --------------------
 
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h
index 6da4f946cac0..5b639276939d 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 b1818e87889a..f13e5eecd32a 100644
--- a/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c
+++ b/drivers/gpu/drm/amd/amdgpu/sdma_v5_2.c
@@ -1339,7 +1339,7 @@ static int sdma_v5_2_sw_init(struct amdgpu_ip_block *ip_block)
 	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 6d99dc4013c2..2890522b5024 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -164,6 +164,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
  *
@@ -4705,7 +4708,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
@@ -4739,11 +4742,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 =
@@ -4753,6 +4772,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
@@ -4784,9 +4806,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,
@@ -4799,9 +4821,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,
@@ -9176,7 +9198,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 81f4c386c287..1f9e10caf456 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;
 };
@@ -3243,6 +3245,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 29606fda029d..ff127f8a405b 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/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c
index c554ad8f246b..b8151db30272 100644
--- a/drivers/gpu/drm/drm_panel_orientation_quirks.c
+++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c
@@ -479,12 +479,48 @@ static const struct dmi_system_id orientation_data[] = {
 		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"),
 		},
 		.driver_data = (void *)&lcd1600x2560_leftside_up,
-	}, {	/* OneXPlayer Mini (Intel) */
+	}, {	/* OneXPlayer X1 AMD */
 		.matches = {
-		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK TECHNOLOGY CO., LTD."),
-		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"),
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 A"),
+		},
+		.driver_data = (void *)&lcd1600x2560_leftside_up,
+	}, {	/* OneXPlayer X1 AMD Strix Point */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Pro"),
+		},
+		.driver_data = (void *)&lcd1600x2560_leftside_up,
+	}, {	/* OneXPlayer X1 Intel */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 i"),
+		},
+		.driver_data = (void *)&lcd1600x2560_leftside_up,
+	}, {	/* OneXPlayer X1 mini (AMD) */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 mini"),
 		},
-		.driver_data = (void *)&lcd1200x1920_leftside_up,
+		.driver_data = (void *)&lcd1600x2560_leftside_up,
+	}, {	/* OneXPlayer OneXFly F1 Pro (OLED) */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1Pro"),
+		},
+		.driver_data = (void *)&lcd1080x1920_leftside_up,
+	}, {	/* OneXPlayer OneXFly F1 Pro (OLED) LE Red variant */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1 EVA-02"),
+		},
+		.driver_data = (void *)&lcd1080x1920_leftside_up,
+	}, {	/* Zotac Gaming Zone (OLED) */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ZOTAC"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ZOTAC GAMING ZONE"),
+		},
+		.driver_data = (void *)&lcd1080x1920_leftside_up,
 	}, {	/* OrangePi Neo */
 		.matches = {
 		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OrangePi"),
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index b716cafc63b1..49816393230e 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1045,6 +1045,7 @@
 #define USB_VENDOR_ID_NOVATEK		0x0603
 #define USB_DEVICE_ID_NOVATEK_PCT	0x0600
 #define USB_DEVICE_ID_NOVATEK_MOUSE	0x1602
+#define I2C_DEVICE_ID_ONEXPLAYER_X1    0xF001
 
 #define USB_VENDOR_ID_NTI               0x0757
 #define USB_DEVICE_ID_USB_SUN           0x0a00
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 9d80635a91eb..08e87577feef 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -390,6 +390,8 @@ static const struct hid_device_id hid_battery_quirks[] = {
 	 * set HID_BATTERY_QUIRK_IGNORE for all Elan I2C-HID devices.
 	 */
 	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE },
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_NOVATEK, I2C_DEVICE_ID_ONEXPLAYER_X1),
+	  HID_BATTERY_QUIRK_IGNORE },
 	{}
 };
 
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 4cbaba15d86e..12bfba7491c1 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2089,6 +2089,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 b7ef0f0562d3..664e79b0995f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -210,6 +210,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 2b27d043921c..e39a279d5290 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -1003,6 +1003,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 6ad52e219ec6..2ec1c891388e 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -84,6 +84,7 @@ obj-$(CONFIG_LEDS_QNAP_MCU)		+= leds-qnap-mcu.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_ST1202)		+= leds-st1202.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 6b0682af6e32..d4ce0aaa652b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2439,5 +2439,16 @@ config MFD_UPBOARD_FPGA
 	  To compile this driver as a module, choose M here: the module will be
 	  called upboard-fpga.
 
+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 9220eaf7cf12..16f91a3e162e 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -293,4 +293,6 @@ obj-$(CONFIG_MFD_QNAP_MCU)	+= qnap-mcu.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
+
 obj-$(CONFIG_MFD_UPBOARD_FPGA)	+= upboard-fpga.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 12dd37c2e904..c61a9230847e 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -732,7 +732,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 7edab99d3ae9..3d0fd8df9392 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -750,8 +750,11 @@ config MSI_WMI
 
 config MSI_WMI_PLATFORM
 	tristate "MSI WMI Platform features"
+	depends on ACPI_BATTERY
 	depends on ACPI_WMI
 	depends on HWMON
+	select ACPI_PLATFORM_PROFILE
+	select FW_ATTR_CLASS
 	help
 	  Say Y here if you want to have support for WMI-based platform features
 	  like fan sensor access on MSI machines.
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index 9b5c7f8c79b0..acd343f53be1 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -13,26 +13,36 @@
 #include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/device/driver.h>
+#include <linux/dmi.h>
 #include <linux/errno.h>
+#include <linux/fixp-arith.h>
+#include <linux/platform_profile.h>
 #include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
 #include <linux/kernel.h>
+#include <linux/kstrtox.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/printk.h>
 #include <linux/rwsem.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
 #include <linux/types.h>
 #include <linux/wmi.h>
+#include <acpi/battery.h>
 
 #include <linux/unaligned.h>
 
+#include "firmware_attributes_class.h"
+
 #define DRIVER_NAME	"msi-wmi-platform"
 
 #define MSI_PLATFORM_GUID	"ABBC0F6E-8EA1-11d1-00A0-C90629100000"
 
 #define MSI_WMI_PLATFORM_INTERFACE_VERSION	2
 
-#define MSI_PLATFORM_WMI_MAJOR_OFFSET	1
-#define MSI_PLATFORM_WMI_MINOR_OFFSET	2
-
+/* Get_EC() and Set_EC() WMI methods */
 #define MSI_PLATFORM_EC_FLAGS_OFFSET	1
 #define MSI_PLATFORM_EC_MINOR_MASK	GENMASK(3, 0)
 #define MSI_PLATFORM_EC_MAJOR_MASK	GENMASK(5, 4)
@@ -40,6 +50,37 @@
 #define MSI_PLATFORM_EC_IS_TIGERLAKE	BIT(7)
 #define MSI_PLATFORM_EC_VERSION_OFFSET	2
 
+/* Get_Fan() and Set_Fan() WMI methods */
+#define MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED		0x0
+#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE	0x1
+#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE	0x2
+#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE	0x1
+#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE	0x2
+
+/* Get_AP() and Set_AP() WMI methods */
+#define MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE	0x1
+#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET	1
+#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES	BIT(7)
+
+/* Get_Data() and Set_Data() Shift Mode Register */
+#define MSI_PLATFORM_SHIFT_ADDR		0xd2
+#define MSI_PLATFORM_SHIFT_DISABLE	BIT(7)
+#define MSI_PLATFORM_SHIFT_ENABLE	(BIT(7) | BIT(6))
+#define MSI_PLATFORM_SHIFT_SPORT	(MSI_PLATFORM_SHIFT_ENABLE + 4)
+#define MSI_PLATFORM_SHIFT_COMFORT 	(MSI_PLATFORM_SHIFT_ENABLE + 0)
+#define MSI_PLATFORM_SHIFT_GREEN	(MSI_PLATFORM_SHIFT_ENABLE + 1)
+#define MSI_PLATFORM_SHIFT_ECO		(MSI_PLATFORM_SHIFT_ENABLE + 2)
+#define MSI_PLATFORM_SHIFT_USER		(MSI_PLATFORM_SHIFT_ENABLE + 3)
+
+/* Get_Data() and Set_Data() Params */
+#define MSI_PLATFORM_PL1_ADDR	0x50
+#define MSI_PLATFORM_PL2_ADDR	0x51
+#define MSI_PLATFORM_BAT_ADDR	0xd7
+
+/* Get_WMI() WMI method */
+#define MSI_PLATFORM_WMI_MAJOR_OFFSET		1
+#define MSI_PLATFORM_WMI_MINOR_OFFSET		2
+
 static bool force;
 module_param_unsafe(force, bool, 0);
 MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
@@ -76,6 +117,71 @@ enum msi_wmi_platform_method {
 	MSI_PLATFORM_GET_WMI		= 0x1d,
 };
 
+struct msi_wmi_platform_quirk {
+	bool shift_mode;	/* Shift mode is supported */
+	bool charge_threshold;	/* Charge threshold is supported */
+	bool dual_fans; 	/* For devices with two hwmon fans */
+	bool restore_curves;	/* Restore factory curves on unload */
+	int pl_min;		/* Minimum PLx value */
+	int pl1_max;		/* Maximum PL1 value */
+	int pl2_max;		/* Maximum PL2 value */
+};
+
+struct msi_wmi_platform_factory_curves {
+	u8 cpu_fan_table[32];
+	u8 gpu_fan_table[32];
+	u8 cpu_temp_table[32];
+	u8 gpu_temp_table[32];
+};
+
+struct msi_wmi_platform_data {
+	struct wmi_device *wdev;
+	struct msi_wmi_platform_quirk *quirks;
+	struct device *ppdev;
+	struct msi_wmi_platform_factory_curves factory_curves;
+	struct acpi_battery_hook battery_hook;
+	struct device_attribute battery_attr;
+	struct device *fw_attrs_dev;
+	struct kset *fw_attrs_kset;
+	struct mutex write_lock;
+};
+
+enum msi_fw_attr_id {
+	MSI_ATTR_PPT_PL1_SPL,
+	MSI_ATTR_PPT_PL2_SPPT,
+};
+
+static const char *const msi_fw_attr_name[] = {
+	[MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl",
+	[MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt",
+};
+
+static const char *const msi_fw_attr_desc[] = {
+	[MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)",
+	[MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)",
+};
+
+#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8"
+
+struct msi_fw_attr {
+	struct msi_wmi_platform_data *data;
+	enum msi_fw_attr_id fw_attr_id;
+	struct attribute_group attr_group;
+	struct kobj_attribute display_name;
+	struct kobj_attribute current_value;
+	struct kobj_attribute min_value;
+	struct kobj_attribute max_value;
+
+	u32 min;
+	u32 max;
+
+	int (*get_value)(struct msi_wmi_platform_data *data,
+			 struct msi_fw_attr *fw_attr, char *buf);
+	ssize_t (*set_value)(struct msi_wmi_platform_data *data,
+			     struct msi_fw_attr *fw_attr, const char *buf,
+			     size_t count);
+};
+
 struct msi_wmi_platform_debugfs_data {
 	struct wmi_device *wdev;
 	enum msi_wmi_platform_method method;
@@ -116,6 +222,53 @@ static const char * const msi_wmi_platform_debugfs_names[] = {
 	"get_wmi"
 };
 
+static struct msi_wmi_platform_quirk quirk_default = {};
+static struct msi_wmi_platform_quirk quirk_gen1 = {
+	.shift_mode = true,
+	.charge_threshold = true,
+	.dual_fans = true,
+	.restore_curves = true,
+	.pl_min = 8,
+	.pl1_max = 43,
+	.pl2_max = 45
+};
+static struct msi_wmi_platform_quirk quirk_gen2 = {
+	.shift_mode = true,
+	.charge_threshold = true,
+	.dual_fans = true,
+	.restore_curves = true,
+	.pl_min = 8,
+	.pl1_max = 30,
+	.pl2_max = 37
+};
+
+static const struct dmi_system_id msi_quirks[] = {
+	{
+		.ident = "MSI Claw (gen 1)",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1T41"),
+		},
+		.driver_data = &quirk_gen1,
+	},
+	{
+		.ident = "MSI Claw AI+ 7",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1T42"),
+		},
+		.driver_data = &quirk_gen2,
+	},
+	{
+		.ident = "MSI Claw AI+ 8",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"),
+		},
+		.driver_data = &quirk_gen2,
+	},
+};
+
 static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
 {
 	if (obj->type != ACPI_TYPE_BUFFER)
@@ -133,18 +286,18 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
 }
 
 static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method,
-				  u8 *input, size_t input_length, u8 *output, size_t output_length)
+				  u8 *data, size_t length)
 {
 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
 	struct acpi_buffer in = {
-		.length = input_length,
-		.pointer = input
+		.length = length,
+		.pointer = data
 	};
 	union acpi_object *obj;
 	acpi_status status;
 	int ret;
 
-	if (!input_length || !output_length)
+	if (!length)
 		return -EINVAL;
 
 	status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
@@ -155,44 +308,406 @@ static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform
 	if (!obj)
 		return -ENODATA;
 
-	ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
+	ret = msi_wmi_platform_parse_buffer(obj, data, length);
 	kfree(obj);
 
 	return ret;
 }
 
+static ssize_t msi_wmi_platform_fan_table_show(struct device *dev, struct device_attribute *attr,
+					       char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { sattr->nr };
+	u8 fan_percent;
+	int ret;
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	fan_percent = buffer[sattr->index + 1];
+	if (fan_percent > 100)
+		return -EIO;
+
+	return sysfs_emit(buf, "%d\n", fixp_linear_interpolate(0, 0, 100, 255, fan_percent));
+}
+
+static ssize_t msi_wmi_platform_fan_table_store(struct device *dev, struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { sattr->nr };
+	long speed;
+	int ret;
+
+	ret = kstrtol(buf, 10, &speed);
+	if (ret < 0)
+		return ret;
+
+	speed = clamp_val(speed, 0, 255);
+
+	guard(mutex)(&data->write_lock);
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	buffer[0] = sattr->nr;
+	buffer[sattr->index + 1] = fixp_linear_interpolate(0, 0, 255, 100, speed);
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_FAN, buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t msi_wmi_platform_temp_table_show(struct device *dev, struct device_attribute *attr,
+						char *buf)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { sattr->nr };
+	u8 temp_c;
+	int ret;
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_TEMPERATURE,
+				     buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	temp_c = buffer[sattr->index + 1];
+
+	return sysfs_emit(buf, "%d\n", temp_c);
+}
+
+static ssize_t msi_wmi_platform_temp_table_store(struct device *dev, struct device_attribute *attr,
+						 const char *buf, size_t count)
+{
+	struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr);
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { sattr->nr };
+	long temp_c;
+	int ret;
+
+	ret = kstrtol(buf, 10, &temp_c);
+	if (ret < 0)
+		return ret;
+
+	temp_c = clamp_val(temp_c, 0, 255);
+
+	guard(mutex)(&data->write_lock);
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_TEMPERATURE,
+				     buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	buffer[0] = sattr->nr;
+	buffer[sattr->index + 1] = temp_c;
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_TEMPERATURE,
+				     buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x0);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x6);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x7);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x1);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x2);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x6);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x0);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x6);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, msi_wmi_platform_temp_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x7);
+
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x1);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x2);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x3);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x4);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x5);
+static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, msi_wmi_platform_fan_table,
+			       MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x6);
+
+static struct attribute *msi_wmi_platform_hwmon_attrs[] = {
+	&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
+
+	&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
+
+	&sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr,
+
+	&sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr,
+	&sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon);
+
+static int msi_wmi_platform_curves_save(struct msi_wmi_platform_data *data)
+{
+	int ret;
+
+	data->factory_curves.cpu_fan_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
+	ret = msi_wmi_platform_query(
+		data->wdev, MSI_PLATFORM_GET_FAN,
+		data->factory_curves.cpu_fan_table,
+		sizeof(data->factory_curves.cpu_fan_table));
+	if (ret < 0)
+		return ret;
+	data->factory_curves.cpu_fan_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE;
+
+	data->factory_curves.gpu_fan_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
+	ret = msi_wmi_platform_query(
+		data->wdev, MSI_PLATFORM_GET_FAN,
+		data->factory_curves.gpu_fan_table,
+		sizeof(data->factory_curves.gpu_fan_table));
+	if (ret < 0)
+		return ret;
+	data->factory_curves.gpu_fan_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE;
+
+	data->factory_curves.cpu_temp_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
+	ret = msi_wmi_platform_query(
+		data->wdev, MSI_PLATFORM_GET_TEMPERATURE,
+		data->factory_curves.cpu_temp_table,
+		sizeof(data->factory_curves.cpu_temp_table));
+	if (ret < 0)
+		return ret;
+	data->factory_curves.cpu_temp_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE;
+
+	data->factory_curves.gpu_temp_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
+	ret = msi_wmi_platform_query(
+		data->wdev, MSI_PLATFORM_GET_TEMPERATURE,
+		data->factory_curves.gpu_temp_table,
+		sizeof(data->factory_curves.gpu_temp_table));
+	if (ret < 0)
+		return ret;
+	data->factory_curves.gpu_temp_table[0] =
+		MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE;
+
+	return 0;
+}
+
+static int msi_wmi_platform_curves_load(struct msi_wmi_platform_data *data)
+{
+	u8 buffer[32] = { };
+	int ret;
+
+	memcpy(buffer, data->factory_curves.cpu_fan_table,
+	       sizeof(data->factory_curves.cpu_fan_table));
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_FAN, buffer,
+				     sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	memcpy(buffer, data->factory_curves.gpu_fan_table,
+	       sizeof(data->factory_curves.gpu_fan_table));
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_FAN, buffer,
+				     sizeof(buffer));
+	if (ret < 0)
+		return ret;
+	
+	memcpy(buffer, data->factory_curves.cpu_temp_table,
+	       sizeof(data->factory_curves.cpu_temp_table));
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_TEMPERATURE,
+				     buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+	
+	memcpy(buffer, data->factory_curves.gpu_temp_table,
+	       sizeof(data->factory_curves.gpu_temp_table));
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_TEMPERATURE,
+				     buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+
 static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
 					   u32 attr, int channel)
 {
+	if (type == hwmon_pwm && attr == hwmon_pwm_enable)
+		return 0644;
+
 	return 0444;
 }
 
 static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
 				 int channel, long *val)
 {
-	struct wmi_device *wdev = dev_get_drvdata(dev);
-	u8 input[32] = { 0 };
-	u8 output[32];
-	u16 data;
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { };
+	u16 value;
+	u8 flags;
 	int ret;
 
-	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
-				     sizeof(output));
-	if (ret < 0)
-		return ret;
+	switch (type) {
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_input:
+			buffer[0] = MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED;
+			ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_FAN, buffer,
+						     sizeof(buffer));
+			if (ret < 0)
+				return ret;
+
+			value = get_unaligned_be16(&buffer[channel * 2 + 1]);
+			if (!value)
+				*val = 0;
+			else
+				*val = 480000 / value;
+
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+			buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+			ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_AP, buffer,
+						     sizeof(buffer));
+			if (ret < 0)
+				return ret;
+
+			flags = buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET];
+			if (flags & MSI_PLATFORM_AP_ENABLE_FAN_TABLES)
+				*val = 1;	// TODO: Do fan tables count as "manual fan control"?
+			else
+				*val = 2;
+
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
 
-	data = get_unaligned_be16(&output[channel * 2 + 1]);
-	if (!data)
-		*val = 0;
-	else
-		*val = 480000 / data;
+static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+				  int channel, long val)
+{
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { };
+	int ret;
 
-	return 0;
+	switch (type) {
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+			guard(mutex)(&data->write_lock);
+
+			buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+			ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_AP, buffer,
+						     sizeof(buffer));
+			if (ret < 0)
+				return ret;
+
+			buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+			switch (val) {
+			case 1:
+				buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] |=
+					MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
+				break;
+			case 2:
+				buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] &=
+					~MSI_PLATFORM_AP_ENABLE_FAN_TABLES;
+				break;
+			default:
+				return -EINVAL;
+			}
+
+			ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_AP, buffer,
+						     sizeof(buffer));
+			if (ret < 0)
+				return ret;
+			
+			if (val == 2 && data->quirks->restore_curves) {
+				ret = msi_wmi_platform_curves_load(data);
+				if (ret < 0)
+					return ret;
+			}
+
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
 }
 
 static const struct hwmon_ops msi_wmi_platform_ops = {
 	.is_visible = msi_wmi_platform_is_visible,
 	.read = msi_wmi_platform_read,
+	.write = msi_wmi_platform_write,
 };
 
 static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
@@ -202,6 +717,10 @@ static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
 			   HWMON_F_INPUT,
 			   HWMON_F_INPUT
 			   ),
+	HWMON_CHANNEL_INFO(pwm,
+			   HWMON_PWM_ENABLE,
+			   HWMON_PWM_ENABLE
+			   ),
 	NULL
 };
 
@@ -210,8 +729,496 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
 	.info = msi_wmi_platform_info,
 };
 
-static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
-				      loff_t *offset)
+static const struct hwmon_channel_info * const msi_wmi_platform_info_dual[] = {
+	HWMON_CHANNEL_INFO(fan,
+			   HWMON_F_INPUT,
+			   HWMON_F_INPUT
+			   ),
+	HWMON_CHANNEL_INFO(pwm,
+			   HWMON_PWM_ENABLE,
+			   HWMON_PWM_ENABLE
+			   ),
+	NULL
+};
+
+static const struct hwmon_chip_info msi_wmi_platform_chip_info_dual = {
+	.ops = &msi_wmi_platform_ops,
+	.info = msi_wmi_platform_info_dual,
+};
+
+static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+	set_bit(PLATFORM_PROFILE_BALANCED, choices);
+	set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+	return 0;
+}
+
+static int msi_wmi_platform_profile_get(struct device *dev,
+					enum platform_profile_option *profile)
+{
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+
+	u8 buffer[32] = { };
+
+	buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
+
+	int ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
+	
+	if (buffer[0] != 1)
+		return -EINVAL;
+	
+	switch (buffer[1]) {
+	case MSI_PLATFORM_SHIFT_SPORT:
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		return 0;
+	case MSI_PLATFORM_SHIFT_COMFORT:
+		*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+		return 0;
+	case MSI_PLATFORM_SHIFT_GREEN:
+		*profile = PLATFORM_PROFILE_BALANCED;
+		return 0;
+	case MSI_PLATFORM_SHIFT_ECO:
+		*profile = PLATFORM_PROFILE_LOW_POWER;
+		return 0;
+	case MSI_PLATFORM_SHIFT_USER:
+		*profile = PLATFORM_PROFILE_CUSTOM;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int msi_wmi_platform_profile_set(struct device *dev,
+					enum platform_profile_option profile)
+{
+	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
+	u8 buffer[32] = { };
+
+	buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
+
+	switch (profile) {
+	case PLATFORM_PROFILE_PERFORMANCE:
+		buffer[1] = MSI_PLATFORM_SHIFT_SPORT;
+		break;
+	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+		buffer[1] = MSI_PLATFORM_SHIFT_COMFORT;
+		break;
+	case PLATFORM_PROFILE_BALANCED:
+		buffer[1] = MSI_PLATFORM_SHIFT_GREEN;
+		break;
+	case PLATFORM_PROFILE_LOW_POWER:
+		buffer[1] = MSI_PLATFORM_SHIFT_ECO;
+		break;
+	case PLATFORM_PROFILE_CUSTOM:
+		buffer[1] = MSI_PLATFORM_SHIFT_USER;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
+}
+
+static const struct platform_profile_ops msi_wmi_platform_profile_ops = {
+	.probe = msi_wmi_platform_profile_probe,
+	.profile_get = msi_wmi_platform_profile_get,
+	.profile_set = msi_wmi_platform_profile_set,
+};
+
+/* Firmware Attributes setup */
+static int data_get_addr(struct msi_wmi_platform_data *data,
+			 const enum msi_fw_attr_id id)
+{
+	switch (id) {
+	case MSI_ATTR_PPT_PL1_SPL:
+		return MSI_PLATFORM_PL1_ADDR;
+	case MSI_ATTR_PPT_PL2_SPPT:
+		return MSI_PLATFORM_PL2_ADDR;
+	default:
+		pr_warn("Invalid attribute id %d\n", id);
+		return -EINVAL;
+	}
+}
+
+static ssize_t data_set_value(struct msi_wmi_platform_data *data,
+			      struct msi_fw_attr *fw_attr, const char *buf,
+			      size_t count)
+{
+	u8 buffer[32] = { 0 };
+	int ret, fwid;
+	u32 value;
+
+	fwid = data_get_addr(data, fw_attr->fw_attr_id);
+	if (fwid < 0)
+		return fwid;
+
+	ret = kstrtou32(buf, 10, &value);
+	if (ret)
+		return ret;
+
+	if (fw_attr->min >= 0 && value < fw_attr->min)
+		return -EINVAL;
+	if (fw_attr->max >= 0 && value > fw_attr->max)
+		return -EINVAL;
+
+	buffer[0] = fwid;
+	put_unaligned_le32(value, &buffer[1]);
+
+	guard(mutex)(&data->write_lock);
+	
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer));
+	if (ret) {
+		pr_warn("Failed to set_data with id %d: %d\n",
+			fw_attr->fw_attr_id, ret);
+		return ret;
+	}
+
+	return count;
+}
+
+static int data_get_value(struct msi_wmi_platform_data *data,
+			  struct msi_fw_attr *fw_attr, char *buf)
+{
+	u8 buffer[32] = { 0 };
+	u32 value;
+	int ret, addr;
+
+	addr = data_get_addr(data, fw_attr->fw_attr_id);
+	if (addr < 0)
+		return addr;
+
+	buffer[0] = addr;
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_DATA, buffer,
+				     sizeof(buffer));
+	if (ret) {
+		pr_warn("Failed to show set_data for id %d: %d\n",
+			fw_attr->fw_attr_id, ret);
+		return ret;
+	}
+
+	value = get_unaligned_le32(&buffer[1]);
+
+	return sysfs_emit(buf, "%d\n", value);
+}
+
+static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr,
+					       char *buf)
+{
+	return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE);
+}
+
+static struct kobj_attribute fw_attr_display_name_language_code =
+	__ATTR_RO(display_name_language_code);
+
+static ssize_t scalar_increment_show(struct kobject *kobj,
+					       struct kobj_attribute *attr,
+					       char *buf)
+{
+	return sysfs_emit(buf, "1\n");
+}
+
+static struct kobj_attribute fw_attr_scalar_increment =
+	__ATTR_RO(scalar_increment);
+
+static ssize_t pending_reboot_show(struct kobject *kobj,
+				     struct kobj_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "0\n");
+}
+
+static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot);
+
+static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct msi_fw_attr *fw_attr =
+		container_of(attr, struct msi_fw_attr, display_name);
+
+	return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]);
+}
+
+static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct msi_fw_attr *fw_attr =
+		container_of(attr, struct msi_fw_attr, current_value);
+
+	return fw_attr->get_value(fw_attr->data, fw_attr, buf);
+}
+
+static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct msi_fw_attr *fw_attr =
+		container_of(attr, struct msi_fw_attr, current_value);
+
+	return fw_attr->set_value(fw_attr->data, fw_attr, buf, count);
+}
+
+static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr,
+			 char *buf)
+{
+	return sysfs_emit(buf, "integer\n");
+}
+
+static struct kobj_attribute fw_attr_type_int = {
+	.attr = { .name = "type", .mode = 0444 },
+	.show = type_show,
+};
+
+static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+			      char *buf)
+{
+	struct msi_fw_attr *fw_attr =
+		container_of(attr, struct msi_fw_attr, min_value);
+
+	return sysfs_emit(buf, "%d\n", fw_attr->min);
+}
+
+static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+			      char *buf)
+{
+	struct msi_fw_attr *fw_attr =
+		container_of(attr, struct msi_fw_attr, max_value);
+
+	return sysfs_emit(buf, "%d\n", fw_attr->max);
+}
+
+#define FW_ATTR_ENUM_MAX_ATTRS  7
+
+static int
+msi_fw_attr_init(struct msi_wmi_platform_data *data,
+		 const enum msi_fw_attr_id fw_attr_id,
+		 struct kobj_attribute *fw_attr_type, const s32 min,
+		 const s32 max,
+		 int (*get_value)(struct msi_wmi_platform_data *data,
+				  struct msi_fw_attr *fw_attr, char *buf),
+		 ssize_t (*set_value)(struct msi_wmi_platform_data *data,
+				      struct msi_fw_attr *fw_attr,
+				      const char *buf, size_t count))
+{
+	struct msi_fw_attr *fw_attr;
+	struct attribute **attrs;
+	int idx = 0;
+
+	fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL);
+	if (!fw_attr)
+		return -ENOMEM;
+
+	attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1,
+			     sizeof(*attrs), GFP_KERNEL);
+	if (!attrs)
+		return -ENOMEM;
+
+	fw_attr->data = data;
+	fw_attr->fw_attr_id = fw_attr_id;
+	fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id];
+	fw_attr->attr_group.attrs = attrs;
+	fw_attr->get_value = get_value;
+	fw_attr->set_value = set_value;
+
+	attrs[idx++] = &fw_attr_type->attr;
+	if (fw_attr_type == &fw_attr_type_int)
+		attrs[idx++] = &fw_attr_scalar_increment.attr;
+	attrs[idx++] = &fw_attr_display_name_language_code.attr;
+
+	sysfs_attr_init(&fw_attr->display_name.attr);
+	fw_attr->display_name.attr.name = "display_name";
+	fw_attr->display_name.attr.mode = 0444;
+	fw_attr->display_name.show = display_name_show;
+	attrs[idx++] = &fw_attr->display_name.attr;
+
+	sysfs_attr_init(&fw_attr->current_value.attr);
+	fw_attr->current_value.attr.name = "current_value";
+	fw_attr->current_value.attr.mode = 0644;
+	fw_attr->current_value.show = current_value_show;
+	fw_attr->current_value.store = current_value_store;
+	attrs[idx++] = &fw_attr->current_value.attr;
+
+	if (min >= 0) {
+		fw_attr->min = min;
+		sysfs_attr_init(&fw_attr->min_value.attr);
+		fw_attr->min_value.attr.name = "min_value";
+		fw_attr->min_value.attr.mode = 0444;
+		fw_attr->min_value.show = min_value_show;
+		attrs[idx++] = &fw_attr->min_value.attr;
+	} else {
+		fw_attr->min = -1;
+	}
+
+	if (max >= 0) {
+		fw_attr->max = max;
+		sysfs_attr_init(&fw_attr->max_value.attr);
+		fw_attr->max_value.attr.name = "max_value";
+		fw_attr->max_value.attr.mode = 0444;
+		fw_attr->max_value.show = max_value_show;
+		attrs[idx++] = &fw_attr->max_value.attr;
+	} else {
+		fw_attr->max = -1;
+	}
+
+	attrs[idx] = NULL;
+	return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group);
+}
+
+static void msi_kset_unregister(void *data)
+{
+	struct kset *kset = data;
+
+	sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr);
+	kset_unregister(kset);
+}
+
+static void msi_fw_attrs_dev_unregister(void *data)
+{
+	struct device *fw_attrs_dev = data;
+
+	device_unregister(fw_attrs_dev);
+}
+
+static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data)
+{
+	int err;
+
+	data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
+						 NULL, "%s", DRIVER_NAME);
+	if (IS_ERR(data->fw_attrs_dev))
+		return PTR_ERR(data->fw_attrs_dev);
+
+	err = devm_add_action_or_reset(&data->wdev->dev,
+				       msi_fw_attrs_dev_unregister,
+				       data->fw_attrs_dev);
+	if (err)
+		return err;
+
+	data->fw_attrs_kset = kset_create_and_add("attributes", NULL,
+						  &data->fw_attrs_dev->kobj);
+	if (!data->fw_attrs_kset)
+		return -ENOMEM;
+
+	err = sysfs_create_file(&data->fw_attrs_kset->kobj,
+				&fw_attr_pending_reboot.attr);
+	if (err) {
+		kset_unregister(data->fw_attrs_kset);
+		return err;
+	}
+
+	err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister,
+				       data->fw_attrs_kset);
+	if (err)
+		return err;
+
+	if (data->quirks->pl1_max) {
+		err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL,
+					&fw_attr_type_int, data->quirks->pl_min,
+					data->quirks->pl1_max, &data_get_value,
+					&data_set_value);
+		if (err)
+			return err;
+	}
+
+	if (data->quirks->pl2_max) {
+		err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT,
+				       &fw_attr_type_int, data->quirks->pl_min,
+				       data->quirks->pl2_max, &data_get_value,
+				       &data_set_value);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+static ssize_t charge_control_end_threshold_store(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t count)
+{
+	const struct msi_wmi_platform_data *data =
+		container_of(attr, struct msi_wmi_platform_data, battery_attr);
+	u8 buffer[32] = { 0 };
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val > 100)
+		return -EINVAL;
+
+	buffer[0] = MSI_PLATFORM_BAT_ADDR;
+	buffer[1] = val | BIT(7);
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_SET_DATA, buffer,
+				     sizeof(buffer));
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t charge_control_end_threshold_show(struct device *dev,
+						 struct device_attribute *attr,
+						 char *buf)
+{
+	const struct msi_wmi_platform_data *data =
+		container_of(attr, struct msi_wmi_platform_data, battery_attr);
+	u8 buffer[32] = { 0 };
+	u8 value;
+	int ret;
+
+	buffer[0] = MSI_PLATFORM_BAT_ADDR;
+
+	ret = msi_wmi_platform_query(data->wdev, MSI_PLATFORM_GET_DATA, buffer,
+				     sizeof(buffer));
+
+	if (ret)
+		return ret;
+
+	value = buffer[1] & ~BIT(7);
+
+	if (value > 100)
+		return -EIO;
+
+	return sysfs_emit(buf, "%d\n", value);
+}
+
+static int msi_platform_battery_add(struct power_supply *battery,
+			   struct acpi_battery_hook *hook)
+{
+	struct msi_wmi_platform_data *data = container_of(hook,
+						struct msi_wmi_platform_data, battery_hook);
+
+	/* MSI devices only have one battery. */
+	if (strcmp(battery->desc->name, "BAT0") != 0 &&
+	    strcmp(battery->desc->name, "BAT1") != 0 &&
+	    strcmp(battery->desc->name, "BATC") != 0 &&
+	    strcmp(battery->desc->name, "BATT") != 0)
+		return -ENODEV;
+
+	if (device_create_file(&battery->dev,
+			       &data->battery_attr))
+		return -ENODEV;
+
+	return 0;
+}
+
+static int msi_platform_battery_remove(struct power_supply *battery,
+			      struct acpi_battery_hook *hook)
+{
+	struct msi_wmi_platform_data *data = container_of(hook,
+						struct msi_wmi_platform_data, battery_hook);
+
+	device_remove_file(&battery->dev,
+			   &data->battery_attr);
+	return 0;
+}
+
+static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input,
+					      size_t length, loff_t *offset)
 {
 	struct seq_file *seq = fp->private_data;
 	struct msi_wmi_platform_debugfs_data *data = seq->private;
@@ -230,18 +1237,18 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
 	if (ret < 0)
 		return ret;
 
-	down_write(&data->buffer_lock);
-	ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
-				     data->length);
-	up_write(&data->buffer_lock);
-
+	ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length);
 	if (ret < 0)
 		return ret;
 
+	down_write(&data->buffer_lock);
+	memcpy(data->buffer, payload, data->length);
+	up_write(&data->buffer_lock);
+
 	return length;
 }
 
-static int msi_wmi_platform_show(struct seq_file *seq, void *p)
+static int msi_wmi_platform_debugfs_show(struct seq_file *seq, void *p)
 {
 	struct msi_wmi_platform_debugfs_data *data = seq->private;
 	int ret;
@@ -253,19 +1260,19 @@ static int msi_wmi_platform_show(struct seq_file *seq, void *p)
 	return ret;
 }
 
-static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
+static int msi_wmi_platform_debugfs_open(struct inode *inode, struct file *fp)
 {
 	struct msi_wmi_platform_debugfs_data *data = inode->i_private;
 
 	/* The seq_file uses the last byte of the buffer for detecting buffer overflows */
-	return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
+	return single_open_size(fp, msi_wmi_platform_debugfs_show, data, data->length + 1);
 }
 
 static const struct file_operations msi_wmi_platform_debugfs_fops = {
 	.owner = THIS_MODULE,
-	.open = msi_wmi_platform_open,
+	.open = msi_wmi_platform_debugfs_open,
 	.read = seq_read,
-	.write = msi_wmi_platform_write,
+	.write = msi_wmi_platform_debugfs_write,
 	.llseek = seq_lseek,
 	.release = single_release,
 };
@@ -322,35 +1329,36 @@ static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
 					     method);
 }
 
-static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
+static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
 {
 	struct device *hdev;
 
-	hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
-						    &msi_wmi_platform_chip_info, NULL);
+	hdev = devm_hwmon_device_register_with_info(
+		&data->wdev->dev, "msi_wmi_platform", data,
+		data->quirks->dual_fans ? &msi_wmi_platform_chip_info_dual :
+					&msi_wmi_platform_chip_info,
+		msi_wmi_platform_hwmon_groups);
 
 	return PTR_ERR_OR_ZERO(hdev);
 }
 
 static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
 {
-	u8 input[32] = { 0 };
-	u8 output[32];
+	u8 data[32] = { 0 };
 	u8 flags;
 	int ret;
 
-	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
-				     sizeof(output));
+	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, data, sizeof(data));
 	if (ret < 0)
 		return ret;
 
-	flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
+	flags = data[MSI_PLATFORM_EC_FLAGS_OFFSET];
 
 	dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
 		FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
 		FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
 	dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
-		&output[MSI_PLATFORM_EC_VERSION_OFFSET]);
+		&data[MSI_PLATFORM_EC_VERSION_OFFSET]);
 
 	if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
 		if (!force)
@@ -364,33 +1372,49 @@ static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
 
 static int msi_wmi_platform_init(struct wmi_device *wdev)
 {
-	u8 input[32] = { 0 };
-	u8 output[32];
+	u8 data[32] = { 0 };
 	int ret;
 
-	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
-				     sizeof(output));
+	ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, data, sizeof(data));
 	if (ret < 0)
 		return ret;
 
 	dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
-		output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
-		output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
+		data[MSI_PLATFORM_WMI_MAJOR_OFFSET],
+		data[MSI_PLATFORM_WMI_MINOR_OFFSET]);
 
-	if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
+	if (data[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
 		if (!force)
 			return -ENODEV;
 
 		dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
-			 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
-			 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
+			 data[MSI_PLATFORM_WMI_MAJOR_OFFSET],
+			 data[MSI_PLATFORM_WMI_MINOR_OFFSET]);
 	}
 
 	return 0;
 }
 
+static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
+{
+	int err;
+
+	if (!data->quirks->shift_mode)
+		return 0;
+
+	data->ppdev = devm_platform_profile_register(
+		&data->wdev->dev, "msi-wmi-platform", data,
+		&msi_wmi_platform_profile_ops);
+	if (err)
+		return err;
+
+	return PTR_ERR_OR_ZERO(data->ppdev);
+}
+
 static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
 {
+	struct msi_wmi_platform_data *data;
+	const struct dmi_system_id *dmi_id;
 	int ret;
 
 	ret = msi_wmi_platform_init(wdev);
@@ -401,9 +1425,66 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
 	if (ret < 0)
 		return ret;
 
+	data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENODATA;
+
+	data->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, data);
+
+	dmi_id = dmi_first_match(msi_quirks);
+	if (dmi_id)
+		data->quirks = dmi_id->driver_data;
+	else
+		data->quirks = &quirk_default;
+
+	ret = devm_mutex_init(&wdev->dev, &data->write_lock);
+	if (ret < 0)
+		return ret;
+
+	ret = msi_wmi_platform_hwmon_init(data);
+	if (ret < 0)
+		return ret;
+
+	ret = msi_wmi_fw_attrs_init(data);
+	if (ret < 0)
+		return ret;
+	
+	if (data->quirks->charge_threshold) {
+		data->battery_hook.name = "MSI Battery";
+		data->battery_hook.add_battery = msi_platform_battery_add;
+		data->battery_hook.remove_battery = msi_platform_battery_remove;
+		data->battery_attr.attr.name = "charge_control_end_threshold";
+		data->battery_attr.attr.mode = 0644;
+		data->battery_attr.show = charge_control_end_threshold_show;
+		data->battery_attr.store = charge_control_end_threshold_store;
+		battery_hook_register(&data->battery_hook);
+	}
+
+	msi_wmi_platform_profile_setup(data);
+
 	msi_wmi_platform_debugfs_init(wdev);
 
-	return msi_wmi_platform_hwmon_init(wdev);
+	if (data->quirks->restore_curves) {
+		ret = msi_wmi_platform_curves_save(data);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void msi_wmi_platform_remove(struct wmi_device *wdev)
+{
+	struct msi_wmi_platform_data *data = dev_get_drvdata(&wdev->dev);
+	
+	if (data->quirks->charge_threshold)
+		battery_hook_unregister(&data->battery_hook);
+
+	if (data->quirks->restore_curves) {
+		guard(mutex)(&data->write_lock);
+		msi_wmi_platform_curves_load(data);
+	}
 }
 
 static const struct wmi_device_id msi_wmi_platform_id_table[] = {
@@ -419,6 +1500,7 @@ static struct wmi_driver msi_wmi_platform_driver = {
 	},
 	.id_table = msi_wmi_platform_id_table,
 	.probe = msi_wmi_platform_probe,
+	.remove = msi_wmi_platform_remove,
 	.no_singleton = true,
 };
 module_wmi_driver(msi_wmi_platform_driver);
diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
index f699e5827ccb..99e86b983c77 100644
--- a/drivers/video/backlight/backlight.c
+++ b/drivers/video/backlight/backlight.c
@@ -161,6 +161,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) {
@@ -176,6 +177,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/sound/soc/amd/acp/acp-mach.h b/sound/soc/amd/acp/acp-mach.h
index f94c30c20f20..c3b3f047969c 100644
--- a/sound/soc/amd/acp/acp-mach.h
+++ b/sound/soc/amd/acp/acp-mach.h
@@ -29,8 +29,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.49.0.391.g4bbb303af6

