From f6d8f6b0166819f536040ccafa655f166523423b Mon Sep 17 00:00:00 2001
From: Peter Jung <admin@ptr1337.dev>
Date: Sun, 6 Jul 2025 12:04:35 +0200
Subject: [PATCH] handheld

Signed-off-by: Peter Jung <admin@ptr1337.dev>
---
 .../wmi/devices/lenovo-wmi-gamezone.rst       |  203 ++
 .../wmi/devices/lenovo-wmi-other.rst          |  108 +
 .../wmi/devices/msi-wmi-platform.rst          |   26 +
 MAINTAINERS                                   |   23 +
 drivers/acpi/acpica/acinterp.h                |    3 +
 drivers/acpi/acpica/dsmthdat.c                |    1 +
 drivers/acpi/acpica/extrace.c                 |   51 +
 drivers/extcon/Kconfig                        |    7 +
 drivers/extcon/Makefile                       |    1 +
 drivers/extcon/extcon-steamdeck.c             |  180 ++
 drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h      |    2 -
 .../gpu/drm/amd/amdgpu/atombios_encoders.c    |   10 +-
 .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c |    5 +-
 drivers/gpu/drm/amd/display/dc/dc.h           |    2 +-
 .../amd/display/dc/hwss/dce110/dce110_hwseq.c |    9 +
 .../drm/amd/display/dc/link/link_validation.c |   11 +
 .../dc/resource/dcn301/dcn301_resource.c      |    6 +-
 .../gpu/drm/drm_panel_orientation_quirks.c    |   36 +
 drivers/hid/Kconfig                           |    8 +
 drivers/hid/Makefile                          |    6 +
 drivers/hid/hid-core.c                        |   11 +
 drivers/hid/hid-ids.h                         |    9 +
 drivers/hid/hid-input.c                       |    2 +
 drivers/hid/hid-msi-claw.c                    |  484 ++++
 drivers/hid/lenovo-legos-hid/Kconfig          |   11 +
 drivers/hid/lenovo-legos-hid/Makefile         |    6 +
 .../lenovo-legos-hid-config.c                 | 1571 ++++++++++++
 .../lenovo-legos-hid-config.h                 |   19 +
 .../lenovo-legos-hid/lenovo-legos-hid-core.c  |  122 +
 .../lenovo-legos-hid/lenovo-legos-hid-core.h  |   25 +
 drivers/hid/zotac-zone-hid/Kconfig            |    8 +
 drivers/hid/zotac-zone-hid/Makefile           |    6 +
 .../zotac-zone-hid/zotac-zone-hid-config.c    | 2231 +++++++++++++++++
 .../hid/zotac-zone-hid/zotac-zone-hid-core.c  |  597 +++++
 .../hid/zotac-zone-hid/zotac-zone-hid-input.c |  522 ++++
 .../hid/zotac-zone-hid/zotac-zone-hid-rgb.c   |  717 ++++++
 drivers/hid/zotac-zone-hid/zotac-zone.h       |  214 ++
 drivers/hwmon/Kconfig                         |   11 +
 drivers/hwmon/Makefile                        |    1 +
 drivers/hwmon/steamdeck-hwmon.c               |  294 +++
 drivers/input/joystick/xpad.c                 |    4 +
 drivers/leds/Kconfig                          |    7 +
 drivers/leds/Makefile                         |    1 +
 drivers/leds/leds-steamdeck.c                 |  123 +
 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                  |   54 +
 drivers/platform/x86/Makefile                 |    7 +
 drivers/platform/x86/lenovo-wmi-capdata01.c   |  302 +++
 drivers/platform/x86/lenovo-wmi-capdata01.h   |   25 +
 drivers/platform/x86/lenovo-wmi-events.c      |  196 ++
 drivers/platform/x86/lenovo-wmi-events.h      |   20 +
 drivers/platform/x86/lenovo-wmi-gamezone.c    |  407 +++
 drivers/platform/x86/lenovo-wmi-gamezone.h    |   20 +
 drivers/platform/x86/lenovo-wmi-helpers.c     |   74 +
 drivers/platform/x86/lenovo-wmi-helpers.h     |   20 +
 drivers/platform/x86/lenovo-wmi-other.c       |  665 +++++
 drivers/platform/x86/lenovo-wmi-other.h       |   16 +
 drivers/platform/x86/msi-wmi-platform.c       | 1177 ++++++++-
 drivers/platform/x86/zotac-zone-platform.c    | 1122 +++++++++
 drivers/usb/usbip/vhci_hcd.c                  |   35 +-
 drivers/video/backlight/backlight.c           |    2 +
 include/linux/hid.h                           |    2 +
 sound/soc/amd/acp/acp-mach.h                  |    2 +-
 sound/soc/codecs/max98388.c                   |   24 +-
 67 files changed, 11927 insertions(+), 99 deletions(-)
 create mode 100644 Documentation/wmi/devices/lenovo-wmi-gamezone.rst
 create mode 100644 Documentation/wmi/devices/lenovo-wmi-other.rst
 create mode 100644 drivers/extcon/extcon-steamdeck.c
 create mode 100644 drivers/hid/hid-msi-claw.c
 create mode 100644 drivers/hid/lenovo-legos-hid/Kconfig
 create mode 100644 drivers/hid/lenovo-legos-hid/Makefile
 create mode 100644 drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.c
 create mode 100644 drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.h
 create mode 100644 drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.c
 create mode 100644 drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.h
 create mode 100644 drivers/hid/zotac-zone-hid/Kconfig
 create mode 100644 drivers/hid/zotac-zone-hid/Makefile
 create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c
 create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c
 create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c
 create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c
 create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone.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/lenovo-wmi-capdata01.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-events.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-events.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-gamezone.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-helpers.h
 create mode 100644 drivers/platform/x86/lenovo-wmi-other.c
 create mode 100644 drivers/platform/x86/lenovo-wmi-other.h
 create mode 100644 drivers/platform/x86/zotac-zone-platform.c

diff --git a/Documentation/wmi/devices/lenovo-wmi-gamezone.rst b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
new file mode 100644
index 000000000000..997263e51a7d
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-gamezone.rst
@@ -0,0 +1,203 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+==========================================================
+Lenovo WMI Interface Gamezone Driver (lenovo-wmi-gamezone)
+==========================================================
+
+Introduction
+============
+The Lenovo WMI gamezone interface is broken up into multiple GUIDs,
+The primary "Gamezone" GUID provides advanced features such as fan
+profiles and overclocking. It is paired with multiple event GUIDs
+and data block GUIDs that provide context for the various methods.
+
+Gamezone Data
+-------------
+
+WMI GUID ``887B54E3-DDDC-4B2C-8B88-68A26A8835D0``
+
+The Gamezone Data WMI interface provides platform-profile and fan curve
+settings for devices that fall under the "Gaming Series" of Lenovo devices.
+It uses a notifier chain to inform other Lenovo WMI interface drivers of the
+current platform profile when it changes.
+
+The following platform profiles are supported:
+ - low-power
+ - balanced
+ - balanced-performance
+ - performance
+ - custom
+
+Balanced-Performance
+~~~~~~~~~~~~~~~~~~~~
+Some newer Lenovo "Gaming Series" laptops have an "Extreme Mode" profile
+enabled in their BIOS. For these devices, the performance platform profile
+corresponds to the BIOS Extreme Mode, while the balanced-performance
+platform profile corresponds to the BIOS Performance mode. For legacy
+devices, the performance platform profile will correspond with the BIOS
+Performance mode.
+
+For some newer devices the "Extreme Mode" profile is incomplete in the BIOS
+and setting it will cause undefined behavior. A BIOS bug quirk table is
+provided to ensure these devices cannot set "Extreme Mode" from the driver.
+
+Custom Profile
+~~~~~~~~~~~~~~
+The custom profile represents a hardware mode on Lenovo devices that enables
+user modifications to Package Power Tracking (PPT) and fan curve settings.
+When an attribute exposed by the Other Mode WMI interface is to be modified,
+the Gamezone driver must first be switched to the "custom" profile manually,
+or the setting will have no effect. If another profile is set from the list
+of supported profiles, the BIOS will override any user PPT settings when
+switching to that profile.
+
+Gamezone Thermal Mode Event
+---------------------------
+
+WMI GUID ``D320289E-8FEA-41E0-86F9-911D83151B5F``
+
+The Gamezone Thermal Mode Event interface notifies the system when the platform
+profile has changed, either through the hardware event (Fn+Q for laptops or
+Legion + Y for Go Series), or through the Gamezone WMI interface. This event is
+implemented in the Lenovo WMI Events driver (lenovo-wmi-events).
+
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")]
+  class LENOVO_GAMEZONE_DATA {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data);
+    [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion ([out, Description("AslCode version")] UINT32 Data);
+    [WmiMethodId(12), Implemented, Description("Fan cooling capability")] void IsSupportFanCooling([out, Description("Fan cooling capability")] UINT32 Data);
+    [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling ([in, Description("Set Fan cooling on/off")] UINT32 Data);
+    [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC ([out, Description("cpu oc capability")] UINT32 Data);
+    [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC ([out, Description("bios has overclock capability")] UINT32 Data);
+    [WmiMethodId(16), Implemented, Description("enable or disable overclock in bios")] void SetBIOSOC ([in, Description("enable or disable overclock in bios")] UINT32 Data);
+    [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp ([out, Description("Get CPU temperature")] UINT32 Data);
+    [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp ([out, Description("Get GPU temperature")] UINT32 Data);
+    [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus ([out, Description("Get Fan cooling on/off status")] UINT32 Data);
+    [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey ([out, Description("EC support disable windows key capability")] UINT32 Data);
+    [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus ([in, Description("Set windows key disable/enable")] UINT32 Data);
+    [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus ([out, Description("Get windows key disable/enable status")] UINT32 Data);
+    [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP ([out, Description("EC support disable touchpad capability")] UINT32 Data);
+    [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus ([in, Description("Set touchpad disable/enable")] UINT32 Data);
+    [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus ([out, Description("Get touchpad disable/enable status")] UINT32 Data);
+    [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist ([out, Description("Get Keyboard feature list")] UINT32 Data);
+    [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo ([out, Description("Get Memory OC Information")] UINT32 Data);
+    [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling ([out, Description("Water Cooling feature capability")] UINT32 Data);
+    [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus ([in, Description("Set Water Cooling status")] UINT32 Data);
+    [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus ([out, Description("Get Water Cooling status")] UINT32 Data);
+    [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature ([out, Description("Lighting feature capability")] UINT32 Data);
+    [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight ([in, Description("keyboard light off or on switch")] UINT32 Data);
+    [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight ([out, Description("Get keyboard light on/off status")] UINT32 Data);
+    [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode ([in, Description("Macrokey index")] UINT32 idx, [out, Description("Scan code")] UINT32 scancode);
+    [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount ([out, Description("Macrokey count")] UINT32 Data);
+    [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync ([out, Description("Support G-Sync feature")] UINT32 Data);
+    [WmiMethodId(41), Implemented, Description("Get G-Sync Status")] void GetGSyncStatus ([out, Description("Get G-Sync Status")] UINT32 Data);
+    [WmiMethodId(42), Implemented, Description("Set G-Sync Status")] void SetGSyncStatus ([in, Description("Set G-Sync Status")] UINT32 Data);
+    [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan ([out, Description("Support Smart Fan feature")] UINT32 Data);
+    [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode ([in, Description("Set Smart Fan Mode")] UINT32 Data);
+    [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode ([out, Description("Get Smart Fan Mode")] UINT32 Data);
+    [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting ([out, Description("Get Smart Setting Mode")] UINT32 Data);
+    [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode ([out, Description("Get Power Charge Mode")] UINT32 Data);
+    [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo ([out, Description("Get Gaming Product Info")] UINT32 Data);
+    [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD ([out, Description("Over Drive feature capability")] UINT32 Data);
+    [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus ([out, Description("Get Over Drive status")] UINT32 Data);
+    [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus ([in, Description("Set Over Drive status")] UINT32 Data);
+    [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner ([in, Description("Set Light Control Owner")] UINT32 Data);
+    [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner ([in, Description("Set DDS Control Owner")] UINT32 Data);
+    [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue ([in, Description("Clean this flag")] UINT32 idx, [out, Description("Restore oc value flag")] UINT32 Data);
+    [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode ([out, Description("Real Thremal Mode")] UINT32 Data);
+    [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode ([out, Description("OC Mode")] UINT32 Data);
+    [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion ([out, Description("version")] UINT32 Data);
+    [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency ([out, Description("frequency")] UINT32 Data);
+    [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC ([out, Description("AC check result")] UINT32 Data);
+    [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode ([out, Description("IGPU modes")] UINT32 Data);
+    [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] UINT32 Data);
+    [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] UINT32 mode, [out, Description("return code")] UINT32 Data);
+    [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] UINT32 status, [out, Description("return code")] UINT32 Data);
+    [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] UINT32 Data);
+    [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data);
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of CPU OC parameter list"), guid("{B7F3CA0A-ACDC-42D2-9217-77C6C628FBD2}")]
+  class LENOVO_GAMEZONE_CPU_OC_DATA {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("OC tune id.")] uint32 Tuneid;
+    [WmiDataId(2), read, Description("Default value.")] uint32 DefaultValue;
+    [WmiDataId(3), read, Description("OC Value.")] uint32 OCValue;
+    [WmiDataId(4), read, Description("Min Value.")] uint32 MinValue;
+    [WmiDataId(5), read, Description("Max Value.")] uint32 MaxValue;
+    [WmiDataId(6), read, Description("Scale Value.")] uint32 ScaleValue;
+    [WmiDataId(7), read, Description("OC Order id.")] uint32 OCOrderid;
+    [WmiDataId(8), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
+    [WmiDataId(9), read, Description("Delay time in ms.")] uint32 Interval;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of GPU OC parameter list"), guid("{887B54E2-DDDC-4B2C-8B88-68A26A8835D0}")]
+  class LENOVO_GAMEZONE_GPU_OC_DATA {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("P-State ID.")] uint32 PStateID;
+    [WmiDataId(2), read, Description("CLOCK ID.")] uint32 ClockID;
+    [WmiDataId(3), read, Description("Default value.")] uint32 defaultvalue;
+    [WmiDataId(4), read, Description("OC Offset freqency.")] uint32 OCOffsetFreq;
+    [WmiDataId(5), read, Description("OC Min offset value.")] uint32 OCMinOffset;
+    [WmiDataId(6), read, Description("OC Max offset value.")] uint32 OCMaxOffset;
+    [WmiDataId(7), read, Description("OC Offset Scale.")] uint32 OCOffsetScale;
+    [WmiDataId(8), read, Description("OC Order id.")] uint32 OCOrderid;
+    [WmiDataId(9), read, Description("NON-OC Order id.")] uint32 NOCOrderid;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Fancooling finish event"), guid("{BC72A435-E8C1-4275-B3E2-D8B8074ABA59}")]
+  class LENOVO_GAMEZONE_FAN_COOLING_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Fancooling clean finish event")] uint32 EventId;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan mode change event"), guid("{D320289E-8FEA-41E0-86F9-611D83151B5F}")]
+  class LENOVO_GAMEZONE_SMART_FAN_MODE_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Smart Fan Mode change event")] uint32 mode;
+    [WmiDataId(2), read, Description("version of FN+Q")] uint32 version;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Smart Fan setting mode change event"), guid("{D320289E-8FEA-41E1-86F9-611D83151B5F}")]
+  class LENOVO_GAMEZONE_SMART_FAN_SETTING_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Smart Fan Setting mode change event")] uint32 mode;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("POWER CHARGE MODE Change EVENT"), guid("{D320289E-8FEA-41E0-86F9-711D83151B5F}")]
+  class LENOVO_GAMEZONE_POWER_CHARGE_MODE_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("POWER CHARGE MODE Change EVENT")] uint32 mode;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Thermal Mode Real Mode change event"), guid("{D320289E-8FEA-41E0-86F9-911D83151B5F}")]
+  class LENOVO_GAMEZONE_THERMAL_MODE_EVENT: WMIEvent {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description("Thermal Mode Real Mode")] uint32 mode;
+  };
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
new file mode 100644
index 000000000000..d7928b8dfb4b
--- /dev/null
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -0,0 +1,108 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+===========================================================
+Lenovo WMI Interface Other Mode Driver (lenovo-wmi-other)
+===========================================================
+
+Introduction
+============
+Lenovo WMI Other Mode interface is broken up into multiple GUIDs,
+The primary Other Mode interface provides advanced power tuning features
+such as Package Power Tracking (PPT). It is paired with multiple data block
+GUIDs that provide context for the various methods.
+
+
+Other Mode
+----------
+
+WMI GUID ``DC2A8805-3A8C-41BA-A6F7-092E0089CD3B``
+
+The Other Mode WMI interface uses the firmware_attributes class to expose
+various WMI attributes provided by the interface in the sysfs. This enables
+CPU and GPU power limit tuning as well as various other attributes for
+devices that fall under the "Gaming Series" of Lenovo devices. Each
+attribute exposed by the Other Mode interface has corresponding
+capability data blocks which allow the driver to probe details about the
+attribute. Each attribute has multiple pages, one for each of the platform
+profiles managed by the Gamezone interface. Attributes are exposed in sysfs
+under the following path:
+
+::
+
+  /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+
+LENOVO_CAPABILITY_DATA_01
+-------------------------
+
+WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154``
+
+The LENOVO_CAPABILITY_DATA_01 interface provides information on various
+power limits of integrated CPU and GPU components.
+
+Each attribute has the following properties:
+ - current_value
+ - default_value
+ - display_name
+ - max_value
+ - min_value
+ - scalar_increment
+ - type
+
+The following attributes are implemented:
+ - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
+ - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
+ - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
+
+
+WMI interface description
+=========================
+
+The WMI interface description can be decoded from the embedded binary MOF (bmof)
+data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
+
+::
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_OTHER_METHOD class"), guid("{dc2a8805-3a8c-41ba-a6f7-092e0089cd3b}")]
+  class LENOVO_OTHER_METHOD {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiMethodId(17), Implemented, Description("Get Feature Value ")] void GetFeatureValue([in] uint32 IDs, [out] uint32 value);
+    [WmiMethodId(18), Implemented, Description("Set Feature Value ")] void SetFeatureValue([in] uint32 IDs, [in] uint32 value);
+    [WmiMethodId(19), Implemented, Description("Get Data By Command ")] void GetDataByCommand([in] uint32 IDs, [in] uint32 Command, [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint32 Data[]);
+    [WmiMethodId(99), Implemented, Description("Get Data By Package for TAC")] void GetDataByPackage([in, Max(40)] uint8 Input[], [out] uint32 DataSize, [out, WmiSizeIs("DataSize")] uint8 Data[]);
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 00"), guid("{362a3afe-3d96-4665-8530-96dad5bb300e}")]
+  class LENOVO_CAPABILITY_DATA_00 {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
+    [WmiDataId(3), read, Description("Capability Default Value.")] uint32 DefaultValue;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 01"), guid("{7a8f5407-cb67-4d6e-b547-39b3be018154}")]
+  class LENOVO_CAPABILITY_DATA_01 {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
+    [WmiDataId(3), read, Description("Default Value.")] uint32 DefaultValue;
+    [WmiDataId(4), read, Description("Step.")] uint32 Step;
+    [WmiDataId(5), read, Description("Minimum Value.")] uint32 MinValue;
+    [WmiDataId(6), read, Description("Maximum Value.")] uint32 MaxValue;
+  };
+
+  [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO CAPABILITY DATA 02"), guid("{bbf1f790-6c2f-422b-bc8c-4e7369c7f6ab}")]
+  class LENOVO_CAPABILITY_DATA_02 {
+    [key, read] string InstanceName;
+    [read] boolean Active;
+
+    [WmiDataId(1), read, Description(" IDs.")] uint32 IDs;
+    [WmiDataId(2), read, Description("Capability.")] uint32 Capability;
+    [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
+    [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
+  };
diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst
index 73197b31926a..704bfdac5203 100644
--- a/Documentation/wmi/devices/msi-wmi-platform.rst
+++ b/Documentation/wmi/devices/msi-wmi-platform.rst
@@ -169,6 +169,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/MAINTAINERS b/MAINTAINERS
index 4feb3b9f8f73..eae65abb447e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13479,12 +13479,35 @@ S:	Maintained
 W:	http://legousb.sourceforge.net/
 F:	drivers/usb/misc/legousbtower.c
 
+LENOVO WMI DRIVERS
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	platform-driver-x86@vger.kernel.org
+S:	Maintained
+F:	Documentation/wmi/devices/lenovo-wmi-gamezone.rst
+F:	Documentation/wmi/devices/lenovo-wmi-other.rst
+F:	drivers/platform/x86/lenovo-wmi-capdata01.*
+F:	drivers/platform/x86/lenovo-wmi-events.*
+F:	drivers/platform/x86/lenovo-wmi-gamezone.*
+F:	drivers/platform/x86/lenovo-wmi-helpers.*
+F:	drivers/platform/x86/lenovo-wmi-other.*
+
 LENOVO WMI HOTKEY UTILITIES DRIVER
 M:	Jackie Dong <xy-jackie@139.com>
 L:	platform-driver-x86@vger.kernel.org
 S:	Maintained
 F:	drivers/platform/x86/lenovo-wmi-hotkey-utilities.c
 
+LENOVO LEGION GO S HID
+M:	Derek J. Clark <derekjohn.clark@gmail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/lenovo-legos-hid/Kconfig
+F:	drivers/hid/lenovo-legos-hid/Makefile
+F:	drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.c
+F:	drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.h
+F:	drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.c
+F:	drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.h
+
 LETSKETCH HID TABLET DRIVER
 M:	Hans de Goede <hdegoede@redhat.com>
 L:	linux-input@vger.kernel.org
diff --git a/drivers/acpi/acpica/acinterp.h b/drivers/acpi/acpica/acinterp.h
index 955114c926bd..d02779ee9210 100644
--- a/drivers/acpi/acpica/acinterp.h
+++ b/drivers/acpi/acpica/acinterp.h
@@ -120,6 +120,9 @@ void
 acpi_ex_trace_point(acpi_trace_event_type type,
 		    u8 begin, u8 *aml, char *pathname);
 
+void
+acpi_ex_trace_args(union acpi_operand_object **params, u32 count);
+
 /*
  * exfield - ACPI AML (p-code) execution - field manipulation
  */
diff --git a/drivers/acpi/acpica/dsmthdat.c b/drivers/acpi/acpica/dsmthdat.c
index eca50517ad82..5393de4dbc4c 100644
--- a/drivers/acpi/acpica/dsmthdat.c
+++ b/drivers/acpi/acpica/dsmthdat.c
@@ -188,6 +188,7 @@ acpi_ds_method_data_init_args(union acpi_operand_object **params,
 
 		index++;
 	}
+	acpi_ex_trace_args(params, index);
 
 	ACPI_DEBUG_PRINT((ACPI_DB_EXEC, "%u args passed to method\n", index));
 	return_ACPI_STATUS(AE_OK);
diff --git a/drivers/acpi/acpica/extrace.c b/drivers/acpi/acpica/extrace.c
index f1730221ff13..08fb94eb7870 100644
--- a/drivers/acpi/acpica/extrace.c
+++ b/drivers/acpi/acpica/extrace.c
@@ -147,6 +147,57 @@ acpi_ex_trace_point(acpi_trace_event_type type,
 	}
 }
 
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_ex_trace_args
+ *
+ * PARAMETERS:  params            - AML method arguments
+ *              count             - numer of method arguments
+ *
+ * RETURN:      None
+ *
+ * DESCRIPTION: Trace any arguments
+ *
+ ******************************************************************************/
+
+void
+acpi_ex_trace_args(union acpi_operand_object **params, u32 count)
+{
+	u32 i;
+
+	ACPI_FUNCTION_NAME(ex_trace_args);
+
+	for (i = 0; i < count; i++) {
+		union acpi_operand_object *obj_desc = params[i];
+
+		if (!i) {
+			ACPI_DEBUG_PRINT((ACPI_DB_TRACE_POINT, " "));
+		}
+
+		switch (obj_desc->common.type) {
+		case ACPI_TYPE_INTEGER:
+			ACPI_DEBUG_PRINT_RAW((ACPI_DB_TRACE_POINT, "%llx", obj_desc->integer.value));
+			break;
+		case ACPI_TYPE_STRING:
+			if (!obj_desc->string.length) {
+				ACPI_DEBUG_PRINT_RAW((ACPI_DB_TRACE_POINT, "NULL"));
+				continue;
+			}
+			if (ACPI_IS_DEBUG_ENABLED(ACPI_LV_TRACE_POINT, _COMPONENT))
+				acpi_ut_print_string(obj_desc->string.pointer, ACPI_UINT8_MAX);
+			break;
+		default:
+			ACPI_DEBUG_PRINT_RAW((ACPI_DB_TRACE_POINT, "Unknown"));
+			break;
+		}
+		if (i+1 == count) {
+			ACPI_DEBUG_PRINT_RAW((ACPI_DB_TRACE_POINT, "\n"));
+		} else {
+			ACPI_DEBUG_PRINT_RAW((ACPI_DB_TRACE_POINT, ", "));
+		}
+	}
+}
+
 /*******************************************************************************
  *
  * FUNCTION:    acpi_ex_start_trace_method
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index a6f6d467aacf..4adec844c4b4 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -214,4 +214,11 @@ config EXTCON_RTK_TYPE_C
 	  The DHC (Digital Home Hub) RTD series SoC contains a type c module.
 	  This driver will detect the status of the type-c port.
 
+config EXTCON_STEAMDECK
+	tristate "Steam Deck extcon support"
+	depends on MFD_STEAMDECK
+	help
+	  Say Y here to enable support of USB Type C cable detection extcon
+	  support on Steam Deck devices
+
 endif
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 0d6d23faf748..6eb613e0bcba 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -27,3 +27,4 @@ obj-$(CONFIG_EXTCON_USB_GPIO)	+= extcon-usb-gpio.o
 obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o
 obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o
 obj-$(CONFIG_EXTCON_RTK_TYPE_C) += extcon-rtk-type-c.o
+obj-$(CONFIG_EXTCON_STEAMDECK)  += extcon-steamdeck.o
diff --git a/drivers/extcon/extcon-steamdeck.c b/drivers/extcon/extcon-steamdeck.c
new file mode 100644
index 000000000000..49cb5d30bf6b
--- /dev/null
+++ b/drivers/extcon/extcon-steamdeck.c
@@ -0,0 +1,180 @@
+
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/extcon-provider.h>
+
+#define ACPI_STEAMDECK_NOTIFY_STATUS	0x80
+
+/* 0 - port connected, 1 -port disconnected */
+#define ACPI_STEAMDECK_PORT_CONNECT	BIT(0)
+/* 0 - Upstream Facing Port, 1 - Downdstream Facing Port */
+#define ACPI_STEAMDECK_CUR_DATA_ROLE	BIT(3)
+/*
+ * Debouncing delay to allow negotiation process to settle. 2s value
+ * was arrived at via trial and error.
+ */
+#define STEAMDECK_ROLE_SWITCH_DELAY	(msecs_to_jiffies(2000))
+
+struct steamdeck_extcon {
+	struct acpi_device *adev;
+	struct delayed_work role_work;
+	struct extcon_dev *edev;
+	struct device *dev;
+};
+
+static int steamdeck_read_pdcs(struct steamdeck_extcon *sd, unsigned long long *pdcs)
+{
+	acpi_status status;
+
+	status = acpi_evaluate_integer(sd->adev->handle, "PDCS", NULL, pdcs);
+	if (ACPI_FAILURE(status)) {
+		dev_err(sd->dev, "PDCS evaluation failed: %s\n",
+			acpi_format_exception(status));
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void steamdeck_usb_role_work(struct work_struct *work)
+{
+	struct steamdeck_extcon *sd =
+		container_of(work, struct steamdeck_extcon, role_work.work);
+	unsigned long long pdcs;
+	bool usb_host;
+
+	if (steamdeck_read_pdcs(sd, &pdcs))
+		return;
+
+	/*
+	 * We only care about these two
+	 */
+	pdcs &= ACPI_STEAMDECK_PORT_CONNECT | ACPI_STEAMDECK_CUR_DATA_ROLE;
+
+	/*
+	 * For "connect" events our role is determined by a bit in
+	 * PDCS, for "disconnect" we switch to being a gadget
+	 * unconditionally. The thinking for the latter is we don't
+	 * want to start acting as a USB host until we get
+	 * confirmation from the firmware that we are a USB host
+	 */
+	usb_host = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ?
+		pdcs & ACPI_STEAMDECK_CUR_DATA_ROLE : false;
+
+	dev_dbg(sd->dev, "USB role is %s\n", usb_host ? "host" : "device");
+	WARN_ON(extcon_set_state_sync(sd->edev, EXTCON_USB_HOST,
+				      usb_host));
+
+}
+
+static void steamdeck_notify(acpi_handle handle, u32 event, void *context)
+{
+	struct device *dev = context;
+	struct steamdeck_extcon *sd = dev_get_drvdata(dev);
+	unsigned long long pdcs;
+	unsigned long delay;
+
+	switch (event) {
+	case ACPI_STEAMDECK_NOTIFY_STATUS:
+		if (steamdeck_read_pdcs(sd, &pdcs))
+			return;
+		/*
+		 * We process "disconnect" events immediately and
+		 * "connect" events with a delay to give the HW time
+		 * to settle. For example attaching USB hub (at least
+		 * for HW used for testing) will generate intermediary
+		 * event with "host" bit not set, followed by the one
+		 * that does have it set.
+		 */
+		delay = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ?
+			STEAMDECK_ROLE_SWITCH_DELAY : 0;
+
+		queue_delayed_work(system_long_wq, &sd->role_work, delay);
+		break;
+	default:
+		dev_warn(dev, "Unsupported event [0x%x]\n", event);
+	}
+}
+
+static void steamdeck_remove_notify_handler(void *data)
+{
+	struct steamdeck_extcon *sd = data;
+
+	acpi_remove_notify_handler(sd->adev->handle, ACPI_DEVICE_NOTIFY,
+				   steamdeck_notify);
+	cancel_delayed_work_sync(&sd->role_work);
+}
+
+static const unsigned int steamdeck_extcon_cable[] = {
+	EXTCON_USB,
+	EXTCON_USB_HOST,
+	EXTCON_CHG_USB_SDP,
+	EXTCON_CHG_USB_CDP,
+	EXTCON_CHG_USB_DCP,
+	EXTCON_CHG_USB_ACA,
+	EXTCON_NONE,
+};
+
+static int steamdeck_extcon_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct steamdeck_extcon *sd;
+	acpi_status status;
+	int ret;
+
+	sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
+	if (!sd)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&sd->role_work, steamdeck_usb_role_work);
+	platform_set_drvdata(pdev, sd);
+	sd->adev = ACPI_COMPANION(dev->parent);
+	sd->dev  = dev;
+	sd->edev = devm_extcon_dev_allocate(dev, steamdeck_extcon_cable);
+	if (IS_ERR(sd->edev))
+		return PTR_ERR(sd->edev);
+
+	ret = devm_extcon_dev_register(dev, sd->edev);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register extcon device: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Set initial role value
+	 */
+	queue_delayed_work(system_long_wq, &sd->role_work, 0);
+	flush_delayed_work(&sd->role_work);
+
+	status = acpi_install_notify_handler(sd->adev->handle,
+					     ACPI_DEVICE_NOTIFY,
+					     steamdeck_notify,
+					     dev);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "Error installing ACPI notify handler\n");
+		return -EIO;
+	}
+
+	ret = devm_add_action_or_reset(dev, steamdeck_remove_notify_handler,
+				       sd);
+	return ret;
+}
+
+static const struct platform_device_id steamdeck_extcon_id_table[] = {
+	{ .name = "steamdeck-extcon" },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, steamdeck_extcon_id_table);
+
+static struct platform_driver steamdeck_extcon_driver = {
+	.probe = steamdeck_extcon_probe,
+	.driver = {
+		.name = "steamdeck-extcon",
+	},
+	.id_table = steamdeck_extcon_id_table,
+};
+module_platform_driver(steamdeck_extcon_driver);
+
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("Steam Deck extcon driver");
+MODULE_LICENSE("GPL");
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/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 927fb4992a6a..2f8193a5c803 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -166,6 +166,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
  *
@@ -9171,7 +9174,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/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h
index 0761fc95a158..2081f0f5b907 100644
--- a/drivers/gpu/drm/amd/display/dc/dc.h
+++ b/drivers/gpu/drm/amd/display/dc/dc.h
@@ -58,7 +58,7 @@ struct dmub_notification;
 /**
  * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC
  */
-#define MAX_SURFACES 4
+#define MAX_SURFACES 6
 /**
  * MAX_PLANES - representative of the upper bound of planes that are supported by the HW
  */
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 a998c498a477..f4ef774e07e0 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
@@ -96,6 +96,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;
 };
@@ -3246,6 +3248,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/amd/display/dc/resource/dcn301/dcn301_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c
index 121a86a59833..60ab2e3a5921 100644
--- a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c
+++ b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c
@@ -1437,9 +1437,9 @@ static bool dcn301_resource_construct(
 	dc->caps.max_cursor_size = 256;
 	dc->caps.min_horizontal_blanking_period = 80;
 	dc->caps.dmdata_alloc_size = 2048;
-	dc->caps.max_slave_planes = 2;
-	dc->caps.max_slave_yuv_planes = 2;
-	dc->caps.max_slave_rgb_planes = 2;
+	dc->caps.max_slave_planes = 3;
+	dc->caps.max_slave_yuv_planes = 3;
+	dc->caps.max_slave_rgb_planes = 3;
 	dc->caps.is_apu = true;
 	dc->caps.post_blend_color_processing = true;
 	dc->caps.force_dp_tps4_for_cp2520 = true;
diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c
index 7ac0fd5391fe..323dd636352b 100644
--- a/drivers/gpu/drm/drm_panel_orientation_quirks.c
+++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c
@@ -485,6 +485,42 @@ static const struct dmi_system_id orientation_data[] = {
 		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"),
 		},
 		.driver_data = (void *)&lcd1200x1920_leftside_up,
+	}, {	/* OneXPlayer X1 AMD */
+		.matches = {
+		  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 mini (AMD) */
+		.matches = {
+		  DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"),
+		  DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 mini"),
+		},
+		.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/Kconfig b/drivers/hid/Kconfig
index e7688c7fdc56..4f7feaac983c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1418,6 +1418,10 @@ config HID_KUNIT_TEST
 
 	  If in doubt, say "N".
 
+config HID_MSI_CLAW
+	tristate "MSI Claw series gamepad support (work-in-progress)"
+	depends on USB_HID
+
 endmenu
 
 source "drivers/hid/bpf/Kconfig"
@@ -1444,4 +1448,8 @@ source "drivers/hid/spi-hid/Kconfig"
 
 source "drivers/hid/dockchannel-hid/Kconfig"
 
+source "drivers/hid/zotac-zone-hid/Kconfig"
+
+source "drivers/hid/lenovo-legos-hid/Kconfig"
+
 endif # HID_SUPPORT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index a0f58475434c..137fc917a174 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -174,10 +174,16 @@ obj-$(CONFIG_AMD_SFH_HID)       += amd-sfh-hid/
 
 obj-$(CONFIG_ASUS_ALLY_HID)  += asus-ally-hid/
 
+obj-$(CONFIG_ZOTAC_ZONE_HID)  += zotac-zone-hid/
+
 obj-$(CONFIG_SURFACE_HID_CORE)  += surface-hid/
 
 obj-$(CONFIG_INTEL_THC_HID)     += intel-thc-hid/
 
+obj-$(CONFIG_HID_MSI_CLAW)      += hid-msi-claw.o
+
+obj-$(CONFIG_LENOVO_LEGOS_HID)  += lenovo-legos-hid/
+
 obj-$(CONFIG_SPI_HID_APPLE_CORE)	+= spi-hid/
 
 obj-$(CONFIG_HID_DOCKCHANNEL)   += dockchannel-hid/
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index b991ed962366..a589f9c9b661 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2838,6 +2838,17 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
 	if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X",
 			   hdev->bus, hdev->group, hdev->vendor, hdev->product))
 		return -ENOMEM;
+	if (hdev->firmware_version) {
+		if (add_uevent_var(env, "HID_FIRMWARE_VERSION=0x%04llX",
+				   hdev->firmware_version))
+			return -ENOMEM;
+	}
+	if (hdev->uevent) {
+		int ret = hdev->uevent(dev, env);
+
+		if (ret)
+			return ret;
+	}
 
 	return 0;
 }
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 70efd28b6bc3..69f6368d3f71 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -720,6 +720,10 @@
 #define USB_DEVICE_ID_ITE8595		0x8595
 #define USB_DEVICE_ID_ITE_MEDION_E1239T	0xce50
 
+#define USB_VENDOR_ID_QHE		0x1a86
+#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310
+#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311
+
 #define USB_VENDOR_ID_JABRA		0x0b0e
 #define USB_DEVICE_ID_JABRA_SPEAK_410	0x0412
 #define USB_DEVICE_ID_JABRA_SPEAK_510	0x0420
@@ -1049,6 +1053,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
@@ -1502,6 +1507,10 @@
 #define USB_VENDOR_ID_ZYTRONIC		0x14c8
 #define USB_DEVICE_ID_ZYTRONIC_ZXY100	0x0005
 
+#define USB_VENDOR_ID_ZOTAC		0x1EE9
+#define USB_VENDOR_ID_ZOTAC_ALT		0x1E19
+#define USB_DEVICE_ID_ZOTAC_ZONE_GAME_CONTROLLER	0x1590
+
 #define USB_VENDOR_ID_PRIMAX	0x0461
 #define USB_DEVICE_ID_PRIMAX_MOUSE_4D22	0x4d22
 #define USB_DEVICE_ID_PRIMAX_MOUSE_4E2A	0x4e2a
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/hid/hid-msi-claw.c b/drivers/hid/hid-msi-claw.c
new file mode 100644
index 000000000000..2d009bc5f85b
--- /dev/null
+++ b/drivers/hid/hid-msi-claw.c
@@ -0,0 +1,484 @@
+#include <linux/dmi.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+//#include "hid-ids.h"
+
+#define MSI_CLAW_FEATURE_GAMEPAD_REPORT_ID 0x0f
+
+#define MSI_CLAW_GAME_CONTROL_DESC   0x05
+#define MSI_CLAW_DEVICE_CONTROL_DESC 0x06
+
+enum msi_claw_gamepad_mode {
+	MSI_CLAW_GAMEPAD_MODE_OFFLINE,
+	MSI_CLAW_GAMEPAD_MODE_XINPUT,
+	MSI_CLAW_GAMEPAD_MODE_DINPUT,
+	MSI_CLAW_GAMEPAD_MODE_MSI,
+	MSI_CLAW_GAMEPAD_MODE_DESKTOP,
+	MSI_CLAW_GAMEPAD_MODE_BIOS,
+	MSI_CLAW_GAMEPAD_MODE_TESTING,
+};
+
+static const bool gamepad_mode_debug = false;
+
+static const struct {
+	const char* name;
+	const bool available;
+} gamepad_mode_map[] = {
+	{"offline", gamepad_mode_debug},
+	{"xinput", true},
+	{"dinput", gamepad_mode_debug},
+	{"msi", gamepad_mode_debug},
+	{"desktop", true},
+	{"bios", gamepad_mode_debug},
+	{"testing", gamepad_mode_debug},
+};
+
+enum msi_claw_mkeys_function {
+	MSI_CLAW_MKEY_FUNCTION_MACRO,
+	MSI_CLAW_MKEY_FUNCTION_COMBINATION,
+};
+
+static const char* mkeys_function_map[] =
+{
+	"macro",
+	"combination",
+};
+
+enum msi_claw_command_type {
+	MSI_CLAW_COMMAND_TYPE_ENTER_PROFILE_CONFIG = 1,
+	MSI_CLAW_COMMAND_TYPE_EXIT_PROFILE_CONFIG,
+	MSI_CLAW_COMMAND_TYPE_WRITE_PROFILE,
+	MSI_CLAW_COMMAND_TYPE_READ_PROFILE,
+	MSI_CLAW_COMMAND_TYPE_READ_PROFILE_ACK,
+	MSI_CLAW_COMMAND_TYPE_ACK,
+	MSI_CLAW_COMMAND_TYPE_SWITCH_PROFILE,
+	MSI_CLAW_COMMAND_TYPE_WRITE_PROFILE_TO_EEPROM,
+	MSI_CLAW_COMMAND_TYPE_READ_FIRMWARE_VERSION,
+	MSI_CLAW_COMMAND_TYPE_READ_RGB_STATUS_ACK,
+	MSI_CLAW_COMMAND_TYPE_READ_CURRENT_PROFILE,
+	MSI_CLAW_COMMAND_TYPE_READ_CURRENT_PROFILE_ACK,
+	MSI_CLAW_COMMAND_TYPE_READ_RGB_STATUS,
+	MSI_CLAW_COMMAND_TYPE_SYNC_TO_ROM = 34,
+	MSI_CLAW_COMMAND_TYPE_RESTORE_FROM_ROM,
+	MSI_CLAW_COMMAND_TYPE_SWITCH_MODE,
+	MSI_CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE = 38,
+	MSI_CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK,
+	MSI_CLAW_COMMAND_TYPE_RESET_DEVICE,
+	MSI_CLAW_COMMAND_TYPE_RGB_CONTROL = 224,
+	MSI_CLAW_COMMAND_TYPE_CALIBRATION_CONTROL = 253,
+	MSI_CLAW_COMMAND_TYPE_CALIBRATION_ACK,
+};
+
+struct msi_claw_control_status {
+	enum msi_claw_gamepad_mode gamepad_mode;
+	enum msi_claw_mkeys_function mkeys_function;
+};
+
+struct msi_claw_drvdata {
+	struct hid_device *hdev;
+	struct input_dev *input;
+	struct input_dev *tp_kbd_input;
+
+	struct msi_claw_control_status *control;
+};
+
+static int msi_claw_write_cmd(struct hid_device *hdev, enum msi_claw_command_type,
+        u8 b1, u8 b2, u8 b3)
+{
+	int ret;
+	const unsigned char buf[] = {
+		MSI_CLAW_FEATURE_GAMEPAD_REPORT_ID, 0, 0, 60,
+		MSI_CLAW_COMMAND_TYPE_SWITCH_MODE, b1, b2, b3
+	};
+	unsigned char *dmabuf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
+	if (!dmabuf) {
+		ret = -ENOMEM;
+		hid_err(hdev, "hid-msi-claw failed to alloc dma buf: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_output_report(hdev, dmabuf, sizeof(buf));
+
+	kfree(dmabuf);
+
+	if (ret != sizeof(buf)) {
+		hid_err(hdev, "hid-msi-claw failed to switch controller mode: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int msi_claw_read(struct hid_device *hdev, u8 *const buffer)
+{
+	int ret;
+
+	unsigned char *dmabuf = kmemdup(buffer, 8, GFP_KERNEL);
+	if (!dmabuf) {
+		ret = -ENOMEM;
+		hid_err(hdev, "hid-msi-claw failed to alloc dma buf: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_raw_request(hdev, 0x82, dmabuf, 8, HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret >= 8) {
+		hid_err(hdev, "hid-msi-claw read %d bytes: %02x %02x %02x %02x %02x %02x %02x %02x \n", ret,
+			dmabuf[0], dmabuf[1], dmabuf[2], dmabuf[3], dmabuf[4], dmabuf[5], dmabuf[6], dmabuf[7]);
+		memcpy((void*)buffer, dmabuf, 8);
+		ret = 0;
+	} else if (ret < 0) {
+		hid_err(hdev, "hid-msi-claw failed to read: %d\n", ret);
+		goto msi_claw_read_err;
+	} else {
+		hid_err(hdev, "hid-msi-claw read %d bytes\n", ret);
+		ret = -EINVAL;
+		goto msi_claw_read_err;
+	}
+
+msi_claw_read_err:
+	kfree(dmabuf);
+
+	return ret;
+}
+
+static int msi_claw_switch_gamepad_mode(struct hid_device *hdev, enum msi_claw_gamepad_mode mode,
+	enum msi_claw_mkeys_function mkeys)
+{
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	int ret;
+
+	if (!drvdata->control) {
+		hid_err(hdev, "hid-msi-claw couldn't find control interface\n");
+		ret = -ENODEV;
+		return ret;
+	}
+
+	ret = msi_claw_write_cmd(hdev, MSI_CLAW_COMMAND_TYPE_SWITCH_MODE, (u8)mode, (u8)mkeys, (u8)0);
+	if (ret) {
+		hid_err(hdev, "hid-msi-claw failed to send write request for switch controller mode: %d\n", ret);
+		return ret;
+	}
+
+	drvdata->control->gamepad_mode = mode;
+
+	ret = msi_claw_write_cmd(hdev, MSI_CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, (u8)0, (u8)0, (u8)0);
+	if (ret) {
+		hid_err(hdev, "hid-msi-claw failed to send read request for controller mode: %d\n", ret);
+		return ret;
+	}
+
+	drvdata->control->mkeys_function = mkeys;
+
+	return 0;
+}
+
+static ssize_t gamepad_mode_available_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int ret = 0;
+	int len = ARRAY_SIZE(gamepad_mode_map);
+
+	for (int i = 0; i < len; i++)
+	{
+		if (!gamepad_mode_map[i].available)
+			continue;
+
+		ret += sysfs_emit_at(buf, ret, "%s", gamepad_mode_map[i].name);
+
+		if (i < len-1)
+			ret += sysfs_emit_at(buf, ret, " ");
+	}
+	ret += sysfs_emit_at(buf, ret, "\n");
+
+	return ret;
+}
+static DEVICE_ATTR_RO(gamepad_mode_available);
+
+static ssize_t gamepad_mode_current_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	return sysfs_emit(buf, "%s\n", gamepad_mode_map[drvdata->control->gamepad_mode].name);
+}
+
+static ssize_t gamepad_mode_current_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	enum msi_claw_gamepad_mode new_gamepad_mode = ARRAY_SIZE(gamepad_mode_map);
+	ssize_t ret;
+
+	if (!count) {
+		ret = -EINVAL;
+		goto gamepad_mode_current_store_err;
+	}
+
+	char* input = kmemdup(buf, count+1, GFP_KERNEL);
+	if (!input) {
+		ret = -ENOMEM;
+		goto gamepad_mode_current_store_err;
+	}
+
+	input[count] = '\0';
+	if (input[count-1] == '\n')
+		input[count-1] = '\0';
+
+	for (size_t i = 0; i < (size_t)new_gamepad_mode; i++)
+		if ((!strcmp(input, gamepad_mode_map[i].name)) && (gamepad_mode_map[i].available))
+			new_gamepad_mode = (enum msi_claw_gamepad_mode)i;
+
+	kfree(input);
+
+	if (new_gamepad_mode == ARRAY_SIZE(gamepad_mode_map)) {
+		hid_err(hdev, "Invalid gamepad mode selected\n");
+		ret= -EINVAL;
+		goto gamepad_mode_current_store_err;
+	}
+
+	ret = msi_claw_switch_gamepad_mode(hdev, new_gamepad_mode, drvdata->control->mkeys_function);
+	if (ret < 0) {
+		hid_err(hdev, "Error changing gamepad mode: %d\n", (int)ret);
+		goto gamepad_mode_current_store_err;
+	}
+
+	ret = count;
+
+gamepad_mode_current_store_err:
+	return ret;
+}
+static DEVICE_ATTR_RW(gamepad_mode_current);
+
+static ssize_t mkeys_function_available_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	int ret = 0;
+	int len = ARRAY_SIZE(mkeys_function_map);
+
+	for (int i = 0; i < len; i++)
+	{
+		ret += sysfs_emit_at(buf, ret, "%s", mkeys_function_map[i]);
+
+		if (i < len-1)
+			ret += sysfs_emit_at(buf, ret, " ");
+	}
+	ret += sysfs_emit_at(buf, ret, "\n");
+
+	return ret;
+}
+static DEVICE_ATTR_RO(mkeys_function_available);
+
+static ssize_t mkeys_function_current_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	return sysfs_emit(buf, "%s\n", mkeys_function_map[drvdata->control->mkeys_function]);
+}
+
+static ssize_t mkeys_function_current_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	enum msi_claw_mkeys_function new_mkeys_function = ARRAY_SIZE(mkeys_function_map);
+
+	ssize_t ret;
+
+	if (!count) {
+		ret = -EINVAL;
+		goto mkeys_function_current_store_err;
+	}
+
+	char* input = kmemdup(buf, count+1, GFP_KERNEL);
+	if (!input)
+		return -ENOMEM;
+
+	input[count] = '\0';
+	if (input[count-1] == '\n')
+		input[count-1] = '\0';
+
+	for (size_t i = 0; i < (size_t)new_mkeys_function; i++)
+		if (!strcmp(input, mkeys_function_map[i]))
+			new_mkeys_function = i;
+
+	kfree(input);
+
+	if (new_mkeys_function == ARRAY_SIZE(mkeys_function_map)) {
+		hid_err(hdev, "Invalid mkeys function selected\n");
+		ret= -EINVAL;
+		goto mkeys_function_current_store_err;
+	}
+
+	ret = msi_claw_switch_gamepad_mode(hdev, drvdata->control->gamepad_mode, new_mkeys_function);
+	if (ret < 0) {
+		hid_err(hdev, "Error changing mkeys function: %d\n", (int)ret);
+		goto mkeys_function_current_store_err;
+	}
+
+	ret = count;
+
+mkeys_function_current_store_err:
+	return ret;
+}
+static DEVICE_ATTR_RW(mkeys_function_current);
+
+static ssize_t debug_read_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	u8 buffer[8] = {};
+
+	const int res = msi_claw_read(hdev, buffer);
+
+	return sysfs_emit(buf, "%d -> %02x%02x%02x%02x%02x%02x%02x%02x\n",
+		res,
+		buffer[0], buffer[1], buffer[2], buffer[3],
+		buffer[4], buffer[5], buffer[6], buffer[7]
+	);
+}
+static DEVICE_ATTR_RO(debug_read);
+
+static int msi_claw_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct msi_claw_drvdata *drvdata;
+
+	if (!hid_is_usb(hdev)) {
+		hid_err(hdev, "hid-msi-claw hid not usb\n");
+		return -ENODEV;
+	}
+
+	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		hid_err(hdev, "hid-msi-claw can't alloc descriptor\n");
+		return -ENOMEM;
+	}
+
+	drvdata->control = NULL;
+
+	hid_set_drvdata(hdev, drvdata);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid-msi-claw hid parse failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hid-msi-claw hw start failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_err(hdev, "hid-msi-claw failed to open HID device: %d\n", ret);
+		goto err_stop_hw;
+	}
+
+//	hid_err(hdev, "hid-msi-claw on %d\n", (int)hdev->rdesc[0]);
+
+	if (hdev->rdesc[0] == MSI_CLAW_DEVICE_CONTROL_DESC) {
+		drvdata->control = devm_kzalloc(&hdev->dev, sizeof(*(drvdata->control)), GFP_KERNEL);
+		if (drvdata->control == NULL) {
+			hid_err(hdev, "hid-msi-claw can't alloc control interface data\n");
+			ret = -ENOMEM;
+			goto err_close;
+		}
+
+		drvdata->control->gamepad_mode = MSI_CLAW_GAMEPAD_MODE_XINPUT;
+		drvdata->control->mkeys_function = MSI_CLAW_MKEY_FUNCTION_MACRO;
+
+		ret = msi_claw_switch_gamepad_mode(hdev, drvdata->control->gamepad_mode, drvdata->control->mkeys_function);
+		if (ret != 0) {
+			hid_err(hdev, "hid-msi-claw failed to initialize controller mode: %d\n", ret);
+			goto err_close;
+		}
+
+		ret = sysfs_create_file(&hdev->dev.kobj, &dev_attr_gamepad_mode_available.attr);
+		if (ret) {
+			hid_err(hdev, "hid-msi-claw failed to sysfs_create_file dev_attr_gamepad_mode_available: %d\n", ret);
+			goto err_close;
+		}
+
+		ret = sysfs_create_file(&hdev->dev.kobj, &dev_attr_gamepad_mode_current.attr);
+		if (ret) {
+			hid_err(hdev, "hid-msi-claw failed to sysfs_create_file dev_attr_gamepad_mode_current: %d\n", ret);
+			goto err_close;
+		}
+
+		ret = sysfs_create_file(&hdev->dev.kobj, &dev_attr_mkeys_function_available.attr);
+		if (ret) {
+			hid_err(hdev, "hid-msi-claw failed to sysfs_create_file dev_attr_mkeys_function_available: %d\n", ret);
+			goto err_close;
+		}
+
+		ret = sysfs_create_file(&hdev->dev.kobj, &dev_attr_mkeys_function_current.attr);
+		if (ret) {
+			hid_err(hdev, "hid-msi-claw failed to sysfs_create_file dev_attr_mkeys_function_current: %d\n", ret);
+			goto err_close;
+		}
+
+		ret = sysfs_create_file(&hdev->dev.kobj, &dev_attr_debug_read.attr);
+		if (ret) {
+			hid_err(hdev, "hid-msi-claw failed to sysfs_create_file dev_attr_debug_read: %d\n", ret);
+			goto err_close;
+		}
+	}
+
+	return 0;
+
+err_close:
+	hid_hw_close(hdev);
+err_stop_hw:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void msi_claw_remove(struct hid_device *hdev)
+{
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->control) {
+		sysfs_remove_file(&hdev->dev.kobj, &dev_attr_gamepad_mode_available.attr);
+		sysfs_remove_file(&hdev->dev.kobj, &dev_attr_gamepad_mode_current.attr);
+		sysfs_remove_file(&hdev->dev.kobj, &dev_attr_mkeys_function_available.attr);
+		sysfs_remove_file(&hdev->dev.kobj, &dev_attr_mkeys_function_current.attr);
+	}
+
+	hid_hw_stop(hdev);
+}
+
+static int msi_claw_resume(struct hid_device *hdev) {
+	struct msi_claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	int ret = 0;
+
+	if (drvdata->control) {
+		// the hardware needs some time to re-initialize
+		ssleep(3);
+
+		ret = msi_claw_switch_gamepad_mode(hdev, drvdata->control->gamepad_mode, drvdata->control->mkeys_function);
+	}
+
+	return ret;
+}
+
+static const struct hid_device_id msi_claw_devices[] = {
+	{ HID_USB_DEVICE(0x0DB0, 0x1901) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, msi_claw_devices);
+
+static struct hid_driver msi_claw_driver = {
+	.name			= "hid-msi-claw",
+	.id_table		= msi_claw_devices,
+	.probe			= msi_claw_probe,
+	.remove			= msi_claw_remove,
+	.resume			= msi_claw_resume,
+};
+module_hid_driver(msi_claw_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/lenovo-legos-hid/Kconfig b/drivers/hid/lenovo-legos-hid/Kconfig
new file mode 100644
index 000000000000..6918b25e191c
--- /dev/null
+++ b/drivers/hid/lenovo-legos-hid/Kconfig
@@ -0,0 +1,11 @@
+config LENOVO_LEGOS_HID
+	tristate "Lenovo Legion Go S HID"
+	depends on USB_HID
+	depends on LEDS_CLASS
+	depends on LEDS_CLASS_MULTICOLOR
+	help
+	  Say Y here to include support for the Lenovo Legion Go S Handheld
+	  Console Controller.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo-legos-hid.
diff --git a/drivers/hid/lenovo-legos-hid/Makefile b/drivers/hid/lenovo-legos-hid/Makefile
new file mode 100644
index 000000000000..e346f70f78ae
--- /dev/null
+++ b/drivers/hid/lenovo-legos-hid/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Makefile - Lenovo Legion Go S Handheld Console Controller driver
+#
+lenovo-legos-hid-y := lenovo-legos-hid-core.o lenovo-legos-hid-config.o
+obj-$(CONFIG_LENOVO_LEGOS_HID)	:= lenovo-legos-hid.o
diff --git a/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.c b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.c
new file mode 100644
index 000000000000..7a42cf5bfd2b
--- /dev/null
+++ b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.c
@@ -0,0 +1,1571 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID driver for Lenovo Legion Go S devices.
+ *
+ *  Copyright (c) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/kstrtox.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <linux/workqueue_types.h>
+
+#include "lenovo-legos-hid-core.h"
+#include "lenovo-legos-hid-config.h"
+
+struct legos_cfg {
+	struct delayed_work legos_cfg_setup;
+	struct completion send_cmd_complete;
+	struct led_classdev *led_cdev;
+	struct hid_device *hdev;
+	struct mutex cfg_mutex; /* Avoid Send/Rx MCU locking */
+	u8 gp_auto_sleep_time;
+	u8 gp_dpad_mode;
+	u8 gp_mode;
+	u8 gp_poll_rate;
+	u8 imu_bypass_en;
+	u8 imu_manufacturer;
+	u8 imu_sensor_en;
+	u8 mcu_id[12];
+	u8 mouse_step;
+	u8 os_mode;
+	u8 rgb_effect;
+	u8 rgb_en;
+	u8 rgb_mode;
+	u8 rgb_profile;
+	u8 rgb_speed;
+	u8 tp_en;
+	u8 tp_linux_mode;
+	u8 tp_manufacturer;
+	u8 tp_version;
+	u8 tp_windows_mode;
+} drvdata;
+
+/* GET/SET_GAMEPAD_CFG */
+enum GAMEPAD_MODE {
+	XINPUT,
+	DINPUT,
+};
+
+static const char *const GAMEPAD_MODE_TEXT[] = {
+	[XINPUT] = "xinput",
+	[DINPUT] = "dinput",
+};
+
+enum FEATURE_ENABLE_STATUS {
+	FEATURE_DISABLED,
+	FEATURE_ENABLED,
+};
+
+static const char *const FEATURE_ENABLE_STATUS_TEXT[] = {
+	[FEATURE_DISABLED] = "false",
+	[FEATURE_ENABLED] = "true",
+};
+
+enum IMU_ENABLED {
+	IMU_OFF,
+	IMU_ON,
+	IMU_OFF_2S,
+};
+
+static const char *const IMU_ENABLED_TEXT[] = {
+	[IMU_OFF] = "off",
+	[IMU_ON] = "on",
+	[IMU_OFF_2S] = "off-2sec",
+};
+
+enum OS_TYPE {
+	WINDOWS,
+	LINUX,
+};
+
+static const char *const OS_TYPE_TEXT[] = {
+	[WINDOWS] = "windows",
+	[LINUX] = "linux",
+};
+
+enum POLL_RATE {
+	HZ125,
+	HZ250,
+	HZ500,
+	HZ1000,
+};
+
+static const char *const POLL_RATE_TEXT[] = {
+	[HZ125] = "125",
+	[HZ250] = "250",
+	[HZ500] = "500",
+	[HZ1000] = "1000",
+};
+
+enum DPAD_MODE {
+	DIR8,
+	DIR4,
+};
+
+static const char *const DPAD_MODE_TEXT[] = {
+	[DIR8] = "8-way",
+	[DIR4] = "4-way",
+};
+
+enum GAMEPAD_CFG_INDEX {
+	NONE = 0x00,
+	CFG_GAMEPAD_MODE, // GAMEPAD_MODE
+	CFG_AUTO_SLP_TIME = 0x04, // 1-255
+	CFG_PASS_ENABLE, // FEATURE_ENABLED
+	CFG_LIGHT_ENABLE, // FEATURE_ENABLED
+	CFG_IMU_ENABLE, // FEATURE_ENABLED
+	CFG_TP_ENABLE, // FEATURE_ENABLED
+	CFG_OS_TYPE = 0x0A, // OS_TYPE
+	CFG_POLL_RATE = 0x10, // POLL_RATE
+	CFG_DPAD_MODE, // DPAD_MODE
+	CFG_MS_WHEEL_STEP, // 1-127
+};
+
+/* GET/SET_TP_PARAM */
+enum TOUCHPAD_MODE {
+	TP_REL,
+	TP_ABS,
+};
+
+static const char *const TOUCHPAD_MODE_TEXT[] = {
+	[TP_REL] = "relative",
+	[TP_ABS] = "absolute",
+};
+
+enum TOUCHPAD_CFG_INDEX {
+	CFG_WINDOWS_MODE = 0x03, // TOUCHPAD_MODE
+	CFG_LINUX_MODE, // TOUCHPAD_MODE
+
+};
+
+enum RGB_MODE {
+	RGB_MODE_DYNAMIC,
+	RGB_MODE_CUSTOM,
+};
+
+static const char *const RGB_MODE_TEXT[] = {
+	[RGB_MODE_DYNAMIC] = "dynamic",
+	[RGB_MODE_CUSTOM] = "custom",
+};
+
+enum RGB_EFFECT {
+	RGB_EFFECT_MONO,
+	RGB_EFFECT_BREATHE,
+	RGB_EFFECT_CHROMA,
+	RGB_EFFECT_RAINBOW,
+};
+
+static const char *const RGB_EFFECT_TEXT[] = {
+	[RGB_EFFECT_MONO] = "monocolor",
+	[RGB_EFFECT_BREATHE] = "breathe",
+	[RGB_EFFECT_CHROMA] = "chroma",
+	[RGB_EFFECT_RAINBOW] = "rainbow",
+};
+
+/* GET/SET_LIGHT_CFG */
+enum LIGHT_CFG_INDEX {
+	LIGHT_MODE_SEL = 0x01,
+	LIGHT_PROFILE_SEL,
+	USR_LIGHT_PROFILE_1,
+	USR_LIGHT_PROFILE_2,
+	USR_LIGHT_PROFILE_3,
+};
+
+enum MCU_COMMAND {
+	SEND_HEARTBEAT,
+	GET_VERSION,
+	GET_MCU_ID,
+	GET_GAMEPAD_CFG,
+	SET_GAMEPAD_CFG,
+	GET_TP_PARAM,
+	SET_TP_PARAM,
+	GET_MOTOR_CFG,
+	SET_MOTOR_CFG,
+	GET_TRIGGER_CFG,
+	SET_TRIGGER_CFG,
+	GET_STICK_CFG,
+	SET_STICK_CFG,
+	GET_GYRO_CFG,
+	SET_GYRO_CFG,
+	GET_LIGHT_CFG,
+	SET_LIGHT_CFG,
+	GET_KEY_MAP,
+	SET_KEY_MAP,
+	INT_EVENT_REPORT = 0xc0,
+	INT_EVENT_CLEAR,
+	GET_PL_TEST = 0xdf,
+	SET_PL_TEST,
+	START_IAP_UPGRADE,
+	DBG_CTRL,
+	PL_TP_TEST,
+	RESTORE_FACTORY,
+	IC_RESET,
+};
+
+/*GET/SET_PL_TEST */
+enum TEST_INDEX {
+	TEST_EN = 0x01,
+	TEST_TP_MFR, // TP_MANUFACTURER
+	TEST_IMU_MFR, // IMU_MANUFACTURER
+	TEST_TP_VER, // u8
+	MOTOR_F0_CALI = 0x10,
+	READ_MOTOR_F0,
+	SAVE_MOTOR_F0,
+	TEST_LED_L = 0x20,
+	TEST_LED_R,
+	LED_COLOR_CALI,
+	STICK_CALI_TH = 0x30,
+	TRIGGER_CALI_TH,
+	STICK_CALI_DEAD,
+	TRIGGER_CALI_DEAD,
+	STICK_CALI_POLARITY,
+	TRIGGER_CALI_POLARITY,
+	GYRO_CALI_CFG,
+	STICK_CALI_TOUT,
+	TRIGGER_CALI_TOUT,
+};
+
+enum TP_MANUFACTURER {
+	TP_NONE,
+	TP_BETTERLIFE,
+	TP_SIPO,
+};
+
+static const char *const TP_MANUFACTURER_TEXT[] = {
+	[TP_NONE] = "none",
+	[TP_BETTERLIFE] = "BetterLife",
+	[TP_SIPO] = "SIPO",
+};
+
+enum IMU_MANUFACTURER {
+	IMU_NONE,
+	IMU_BOSCH,
+	IMU_ST,
+};
+
+static const char *const IMU_MANUFACTURER_TEXT[] = {
+	[IMU_NONE] = "none",
+	[IMU_BOSCH] = "Bosch",
+	[IMU_ST] = "ST",
+};
+
+struct command_report {
+	u8 cmd;
+	u8 sub_cmd;
+	u8 data[63];
+} __packed;
+
+struct version_report {
+	u8 cmd;
+	u32 version;
+	u8 reserved[59];
+} __packed;
+
+struct legos_cfg_rw_attr {
+	u8 index;
+};
+
+int legos_cfg_raw_event(u8 *data, int size)
+{
+	struct led_classdev_mc *mc_cdev;
+	struct command_report *cmd_rep;
+	struct version_report *ver_rep;
+	int ret;
+
+	print_hex_dump_debug("", DUMP_PREFIX_NONE, 16, 1, data, size, false);
+
+	if (size != GO_S_PACKET_SIZE)
+		return -EINVAL;
+
+	cmd_rep = (struct command_report *)data;
+	switch (cmd_rep->cmd) {
+	case GET_VERSION:
+		ver_rep = (struct version_report *)data;
+		drvdata.hdev->firmware_version =
+			__cpu_to_le32(ver_rep->version);
+		ret = 0;
+		break;
+	case GET_MCU_ID:
+		drvdata.mcu_id[0] = cmd_rep->sub_cmd;
+		memcpy(&drvdata.mcu_id[1], cmd_rep->data, 11);
+		ret = 0;
+		break;
+	case GET_GAMEPAD_CFG:
+		switch (cmd_rep->sub_cmd) {
+		case CFG_GAMEPAD_MODE:
+			drvdata.gp_mode = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_AUTO_SLP_TIME:
+			drvdata.gp_auto_sleep_time = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_PASS_ENABLE:
+			drvdata.imu_bypass_en = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_LIGHT_ENABLE:
+			drvdata.rgb_en = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_IMU_ENABLE:
+			drvdata.imu_sensor_en = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_TP_ENABLE:
+			drvdata.tp_en = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_OS_TYPE:
+			drvdata.os_mode = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_POLL_RATE:
+			drvdata.gp_poll_rate = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_DPAD_MODE:
+			drvdata.gp_dpad_mode = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_MS_WHEEL_STEP:
+			drvdata.mouse_step = cmd_rep->data[0];
+			ret = 0;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	case GET_TP_PARAM:
+		switch (cmd_rep->sub_cmd) {
+		case CFG_LINUX_MODE:
+			drvdata.tp_linux_mode = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case CFG_WINDOWS_MODE:
+			drvdata.tp_windows_mode = cmd_rep->data[0];
+			ret = 0;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	case GET_PL_TEST:
+		switch (cmd_rep->sub_cmd) {
+		case TEST_TP_MFR:
+			drvdata.tp_manufacturer = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case TEST_IMU_MFR:
+			drvdata.imu_manufacturer = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case TEST_TP_VER:
+			drvdata.tp_version = cmd_rep->data[0];
+			ret = 0;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	case GET_LIGHT_CFG:
+		switch (cmd_rep->sub_cmd) {
+		case LIGHT_MODE_SEL:
+			drvdata.rgb_mode = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case LIGHT_PROFILE_SEL:
+			drvdata.rgb_profile = cmd_rep->data[0];
+			ret = 0;
+			break;
+		case USR_LIGHT_PROFILE_1:
+		case USR_LIGHT_PROFILE_2:
+		case USR_LIGHT_PROFILE_3:
+			mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
+			drvdata.rgb_effect = cmd_rep->data[0];
+			mc_cdev->subled_info[0].intensity = cmd_rep->data[1];
+			mc_cdev->subled_info[1].intensity = cmd_rep->data[2];
+			mc_cdev->subled_info[2].intensity = cmd_rep->data[3];
+			drvdata.led_cdev->brightness = cmd_rep->data[4];
+			drvdata.rgb_speed = cmd_rep->data[5];
+			ret = 0;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+		}
+		break;
+	case GET_GYRO_CFG:
+	case GET_KEY_MAP:
+	case GET_MOTOR_CFG:
+	case GET_STICK_CFG:
+	case GET_TRIGGER_CFG:
+		ret = -EINVAL;
+		break;
+	case SET_GAMEPAD_CFG:
+	case SET_GYRO_CFG:
+	case SET_KEY_MAP:
+	case SET_LIGHT_CFG:
+	case SET_MOTOR_CFG:
+	case SET_STICK_CFG:
+	case SET_TP_PARAM:
+	case SET_TRIGGER_CFG:
+		ret = -cmd_rep->data[0];
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	};
+
+	if (ret && cmd_rep->cmd != START_IAP_UPGRADE)
+		dev_err(&drvdata.hdev->dev,
+			"Command %u with index %u failed with error code: %x\n",
+			cmd_rep->cmd, cmd_rep->sub_cmd, ret);
+
+	pr_debug("Last command: %u, sub_cmd: %u, ret: %u, val: [%ph]\n",
+		 cmd_rep->cmd, cmd_rep->sub_cmd, ret, cmd_rep->data);
+
+	complete(&drvdata.send_cmd_complete);
+	return ret;
+}
+
+static int legos_cfg_send_cmd(struct hid_device *hdev, u8 *buf, int ep)
+{
+	unsigned char *dmabuf __free(kfree) = NULL;
+	size_t size = GO_S_PACKET_SIZE;
+	int ret;
+
+	pr_debug("Send data as raw output report: [%*ph]\n", GO_S_PACKET_SIZE,
+		 buf);
+
+	dmabuf = kmemdup(buf, size, GFP_KERNEL);
+	if (!dmabuf)
+		return -ENOMEM;
+
+	ret = hid_hw_output_report(hdev, dmabuf, size);
+	if (ret < 0)
+		return ret;
+
+	return ret == size ? 0 : -EINVAL;
+}
+
+static int mcu_property_out(struct hid_device *hdev, enum MCU_COMMAND command,
+			    u8 index, u8 *val, size_t size)
+{
+	u8 outbuf[GO_S_PACKET_SIZE] = { command, index };
+	int ep = get_endpoint_address(hdev);
+	unsigned int i;
+	int timeout = 5;
+	int ret;
+
+	if (ep != LEGION_GO_S_CFG_INTF_IN)
+		return -ENODEV;
+
+	for (i = 0; i < size; i++)
+		outbuf[i + 2] = val[i];
+
+	guard(mutex)(&drvdata.cfg_mutex);
+	ret = legos_cfg_send_cmd(hdev, outbuf, ep);
+	if (ret)
+		return ret;
+
+	/* PL_TEST commands can take longer because they go out to another device */
+	if (command == GET_PL_TEST)
+		timeout = 200;
+
+	ret = wait_for_completion_interruptible_timeout(&drvdata.send_cmd_complete,
+							msecs_to_jiffies(timeout));
+
+	if (ret == 0) /* timeout occurred */
+		ret = -EBUSY;
+	if (ret > 0) /* timeout/interrupt didn't occur */
+		ret = 0;
+
+	reinit_completion(&drvdata.send_cmd_complete);
+	return ret;
+}
+
+/* Read-Write Attributes */
+static ssize_t gamepad_property_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count,
+				      enum GAMEPAD_CFG_INDEX index)
+{
+	size_t size = 1;
+	u8 val = 0;
+	int ret;
+
+	switch (index) {
+	case CFG_GAMEPAD_MODE: {
+		ret = sysfs_match_string(GAMEPAD_MODE_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.gp_mode = val;
+		break;
+	}
+	case CFG_AUTO_SLP_TIME:
+		ret = kstrtou8(buf, 10, &val);
+		if (ret)
+			return ret;
+
+		if (val < 0 || val > 255)
+			return -EINVAL;
+		drvdata.gp_auto_sleep_time = val;
+		break;
+	case CFG_IMU_ENABLE:
+		ret = sysfs_match_string(IMU_ENABLED_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.imu_sensor_en = val;
+		break;
+	case CFG_PASS_ENABLE:
+		ret = sysfs_match_string(FEATURE_ENABLE_STATUS_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.imu_bypass_en = val;
+		break;
+	case CFG_LIGHT_ENABLE:
+		ret = sysfs_match_string(FEATURE_ENABLE_STATUS_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.rgb_en = val;
+		break;
+	case CFG_TP_ENABLE:
+		ret = sysfs_match_string(FEATURE_ENABLE_STATUS_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.tp_en = val;
+		break;
+	case CFG_OS_TYPE:
+		ret = sysfs_match_string(OS_TYPE_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.os_mode = val;
+		break;
+	case CFG_POLL_RATE:
+		ret = sysfs_match_string(POLL_RATE_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.gp_poll_rate = val;
+		break;
+	case CFG_DPAD_MODE:
+		ret = sysfs_match_string(DPAD_MODE_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.gp_dpad_mode = val;
+		break;
+	case CFG_MS_WHEEL_STEP:
+		ret = kstrtou8(buf, 10, &val);
+		if (ret)
+			return ret;
+		if (val < 1 || val > 127)
+			return -EINVAL;
+		drvdata.mouse_step = val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (!val)
+		size = 0;
+
+	ret = mcu_property_out(drvdata.hdev, SET_GAMEPAD_CFG, index, &val,
+			       size);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t gamepad_property_show(struct device *dev,
+				     struct device_attribute *attr, char *buf,
+				     enum GAMEPAD_CFG_INDEX index)
+{
+	size_t count = 0;
+	u8 i;
+
+	count = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, index, 0, 0);
+	if (count < 0)
+		return count;
+
+	switch (index) {
+	case CFG_GAMEPAD_MODE:
+		i = drvdata.gp_mode;
+		if (i > DINPUT)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n", GAMEPAD_MODE_TEXT[i]);
+		break;
+	case CFG_AUTO_SLP_TIME:
+		count = sysfs_emit(buf, "%u\n", drvdata.gp_auto_sleep_time);
+		break;
+	case CFG_IMU_ENABLE:
+		i = drvdata.imu_sensor_en;
+		if (i > IMU_OFF_2S)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n", IMU_ENABLED_TEXT[i]);
+		break;
+	case CFG_PASS_ENABLE:
+		i = drvdata.imu_bypass_en;
+		if (i > FEATURE_ENABLED)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n",
+					   FEATURE_ENABLE_STATUS_TEXT[i]);
+		break;
+	case CFG_LIGHT_ENABLE:
+		i = drvdata.rgb_en;
+		if (i > FEATURE_ENABLED)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n",
+					   FEATURE_ENABLE_STATUS_TEXT[i]);
+		break;
+	case CFG_TP_ENABLE:
+		i = drvdata.tp_en;
+		if (i > FEATURE_ENABLED)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n",
+					   FEATURE_ENABLE_STATUS_TEXT[i]);
+		break;
+	case CFG_OS_TYPE:
+		i = drvdata.os_mode;
+		if (i > LINUX)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n", OS_TYPE_TEXT[i]);
+		break;
+	case CFG_POLL_RATE:
+		i = drvdata.gp_poll_rate;
+		if (i > HZ1000)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n", POLL_RATE_TEXT[i]);
+		break;
+	case CFG_DPAD_MODE:
+		i = drvdata.gp_dpad_mode;
+		if (i > DIR4)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%s\n", DPAD_MODE_TEXT[i]);
+		break;
+	case CFG_MS_WHEEL_STEP:
+		i = drvdata.mouse_step;
+		if (i < 1 || i > 127)
+			count = -EINVAL;
+		else
+			count = sysfs_emit(buf, "%u\n", i);
+		break;
+	default:
+		count = -EINVAL;
+		break;
+	}
+
+	return count;
+}
+
+static ssize_t gamepad_property_options(struct device *dev,
+					struct device_attribute *attr,
+					char *buf, enum GAMEPAD_CFG_INDEX index)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	switch (index) {
+	case CFG_GAMEPAD_MODE:
+		for (i = 0; i < ARRAY_SIZE(GAMEPAD_MODE_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       GAMEPAD_MODE_TEXT[i]);
+		}
+		break;
+	case CFG_AUTO_SLP_TIME:
+		return sysfs_emit(buf, "0-255\n");
+	case CFG_IMU_ENABLE:
+		for (i = 0; i < ARRAY_SIZE(IMU_ENABLED_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       IMU_ENABLED_TEXT[i]);
+		}
+		break;
+	case CFG_PASS_ENABLE:
+	case CFG_LIGHT_ENABLE:
+	case CFG_TP_ENABLE:
+		for (i = 0; i < ARRAY_SIZE(FEATURE_ENABLE_STATUS_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       FEATURE_ENABLE_STATUS_TEXT[i]);
+		}
+		break;
+	case CFG_OS_TYPE:
+		for (i = 0; i < ARRAY_SIZE(OS_TYPE_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       OS_TYPE_TEXT[i]);
+		}
+		break;
+	case CFG_POLL_RATE:
+		for (i = 0; i < ARRAY_SIZE(POLL_RATE_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       POLL_RATE_TEXT[i]);
+		}
+		break;
+	case CFG_DPAD_MODE:
+		for (i = 0; i < ARRAY_SIZE(DPAD_MODE_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       DPAD_MODE_TEXT[i]);
+		}
+		break;
+	case CFG_MS_WHEEL_STEP:
+		return sysfs_emit(buf, "1-127\n");
+	default:
+		return count;
+	}
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
+static ssize_t touchpad_property_store(struct device *dev,
+				       struct device_attribute *attr,
+				       const char *buf, size_t count,
+				       enum TOUCHPAD_CFG_INDEX index)
+{
+	size_t size = 1;
+	u8 val = 0;
+	int ret;
+
+	switch (index) {
+	case CFG_WINDOWS_MODE:
+		ret = sysfs_match_string(TOUCHPAD_MODE_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.tp_windows_mode = val;
+		break;
+	case CFG_LINUX_MODE:
+		ret = sysfs_match_string(TOUCHPAD_MODE_TEXT, buf);
+		if (ret < 0)
+			return ret;
+		val = ret;
+		drvdata.tp_linux_mode = val;
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (!val)
+		size = 0;
+
+	ret = mcu_property_out(drvdata.hdev, SET_TP_PARAM, index, &val, size);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+static ssize_t touchpad_property_show(struct device *dev,
+				      struct device_attribute *attr, char *buf,
+				      enum TOUCHPAD_CFG_INDEX index)
+{
+	int ret = 0;
+	u8 i;
+
+	ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, index, 0, 0);
+	if (ret < 0)
+		return ret;
+
+	switch (index) {
+	case CFG_WINDOWS_MODE:
+		i = drvdata.tp_windows_mode;
+		break;
+	case CFG_LINUX_MODE:
+		i = drvdata.tp_linux_mode;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (i > TP_ABS)
+		return -EINVAL;
+
+	return sysfs_emit(buf, "%s\n", TOUCHPAD_MODE_TEXT[i]);
+}
+
+static ssize_t touchpad_property_options(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf,
+					 enum TOUCHPAD_CFG_INDEX index)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	switch (index) {
+	case CFG_WINDOWS_MODE:
+	case CFG_LINUX_MODE:
+		for (i = 0; i < ARRAY_SIZE(TOUCHPAD_MODE_TEXT); i++) {
+			count += sysfs_emit_at(buf, count, "%s ",
+					       TOUCHPAD_MODE_TEXT[i]);
+		}
+		break;
+	default:
+		return count;
+	}
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
+/* RGB LED */
+static int rgb_cfg_call(struct hid_device *hdev, enum MCU_COMMAND cmd,
+			enum LIGHT_CFG_INDEX index, u8 *val, size_t size)
+{
+	if (cmd != SET_LIGHT_CFG && cmd != GET_LIGHT_CFG)
+		return -EINVAL;
+
+	if (index < LIGHT_MODE_SEL || index > USR_LIGHT_PROFILE_3)
+		return -EINVAL;
+
+	return mcu_property_out(hdev, cmd, index, val, size);
+}
+
+static int rgb_profile_call(enum MCU_COMMAND cmd, u8 *rgb_profile, size_t size)
+{
+	enum LIGHT_CFG_INDEX index;
+
+	index = drvdata.rgb_profile + 2;
+
+	return rgb_cfg_call(drvdata.hdev, cmd, index, rgb_profile, size);
+}
+
+static int rgb_write_profile(void)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(drvdata.led_cdev);
+
+	u8 rgb_profile[6] = { drvdata.rgb_effect,
+			      mc_cdev->subled_info[0].intensity,
+			      mc_cdev->subled_info[1].intensity,
+			      mc_cdev->subled_info[2].intensity,
+			      drvdata.led_cdev->brightness,
+			      drvdata.rgb_speed };
+
+	return rgb_profile_call(SET_LIGHT_CFG, rgb_profile, 6);
+}
+
+static int rgb_attr_show(void)
+{
+	return rgb_profile_call(GET_LIGHT_CFG, 0, 0);
+};
+
+static int rgb_attr_store(void)
+{
+	if (drvdata.rgb_mode != RGB_MODE_CUSTOM)
+		return -EINVAL;
+
+	return rgb_write_profile();
+}
+
+static ssize_t rgb_effect_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int ret;
+
+	ret = rgb_attr_show();
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%s\n", RGB_EFFECT_TEXT[drvdata.rgb_effect]);
+}
+
+static ssize_t rgb_effect_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	int ret;
+
+	ret = sysfs_match_string(RGB_EFFECT_TEXT, buf);
+	if (ret < 0)
+		return ret;
+
+	drvdata.rgb_effect = ret;
+
+	ret = rgb_attr_store();
+	if (ret)
+		return ret;
+
+	return count;
+};
+
+static ssize_t rgb_effect_index_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(RGB_EFFECT_TEXT); i++)
+		count += sysfs_emit_at(buf, count, "%s ", RGB_EFFECT_TEXT[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
+static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	int ret;
+
+	ret = rgb_attr_show();
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%hhu\n", drvdata.rgb_speed);
+}
+
+static ssize_t rgb_speed_store(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	int val = 0;
+	int ret;
+
+	ret = kstrtoint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val > 100 || val < 0)
+		return -EINVAL;
+
+	drvdata.rgb_speed = val;
+
+	ret = rgb_attr_store();
+	if (ret)
+		return ret;
+
+	return count;
+};
+
+static ssize_t rgb_speed_range_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "0-100\n");
+}
+
+static ssize_t rgb_mode_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	return sysfs_emit(buf, "%s\n", RGB_MODE_TEXT[drvdata.rgb_mode]);
+};
+
+static ssize_t rgb_mode_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	size_t size = 1;
+	int ret;
+
+	ret = sysfs_match_string(RGB_MODE_TEXT, buf);
+	if (ret < 0)
+		return ret;
+
+	drvdata.rgb_mode = ret;
+
+	if (!drvdata.rgb_mode)
+		size = 0;
+
+	ret = rgb_cfg_call(drvdata.hdev, SET_LIGHT_CFG, LIGHT_MODE_SEL,
+			   &drvdata.rgb_mode, size);
+	if (ret < 0)
+		return ret;
+
+	return count;
+};
+
+static ssize_t rgb_mode_index_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	size_t count = 0;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(RGB_MODE_TEXT); i++)
+		count += sysfs_emit_at(buf, count, "%s ", RGB_MODE_TEXT[i]);
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
+static ssize_t rgb_profile_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "%hhu\n", drvdata.rgb_profile);
+};
+
+static ssize_t rgb_profile_store(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	size_t size = 1;
+	int ret;
+	u8 val;
+
+	ret = kstrtou8(buf, 10, &val);
+	if (ret < 0)
+		return ret;
+
+	if (val > 3 || val < 1)
+		return -EINVAL;
+
+	drvdata.rgb_profile = val;
+
+	if (!drvdata.rgb_profile)
+		size = 0;
+
+	ret = rgb_cfg_call(drvdata.hdev, SET_LIGHT_CFG, LIGHT_PROFILE_SEL,
+			   &drvdata.rgb_profile, size);
+	if (ret < 0)
+		return ret;
+
+	return count;
+};
+
+static ssize_t rgb_profile_range_show(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	return sysfs_emit(buf, "1-3\n");
+}
+
+static enum led_brightness legos_rgb_color_get(struct led_classdev *led_cdev)
+{
+	return led_cdev->brightness;
+};
+
+static void legos_rgb_color_set(struct led_classdev *led_cdev,
+				enum led_brightness brightness)
+{
+	int ret;
+
+	led_cdev->brightness = brightness;
+
+	ret = rgb_attr_store();
+	switch (ret) {
+	case 0:
+		break;
+	case -ENODEV: /* during switch to IAP -ENODEV is expected */
+	case -ENOSYS: /* during rmmod -ENOSYS is expected */
+		dev_dbg(led_cdev->dev, "Failed to write RGB profile: %i\n",
+			ret);
+		break;
+	default:
+		dev_err(led_cdev->dev, "Failed to write RGB profile: %i\n",
+			ret);
+	};
+}
+
+#define DEVICE_ATTR_RO_NAMED(_name, _attrname)               \
+	struct device_attribute dev_attr_##_name = {         \
+		.attr = { .name = _attrname, .mode = 0444 }, \
+		.show = _name##_show,                        \
+	}
+
+#define DEVICE_ATTR_RW_NAMED(_name, _attrname)               \
+	struct device_attribute dev_attr_##_name = {         \
+		.attr = { .name = _attrname, .mode = 0644 }, \
+		.show = _name##_show,                        \
+		.store = _name##_store,                      \
+	}
+
+#define ATTR_LEGOS_GAMEPAD_RW(_name, _attrname, _rtype)                       \
+	static ssize_t _name##_store(struct device *dev,                      \
+				     struct device_attribute *attr,           \
+				     const char *buf, size_t count)           \
+	{                                                                     \
+		return gamepad_property_store(dev, attr, buf, count,          \
+					      _name.index);                   \
+	}                                                                     \
+	static ssize_t _name##_show(struct device *dev,                       \
+				    struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return gamepad_property_show(dev, attr, buf, _name.index);    \
+	}                                                                     \
+	static ssize_t _name##_##_rtype##_show(                               \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return gamepad_property_options(dev, attr, buf, _name.index); \
+	}                                                                     \
+	DEVICE_ATTR_RW_NAMED(_name, _attrname)
+
+#define ATTR_LEGOS_TOUCHPAD_RW(_name, _attrname, _rtype)                       \
+	static ssize_t _name##_store(struct device *dev,                       \
+				     struct device_attribute *attr,            \
+				     const char *buf, size_t count)            \
+	{                                                                      \
+		return touchpad_property_store(dev, attr, buf, count,          \
+					       _name.index);                   \
+	}                                                                      \
+	static ssize_t _name##_show(struct device *dev,                        \
+				    struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		return touchpad_property_show(dev, attr, buf, _name.index);    \
+	}                                                                      \
+	static ssize_t _name##_##_rtype##_show(                                \
+		struct device *dev, struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		return touchpad_property_options(dev, attr, buf, _name.index); \
+	}                                                                      \
+	DEVICE_ATTR_RW_NAMED(_name, _attrname)
+
+/* Gamepad */
+struct legos_cfg_rw_attr auto_sleep_time = { CFG_AUTO_SLP_TIME };
+struct legos_cfg_rw_attr dpad_mode = { CFG_DPAD_MODE };
+struct legos_cfg_rw_attr gamepad_mode = { CFG_GAMEPAD_MODE };
+struct legos_cfg_rw_attr gamepad_poll_rate = { CFG_POLL_RATE };
+
+ATTR_LEGOS_GAMEPAD_RW(auto_sleep_time, "auto_sleep_time", range);
+ATTR_LEGOS_GAMEPAD_RW(dpad_mode, "dpad_mode", index);
+ATTR_LEGOS_GAMEPAD_RW(gamepad_mode, "mode", index);
+ATTR_LEGOS_GAMEPAD_RW(gamepad_poll_rate, "poll_rate", index);
+static DEVICE_ATTR_RO(auto_sleep_time_range);
+static DEVICE_ATTR_RO(dpad_mode_index);
+static DEVICE_ATTR_RO_NAMED(gamepad_mode_index, "mode_index");
+static DEVICE_ATTR_RO_NAMED(gamepad_poll_rate_index, "poll_rate_index");
+
+static struct attribute *legos_gamepad_attrs[] = {
+	&dev_attr_auto_sleep_time.attr,
+	&dev_attr_auto_sleep_time_range.attr,
+	&dev_attr_dpad_mode.attr,
+	&dev_attr_dpad_mode_index.attr,
+	&dev_attr_gamepad_mode.attr,
+	&dev_attr_gamepad_mode_index.attr,
+	&dev_attr_gamepad_poll_rate.attr,
+	&dev_attr_gamepad_poll_rate_index.attr,
+	NULL,
+};
+
+/* IMU */
+struct legos_cfg_rw_attr imu_bypass_enabled = { CFG_PASS_ENABLE };
+struct legos_cfg_rw_attr imu_manufacturer = { TEST_IMU_MFR };
+struct legos_cfg_rw_attr imu_sensor_enabled = { CFG_IMU_ENABLE };
+
+ATTR_LEGOS_GAMEPAD_RW(imu_bypass_enabled, "bypass_enabled", index);
+ATTR_LEGOS_GAMEPAD_RW(imu_sensor_enabled, "sensor_enabled", index);
+static DEVICE_ATTR_RO_NAMED(imu_bypass_enabled_index, "bypass_enabled_index");
+static DEVICE_ATTR_RO_NAMED(imu_sensor_enabled_index, "sensor_enabled_index");
+
+static struct attribute *legos_imu_attrs[] = {
+	&dev_attr_imu_bypass_enabled.attr,
+	&dev_attr_imu_bypass_enabled_index.attr,
+	&dev_attr_imu_sensor_enabled.attr,
+	&dev_attr_imu_sensor_enabled_index.attr,
+	NULL,
+};
+
+/* MCU */
+struct legos_cfg_rw_attr os_mode = { CFG_OS_TYPE };
+
+ATTR_LEGOS_GAMEPAD_RW(os_mode, "os_mode", index);
+static DEVICE_ATTR_RO(os_mode_index);
+
+static struct attribute *legos_mcu_attrs[] = {
+	&dev_attr_os_mode.attr,
+	&dev_attr_os_mode_index.attr,
+	NULL,
+};
+
+/* Mouse */
+struct legos_cfg_rw_attr mouse_wheel_step = { CFG_MS_WHEEL_STEP };
+
+ATTR_LEGOS_GAMEPAD_RW(mouse_wheel_step, "step", range);
+static DEVICE_ATTR_RO_NAMED(mouse_wheel_step_range, "step_range");
+
+static struct attribute *legos_mouse_attrs[] = {
+	&dev_attr_mouse_wheel_step.attr,
+	&dev_attr_mouse_wheel_step_range.attr,
+	NULL,
+};
+
+/* RGB */
+struct legos_cfg_rw_attr rgb_enabled = { CFG_LIGHT_ENABLE };
+
+ATTR_LEGOS_GAMEPAD_RW(rgb_enabled, "enabled", index);
+static DEVICE_ATTR_RO_NAMED(rgb_effect_index, "effect_index");
+static DEVICE_ATTR_RO_NAMED(rgb_enabled_index, "enabled_index");
+static DEVICE_ATTR_RO_NAMED(rgb_mode_index, "mode_index");
+static DEVICE_ATTR_RO_NAMED(rgb_profile_range, "profile_range");
+static DEVICE_ATTR_RO_NAMED(rgb_speed_range, "speed_range");
+static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect");
+static DEVICE_ATTR_RW_NAMED(rgb_mode, "mode");
+static DEVICE_ATTR_RW_NAMED(rgb_profile, "profile");
+static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed");
+
+static struct attribute *legos_rgb_attrs[] = {
+	&dev_attr_rgb_effect.attr,
+	&dev_attr_rgb_effect_index.attr,
+	&dev_attr_rgb_speed.attr,
+	&dev_attr_rgb_speed_range.attr,
+	&dev_attr_rgb_mode.attr,
+	&dev_attr_rgb_mode_index.attr,
+	&dev_attr_rgb_profile.attr,
+	&dev_attr_rgb_profile_range.attr,
+	&dev_attr_rgb_enabled.attr,
+	&dev_attr_rgb_enabled_index.attr,
+	NULL,
+};
+
+/* Touchpad */
+struct legos_cfg_rw_attr touchpad_enabled = { CFG_TP_ENABLE };
+struct legos_cfg_rw_attr touchpad_linux_mode = { CFG_LINUX_MODE };
+struct legos_cfg_rw_attr touchpad_manufacturer = { TEST_TP_MFR };
+struct legos_cfg_rw_attr touchpad_version = { TEST_TP_VER };
+struct legos_cfg_rw_attr touchpad_windows_mode = { CFG_WINDOWS_MODE };
+
+ATTR_LEGOS_GAMEPAD_RW(touchpad_enabled, "enabled", index);
+ATTR_LEGOS_TOUCHPAD_RW(touchpad_linux_mode, "linux_mode", index);
+ATTR_LEGOS_TOUCHPAD_RW(touchpad_windows_mode, "windows_mode", index);
+static DEVICE_ATTR_RO_NAMED(touchpad_enabled_index, "enabled_index");
+static DEVICE_ATTR_RO_NAMED(touchpad_linux_mode_index, "linux_mode_index");
+static DEVICE_ATTR_RO_NAMED(touchpad_windows_mode_index, "windows_mode_index");
+
+static struct attribute *legos_touchpad_attrs[] = {
+	&dev_attr_touchpad_enabled.attr,
+	&dev_attr_touchpad_enabled_index.attr,
+	&dev_attr_touchpad_linux_mode.attr,
+	&dev_attr_touchpad_linux_mode_index.attr,
+	&dev_attr_touchpad_windows_mode.attr,
+	&dev_attr_touchpad_windows_mode_index.attr,
+	NULL,
+};
+
+static const struct attribute_group gamepad_attr_group = {
+	.name = "gamepad",
+	.attrs = legos_gamepad_attrs,
+};
+
+static const struct attribute_group imu_attr_group = {
+	.name = "imu",
+	.attrs = legos_imu_attrs,
+};
+
+static const struct attribute_group mouse_attr_group = {
+	.name = "mouse",
+	.attrs = legos_mouse_attrs,
+};
+
+static const struct attribute_group mcu_attr_group = {
+	.attrs = legos_mcu_attrs,
+};
+
+static struct attribute_group rgb_attr_group = {
+	.attrs = legos_rgb_attrs,
+};
+
+static const struct attribute_group touchpad_attr_group = {
+	.name = "touchpad",
+	.attrs = legos_touchpad_attrs,
+};
+
+static const struct attribute_group *legos_top_level_attr_groups[] = {
+	&gamepad_attr_group, &imu_attr_group,	   &mouse_attr_group,
+	&mcu_attr_group,     &touchpad_attr_group, NULL,
+};
+
+struct mc_subled legos_rgb_subled_info[] = {
+	{
+		.color_index = LED_COLOR_ID_RED,
+		.brightness = 0x50,
+		.intensity = 0x24,
+		.channel = 0x1,
+	},
+	{
+		.color_index = LED_COLOR_ID_GREEN,
+		.brightness = 0x50,
+		.intensity = 0x22,
+		.channel = 0x2,
+	},
+	{
+		.color_index = LED_COLOR_ID_BLUE,
+		.brightness = 0x50,
+		.intensity = 0x99,
+		.channel = 0x3,
+	},
+};
+
+struct led_classdev_mc legos_cdev_rgb = {
+	.led_cdev = {
+		.name = "go_s:rgb:joystick_rings",
+		.brightness = 0x50,
+		.max_brightness = 0x64,
+		.brightness_set = legos_rgb_color_set,
+		.brightness_get = legos_rgb_color_get,
+	},
+	.num_colors = ARRAY_SIZE(legos_rgb_subled_info),
+	.subled_info = legos_rgb_subled_info,
+};
+
+void cfg_setup(struct work_struct *work)
+{
+	int ret;
+
+	/* Gamepad */
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_AUTO_SLP_TIME,
+			       0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve gamepad auto sleep time: %i\n",
+			ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_DPAD_MODE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve gamepad dpad mode: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_GAMEPAD_MODE,
+			       0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve gamepad mode: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_POLL_RATE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve gamepad poll rate: %i\n", ret);
+		return;
+	}
+
+	/* IMU */
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_PASS_ENABLE,
+			       0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve IMU bypass enabled: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_IMU_MFR, 0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve IMU Manufacturer: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_IMU_ENABLE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve IMU enabled: %i\n", ret);
+		return;
+	}
+
+	/* MCU */
+	ret = mcu_property_out(drvdata.hdev, GET_MCU_ID, NONE, 0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev, "Failed to retrieve MCU ID: %i\n",
+			ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_VERSION, NONE, 0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve MCU Version: %i\n", ret);
+		return;
+	}
+
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve mouse wheel step size: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_OS_TYPE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve MCU OS Mode: %i\n", ret);
+		return;
+	}
+
+	/* RGB */
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_LIGHT_ENABLE,
+			       0, 0);
+	if (ret < 0) {
+		dev_err(drvdata.led_cdev->dev,
+			"Failed to retrieve RGB enabled: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_LIGHT_CFG, LIGHT_MODE_SEL, 0,
+			       0);
+	if (ret < 0) {
+		dev_err(drvdata.led_cdev->dev,
+			"Failed to retrieve RGB Mode: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_LIGHT_CFG, LIGHT_PROFILE_SEL,
+			       0, 0);
+	if (ret < 0) {
+		dev_err(drvdata.led_cdev->dev,
+			"Failed to retrieve RGB Profile: %i\n", ret);
+		return;
+	}
+
+	ret = rgb_attr_show();
+	if (ret < 0) {
+		dev_err(drvdata.led_cdev->dev,
+			"Failed to retrieve RGB Profile Data: %i\n", ret);
+		return;
+	}
+
+	/* Touchpad */
+	ret = mcu_property_out(drvdata.hdev, GET_GAMEPAD_CFG, CFG_TP_ENABLE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve touchpad enabled: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, CFG_LINUX_MODE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve touchpad Linux mode: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_MFR, 0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve touchpad manufacturer: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_TP_PARAM, CFG_WINDOWS_MODE, 0,
+			       0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve touchpad Windows mode: %i\n", ret);
+		return;
+	}
+
+	ret = mcu_property_out(drvdata.hdev, GET_PL_TEST, TEST_TP_VER, 0, 0);
+	if (ret) {
+		dev_err(&drvdata.hdev->dev,
+			"Failed to retrieve touchpad Version: %i\n", ret);
+		return;
+	}
+}
+
+static int legos_cfg_uevent(const struct device *dev,
+			    struct kobj_uevent_env *env)
+{
+	if (add_uevent_var(env, "LEGOS_TP_MANUFACTURER=%s",
+			   TP_MANUFACTURER_TEXT[drvdata.tp_manufacturer]))
+		return -ENOMEM;
+	if (add_uevent_var(env, "LEGOS_TP_VERSION=%u", drvdata.tp_version))
+		return -ENOMEM;
+	if (add_uevent_var(env, "LEGOS_IMU_MANUFACTURER=%s",
+			   IMU_MANUFACTURER_TEXT[drvdata.imu_manufacturer]))
+		return -ENOMEM;
+	if (add_uevent_var(env, "LEGOS_MCU_ID=%*phN", 12, &drvdata.mcu_id))
+		return -ENOMEM;
+	return 0;
+}
+
+int legos_cfg_probe(struct hid_device *hdev, const struct hid_device_id *_id)
+{
+	int ret;
+
+	mutex_init(&drvdata.cfg_mutex);
+
+	hid_set_drvdata(hdev, &drvdata);
+
+	drvdata.hdev = hdev;
+	hdev->uevent = legos_cfg_uevent;
+
+	ret = sysfs_create_groups(&hdev->dev.kobj, legos_top_level_attr_groups);
+	if (ret) {
+		dev_err(&hdev->dev,
+			"Failed to create gamepad configuration attributes: %i\n",
+			ret);
+		return ret;
+	}
+
+	ret = devm_led_classdev_multicolor_register(&hdev->dev,
+						    &legos_cdev_rgb);
+	if (ret) {
+		dev_err(&hdev->dev, "Failed to create RGB device: %i\n", ret);
+		return ret;
+	}
+
+	ret = devm_device_add_group(legos_cdev_rgb.led_cdev.dev,
+				    &rgb_attr_group);
+	if (ret) {
+		dev_err(&hdev->dev,
+			"Failed to create RGB configuratiion attributes: %i\n",
+			ret);
+		return ret;
+	}
+
+	drvdata.led_cdev = &legos_cdev_rgb.led_cdev;
+
+	init_completion(&drvdata.send_cmd_complete);
+
+	/* Executing calls prior to returning from probe will lock the MCU. Schedule
+	 * initial data call after probe has completed and MCU can accept calls.
+	 */
+	INIT_DELAYED_WORK(&drvdata.legos_cfg_setup, &cfg_setup);
+	schedule_delayed_work(&drvdata.legos_cfg_setup, msecs_to_jiffies(2));
+
+	return 0;
+}
+
+void legos_cfg_remove(struct hid_device *hdev)
+{
+	guard(mutex)(&drvdata.cfg_mutex);
+	cancel_delayed_work_sync(&drvdata.legos_cfg_setup);
+	sysfs_remove_groups(&hdev->dev.kobj, legos_top_level_attr_groups);
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+	hdev->uevent = NULL;
+	hid_set_drvdata(hdev, NULL);
+}
diff --git a/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.h b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.h
new file mode 100644
index 000000000000..3d13744e2692
--- /dev/null
+++ b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-config.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_LEGOS_HID_CONFIG_
+#define _LENOVO_LEGOS_HID_CONFIG_
+
+#include <linux/types.h>
+
+struct hid_device;
+struct hid_device_id;
+struct work_struct;
+
+int legos_cfg_raw_event(u8 *data, int size);
+void cfg_setup(struct work_struct *work);
+int legos_cfg_probe(struct hid_device *hdev, const struct hid_device_id *_id);
+void legos_cfg_remove(struct hid_device *hdev);
+
+#endif /* !_LENOVO_LEGOS_HID_CONFIG_*/
diff --git a/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.c b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.c
new file mode 100644
index 000000000000..751f4addabd8
--- /dev/null
+++ b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  HID driver for Lenovo Legion Go S series gamepad.
+ *
+ *  Copyright (c) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hid.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+
+#include "lenovo-legos-hid-core.h"
+#include "lenovo-legos-hid-config.h"
+#include "../hid-ids.h"
+
+u8 get_endpoint_address(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct usb_host_endpoint *ep;
+
+	if (intf) {
+		ep = intf->cur_altsetting->endpoint;
+		if (ep)
+			return ep->desc.bEndpointAddress;
+	}
+
+	return -ENODEV;
+}
+
+static int lenovo_legos_raw_event(struct hid_device *hdev,
+				  struct hid_report *report, u8 *data, int size)
+{
+	int ep;
+
+	ep = get_endpoint_address(hdev);
+
+	switch (ep) {
+	case LEGION_GO_S_CFG_INTF_IN:
+		return legos_cfg_raw_event(data, size);
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int lenovo_legos_hid_probe(struct hid_device *hdev,
+				  const struct hid_device_id *id)
+{
+	int ret, ep;
+
+	ep = get_endpoint_address(hdev);
+	if (ep <= 0)
+		return ep;
+
+	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");
+		hid_hw_stop(hdev);
+		return ret;
+	}
+
+	switch (ep) {
+	case LEGION_GO_S_CFG_INTF_IN:
+		ret = legos_cfg_probe(hdev, id);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static void lenovo_legos_hid_remove(struct hid_device *hdev)
+{
+	int ep = get_endpoint_address(hdev);
+
+	switch (ep) {
+	case LEGION_GO_S_CFG_INTF_IN:
+		legos_cfg_remove(hdev);
+		break;
+	default:
+		hid_hw_close(hdev);
+		hid_hw_stop(hdev);
+
+		break;
+	}
+}
+
+static const struct hid_device_id lenovo_legos_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QHE,
+			 USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QHE,
+			 USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, lenovo_legos_devices);
+static struct hid_driver lenovo_legos_hid = {
+	.name = "lenovo-legos-hid",
+	.id_table = lenovo_legos_devices,
+	.probe = lenovo_legos_hid_probe,
+	.remove = lenovo_legos_hid_remove,
+	.raw_event = lenovo_legos_raw_event,
+};
+module_hid_driver(lenovo_legos_hid);
+
+MODULE_AUTHOR("Derek J. Clark");
+MODULE_DESCRIPTION("HID Driver for Lenovo Legion Go S Series gamepad.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.h b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.h
new file mode 100644
index 000000000000..efbc50896536
--- /dev/null
+++ b/drivers/hid/lenovo-legos-hid/lenovo-legos-hid-core.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright(C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_LEGOS_HID_CORE_
+#define _LENOVO_LEGOS_HID_CORE_
+
+#include <linux/types.h>
+
+#define GO_S_PACKET_SIZE 64
+
+struct hid_device;
+
+enum legos_interface {
+	LEGION_GO_S_IAP_INTF_IN = 0x81,
+	LEGION_GO_S_TP_INTF_IN = 0x83,
+	LEGION_GO_S_CFG_INTF_IN,
+	LEGION_GO_S_IMU_INTF_IN,
+	LEGION_GO_S_GP_INFT_IN,
+	LEGION_GO_S_UNK_INTF_IN,
+};
+
+u8 get_endpoint_address(struct hid_device *hdev);
+
+#endif /* !_LENOVO_LEGOS_HID_CORE_*/
diff --git a/drivers/hid/zotac-zone-hid/Kconfig b/drivers/hid/zotac-zone-hid/Kconfig
new file mode 100644
index 000000000000..89fb2ac080bb
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/Kconfig
@@ -0,0 +1,8 @@
+config ZOTAC_ZONE_HID
+	tristate "Zotac Zone handheld support"
+	depends on USB_HID
+	depends on LEDS_CLASS
+	depends on LEDS_CLASS_MULTICOLOR
+	select POWER_SUPPLY
+	help
+	Support for the Zotac Zone handheld gaming console.
diff --git a/drivers/hid/zotac-zone-hid/Makefile b/drivers/hid/zotac-zone-hid/Makefile
new file mode 100644
index 000000000000..2f7053b981ac
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Makefile - Zotac Zone handheld device driver
+#
+zotac-zone-hid-y := zotac-zone-hid-core.o zotac-zone-hid-rgb.o zotac-zone-hid-input.o zotac-zone-hid-config.o
+obj-$(CONFIG_ZOTAC_ZONE_HID) := zotac-zone-hid.o
diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c
new file mode 100644
index 000000000000..049615194392
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c
@@ -0,0 +1,2231 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+* HID driver for ZOTAC Gaming Zone Controller - RGB LED control
+*
+* Copyright (c) 2025 Luke D. Jones <luke@ljones.dev>
+*/
+
+#include "asm-generic/errno-base.h"
+#include "linux/kstrtox.h"
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "zotac-zone.h"
+
+#define REPORT_SIZE 64
+
+#define HEADER_TAG_POS 0
+#define RESERVED_POS 1
+#define SEQUENCE_POS 2
+#define PAYLOADSIZE_POS 3
+#define COMMAND_POS 4
+#define SETTING_POS 5
+#define VALUE_POS 6
+#define CRC_H_POS 0x3E
+#define CRC_L_POS 0x3F
+
+#define HEADER_TAG 0xE1
+#define PAYLOAD_SIZE 0x3C
+
+/*
+ * Button mapping structure indices (relative to data buffer)
+ * The data buffer is copied to the correct location in the HID report.
+ */
+#define HEADER_LEN 5
+#define BTN_MAP_SOURCE_IDX (0x05 - HEADER_LEN) /* Source button ID */
+#define BTN_MAP_GAMEPAD_START_IDX (0x06 - HEADER_LEN)
+#define BTN_MAP_GAMEPAD_SIZE 4
+#define BTN_MAP_MODIFIER_IDX (0x0A - HEADER_LEN)
+#define BTN_MAP_KEYBOARD_START_IDX (0x0C - HEADER_LEN)
+#define BTN_MAP_KEYBOARD_SIZE 6
+#define BTN_MAP_MOUSE_IDX (0x12 - HEADER_LEN)
+#define BTN_MAP_RESPONSE_MIN_SIZE 14
+
+#define GAMEPAD_DPAD_STICK_IDX 0 /* DPad, Stick buttons */
+#define GAMEPAD_FACE_BUMPER_IDX 1 /* Face buttons, Bumpers */
+#define GAMEPAD_TRIGGER_IDX 2 /* Triggers */
+#define GAMEPAD_RESERVED_IDX 3 /* Reserved/unused */
+/*
+ * Note: The above indices are relative to our data buffer, not the full protocol packet.
+ * In the full protocol packet (as described in documentation), these fields would be
+ * at different positions due to the packet header.
+ *
+ * For example, BTN_MAP_MOUSE_IDX (13 in our buffer) corresponds to offset 0x12-0x13
+ * in the full protocol packet.
+ */
+
+/* Mouse speed constants */
+#define CMD_SET_MOUSE_SPEED 0xA3
+#define CMD_GET_MOUSE_SPEED 0xA4
+#define MOUSE_SPEED_MIN 0x01 /* Slow */
+#define MOUSE_SPEED_MAX 0x0A /* Fast */
+
+#define STICK_SENSITIVITY_NUM_IDX 0
+#define STICK_SENSITIVITY_DATA_IDX 1
+#define STICK_SENSITIVITY_SIZE 8
+
+#define DZ_LEFT_INNER_IDX 0
+#define DZ_LEFT_OUTER_IDX 1
+#define DZ_RIGHT_INNER_IDX 2
+#define DZ_RIGHT_OUTER_IDX 3
+#define DZ_RESPONSE_SIZE 4
+
+#define VIB_LEFT_TRIGGER_IDX 0
+#define VIB_RIGHT_TRIGGER_IDX 1
+#define VIB_LEFT_RUMBLE_IDX 2
+#define VIB_RIGHT_RUMBLE_IDX 3
+#define VIB_RESPONSE_SIZE 4
+
+#define CMD_SET_PROFILE 0xB1
+#define CMD_GET_PROFILE 0xB2
+#define CMD_GET_PROFILE_NUM 0xB3
+#define CMD_RESTORE_PROFILE 0xF1
+#define CMD_SAVE_CONFIG 0xFB
+
+#define CMD_SET_VIBRATION_STRENGTH 0xA9
+#define CMD_GET_VIBRATION_STRENGTH 0xAA
+#define CMD_SET_STICK_DEADZONES 0xA5
+#define CMD_GET_STICK_DEADZONES 0xA6
+#define CMD_SET_TRIGGER_DEADZONES 0xB4
+#define CMD_GET_TRIGGER_DEADZONES 0xB5
+#define CMD_SET_STICK_SENSITIVITY 0xBA
+#define CMD_GET_STICK_SENSITIVITY 0xBB
+#define CMD_SET_BUTTON_TURBO 0xB8
+#define CMD_GET_BUTTON_TURBO 0xB9
+#define CMD_GET_DEVICE_INFO 0xFA
+#define CMD_MOTOR_TEST 0xBD
+
+#define MOTOR_TEST_SIZE 4
+
+#define STICK_LEFT 0
+#define STICK_RIGHT 1
+
+#define PROFILE_DEFAULT 0x00
+#define PROFILE_SECONDARY 0x01
+
+/* Button bit positions within the turbo byte */
+#define A_BTN_POS 0
+#define B_BTN_POS 1
+#define X_BTN_POS 2
+#define Y_BTN_POS 3
+#define LB_BTN_POS 4
+#define RB_BTN_POS 5
+#define LT_BTN_POS 6
+#define RT_BTN_POS 7
+
+/* Button ID definitions */
+#define BUTTON_NONE 0x00
+#define BUTTON_M1 0x01
+#define BUTTON_M2 0x02
+#define BUTTON_L_TOUCH_UP 0x03
+#define BUTTON_L_TOUCH_DOWN 0x04
+#define BUTTON_L_TOUCH_LEFT 0x05
+#define BUTTON_L_TOUCH_RIGHT 0x06
+#define BUTTON_R_TOUCH_UP 0x07
+#define BUTTON_R_TOUCH_DOWN 0x08
+#define BUTTON_R_TOUCH_LEFT 0x09
+#define BUTTON_R_TOUCH_RIGHT 0x0A
+#define BUTTON_LB 0x0B
+#define BUTTON_RB 0x0C
+#define BUTTON_LT 0x0D
+#define BUTTON_RT 0x0E
+#define BUTTON_A 0x0F
+#define BUTTON_B 0x10
+#define BUTTON_X 0x11
+#define BUTTON_Y 0x12
+#define BUTTON_DPAD_UP 0x13
+#define BUTTON_DPAD_DOWN 0x14
+#define BUTTON_DPAD_LEFT 0x15
+#define BUTTON_DPAD_RIGHT 0x16
+#define BUTTON_LS 0x17
+#define BUTTON_RS 0x18
+
+/* Modifier key definitions */
+#define MOD_NONE 0x00
+#define MOD_LEFT_CTRL 0x01
+#define MOD_LEFT_SHIFT 0x02
+#define MOD_LEFT_ALT 0x04
+#define MOD_LEFT_WIN 0x08
+#define MOD_RIGHT_CTRL 0x10
+#define MOD_RIGHT_SHIFT 0x20
+#define MOD_RIGHT_ALT 0x40
+#define MOD_RIGHT_WIN 0x80
+
+int zotac_cfg_refresh(struct zotac_device *zotac);
+
+struct button_directory {
+	const char *name; /* Directory name (e.g., "btn_a") */
+	u8 button_id; /* Associated button ID */
+	bool has_turbo; /* Whether this button supports turbo */
+	struct kobject *
+		kobj; /* kobject for this directory (filled during registration) */
+	struct attribute_group *main_group; /* Main button attributes */
+	struct attribute_group *remap_group; /* Remap subdirectory attributes */
+};
+
+/* Structure to map button names to their byte and bit positions */
+struct button_mapping_entry {
+	const char *name;
+	u8 byte_index;
+	u8 bit_mask;
+};
+
+/* Define all supported buttons with their byte index and bit mask */
+static const struct button_mapping_entry button_map[] = {
+	{ "dpad_up", 0, 0x01 },
+	{ "dpad_down", 0, 0x02 },
+	{ "dpad_left", 0, 0x04 },
+	{ "dpad_right", 0, 0x08 },
+	{ "ls", 0, 0x40 },
+	{ "rs", 0, 0x80 },
+	{ "lb", 1, 0x01 },
+	{ "rb", 1, 0x02 },
+	{ "a", 1, 0x10 },
+	{ "b", 1, 0x20 },
+	{ "x", 1, 0x40 },
+	{ "y", 1, 0x80 },
+	{ "lt", 2, 0x01 },
+	{ "rt", 2, 0x02 },
+	{ NULL, 0, 0 } /* Terminator */
+};
+
+static const struct {
+	const char *name;
+	u8 id;
+} gamepad_button_names[] = {
+	{ "none", BUTTON_NONE },
+	{ "a", BUTTON_A },
+	{ "b", BUTTON_B },
+	{ "x", BUTTON_X },
+	{ "y", BUTTON_Y },
+	{ "lb", BUTTON_LB },
+	{ "rb", BUTTON_RB },
+	{ "lt", BUTTON_LT },
+	{ "rt", BUTTON_RT },
+	{ "ls", BUTTON_LS },
+	{ "rs", BUTTON_RS },
+	{ "dpad_up", BUTTON_DPAD_UP },
+	{ "dpad_down", BUTTON_DPAD_DOWN },
+	{ "dpad_left", BUTTON_DPAD_LEFT },
+	{ "dpad_right", BUTTON_DPAD_RIGHT },
+};
+
+static const struct {
+	const char *name;
+	u8 value;
+} modifier_names[] = {
+	{ "none", MOD_NONE },
+	{ "left_ctrl", MOD_LEFT_CTRL },
+	{ "left_shift", MOD_LEFT_SHIFT },
+	{ "left_alt", MOD_LEFT_ALT },
+	{ "left_win", MOD_LEFT_WIN },
+	{ "right_ctrl", MOD_RIGHT_CTRL },
+	{ "right_shift", MOD_RIGHT_SHIFT },
+	{ "right_alt", MOD_RIGHT_ALT },
+	{ "right_win", MOD_RIGHT_WIN },
+};
+
+/* Keyboard key definitions */
+struct key_name_mapping {
+	const char *name;
+	u8 keycode;
+};
+
+static const struct key_name_mapping keyboard_keys[] = {
+	{ "none", 0x00 },	 { "a", 0x04 },		 { "b", 0x05 },
+	{ "c", 0x06 },		 { "d", 0x07 },		 { "e", 0x08 },
+	{ "f", 0x09 },		 { "g", 0x0a },		 { "h", 0x0b },
+	{ "i", 0x0c },		 { "j", 0x0d },		 { "k", 0x0e },
+	{ "l", 0x0f },		 { "m", 0x10 },		 { "n", 0x11 },
+	{ "o", 0x12 },		 { "p", 0x13 },		 { "q", 0x14 },
+	{ "r", 0x15 },		 { "s", 0x16 },		 { "t", 0x17 },
+	{ "u", 0x18 },		 { "v", 0x19 },		 { "w", 0x1a },
+	{ "x", 0x1b },		 { "y", 0x1c },		 { "z", 0x1d },
+	{ "1", 0x1e },		 { "2", 0x1f },		 { "3", 0x20 },
+	{ "4", 0x21 },		 { "5", 0x22 },		 { "6", 0x23 },
+	{ "7", 0x24 },		 { "8", 0x25 },		 { "9", 0x26 },
+	{ "0", 0x27 },		 { "enter", 0x28 },	 { "esc", 0x29 },
+	{ "backspace", 0x2a },	 { "tab", 0x2b },	 { "space", 0x2c },
+	{ "minus", 0x2d },	 { "equals", 0x2e },	 { "leftbrace", 0x2f },
+	{ "rightbrace", 0x30 },	 { "backslash", 0x31 },	 { "semicolon", 0x33 },
+	{ "apostrophe", 0x34 },	 { "grave", 0x35 },	 { "comma", 0x36 },
+	{ "dot", 0x37 },	 { "slash", 0x38 },	 { "capslock", 0x39 },
+	{ "f1", 0x3a },		 { "f2", 0x3b },	 { "f3", 0x3c },
+	{ "f4", 0x3d },		 { "f5", 0x3e },	 { "f6", 0x3f },
+	{ "f7", 0x40 },		 { "f8", 0x41 },	 { "f9", 0x42 },
+	{ "f10", 0x43 },	 { "f11", 0x44 },	 { "f12", 0x45 },
+	{ "printscreen", 0x46 }, { "scrolllock", 0x47 }, { "pause", 0x48 },
+	{ "insert", 0x49 },	 { "home", 0x4a },	 { "pageup", 0x4b },
+	{ "delete", 0x4c },	 { "end", 0x4d },	 { "pagedown", 0x4e },
+	{ "right", 0x4f },	 { "left", 0x50 },	 { "down", 0x51 },
+	{ "up", 0x52 },		 { "numlock", 0x53 },	 { "kpslash", 0x54 },
+	{ "kpasterisk", 0x55 },	 { "kpminus", 0x56 },	 { "kpplus", 0x57 },
+	{ "kpenter", 0x58 },	 { "kp1", 0x59 },	 { "kp2", 0x5a },
+	{ "kp3", 0x5b },	 { "kp4", 0x5c },	 { "kp5", 0x5d },
+	{ "kp6", 0x5e },	 { "kp7", 0x5f },	 { "kp8", 0x60 },
+	{ "kp9", 0x61 },	 { "kp0", 0x62 },	 { "kpdot", 0x63 },
+	{ "application", 0x65 }
+};
+
+/* Mouse button definitions */
+#define MOUSE_LEFT 0x01
+#define MOUSE_RIGHT 0x02
+#define MOUSE_MIDDLE 0x04
+
+static const struct {
+	const char *name;
+	u8 value;
+} mouse_button_names[] = {
+	{ "left", MOUSE_LEFT },
+	{ "right", MOUSE_RIGHT },
+	{ "middle", MOUSE_MIDDLE },
+};
+
+struct zotac_device_info {
+	u64 device_id;
+	u16 vid;
+	u16 pid;
+	u8 num_led_zones;
+	struct {
+		u8 major;
+		u8 mid;
+		u8 minor;
+		u16 revision;
+	} fw_version;
+	struct {
+		u8 major;
+		u8 mid;
+		u8 minor;
+	} hw_version;
+};
+
+static u16 zotac_calc_crc(u8 *data)
+{
+	const int payload_end = 0x3D;
+	const int payload_start = 4;
+	const u16 crc_seed = 0;
+	u16 crc = crc_seed;
+	u32 h1, h2, h3, h4;
+	int i;
+
+	for (i = payload_start; i <= payload_end; i++) {
+		h1 = (u32)((crc ^ data[i]) & 0xFF);
+		h2 = h1 & 0x0F;
+		h3 = (h2 << 4) ^ h1;
+		h4 = h3 >> 4;
+
+		crc = (u16)((((((h3 << 1) ^ h4) << 4) ^ h2) << 3) ^ h4 ^
+			    (crc >> 8));
+	}
+
+	return crc;
+}
+
+static int zotac_send_command_and_get_response_usb(struct hid_device *hdev,
+						   u8 *send_buf, int send_len,
+						   u8 *recv_buf, int recv_len)
+{
+	struct usb_device *udev =
+		interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	const int polling_interval_ms = 50;
+	int actual_length = 0;
+
+	unsigned int pipe_in, pipe_out;
+	int ret;
+
+	if (!intf || !udev) {
+		hid_err(hdev, "Failed to get USB interface or device\n");
+		return -ENODEV;
+	}
+
+	pipe_out = usb_sndintpipe(udev, 0x05);
+	pipe_in = usb_rcvintpipe(udev, 0x84);
+
+	ret = usb_interrupt_msg(udev, pipe_out, send_buf, send_len,
+				&actual_length, 1000);
+	if (ret < 0) {
+		hid_err(hdev, "Failed to send USB command: %d\n", ret);
+		return ret;
+	}
+
+	memset(recv_buf, 0, recv_len);
+	ret = usb_interrupt_msg(udev, pipe_in, recv_buf, recv_len,
+				&actual_length, polling_interval_ms);
+	if (ret == 0 && actual_length > 0) {
+		return actual_length;
+	}
+
+	hid_err(hdev, "Timeout waiting for USB response\n");
+	return -ETIMEDOUT;
+}
+
+static int zotac_send_command_raw(struct zotac_device *zotac, u8 cmd_code,
+				  u8 setting, const u8 *data, size_t data_len,
+				  u8 *response_buffer, size_t *response_len)
+{
+	bool is_cmd = (cmd_code == CMD_SET_RGB || cmd_code == CMD_GET_RGB);
+	int reply_len, result = -EIO;
+
+	struct zotac_cfg_data *cfg;
+	size_t copy_len;
+	u8 *buffer;
+	u16 crc;
+
+	if (!zotac->cfg_data || !zotac->hdev)
+		return -ENODEV;
+
+	cfg = zotac->cfg_data;
+
+	buffer = kzalloc(REPORT_SIZE, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	if (mutex_lock_interruptible(&cfg->command_mutex)) {
+		kfree(buffer);
+		return -EINTR;
+	}
+
+	memset(buffer, 0, REPORT_SIZE);
+	buffer[HEADER_TAG_POS] = HEADER_TAG;
+	buffer[RESERVED_POS] = 0x00;
+	buffer[SEQUENCE_POS] = cfg->sequence_num;
+	buffer[PAYLOADSIZE_POS] = PAYLOAD_SIZE;
+	buffer[COMMAND_POS] = cmd_code;
+
+	if (is_cmd) {
+		buffer[SETTING_POS] = setting;
+
+		if (data && data_len > 0) {
+			copy_len = min_t(size_t, data_len, PAYLOAD_SIZE - 2);
+			memcpy(&buffer[VALUE_POS], data, copy_len);
+		}
+	} else {
+		if (data && data_len > 0) {
+			copy_len = min_t(size_t, data_len, PAYLOAD_SIZE - 1);
+			memcpy(&buffer[SETTING_POS], data, copy_len);
+		} else {
+			buffer[SETTING_POS] = setting;
+		}
+	}
+
+	crc = zotac_calc_crc(buffer);
+	buffer[CRC_H_POS] = (crc >> 8) & 0xFF;
+	buffer[CRC_L_POS] = crc & 0xFF;
+
+	reply_len = zotac_send_command_and_get_response_usb(
+		zotac->hdev, buffer, REPORT_SIZE, response_buffer, REPORT_SIZE);
+
+	if (reply_len > 0) {
+		if (response_buffer[COMMAND_POS] == cmd_code) {
+			*response_len = reply_len;
+			cfg->sequence_num = (cfg->sequence_num + 1) & 0xFF;
+			result = 0;
+		} else {
+			hid_err(zotac->hdev,
+				"Command mismatch in response: expected 0x%02x, got 0x%02x\n",
+				cmd_code, response_buffer[COMMAND_POS]);
+			result = -EIO;
+		}
+	} else {
+		hid_err(zotac->hdev,
+			"No response received for command 0x%02x: %d\n",
+			cmd_code, reply_len);
+		result = reply_len < 0 ? reply_len : -EIO;
+	}
+
+	mutex_unlock(&cfg->command_mutex);
+	kfree(buffer);
+
+	return result;
+}
+
+int zotac_send_get_command(struct zotac_device *zotac, u8 cmd_code, u8 setting,
+			   const u8 *req_data, size_t req_data_len,
+			   u8 *output_data, size_t *output_len)
+{
+	bool is_status_offset =
+		(cmd_code == CMD_SET_RGB || cmd_code == CMD_SET_BUTTON_MAPPING);
+	size_t response_size = REPORT_SIZE, available, to_copy;
+	int data_offset, ret;
+	u8 *response_buffer;
+
+	response_buffer = kzalloc(REPORT_SIZE, GFP_KERNEL);
+	if (!response_buffer)
+		return -ENOMEM;
+
+	ret = zotac_send_command_raw(zotac, cmd_code, setting, req_data,
+				     req_data_len, response_buffer,
+				     &response_size);
+	if (ret < 0) {
+		kfree(response_buffer);
+		return ret;
+	}
+
+	if (response_size <= COMMAND_POS ||
+	    response_buffer[COMMAND_POS] != cmd_code) {
+		kfree(response_buffer);
+		return -EIO;
+	}
+
+	data_offset = is_status_offset ? 7 : 5;
+
+	if (output_data && output_len && *output_len > 0 &&
+	    response_size > data_offset) {
+		available = response_size - data_offset;
+		to_copy = min_t(size_t, available, *output_len);
+
+		memcpy(output_data, &response_buffer[data_offset], to_copy);
+		*output_len = to_copy;
+	}
+
+	kfree(response_buffer);
+	return 0;
+}
+
+int zotac_send_set_command(struct zotac_device *zotac, u8 cmd_code, u8 setting,
+			   const u8 *data, size_t data_len)
+{
+	bool is_status_offset =
+		(cmd_code == CMD_SET_RGB || cmd_code == CMD_SET_BUTTON_MAPPING);
+	size_t response_size = REPORT_SIZE;
+
+	int ret, status_offset;
+	u8 *response_buffer;
+
+	response_buffer = kzalloc(REPORT_SIZE, GFP_KERNEL);
+	if (!response_buffer) {
+		hid_err(zotac->hdev,
+			"SET_COMMAND: Failed to allocate response buffer");
+		return -ENOMEM;
+	}
+
+	ret = zotac_send_command_raw(zotac, cmd_code, setting, data, data_len,
+				     response_buffer, &response_size);
+
+	if (ret < 0) {
+		hid_err(zotac->hdev,
+			"SET_COMMAND: Command failed with error %d", ret);
+		kfree(response_buffer);
+		return ret;
+	}
+
+	if (response_size <= COMMAND_POS ||
+	    response_buffer[COMMAND_POS] != cmd_code) {
+		hid_err(zotac->hdev,
+			"SET_COMMAND: Invalid response - size=%zu, cmd=0x%02x",
+			response_size, response_buffer[COMMAND_POS]);
+		kfree(response_buffer);
+		return -EIO;
+	}
+
+	status_offset = is_status_offset ? 6 : 5;
+
+	if (response_size > status_offset) {
+		if (response_buffer[status_offset] != 0) {
+			hid_err(zotac->hdev,
+				"SET_COMMAND: Command rejected by device, status=0x%02x",
+				response_buffer[status_offset]);
+			kfree(response_buffer);
+			return -EIO;
+		}
+	}
+
+	kfree(response_buffer);
+	return 0;
+}
+
+int zotac_send_get_byte(struct zotac_device *zotac, u8 cmd_code, u8 setting,
+			const u8 *req_data, size_t req_data_len)
+{
+	size_t output_len = 1;
+	u8 output_data = 0;
+	int ret;
+
+	ret = zotac_send_get_command(zotac, cmd_code, setting, req_data,
+				     req_data_len, &output_data, &output_len);
+	if (ret < 0)
+		return ret;
+
+	if (output_len < 1)
+		return -EIO;
+
+	return output_data;
+}
+
+static int zotac_get_device_info(struct zotac_device *zotac,
+				 struct zotac_device_info *info)
+{
+	u8 data[21];
+	size_t data_len = sizeof(data);
+	int ret;
+
+	if (!zotac || !info)
+		return -EINVAL;
+
+	ret = zotac_send_get_command(zotac, CMD_GET_DEVICE_INFO, 0, NULL, 0,
+				     data, &data_len);
+	if (ret < 0)
+		return ret;
+
+	if (data_len < 20) {
+		dev_err(&zotac->hdev->dev,
+			"Incomplete device info received: %zu bytes\n",
+			data_len);
+		return -EIO;
+	}
+
+	info->device_id = ((u64)data[0] | ((u64)data[1] << 8) |
+			   ((u64)data[2] << 16) | ((u64)data[3] << 24) |
+			   ((u64)data[4] << 32) | ((u64)data[5] << 40) |
+			   ((u64)data[6] << 48) | ((u64)data[7] << 56));
+
+	info->vid = data[8] | (data[9] << 8);
+	info->pid = data[10] | (data[11] << 8);
+
+	info->num_led_zones = data[12];
+
+	info->fw_version.major = data[13];
+	info->fw_version.mid = data[14];
+	info->fw_version.minor = data[15];
+	info->fw_version.revision = data[19] | (data[20] << 8);
+
+	info->hw_version.major = data[16];
+	info->hw_version.mid = data[17];
+	info->hw_version.minor = data[18];
+
+	return 0;
+}
+
+static void zotac_log_device_info(struct zotac_device *zotac)
+{
+	struct zotac_device_info info;
+	int ret;
+
+	ret = zotac_get_device_info(zotac, &info);
+	if (ret < 0) {
+		dev_err(&zotac->hdev->dev, "Failed to get device info: %d\n",
+			ret);
+		return;
+	}
+
+	dev_info(&zotac->hdev->dev,
+		 "Device Info:\n"
+		 "  Device ID: %016llx\n"
+		 "  VID/PID: %04x:%04x\n"
+		 "  LED Zones: %u\n"
+		 "  Firmware: %u.%u.%u (revision %u)\n"
+		 "  Hardware: %u.%u.%u\n",
+		 info.device_id, info.vid, info.pid, info.num_led_zones,
+		 info.fw_version.major, info.fw_version.mid,
+		 info.fw_version.minor, info.fw_version.revision,
+		 info.hw_version.major, info.hw_version.mid,
+		 info.hw_version.minor);
+}
+
+static const struct button_mapping_entry *find_button_by_name(const char *name)
+{
+	int i;
+	for (i = 0; button_map[i].name != NULL; i++) {
+		if (strcmp(button_map[i].name, name) == 0)
+			return &button_map[i];
+	}
+	return NULL;
+}
+
+static bool is_button_in_mapping(u8 *mapping_bytes,
+				 const struct button_mapping_entry *button)
+{
+	return (mapping_bytes[button->byte_index] & button->bit_mask) != 0;
+}
+
+static void add_button_to_mapping(u8 *mapping_bytes,
+				  const struct button_mapping_entry *button)
+{
+	mapping_bytes[button->byte_index] |= button->bit_mask;
+}
+
+static int zotac_get_button_mapping(struct zotac_device *zotac, u8 button_id)
+{
+	if (!zotac || !zotac->cfg_data || button_id > BUTTON_MAX ||
+	    button_id == 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int zotac_set_button_mapping(struct zotac_device *zotac, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac->cfg_data->button_mappings[button_id];
+	u8 data[BTN_MAP_RESPONSE_MIN_SIZE] = { 0 };
+	u8 *gamepad_bytes = (u8 *)&mapping->target_gamepad_buttons;
+
+	/* Source button goes at offset 0 */
+	data[BTN_MAP_SOURCE_IDX] = button_id;
+
+	/* Controller button mappings */
+	data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_DPAD_STICK_IDX] =
+		gamepad_bytes[GAMEPAD_DPAD_STICK_IDX];
+	data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_FACE_BUMPER_IDX] =
+		gamepad_bytes[GAMEPAD_FACE_BUMPER_IDX];
+	data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_TRIGGER_IDX] =
+		gamepad_bytes[GAMEPAD_TRIGGER_IDX];
+	data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_RESERVED_IDX] =
+		gamepad_bytes[GAMEPAD_RESERVED_IDX];
+
+	data[BTN_MAP_MODIFIER_IDX] = mapping->target_modifier_keys;
+
+	memcpy(&data[BTN_MAP_KEYBOARD_START_IDX], mapping->target_keyboard_keys,
+	       BTN_MAP_KEYBOARD_SIZE);
+
+	data[BTN_MAP_MOUSE_IDX] = mapping->target_mouse_buttons;
+
+	return zotac_send_set_command(zotac, CMD_SET_BUTTON_MAPPING, 0, data,
+				      BTN_MAP_RESPONSE_MIN_SIZE);
+}
+
+static void zotac_modifier_value_to_names(u8 modifiers, char *buf,
+					  size_t buf_size)
+{
+	bool first = true;
+	int i, pos = 0;
+
+	if (modifiers == 0) {
+		strscpy(buf, "none", buf_size);
+		return;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(modifier_names); i++) {
+		if (modifier_names[i].value != MOD_NONE &&
+		    (modifiers & modifier_names[i].value)) {
+			if (!first)
+				pos += scnprintf(buf + pos, buf_size - pos,
+						 " ");
+			pos += scnprintf(buf + pos, buf_size - pos, "%s",
+					 modifier_names[i].name);
+			first = false;
+
+			if (pos >= buf_size - 1)
+				break;
+		}
+	}
+}
+
+static ssize_t gamepad_show(struct device *dev, struct device_attribute *attr,
+			    char *buf, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	bool found = false;
+	char *p = buf;
+
+	u8 *mapping_bytes;
+	int i;
+
+	if (zotac_get_button_mapping(&zotac, button_id) < 0)
+		return -EIO;
+
+	mapping_bytes = (u8 *)&mapping->target_gamepad_buttons;
+
+	for (i = 0; button_map[i].name != NULL; i++) {
+		if (is_button_in_mapping(mapping_bytes, &button_map[i])) {
+			p += sprintf(p, "%s ", button_map[i].name);
+			found = true;
+		}
+	}
+
+	if (found) {
+		*(p - 1) = '\n';
+	} else {
+		p += sprintf(p, "none\n");
+	}
+
+	return p - buf;
+}
+
+static ssize_t gamepad_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	u8 *mapping_bytes = (u8 *)&mapping->target_gamepad_buttons;
+	char *buffer, *token, *cursor;
+	bool any_valid = false;
+
+	/* Make a copy of the input buffer for tokenization */
+	buffer = kstrndup(buf, count, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	memset(mapping_bytes, 0, 4);
+
+	cursor = buffer;
+	while ((token = strsep(&cursor, " \t\n")) != NULL) {
+		if (*token == '\0')
+			continue;
+
+		if (strcmp(token, "none") == 0) {
+			any_valid = true;
+			break;
+		}
+
+		const struct button_mapping_entry *button =
+			find_button_by_name(token);
+		if (button) {
+			add_button_to_mapping(mapping_bytes, button);
+			any_valid = true;
+		}
+	}
+
+	kfree(buffer);
+
+	if (!any_valid)
+		return -EINVAL;
+
+	return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count;
+}
+
+static ssize_t modifier_show(struct device *dev, struct device_attribute *attr,
+			     char *buf, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	u8 modifiers;
+
+	modifiers = mapping->target_modifier_keys;
+	/* Leave room for newline and null */
+	zotac_modifier_value_to_names(modifiers, buf, PAGE_SIZE - 2);
+	strcat(buf, "\n");
+	return strlen(buf);
+}
+
+static ssize_t modifier_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	char *buffer, *token, *cursor;
+	u8 new_modifiers = 0;
+	bool any_valid = false;
+
+	buffer = kstrndup(buf, count, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	cursor = buffer;
+	while ((token = strsep(&cursor, " \t\n")) != NULL) {
+		if (*token == '\0')
+			continue;
+
+		if (strcmp(token, "none") == 0) {
+			any_valid = true;
+			new_modifiers = 0;
+			break;
+		}
+
+		int i;
+		for (i = 0; i < ARRAY_SIZE(modifier_names); i++) {
+			if (strcmp(token, modifier_names[i].name) == 0) {
+				new_modifiers |= modifier_names[i].value;
+				any_valid = true;
+				break;
+			}
+		}
+	}
+
+	kfree(buffer);
+
+	if (!any_valid)
+		return -EINVAL;
+
+	mapping->target_modifier_keys = new_modifiers;
+
+	return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count;
+}
+
+static u8 find_key_code_by_name(const char *name)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(keyboard_keys); i++) {
+		if (strcmp(keyboard_keys[i].name, name) == 0)
+			return keyboard_keys[i].keycode;
+	}
+	return 0; /* Return "none" if not found */
+}
+
+static const char *find_key_name_by_code(u8 code)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(keyboard_keys); i++) {
+		if (keyboard_keys[i].keycode == code)
+			return keyboard_keys[i].name;
+	}
+	return "none";
+}
+
+static ssize_t keyboard_keys_show(struct device *dev,
+				  struct device_attribute *attr, char *buf,
+				  u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	bool any_key = false;
+	char *p = buf;
+	int i;
+
+	if (zotac_get_button_mapping(&zotac, button_id) < 0)
+		return -EIO;
+
+	/* Check each key code in the mapping */
+	for (i = 0; i < BTN_MAP_KEYBOARD_SIZE; i++) {
+		u8 keycode = mapping->target_keyboard_keys[i];
+		if (keycode != 0) {
+			p += sprintf(p, "%s ", find_key_name_by_code(keycode));
+			any_key = true;
+		}
+	}
+
+	if (any_key) {
+		*(p - 1) = '\n';
+	} else {
+		p += sprintf(p, "none\n");
+	}
+
+	return p - buf;
+}
+
+static ssize_t keyboard_keys_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	u8 new_keys[BTN_MAP_KEYBOARD_SIZE] = { 0 };
+	bool any_valid = false;
+	int key_count = 0;
+
+	char *buffer, *token, *cursor;
+
+	buffer = kstrndup(buf, count, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	cursor = buffer;
+	while ((token = strsep(&cursor, " \t\n")) != NULL) {
+		if (*token == '\0')
+			continue;
+
+		if (strcmp(token, "none") == 0) {
+			any_valid = true;
+			key_count = 0; /* Clear all keys */
+			break;
+		}
+
+		if (key_count < BTN_MAP_KEYBOARD_SIZE) {
+			u8 keycode = find_key_code_by_name(token);
+			if (keycode != 0) {
+				new_keys[key_count++] = keycode;
+				any_valid = true;
+			}
+		}
+	}
+
+	kfree(buffer);
+
+	if (!any_valid)
+		return -EINVAL;
+
+	memcpy(mapping->target_keyboard_keys, new_keys, BTN_MAP_KEYBOARD_SIZE);
+
+	return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count;
+}
+
+static ssize_t keyboard_list_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	char *p = buf;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(keyboard_keys); i++) {
+		if (keyboard_keys[i].keycode != 0x00) {
+			int len = sprintf(p, "%s ", keyboard_keys[i].name);
+			p += len;
+
+			if (p - buf > PAGE_SIZE - 32) {
+				/* Add an indication that the list was truncated */
+				p += sprintf(p, "...");
+				break;
+			}
+		}
+	}
+
+	if (p > buf)
+		*(p - 1) = '\n';
+	else
+		*p++ = '\n';
+
+	return p - buf;
+}
+static DEVICE_ATTR_RO_NAMED(keyboard_list, "keyboard_list");
+
+static ssize_t mouse_buttons_show(struct device *dev,
+				  struct device_attribute *attr, char *buf,
+				  u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	u8 mouse_buttons = mapping->target_mouse_buttons;
+	bool found = false;
+	char *p = buf;
+	int i;
+
+	if (zotac_get_button_mapping(&zotac, button_id) < 0)
+		return -EIO;
+
+	for (i = 0; i < ARRAY_SIZE(mouse_button_names); i++) {
+		if (mouse_buttons & mouse_button_names[i].value) {
+			p += sprintf(p, "%s ", mouse_button_names[i].name);
+			found = true;
+		}
+	}
+
+	if (found) {
+		*(p - 1) = '\n';
+	} else {
+		p += sprintf(p, "none\n");
+	}
+
+	return p - buf;
+}
+
+static ssize_t mouse_buttons_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count, u8 button_id)
+{
+	struct button_mapping *mapping =
+		&zotac.cfg_data->button_mappings[button_id];
+	char *buffer, *token, *cursor;
+	u8 new_mouse_buttons = 0;
+	bool any_valid = false;
+
+	buffer = kstrndup(buf, count, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	cursor = buffer;
+	while ((token = strsep(&cursor, " \t\n")) != NULL) {
+		if (*token == '\0')
+			continue;
+
+		if (strcmp(token, "none") == 0) {
+			any_valid = true;
+			new_mouse_buttons = 0;
+			break;
+		}
+
+		/* Find the button value */
+		int i;
+		for (i = 0; i < ARRAY_SIZE(mouse_button_names); i++) {
+			if (strcmp(token, mouse_button_names[i].name) == 0) {
+				new_mouse_buttons |=
+					mouse_button_names[i].value;
+				any_valid = true;
+				break;
+			}
+		}
+	}
+
+	kfree(buffer);
+
+	if (!any_valid)
+		return -EINVAL;
+
+	mapping->target_mouse_buttons = new_mouse_buttons;
+
+	return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count;
+}
+
+static ssize_t mouse_list_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	char *p = buf;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(mouse_button_names); i++) {
+		p += sprintf(p, "%s ", mouse_button_names[i].name);
+	}
+
+	if (p > buf)
+		*(p - 1) = '\n';
+	else
+		*p++ = '\n';
+
+	return p - buf;
+}
+static DEVICE_ATTR_RO_NAMED(mouse_list, "mouse_list");
+
+static ssize_t modifier_list_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	char *p = buf;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(modifier_names); i++) {
+		p += sprintf(p, "%s ", modifier_names[i].name);
+	}
+
+	if (p > buf)
+		*(p - 1) = '\n';
+	else
+		*p++ = '\n';
+
+	return p - buf;
+}
+static DEVICE_ATTR_RO_NAMED(modifier_list, "modifier_list");
+
+/* List attributes for showing available options */
+static ssize_t gamepad_list_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	char *p = buf;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(gamepad_button_names); i++) {
+		if (gamepad_button_names[i].id != BUTTON_NONE)
+			p += sprintf(p, "%s ", gamepad_button_names[i].name);
+	}
+
+	if (p > buf)
+		*(p - 1) = '\n';
+	else
+		*p++ = '\n';
+
+	return p - buf;
+}
+
+static ssize_t gamepad_max_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", MAX_GAMEPAD_BUTTONS);
+}
+
+static ssize_t keyboard_max_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", MAX_KEYBOARD_KEYS);
+}
+
+static ssize_t mouse_max_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	return sprintf(buf, "%d\n", MAX_MOUSE_BUTTONS);
+}
+
+static DEVICE_ATTR_RO_NAMED(gamepad_list, "gamepad_list");
+static DEVICE_ATTR_RO_NAMED(gamepad_max, "gamepad_max");
+static DEVICE_ATTR_RO_NAMED(keyboard_max, "keyboard_max");
+static DEVICE_ATTR_RO_NAMED(mouse_max, "mouse_max");
+
+#define DEFINE_BUTTON_REMAP_ATTRS(btn_name, btn_id)                            \
+	static ssize_t btn_name##_gamepad_show(                                \
+		struct device *dev, struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		return gamepad_show(dev, attr, buf, btn_id);                   \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_gamepad_store(struct device *dev,            \
+						struct device_attribute *attr, \
+						const char *buf, size_t count) \
+	{                                                                      \
+		return gamepad_store(dev, attr, buf, count, btn_id);           \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_modifier_show(                               \
+		struct device *dev, struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		return modifier_show(dev, attr, buf, btn_id);                  \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_modifier_store(                              \
+		struct device *dev, struct device_attribute *attr,             \
+		const char *buf, size_t count)                                 \
+	{                                                                      \
+		return modifier_store(dev, attr, buf, count, btn_id);          \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_keyboard_keys_show(                          \
+		struct device *dev, struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		return keyboard_keys_show(dev, attr, buf, btn_id);             \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_keyboard_keys_store(                         \
+		struct device *dev, struct device_attribute *attr,             \
+		const char *buf, size_t count)                                 \
+	{                                                                      \
+		return keyboard_keys_store(dev, attr, buf, count, btn_id);     \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_mouse_buttons_show(                          \
+		struct device *dev, struct device_attribute *attr, char *buf)  \
+	{                                                                      \
+		return mouse_buttons_show(dev, attr, buf, btn_id);             \
+	}                                                                      \
+                                                                               \
+	static ssize_t btn_name##_mouse_buttons_store(                         \
+		struct device *dev, struct device_attribute *attr,             \
+		const char *buf, size_t count)                                 \
+	{                                                                      \
+		return mouse_buttons_store(dev, attr, buf, count, btn_id);     \
+	}                                                                      \
+                                                                               \
+	static DEVICE_ATTR_RW_NAMED(btn_name##_gamepad, "gamepad");            \
+	static DEVICE_ATTR_RW_NAMED(btn_name##_modifier, "modifier");          \
+	static DEVICE_ATTR_RW_NAMED(btn_name##_keyboard_keys, "keyboard");     \
+	static DEVICE_ATTR_RW_NAMED(btn_name##_mouse_buttons, "mouse");
+
+/* Create all button attribute groups */
+DEFINE_BUTTON_REMAP_ATTRS(btn_a, BUTTON_A);
+DEFINE_BUTTON_REMAP_ATTRS(btn_b, BUTTON_B);
+DEFINE_BUTTON_REMAP_ATTRS(btn_x, BUTTON_X);
+DEFINE_BUTTON_REMAP_ATTRS(btn_y, BUTTON_Y);
+DEFINE_BUTTON_REMAP_ATTRS(btn_lb, BUTTON_LB);
+DEFINE_BUTTON_REMAP_ATTRS(btn_rb, BUTTON_RB);
+DEFINE_BUTTON_REMAP_ATTRS(btn_lt, BUTTON_LT);
+DEFINE_BUTTON_REMAP_ATTRS(btn_rt, BUTTON_RT);
+DEFINE_BUTTON_REMAP_ATTRS(btn_ls, BUTTON_LS);
+DEFINE_BUTTON_REMAP_ATTRS(btn_rs, BUTTON_RS);
+DEFINE_BUTTON_REMAP_ATTRS(dpad_up, BUTTON_DPAD_UP);
+DEFINE_BUTTON_REMAP_ATTRS(dpad_down, BUTTON_DPAD_DOWN);
+DEFINE_BUTTON_REMAP_ATTRS(dpad_left, BUTTON_DPAD_LEFT);
+DEFINE_BUTTON_REMAP_ATTRS(dpad_right, BUTTON_DPAD_RIGHT);
+DEFINE_BUTTON_REMAP_ATTRS(btn_m1, BUTTON_M1);
+DEFINE_BUTTON_REMAP_ATTRS(btn_m2, BUTTON_M2);
+
+static int zotac_get_button_turbo(struct zotac_device *zotac)
+{
+	size_t data_len = 1;
+	u8 turbo_byte;
+	int ret;
+
+	ret = zotac_send_get_command(zotac, CMD_GET_BUTTON_TURBO, 0, NULL, 0,
+				     &turbo_byte, &data_len);
+	if (ret < 0)
+		return ret;
+
+	if (data_len < 1)
+		return -EIO;
+
+	zotac->cfg_data->button_turbo = turbo_byte;
+	return 0;
+}
+
+static int zotac_set_button_turbo(struct zotac_device *zotac, u8 turbo_byte)
+{
+	int ret;
+
+	ret = zotac_send_set_command(zotac, CMD_SET_BUTTON_TURBO, 0,
+				     &turbo_byte, 1);
+	if (ret < 0)
+		return ret;
+
+	zotac->cfg_data->button_turbo = turbo_byte;
+	return 0;
+}
+
+static ssize_t button_turbo_show(struct device *dev,
+				 struct device_attribute *attr, char *buf,
+				 int btn_pos)
+{
+	u8 turbo_val;
+	int ret;
+
+	ret = zotac_get_button_turbo(&zotac);
+	if (ret < 0)
+		return ret;
+
+	turbo_val = (zotac.cfg_data->button_turbo >> btn_pos) & 0x01;
+	return sprintf(buf, "%d\n", turbo_val);
+}
+
+static ssize_t button_turbo_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count, int btn_pos)
+{
+	u8 turbo_byte;
+	int val, ret;
+
+	ret = kstrtoint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val != 0 && val != 1)
+		return -EINVAL;
+
+	ret = zotac_get_button_turbo(&zotac);
+	if (ret < 0)
+		return ret;
+
+	turbo_byte = zotac.cfg_data->button_turbo;
+
+	if (val)
+		turbo_byte |= (1 << btn_pos);
+	else
+		turbo_byte &= ~(1 << btn_pos);
+
+	ret = zotac_set_button_turbo(&zotac, turbo_byte);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+#define DEFINE_BUTTON_TURBO_ATTRS(btn_name, btn_pos)                          \
+	static ssize_t btn_##btn_name##_turbo_show(                           \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return button_turbo_show(dev, attr, buf, btn_pos);            \
+	}                                                                     \
+                                                                              \
+	static ssize_t btn_##btn_name##_turbo_store(                          \
+		struct device *dev, struct device_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		return button_turbo_store(dev, attr, buf, count, btn_pos);    \
+	}                                                                     \
+	static DEVICE_ATTR_RW_NAMED(btn_##btn_name##_turbo, "turbo");
+
+DEFINE_BUTTON_TURBO_ATTRS(a, A_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(b, B_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(x, X_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(y, Y_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(lb, LB_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(rb, RB_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(lt, LT_BTN_POS);
+DEFINE_BUTTON_TURBO_ATTRS(rt, RT_BTN_POS);
+
+/* Create attribute groups for buttons with/without turbo */
+#define DEFINE_BUTTON_TURBO_GROUP(btn_name, btn_id)                   \
+	static struct attribute *btn_name##_main_attrs[] = {          \
+		&dev_attr_##btn_name##_turbo.attr, NULL               \
+	};                                                            \
+	static const struct attribute_group btn_name##_main_group = { \
+		.attrs = btn_name##_main_attrs,                       \
+	};
+
+#define DEFINE_BUTTON_NO_TURBO_GROUP(btn_name, btn_id)                \
+	static struct attribute *btn_name##_main_attrs[] = { NULL };  \
+	static const struct attribute_group btn_name##_main_group = { \
+		.attrs = btn_name##_main_attrs,                       \
+	};
+
+/* Define remap subgroups */
+#define DEFINE_BUTTON_REMAP_GROUP(btn_name, btn_id)                    \
+	static struct attribute *btn_name##_remap_attrs[] = {          \
+		&dev_attr_##btn_name##_gamepad.attr,                   \
+		&dev_attr_##btn_name##_modifier.attr,                  \
+		&dev_attr_##btn_name##_keyboard_keys.attr,             \
+		&dev_attr_##btn_name##_mouse_buttons.attr,             \
+		&dev_attr_gamepad_list.attr,                           \
+		&dev_attr_gamepad_max.attr,                            \
+		&dev_attr_modifier_list.attr,                          \
+		&dev_attr_keyboard_list.attr,                          \
+		&dev_attr_keyboard_max.attr,                           \
+		&dev_attr_mouse_list.attr,                             \
+		&dev_attr_mouse_max.attr,                              \
+		NULL                                                   \
+	};                                                             \
+	static const struct attribute_group btn_name##_remap_group = { \
+		.name = "remap",                                       \
+		.attrs = btn_name##_remap_attrs,                       \
+	};
+
+/* Define button directory */
+#define DEFINE_BUTTON_GROUP_WITH_TURBO(btn_name, btn_id) \
+	DEFINE_BUTTON_TURBO_GROUP(btn_name, btn_id)      \
+	DEFINE_BUTTON_REMAP_GROUP(btn_name, btn_id)
+
+#define DEFINE_BUTTON_DIR_WITH_TURBO(btn_name, btn_id)                     \
+	{                                                                  \
+		.name = #btn_name,                                         \
+		.button_id = btn_id,                                       \
+		.has_turbo = true,                                         \
+		.kobj = NULL,                                              \
+		.main_group =                                              \
+			(struct attribute_group *)&btn_name##_main_group,  \
+		.remap_group =                                             \
+			(struct attribute_group *)&btn_name##_remap_group, \
+	}
+
+#define DEFINE_BUTTON_GROUP_NO_TURBO(btn_name, btn_id) \
+	DEFINE_BUTTON_NO_TURBO_GROUP(btn_name, btn_id) \
+	DEFINE_BUTTON_REMAP_GROUP(btn_name, btn_id)
+
+#define DEFINE_BUTTON_DIR_NO_TURBO(btn_name, btn_id)                       \
+	{                                                                  \
+		.name = #btn_name,                                         \
+		.button_id = btn_id,                                       \
+		.has_turbo = false,                                        \
+		.kobj = NULL,                                              \
+		.main_group =                                              \
+			(struct attribute_group *)&btn_name##_main_group,  \
+		.remap_group =                                             \
+			(struct attribute_group *)&btn_name##_remap_group, \
+	}
+
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_a, BUTTON_A);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_b, BUTTON_B);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_x, BUTTON_X);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_y, BUTTON_Y);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_lb, BUTTON_LB);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_rb, BUTTON_RB);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_lt, BUTTON_LT);
+DEFINE_BUTTON_GROUP_WITH_TURBO(btn_rt, BUTTON_RT);
+DEFINE_BUTTON_GROUP_NO_TURBO(btn_ls, BUTTON_LS);
+DEFINE_BUTTON_GROUP_NO_TURBO(btn_rs, BUTTON_RS);
+DEFINE_BUTTON_GROUP_NO_TURBO(dpad_up, BUTTON_DPAD_UP);
+DEFINE_BUTTON_GROUP_NO_TURBO(dpad_down, BUTTON_DPAD_DOWN);
+DEFINE_BUTTON_GROUP_NO_TURBO(dpad_left, BUTTON_DPAD_LEFT);
+DEFINE_BUTTON_GROUP_NO_TURBO(dpad_right, BUTTON_DPAD_RIGHT);
+DEFINE_BUTTON_GROUP_NO_TURBO(btn_m1, BUTTON_M1);
+DEFINE_BUTTON_GROUP_NO_TURBO(btn_m2, BUTTON_M2);
+
+/* Define all button directories */
+static struct button_directory button_dirs[] = {
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_a, BUTTON_A),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_b, BUTTON_B),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_x, BUTTON_X),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_y, BUTTON_Y),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_lb, BUTTON_LB),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_rb, BUTTON_RB),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_lt, BUTTON_LT),
+	DEFINE_BUTTON_DIR_WITH_TURBO(btn_rt, BUTTON_RT),
+	DEFINE_BUTTON_DIR_NO_TURBO(btn_ls, BUTTON_LS),
+	DEFINE_BUTTON_DIR_NO_TURBO(btn_rs, BUTTON_RS),
+	DEFINE_BUTTON_DIR_NO_TURBO(dpad_up, BUTTON_DPAD_UP),
+	DEFINE_BUTTON_DIR_NO_TURBO(dpad_down, BUTTON_DPAD_DOWN),
+	DEFINE_BUTTON_DIR_NO_TURBO(dpad_left, BUTTON_DPAD_LEFT),
+	DEFINE_BUTTON_DIR_NO_TURBO(dpad_right, BUTTON_DPAD_RIGHT),
+	DEFINE_BUTTON_DIR_NO_TURBO(btn_m1, BUTTON_M1),
+	DEFINE_BUTTON_DIR_NO_TURBO(btn_m2, BUTTON_M2),
+	{ NULL, 0, false, NULL, NULL, NULL } /* Terminator */
+};
+
+static int zotac_get_stick_sensitivity(struct zotac_device *zotac,
+				       int stick_num, u8 *output_values)
+{
+	u8 request_data = stick_num;
+	u8 temp_values[STICK_SENSITIVITY_SIZE];
+	size_t output_len = STICK_SENSITIVITY_SIZE;
+	int i, ret;
+
+	ret = zotac_send_get_command(zotac, CMD_GET_STICK_SENSITIVITY, 0,
+				     &request_data, 1, temp_values,
+				     &output_len);
+
+	if (ret == 0 && output_len == STICK_SENSITIVITY_SIZE) {
+		/* Scale from percentage (0-100) to device values (0-255) */
+		for (i = 0; i < STICK_SENSITIVITY_SIZE; i++) {
+			output_values[i] = temp_values[i] * 255 / 100;
+		}
+	}
+
+	return ret;
+}
+
+static struct stick_sensitivity *
+get_sensitivity_for_stick(struct zotac_device *zotac, int stick_num)
+{
+	if (stick_num == STICK_LEFT)
+		return &zotac->cfg_data->left_stick_sensitivity;
+	else
+		return &zotac->cfg_data->right_stick_sensitivity;
+}
+
+static ssize_t curve_response_show(struct device *dev,
+				   struct device_attribute *attr, char *buf,
+				   int stick_num, int point_index)
+{
+	struct stick_sensitivity *sensitivity =
+		get_sensitivity_for_stick(&zotac, stick_num);
+	int base_idx = point_index * 2;
+
+	// Dev input is 0-255, but it outputs 0-100. So we store as 0-255
+	int x_pct = sensitivity->values[base_idx] * 100 / 255;
+	int y_pct = sensitivity->values[base_idx + 1] * 100 / 255;
+
+	return sprintf(buf, "%d %d\n", x_pct, y_pct);
+}
+
+static ssize_t curve_response_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count,
+				    int stick_num, int point_index)
+{
+	struct stick_sensitivity *sensitivity =
+		get_sensitivity_for_stick(&zotac, stick_num);
+	u8 data[STICK_SENSITIVITY_SIZE + 1]; // +1 for stick ID
+	int base_idx = point_index * 2;
+
+	int x_pct, y_pct;
+	int ret;
+
+	ret = sscanf(buf, "%d %d", &x_pct, &y_pct);
+	if (ret != 2)
+		return -EINVAL;
+
+	x_pct = clamp_val(x_pct, 0, 100);
+	y_pct = clamp_val(y_pct, 0, 100);
+
+	sensitivity->values[base_idx] = x_pct * 255 / 100;
+	sensitivity->values[base_idx + 1] = y_pct * 255 / 100;
+
+	data[STICK_SENSITIVITY_NUM_IDX] = stick_num;
+
+	memcpy(&data[STICK_SENSITIVITY_DATA_IDX], sensitivity->values,
+	       STICK_SENSITIVITY_SIZE);
+
+	ret = zotac_send_set_command(&zotac, CMD_SET_STICK_SENSITIVITY, 0, data,
+				     sizeof(data));
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+#define DEFINE_CURVE_RESPONSE_ATTRS(stick_name, stick_num, point_num)           \
+	static ssize_t stick_##stick_name##_curve_response_##point_num##_show(  \
+		struct device *dev, struct device_attribute *attr, char *buf)   \
+	{                                                                       \
+		return curve_response_show(dev, attr, buf, stick_num,           \
+					   point_num - 1);                      \
+	}                                                                       \
+                                                                                \
+	static ssize_t stick_##stick_name##_curve_response_##point_num##_store( \
+		struct device *dev, struct device_attribute *attr,              \
+		const char *buf, size_t count)                                  \
+	{                                                                       \
+		return curve_response_store(dev, attr, buf, count, stick_num,   \
+					    point_num - 1);                     \
+	}                                                                       \
+	static DEVICE_ATTR_RW_NAMED(                                            \
+		stick_##stick_name##_curve_response_##point_num,                \
+		"curve_response_pct_" #point_num);
+
+DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 1)
+DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 2)
+DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 3)
+DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 4)
+
+DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 1)
+DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 2)
+DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 3)
+DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 4)
+
+static ssize_t axis_xyz_deadzone_index_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buf)
+{
+	return sprintf(buf, "inner outer\n");
+}
+static DEVICE_ATTR_RO_NAMED(axis_xyz_deadzone_index, "deadzone_index");
+
+static ssize_t axis_xyz_deadzone_show(struct device *dev,
+				      struct device_attribute *attr, char *buf,
+				      struct deadzone *dz)
+{
+	return sprintf(buf, "%d %d\n", dz->inner, dz->outer);
+}
+
+static int zotac_apply_deadzones(struct zotac_device *zotac,
+				 struct deadzone *left_dz,
+				 struct deadzone *right_dz, u8 cmd_code)
+{
+	u8 data[DZ_RESPONSE_SIZE];
+	int ret;
+
+	data[DZ_LEFT_INNER_IDX] = left_dz->inner;
+	data[DZ_LEFT_OUTER_IDX] = left_dz->outer;
+	data[DZ_RIGHT_INNER_IDX] = right_dz->inner;
+	data[DZ_RIGHT_OUTER_IDX] = right_dz->outer;
+
+	ret = zotac_send_set_command(zotac, cmd_code, 0, data, sizeof(data));
+
+	return ret;
+}
+
+static ssize_t axis_xyz_deadzone_store(struct device *dev,
+				       struct device_attribute *attr,
+				       const char *buf, size_t count,
+				       struct deadzone *dz)
+{
+	struct zotac_cfg_data *cfg = zotac.cfg_data;
+	int inner, outer;
+	u8 cmd_code;
+	int ret;
+
+	ret = sscanf(buf, "%d %d", &inner, &outer);
+	if (ret != 2)
+		return -EINVAL;
+
+	if (inner < 0 || inner > 100 || outer < 0 || outer > 100)
+		return -EINVAL;
+
+	dz->inner = inner;
+	dz->outer = outer;
+
+	/* Determine which command to use based on which deadzone is being modified */
+	if (dz == &cfg->ls_dz || dz == &cfg->rs_dz) {
+		cmd_code = CMD_SET_STICK_DEADZONES;
+		ret = zotac_apply_deadzones(&zotac, &cfg->ls_dz, &cfg->rs_dz,
+					    cmd_code);
+	} else {
+		cmd_code = CMD_SET_TRIGGER_DEADZONES;
+		ret = zotac_apply_deadzones(&zotac, &cfg->lt_dz, &cfg->rt_dz,
+					    cmd_code);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+
+#define DEFINE_DEADZONE_HANDLERS(axis_name, dz_field)                         \
+	static ssize_t axis_##axis_name##_deadzone_show(                      \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return axis_xyz_deadzone_show(dev, attr, buf,                 \
+					      &zotac.cfg_data->dz_field);    \
+	}                                                                     \
+                                                                              \
+	static ssize_t axis_##axis_name##_deadzone_store(                     \
+		struct device *dev, struct device_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		return axis_xyz_deadzone_store(dev, attr, buf, count,         \
+					       &zotac.cfg_data->dz_field);   \
+	}                                                                     \
+	static DEVICE_ATTR_RW_NAMED(axis_##axis_name##_deadzone, "deadzone");
+
+#define DEFINE_XY_AXIS_ATTR_GROUP(axis_name)                                  \
+	static struct attribute *axis_##axis_name##_attrs[] = {               \
+		&dev_attr_axis_##axis_name##_deadzone.attr,                   \
+		&dev_attr_stick_##axis_name##_curve_response_1.attr,          \
+		&dev_attr_stick_##axis_name##_curve_response_2.attr,          \
+		&dev_attr_stick_##axis_name##_curve_response_3.attr,          \
+		&dev_attr_stick_##axis_name##_curve_response_4.attr,          \
+		&dev_attr_axis_xyz_deadzone_index.attr,                       \
+		NULL                                                          \
+	};                                                                    \
+                                                                              \
+	static const struct attribute_group axis_##axis_name##_attr_group = { \
+		.name = "axis_" #axis_name,                                   \
+		.attrs = axis_##axis_name##_attrs,                            \
+	};
+
+#define DEFINE_Z_AXIS_ATTR_GROUP(axis_name)                                   \
+	static struct attribute *axis_##axis_name##_attrs[] = {               \
+		&dev_attr_axis_##axis_name##_deadzone.attr,                   \
+		&dev_attr_axis_xyz_deadzone_index.attr, NULL                  \
+	};                                                                    \
+                                                                              \
+	static const struct attribute_group axis_##axis_name##_attr_group = { \
+		.name = "axis_" #axis_name,                                   \
+		.attrs = axis_##axis_name##_attrs,                            \
+	};
+
+DEFINE_DEADZONE_HANDLERS(xy_left, ls_dz);
+DEFINE_DEADZONE_HANDLERS(xy_right, rs_dz);
+DEFINE_DEADZONE_HANDLERS(z_left, lt_dz);
+DEFINE_DEADZONE_HANDLERS(z_right, rt_dz);
+
+DEFINE_XY_AXIS_ATTR_GROUP(xy_left);
+DEFINE_XY_AXIS_ATTR_GROUP(xy_right);
+DEFINE_Z_AXIS_ATTR_GROUP(z_left);
+DEFINE_Z_AXIS_ATTR_GROUP(z_right);
+
+static ssize_t vibration_intensity_index_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	return sprintf(buf,
+		       "trigger_left trigger_right rumble_left rumble_right\n");
+}
+static DEVICE_ATTR_RO_NAMED(vibration_intensity_index,
+			    "vibration_intensity_index");
+
+static ssize_t vibration_intensity_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	u8 data[VIB_RESPONSE_SIZE];
+	size_t data_len = sizeof(data);
+	int ret;
+
+	ret = zotac_send_get_command(&zotac, CMD_GET_VIBRATION_STRENGTH, 0, NULL,
+				     0, data, &data_len);
+	if (ret < 0)
+		return ret;
+
+	if (data_len < VIB_RESPONSE_SIZE) {
+		dev_err(&zotac.hdev->dev,
+			"Incomplete vibration data received: %zu bytes\n",
+			data_len);
+		return -EIO;
+	}
+
+	return sprintf(buf, "%d %d %d %d\n", data[VIB_LEFT_TRIGGER_IDX],
+		       data[VIB_RIGHT_TRIGGER_IDX], data[VIB_LEFT_RUMBLE_IDX],
+		       data[VIB_RIGHT_RUMBLE_IDX]);
+}
+
+static ssize_t vibration_intensity_store(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	u8 data[VIB_RESPONSE_SIZE];
+	int lt, rt, lr, rr;
+	int ret;
+
+	ret = sscanf(buf, "%d %d %d %d", &lt, &rt, &lr, &rr);
+	if (ret != 4)
+		return -EINVAL;
+
+	data[VIB_LEFT_TRIGGER_IDX] = clamp_val(lt, 0, 100);
+	data[VIB_RIGHT_TRIGGER_IDX] = clamp_val(rt, 0, 100);
+	data[VIB_LEFT_RUMBLE_IDX] = clamp_val(lr, 0, 100);
+	data[VIB_RIGHT_RUMBLE_IDX] = clamp_val(rr, 0, 100);
+
+	ret = zotac_send_set_command(&zotac, CMD_SET_VIBRATION_STRENGTH, 0, data,
+				     sizeof(data));
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW(vibration_intensity);
+
+static ssize_t mouse_speed_max_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%d\n", MOUSE_SPEED_MAX);
+}
+static DEVICE_ATTR_RO_NAMED(mouse_speed_max, "mouse_speed_max");
+
+static ssize_t mouse_speed_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	int speed;
+
+	speed = zotac_send_get_byte(&zotac, CMD_GET_MOUSE_SPEED, 0, NULL, 0);
+	if (speed < 0)
+		return speed;
+
+	return sprintf(buf, "%d\n", speed);
+}
+
+static ssize_t mouse_speed_store(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	int speed_val;
+	u8 speed;
+	int ret;
+
+	ret = kstrtoint(buf, 10, &speed_val);
+	if (ret)
+		return ret;
+
+	if (speed_val < MOUSE_SPEED_MIN || speed_val > MOUSE_SPEED_MAX)
+		return -EINVAL;
+
+	speed = (u8)speed_val;
+	ret = zotac_send_set_command(&zotac, CMD_SET_MOUSE_SPEED, 0, &speed, 1);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW(mouse_speed);
+
+static ssize_t motor_test_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	u8 data[MOTOR_TEST_SIZE] = { 0 };
+
+	int left_trigger, right_trigger, left_rumble, right_rumble;
+	int ret;
+
+	ret = sscanf(buf, "%d %d %d %d", &left_trigger, &right_trigger,
+		     &left_rumble, &right_rumble);
+	if (ret != 4)
+		return -EINVAL;
+
+	left_trigger = clamp_val(left_trigger, 0, 100);
+	right_trigger = clamp_val(right_trigger, 0, 100);
+	left_rumble = clamp_val(left_rumble, 0, 100);
+	right_rumble = clamp_val(right_rumble, 0, 100);
+
+	data[0] = (u8)left_trigger; /* Left Trigger Motor */
+	data[1] = (u8)right_trigger; /* Right Trigger Motor */
+	data[2] = (u8)left_rumble; /* Left Rumble Motor */
+	data[3] = (u8)right_rumble; /* Right Rumble Motor */
+
+	ret = zotac_send_set_command(&zotac, CMD_MOTOR_TEST, 0, data,
+				     MOTOR_TEST_SIZE);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO(motor_test);
+
+static ssize_t motor_test_index_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf,
+		       "left_trigger right_trigger left_rumble right_rumble\n");
+}
+static DEVICE_ATTR_RO_NAMED(motor_test_index, "motor_test_index");
+
+static ssize_t profile_show(struct device *dev, struct device_attribute *attr,
+			    char *buf)
+{
+	int profile;
+
+	profile = zotac_send_get_byte(&zotac, CMD_GET_PROFILE, 0, NULL, 0);
+	if (profile < 0)
+		return profile;
+
+	return sprintf(buf, "%d\n", profile);
+}
+
+static ssize_t profile_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	int profile_id, ret;
+	u8 profile;
+
+	ret = kstrtoint(buf, 10, &profile_id);
+	if (ret)
+		return ret;
+
+	if (profile_id > PROFILE_SECONDARY)
+		return -EINVAL;
+
+	profile = (u8)profile_id;
+	ret = zotac_send_set_command(&zotac, CMD_SET_PROFILE, 0, &profile, 1);
+	if (ret)
+		return ret;
+
+	ret = zotac_cfg_refresh(&zotac);
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW_NAMED(profile, "current");
+
+static ssize_t profile_count_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+
+	return sprintf(buf, "%d\n",
+		       zotac_send_get_byte(&zotac, CMD_GET_PROFILE_NUM, 0, NULL,
+					   0));
+}
+static DEVICE_ATTR_RO_NAMED(profile_count, "count");
+
+/* The device resets and reconnects */
+static ssize_t restore_profile_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	int val, ret;
+
+	ret = kstrtoint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val != 1)
+		return -EINVAL;
+
+	dev_warn(dev, "Restoring profile, the device will reset and reconnect");
+	ret = zotac_send_set_command(&zotac, CMD_RESTORE_PROFILE, 0, NULL, 0);
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO_NAMED(restore_profile, "restore");
+
+static struct attribute *zotac_profile_attrs[] = {
+	&dev_attr_profile.attr, &dev_attr_profile_count.attr,
+	&dev_attr_restore_profile.attr, NULL
+};
+
+static const struct attribute_group zotac_profile_attr_group = {
+	.name = "profile",
+	.attrs = zotac_profile_attrs,
+};
+
+static ssize_t save_config_store(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	int val, ret;
+
+	ret = kstrtoint(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	if (val != 1)
+		return -EINVAL;
+
+	ret = zotac_send_set_command(&zotac, CMD_SAVE_CONFIG, 0, NULL, 0);
+	if (ret)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_WO(save_config);
+
+static ssize_t qam_mode_show(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	if (!zotac.gamepad)
+		return -ENODEV;
+
+	return sprintf(buf, "%i\n", zotac.gamepad->qam_mode);
+}
+
+static ssize_t qam_mode_store(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	u8 value;
+	int ret;
+
+	if (!zotac.gamepad)
+		return -ENODEV;
+
+	ret = kstrtou8(buf, 10, &value);
+	if (ret)
+		return ret;
+
+	if (value >= QAM_MODE_LENGTH)
+		return -EINVAL;
+
+	zotac.gamepad->qam_mode = value;
+
+	return count;
+}
+DEVICE_ATTR_RW(qam_mode);
+
+static struct attribute *zotac_root_attrs[] = {
+	&dev_attr_save_config.attr,
+	&dev_attr_qam_mode.attr,
+	&dev_attr_vibration_intensity.attr,
+	&dev_attr_vibration_intensity_index.attr,
+	&dev_attr_mouse_speed.attr,
+	&dev_attr_mouse_speed_max.attr,
+	&dev_attr_motor_test.attr,
+	&dev_attr_motor_test_index.attr,
+	NULL
+};
+
+static const struct attribute_group zotac_root_attr_group = {
+	.attrs = zotac_root_attrs,
+};
+
+static const struct attribute_group *zotac_top_level_attr_groups[] = {
+	&zotac_profile_attr_group,
+	&zotac_root_attr_group,
+	&axis_xy_left_attr_group,
+	&axis_xy_right_attr_group,
+	&axis_z_left_attr_group,
+	&axis_z_right_attr_group,
+	NULL
+};
+
+int zotac_register_sysfs(struct zotac_device *zotac)
+{
+	struct device *dev;
+	int ret, i;
+
+	if (!zotac || !zotac->hdev)
+		return -ENODEV;
+
+	dev = &zotac->hdev->dev;
+
+	ret = sysfs_create_groups(&dev->kobj, zotac_top_level_attr_groups);
+	if (ret) {
+		dev_err(dev, "Failed to create top-level sysfs groups: %d\n",
+			ret);
+		return ret;
+	}
+
+	for (i = 0; button_dirs[i].name != NULL; i++) {
+		struct button_directory *btn_dir = &button_dirs[i];
+
+		/* Create the button directory kobject */
+		btn_dir->kobj =
+			kobject_create_and_add(btn_dir->name, &dev->kobj);
+		if (!btn_dir->kobj) {
+			dev_err(dev, "Failed to create kobject for %s\n",
+				btn_dir->name);
+			ret = -ENOMEM;
+			goto cleanup;
+		}
+
+		/* Add the main attributes to the button directory */
+		ret = sysfs_create_group(btn_dir->kobj, btn_dir->main_group);
+		if (ret) {
+			dev_err(dev, "Failed to create main group for %s: %d\n",
+				btn_dir->name, ret);
+			goto cleanup;
+		}
+
+		/* Add the remap subgroup to the button directory */
+		ret = sysfs_create_group(btn_dir->kobj, btn_dir->remap_group);
+		if (ret) {
+			dev_err(dev,
+				"Failed to create remap group for %s: %d\n",
+				btn_dir->name, ret);
+			goto cleanup;
+		}
+	}
+
+	return 0;
+
+cleanup:
+	/* Clean up on error */
+	for (i = 0; button_dirs[i].name != NULL; i++) {
+		if (button_dirs[i].kobj) {
+			kobject_put(button_dirs[i].kobj);
+			button_dirs[i].kobj = NULL;
+		}
+	}
+	sysfs_remove_groups(&dev->kobj, zotac_top_level_attr_groups);
+	return ret;
+}
+
+void zotac_unregister_sysfs(struct zotac_device *zotac)
+{
+	int i;
+	struct device *dev;
+
+	if (!zotac || !zotac->hdev) {
+		pr_err("Invalid zotac device in unregister_sysfs\n");
+		return;
+	}
+
+	dev = &zotac->hdev->dev;
+
+	/* Remove button directories and their attributes */
+	for (i = 0; button_dirs[i].name != NULL; i++) {
+		if (button_dirs[i].kobj) {
+			kobject_put(button_dirs[i].kobj);
+			button_dirs[i].kobj = NULL;
+		}
+	}
+
+	sysfs_remove_groups(&dev->kobj, zotac_top_level_attr_groups);
+}
+
+/**
+ * zotac_cfg_refresh - Refresh all configuration data from the device
+ * @zotac: The zotac device to refresh
+ *
+ * This function queries the device for all current configuration and
+ * updates the driver's cached values. It should be called during
+ * initialization and after profile changes or restores.
+ */
+int zotac_cfg_refresh(struct zotac_device *zotac)
+{
+	struct zotac_cfg_data *cfg;
+	u8 data[DZ_RESPONSE_SIZE];
+	size_t data_len = sizeof(data);
+	int ret, i;
+
+	if (!zotac->cfg_data)
+		return -EINVAL;
+
+	cfg = zotac->cfg_data;
+
+	ret = zotac_send_get_command(zotac, CMD_GET_STICK_DEADZONES, 0, NULL, 0,
+				     data, &data_len);
+	if (ret == 0 && data_len >= DZ_RESPONSE_SIZE) {
+		cfg->ls_dz.inner = data[DZ_LEFT_INNER_IDX];
+		cfg->ls_dz.outer = data[DZ_LEFT_OUTER_IDX];
+		cfg->rs_dz.inner = data[DZ_RIGHT_INNER_IDX];
+		cfg->rs_dz.outer = data[DZ_RIGHT_OUTER_IDX];
+	} else {
+		dev_info(
+			&zotac->hdev->dev,
+			"Could not retrieve stick deadzone settings, using defaults\n");
+		cfg->ls_dz.inner = 0;
+		cfg->ls_dz.outer = 100;
+		cfg->rs_dz.inner = 0;
+		cfg->rs_dz.outer = 100;
+	}
+
+	ret = zotac_send_get_command(zotac, CMD_GET_TRIGGER_DEADZONES, 0, NULL,
+				     0, data, &data_len);
+	if (ret == 0 && data_len >= DZ_RESPONSE_SIZE) {
+		cfg->lt_dz.inner = data[DZ_LEFT_INNER_IDX];
+		cfg->lt_dz.outer = data[DZ_LEFT_OUTER_IDX];
+		cfg->rt_dz.inner = data[DZ_RIGHT_INNER_IDX];
+		cfg->rt_dz.outer = data[DZ_RIGHT_OUTER_IDX];
+	} else {
+		dev_info(
+			&zotac->hdev->dev,
+			"Could not retrieve trigger deadzone settings, using defaults\n");
+		cfg->lt_dz.inner = 0;
+		cfg->lt_dz.outer = 100;
+		cfg->rt_dz.inner = 0;
+		cfg->rt_dz.outer = 100;
+	}
+
+	ret = zotac_get_stick_sensitivity(zotac, STICK_LEFT,
+					  cfg->left_stick_sensitivity.values);
+	if (ret < 0) {
+		dev_info(
+			&zotac->hdev->dev,
+			"Could not retrieve left stick sensitivity, using defaults\n");
+		/* Initialize with linear response: 25%, 50%, 75%, 100% */
+		cfg->left_stick_sensitivity.values[0] = 64; /* X1 = 25% */
+		cfg->left_stick_sensitivity.values[1] = 64; /* Y1 = 25% */
+		cfg->left_stick_sensitivity.values[2] = 128; /* X2 = 50% */
+		cfg->left_stick_sensitivity.values[3] = 128; /* Y2 = 50% */
+		cfg->left_stick_sensitivity.values[4] = 192; /* X3 = 75% */
+		cfg->left_stick_sensitivity.values[5] = 192; /* Y3 = 75% */
+		cfg->left_stick_sensitivity.values[6] = 255; /* X4 = 100% */
+		cfg->left_stick_sensitivity.values[7] = 255; /* Y4 = 100% */
+	}
+
+	ret = zotac_get_stick_sensitivity(zotac, STICK_RIGHT,
+					  cfg->right_stick_sensitivity.values);
+	if (ret < 0) {
+		dev_info(
+			&zotac->hdev->dev,
+			"Could not retrieve right stick sensitivity, using defaults\n");
+		/* Copy the left stick values which may be initialized or linear defaults */
+		memcpy(cfg->right_stick_sensitivity.values,
+		       cfg->left_stick_sensitivity.values,
+		       sizeof(cfg->right_stick_sensitivity.values));
+	}
+
+	ret = zotac_get_button_turbo(zotac);
+	if (ret < 0) {
+		dev_info(
+			&zotac->hdev->dev,
+			"Could not retrieve button turbo settings, using defaults\n");
+		cfg->button_turbo = 0; /* Default: no turbo buttons */
+	}
+
+	for (i = 1; i <= BUTTON_MAX; i++) {
+		u8 request_data = i;
+		u8 response_data[BTN_MAP_RESPONSE_MIN_SIZE];
+		size_t response_len = sizeof(response_data);
+
+		/* Set default values first */
+		cfg->button_mappings[i].target_gamepad_buttons = 0;
+		cfg->button_mappings[i].target_modifier_keys = 0;
+		memset(cfg->button_mappings[i].target_keyboard_keys, 0,
+		       MAX_KEYBOARD_KEYS);
+		cfg->button_mappings[i].target_mouse_buttons = 0;
+
+		ret = zotac_send_get_command(zotac, CMD_GET_BUTTON_MAPPING, 0,
+					     &request_data, 1, response_data,
+					     &response_len);
+		if (ret == 0 && response_len >= BTN_MAP_RESPONSE_MIN_SIZE) {
+			memcpy(&cfg->button_mappings[i].target_gamepad_buttons,
+			       &response_data[BTN_MAP_GAMEPAD_START_IDX],
+			       BTN_MAP_GAMEPAD_SIZE);
+
+			cfg->button_mappings[i].target_modifier_keys =
+				response_data[BTN_MAP_MODIFIER_IDX];
+
+			memcpy(cfg->button_mappings[i].target_keyboard_keys,
+			       &response_data[BTN_MAP_KEYBOARD_START_IDX],
+			       BTN_MAP_KEYBOARD_SIZE);
+
+			cfg->button_mappings[i].target_mouse_buttons =
+				response_data[BTN_MAP_MOUSE_IDX];
+		} else {
+			dev_info(
+				&zotac->hdev->dev,
+				"Could not retrieve button %d mapping, using defaults\n",
+				i);
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * zotac_cfg_setup - Allocate and initialize config data structure
+ * @zotac: The zotac device to set up
+ *
+ * This function allocates the config data structure and initializes
+ * the mutex and sequence number. It should be called once during
+ * driver initialization.
+ */
+static int zotac_cfg_setup(struct zotac_device *zotac)
+{
+	struct zotac_cfg_data *cfg;
+
+	if (!zotac)
+		return -EINVAL;
+
+	cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+	if (!cfg)
+		return -ENOMEM;
+
+	mutex_init(&cfg->command_mutex);
+	cfg->sequence_num = 0;
+	zotac->cfg_data = cfg;
+
+	zotac_log_device_info(zotac);
+
+	return 0;
+}
+
+/**
+ * zotac_cfg_init - Initialize the device configuration system
+ * @zotac: The zotac device to initialize
+ *
+ * This function sets up the configuration system and loads the initial
+ * configuration from the device.
+ */
+int zotac_cfg_init(struct zotac_device *zotac)
+{
+	int ret;
+
+	ret = zotac_cfg_setup(zotac);
+	if (ret < 0)
+		return ret;
+
+	ret = zotac_cfg_refresh(zotac);
+	if (ret < 0) {
+		/* If refresh fails, still keep the structure but log an error */
+		dev_err(&zotac->hdev->dev,
+			"Failed to load initial configuration: %d\n", ret);
+	}
+
+	return 0;
+}
+
+void zotac_cfg_cleanup(struct zotac_device *zotac)
+{
+	if (!zotac || !zotac->cfg_data)
+		return;
+
+	kfree(zotac->cfg_data);
+	zotac->cfg_data = NULL;
+}
diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c
new file mode 100644
index 000000000000..6f80ea2f9aad
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c
@@ -0,0 +1,597 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for ZOTAC Gaming Zone Controller - RGB LED control
+ *
+ * Copyright (c) 2025 Luke D. Jones <luke@ljones.dev>
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+
+#include "zotac-zone.h"
+
+#define ZOTAC_VENDOR_ID 0x1ee9
+#define ZOTAC_ALT_VENDOR_ID 0x1e19
+#define ZOTAC_PRODUCT_ID 0x1590
+
+#define ZOTAC_DIAL_INTERFACE 1
+#define ZOTAC_REPORT_INTERFACE 2
+#define ZOTAC_COMMAND_INTERFACE 3
+
+#define ZOTAC_DIAL_REPORT_ID 0x03
+#define ZOTAC_KBD_REPORT_ID 0x02
+#define ZOTAC_MOUSE_REPORT_ID 0x04
+#define ZOTAC_STATUS_REPORT_ID 0x07
+#define ZOTAC_STATUS2_REPORT_ID 0x08
+#define ZOTAC_STATUS3_REPORT_ID 0x09
+
+#define ZOTAC_RIGHT_DIAL_CW_BIT 0
+#define ZOTAC_RIGHT_DIAL_CCW_BIT 1
+#define ZOTAC_LEFT_DIAL_CW_BIT 3
+#define ZOTAC_LEFT_DIAL_CCW_BIT 4
+
+#define HID_USAGE_F16 0x6B
+#define HID_USAGE_F17 0x6C
+#define HID_USAGE_F18 0x6D
+#define HID_USAGE_F19 0x6E
+#define HID_USAGE_F20 0x6F
+
+struct zotac_device zotac;
+
+typedef void (*zotac_report_handler)(struct zotac_device *zotac, u8 *data,
+				     int size);
+
+struct zotac_report_handler {
+	u8 report_id;
+	zotac_report_handler handler;
+	const char *name;
+};
+
+/**
+	* zotac_get_usb_interface - Get the USB interface from a HID device
+	* @hdev: The HID device
+	*
+	* Returns the USB interface if the device is a USB device, NULL otherwise
+	*/
+struct usb_interface *zotac_get_usb_interface(struct hid_device *hdev)
+{
+	struct usb_interface *intf = NULL;
+
+	if (hid_is_usb(hdev))
+		intf = to_usb_interface(hdev->dev.parent);
+
+	return intf;
+}
+
+static int get_interface_num(struct hid_device *hdev)
+{
+	struct usb_interface *intf = zotac_get_usb_interface(hdev);
+
+	if (intf && intf->cur_altsetting)
+		return intf->cur_altsetting->desc.bInterfaceNumber;
+
+	return -1;
+}
+
+static void process_dial_wheel_report(struct zotac_device *zotac, u8 *data,
+				      int size)
+{
+	u8 value;
+
+	if (size < 4 || !zotac->wheel_input)
+		return;
+
+	value = data[3];
+	if (value == 0)
+		return;
+
+	if (value & BIT(ZOTAC_RIGHT_DIAL_CW_BIT))
+		input_report_rel(zotac->wheel_input, REL_WHEEL, 1);
+	if (value & BIT(ZOTAC_RIGHT_DIAL_CCW_BIT))
+		input_report_rel(zotac->wheel_input, REL_WHEEL, -1);
+
+	if (value & BIT(ZOTAC_LEFT_DIAL_CW_BIT))
+		input_report_rel(zotac->wheel_input, REL_HWHEEL, 1);
+	if (value & BIT(ZOTAC_LEFT_DIAL_CCW_BIT))
+		input_report_rel(zotac->wheel_input, REL_HWHEEL, -1);
+
+	input_sync(zotac->wheel_input);
+}
+
+static int process_keyboard_report(struct zotac_device *zotac, u8 *data,
+				   int size)
+{
+	u32 pattern;
+	enum qam_mode qam_mode;
+
+	if (zotac->gamepad)
+		qam_mode = zotac->gamepad->qam_mode;
+
+	if (size < 5)
+		return 0;
+
+	pattern = (data[1] << 16) | (data[2] << 8) | data[3];
+
+	if (pattern == 0x09006c || pattern == 0x09006d || pattern == 0x080007 || pattern == 0x050063) {
+		switch (data[3]) {
+		case 0x63:
+			switch (qam_mode) {
+			case QAM_MODE_KEYBOARD:
+				data[3] = HID_USAGE_F19;
+				break;
+			case QAM_MODE_CUSTOM:
+				zotac_gamepad_send_button(zotac, (int[]){ BTN_TRIGGER_HAPPY6 }, 1);
+				break;
+			default:
+				zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE, BTN_X }, 2);
+				break;
+			}
+			break;
+		case 0x6C:
+			switch (qam_mode) {
+			case QAM_MODE_KEYBOARD:
+				data[3] = HID_USAGE_F16;
+				break;
+			default:
+				zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE }, 1);
+				break;
+			}
+			break;
+		case 0x6D:
+			switch (qam_mode) {
+			case QAM_MODE_KEYBOARD:
+				data[3] = HID_USAGE_F17;
+				break;
+			default:
+				zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE, BTN_A }, 2);
+				break;
+			}
+			break;
+		case 0x07:
+			switch (qam_mode) {
+			case QAM_MODE_KEYBOARD:
+				data[3] = HID_USAGE_F18;
+				break;
+			case QAM_MODE_CUSTOM:
+				zotac_gamepad_send_button(zotac, (int[]){ BTN_TRIGGER_HAPPY5 }, 1);
+				break;
+			default:
+				zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE, BTN_B }, 2);
+				break;
+			}
+			break;
+		}
+		if (qam_mode) {
+			memset(data, 0, size);
+			return 1;
+		} else {
+			data[1] = 0;
+			return 0;
+		}
+	}
+	return 0;
+}
+
+static void process_mouse_report(struct zotac_device *zotac, u8 *data, int size)
+{
+	s8 x_movement = 0, y_movement = 0, wheel_movement = 0;
+	int bit, i;
+
+	if (size < 5 || !zotac->mouse_input)
+		return;
+
+	x_movement = (s8)data[2];
+	y_movement = (s8)data[3];
+
+	if (size >= 5)
+		wheel_movement = (s8)data[4];
+
+	for (i = 0; i < 8; i++) {
+		bit = 1 << i;
+		input_report_key(zotac->mouse_input, BTN_LEFT + i,
+				 (data[1] & bit) ? 1 : 0);
+	}
+
+	input_report_rel(zotac->mouse_input, REL_X, x_movement);
+	input_report_rel(zotac->mouse_input, REL_Y, y_movement);
+
+	if (wheel_movement)
+		input_report_rel(zotac->mouse_input, REL_WHEEL, wheel_movement);
+
+	input_sync(zotac->mouse_input);
+}
+
+static const struct zotac_report_handler dial_interface_handlers[] = {
+	{ .report_id = ZOTAC_DIAL_REPORT_ID,
+	  .handler = process_dial_wheel_report,
+	  .name = "dial wheel" },
+	{ .report_id = ZOTAC_MOUSE_REPORT_ID,
+	  .handler = process_mouse_report,
+	  .name = "mouse" },
+	{ 0 }
+};
+
+static int zotac_process_report(struct zotac_device *zotac,
+				const struct zotac_report_handler *handlers,
+				u8 *data, int size)
+{
+	const struct zotac_report_handler *handler;
+	u8 report_id;
+
+	if (size < 1)
+		return 0;
+
+	report_id = data[0];
+
+	for (handler = handlers; handler->handler; handler++) {
+		if (handler->report_id == report_id) {
+			handler->handler(zotac, data, size);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int zotac_raw_event(struct hid_device *hdev, struct hid_report *report,
+			   u8 *data, int size)
+{
+	int intf_num = get_interface_num(hdev);
+	int handled = 0;
+
+	if (size < 2)
+		return 0;
+
+	switch (intf_num) {
+	case ZOTAC_GAMEPAD_INTERFACE:
+		if (zotac.gamepad) {
+			zotac_process_gamepad_report(&zotac, data, size);
+			handled = 1;
+		}
+		break;
+	case ZOTAC_DIAL_INTERFACE:
+		if (data[0] == ZOTAC_KBD_REPORT_ID)
+			return process_keyboard_report(&zotac, data, size);
+
+		handled = zotac_process_report(&zotac, dial_interface_handlers,
+					       data, size);
+		break;
+	case ZOTAC_REPORT_INTERFACE:
+		if (data[0] >= ZOTAC_STATUS_REPORT_ID &&
+		    data[0] <= ZOTAC_STATUS3_REPORT_ID)
+			handled = 1;
+		break;
+	}
+
+	return handled;
+}
+
+static int zotac_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+			       struct hid_field *field, struct hid_usage *usage,
+			       unsigned long **bit, int *max)
+{
+	int intf_num = get_interface_num(hdev);
+
+	if (intf_num == ZOTAC_REPORT_INTERFACE ||
+	    intf_num == ZOTAC_COMMAND_INTERFACE)
+		return -1;
+
+	if (intf_num == ZOTAC_DIAL_INTERFACE) {
+		if (field->report &&
+		    (field->report->id == ZOTAC_MOUSE_REPORT_ID ||
+		     field->report->id == ZOTAC_DIAL_REPORT_ID))
+			return -1;
+
+		if (field->report && field->report->id == ZOTAC_KBD_REPORT_ID)
+			return 0;
+	}
+
+	return 0;
+}
+
+/**
+	* zotac_init_input_device - Initialize common input device properties
+	* @input_dev: The input device to initialize
+	* @hdev: The HID device associated with this input device
+	* @name: The name to assign to the input device
+	*
+	* Sets up common properties for an input device based on the HID device
+	*/
+void zotac_init_input_device(struct input_dev *input_dev,
+			     struct hid_device *hdev, const char *name)
+{
+	input_dev->name = name;
+	input_dev->phys = hdev->phys;
+	input_dev->uniq = hdev->uniq;
+	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->dev.parent = &hdev->dev;
+}
+
+static int setup_wheel_input_device(struct zotac_device *zotac)
+{
+	int ret;
+
+	zotac->wheel_input = devm_input_allocate_device(&zotac->hdev->dev);
+	if (!zotac->wheel_input) {
+		hid_err(zotac->hdev, "Failed to allocate wheel input device\n");
+		return -ENOMEM;
+	}
+
+	zotac_init_input_device(zotac->wheel_input, zotac->hdev,
+				"ZOTAC Gaming Zone Dials");
+
+	__set_bit(EV_REL, zotac->wheel_input->evbit);
+	__set_bit(REL_WHEEL, zotac->wheel_input->relbit);
+	__set_bit(REL_HWHEEL, zotac->wheel_input->relbit);
+
+	ret = input_register_device(zotac->wheel_input);
+	if (ret) {
+		hid_err(zotac->hdev, "Failed to register wheel input device\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int setup_mouse_input_device(struct zotac_device *zotac)
+{
+	int ret, i;
+
+	zotac->mouse_input = devm_input_allocate_device(&zotac->hdev->dev);
+	if (!zotac->mouse_input) {
+		hid_err(zotac->hdev, "Failed to allocate mouse input device\n");
+		return -ENOMEM;
+	}
+
+	zotac_init_input_device(zotac->mouse_input, zotac->hdev,
+				"ZOTAC Gaming Zone Mouse");
+
+	__set_bit(EV_KEY, zotac->mouse_input->evbit);
+	__set_bit(EV_REL, zotac->mouse_input->evbit);
+
+	for (i = 0; i < 8; i++)
+		__set_bit(BTN_LEFT + i, zotac->mouse_input->keybit);
+
+	__set_bit(REL_X, zotac->mouse_input->relbit);
+	__set_bit(REL_Y, zotac->mouse_input->relbit);
+	__set_bit(REL_WHEEL, zotac->mouse_input->relbit);
+
+	ret = input_register_device(zotac->mouse_input);
+	if (ret) {
+		hid_err(zotac->hdev, "Failed to register mouse input device\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int zotac_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int intf_num, ret;
+	bool gamepad_initialized = false;
+	bool cfg_initialized = false;
+
+	intf_num = get_interface_num(hdev);
+
+	zotac.hdev = hdev;
+	hid_set_drvdata(hdev, &zotac);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "Parse failed\n");
+		return ret;
+	}
+
+	switch (intf_num) {
+	case ZOTAC_DIAL_INTERFACE:
+		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+		break;
+	case ZOTAC_REPORT_INTERFACE:
+	case ZOTAC_COMMAND_INTERFACE:
+		ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+		break;
+	default:
+		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+		break;
+	}
+
+	if (ret) {
+		hid_err(hdev, "HID hw start failed\n");
+		goto err;
+	}
+
+	if (intf_num == ZOTAC_DIAL_INTERFACE) {
+		ret = setup_wheel_input_device(&zotac);
+		if (ret) {
+			hid_err(hdev, "Wheel input setup failed\n");
+			goto err_stop_hw;
+		}
+
+		ret = setup_mouse_input_device(&zotac);
+		if (ret) {
+			hid_err(hdev, "Mouse input setup failed\n");
+			/* Unregister wheel input before jumping to err_stop_hw */
+			input_unregister_device(zotac.wheel_input);
+			zotac.wheel_input = NULL;
+			goto err_stop_hw;
+		}
+
+		ret = zotac_init_gamepad(&zotac, zotac_get_usb_interface(hdev));
+		if (ret) {
+			hid_warn(hdev, "Gamepad initialization failed: %d\n",
+				 ret);
+		} else {
+			gamepad_initialized = true;
+		}
+	}
+
+	if (intf_num == ZOTAC_COMMAND_INTERFACE) {
+		ret = zotac_cfg_init(&zotac);
+		if (ret) {
+			hid_warn(hdev, "Cfg data initialization failed: %d\n",
+				 ret);
+			goto err_stop_hw;
+		} else {
+			cfg_initialized = true;
+			zotac_register_sysfs(&zotac);
+		}
+		ret = zotac_rgb_init(&zotac);
+		if (ret)
+			hid_warn(hdev, "RGB initialization failed: %d\n", ret);
+	}
+
+	hid_info(hdev, "Loaded version %s\n", ZOTAC_VERSION);
+
+	return 0;
+
+err_stop_hw:
+	if (gamepad_initialized)
+		zotac_cleanup_gamepad(&zotac);
+	if (cfg_initialized)
+		zotac_cfg_cleanup(&zotac);
+
+	hid_hw_stop(hdev);
+err:
+	return ret;
+}
+
+static int zotac_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+	int intf_num = get_interface_num(hdev);
+
+	if (intf_num == ZOTAC_DIAL_INTERFACE)
+		hi->input->name = "ZOTAC Gaming Zone Keyboard";
+
+	return 0;
+}
+
+static int zotac_resubmit_urbs(struct hid_device *hdev)
+{
+	int intf_num = get_interface_num(hdev);
+	int ret = 0;
+
+	if (zotac.gamepad && zotac.gamepad->urbs[0] &&
+	    (intf_num == ZOTAC_GAMEPAD_INTERFACE ||
+	     intf_num == ZOTAC_DIAL_INTERFACE)) {
+		ret = usb_submit_urb(zotac.gamepad->urbs[0], GFP_NOIO);
+		if (ret) {
+			hid_err(hdev, "Failed to resubmit gamepad URB: %d\n",
+				ret);
+			return ret;
+		}
+		hid_dbg(hdev, "Gamepad URB resubmitted successfully\n");
+	}
+
+	return 0;
+}
+
+static int zotac_resume(struct hid_device *hdev)
+{
+	hid_dbg(hdev, "resume called for interface %d\n",
+		get_interface_num(hdev));
+	return zotac_resubmit_urbs(hdev);
+}
+
+static int zotac_reset_resume(struct hid_device *hdev)
+{
+	int intf_num = get_interface_num(hdev);
+
+	hid_info(hdev, "reset_resume called for interface %d\n", intf_num);
+
+	if (zotac.led_rgb_dev && intf_num == ZOTAC_COMMAND_INTERFACE)
+		zotac_rgb_resume(&zotac);
+
+	return zotac_resubmit_urbs(hdev);
+}
+
+static int zotac_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	int intf_num = get_interface_num(hdev);
+	int i;
+
+	hid_dbg(hdev, "suspend called for interface %d\n", intf_num);
+
+	if (zotac.gamepad && (intf_num == ZOTAC_GAMEPAD_INTERFACE ||
+			intf_num == ZOTAC_DIAL_INTERFACE)) {
+		/* Kill all input URBs */
+		for (i = 0; i < ZOTAC_NUM_URBS; i++) {
+			if (zotac.gamepad->urbs[i]) {
+				usb_kill_urb(zotac.gamepad->urbs[i]);
+				hid_dbg(hdev,
+					"Gamepad URB %d killed for suspend\n",
+					i);
+			}
+		}
+
+		/* Kill all force feedback URBs */
+		for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+			if (zotac.gamepad->ff_urbs[i]) {
+				usb_kill_urb(zotac.gamepad->ff_urbs[i]);
+				hid_dbg(hdev,
+					"Force feedback URB %d killed for suspend\n",
+					i);
+			}
+		}
+	}
+
+	if (zotac.led_rgb_dev && intf_num == ZOTAC_COMMAND_INTERFACE)
+		zotac_rgb_suspend(&zotac);
+
+	return 0;
+}
+
+static void zotac_remove(struct hid_device *hdev)
+{
+	int intf_num = get_interface_num(hdev);
+
+	dev_info(&hdev->dev, "Removing driver for interface %d", intf_num);
+
+	if (intf_num == ZOTAC_COMMAND_INTERFACE) {
+		dev_info(&hdev->dev, "Unregistering sysfs entries");
+		zotac_unregister_sysfs(&zotac);
+	}
+
+	if (zotac.gamepad)
+		zotac_cleanup_gamepad(&zotac);
+
+	if (zotac.cfg_data)
+		zotac_cfg_cleanup(&zotac);
+
+	if (zotac.led_rgb_dev)
+		zotac_rgb_cleanup(&zotac);
+
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id zotac_devices[] = {
+	{ HID_USB_DEVICE(ZOTAC_VENDOR_ID, ZOTAC_PRODUCT_ID) },
+	{ HID_USB_DEVICE(ZOTAC_ALT_VENDOR_ID, ZOTAC_PRODUCT_ID) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, zotac_devices);
+
+static struct hid_driver zotac_driver = {
+	.name = "zotac_zone_hid",
+	.id_table = zotac_devices,
+	.probe = zotac_probe,
+	.remove = zotac_remove,
+	.raw_event = zotac_raw_event,
+	.input_mapping = zotac_input_mapping,
+	.input_configured = zotac_input_configured,
+	.reset_resume = zotac_reset_resume,
+	.suspend = zotac_suspend,
+	.resume = zotac_resume,
+};
+
+module_hid_driver(zotac_driver);
+
+MODULE_AUTHOR("Luke D. Jones");
+MODULE_DESCRIPTION("HID driver for ZOTAC Gaming Zone Controller");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c
new file mode 100644
index 000000000000..571eec2df446
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c
@@ -0,0 +1,522 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for ZOTAC Gaming Zone Controller - RGB LED control
+ *
+ * Copyright (c) 2025 Luke D. Jones <luke@ljones.dev>
+ */
+
+#include "linux/input-event-codes.h"
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/atomic.h>
+
+#include "zotac-zone.h"
+
+#define ZOTAC_GAMEPAD_REPORT_SIZE 64
+#define ZOTAC_GAMEPAD_URB_INTERVAL 1
+
+static void zotac_gamepad_urb_irq(struct urb *urb)
+{
+	struct zotac_device *zotac = urb->context;
+	struct zotac_gamepad *gamepad;
+	unsigned char *data = urb->transfer_buffer;
+	int retval, status = urb->status;
+
+	if (!zotac || !zotac->gamepad)
+		return;
+
+	gamepad = zotac->gamepad;
+
+	/* Use memory barrier before reading disconnect flag to ensure latest value */
+	smp_rmb();
+	if (READ_ONCE(gamepad->disconnect)) {
+		return;
+	}
+
+	switch (status) {
+	case 0:
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:
+		goto exit;
+	}
+
+	zotac_process_gamepad_report(zotac, data, urb->actual_length);
+
+exit:
+	/* Use memory barrier before reading disconnect flag to ensure latest value */
+	smp_rmb();
+	if (!READ_ONCE(gamepad->disconnect)) {
+		retval = usb_submit_urb(urb, GFP_ATOMIC);
+		if (retval)
+			dev_err(&urb->dev->dev,
+				"usb_submit_urb failed with result %d\n",
+				retval);
+	}
+}
+
+static void zotac_gamepad_ff_urb_complete(struct urb *urb)
+{
+	struct zotac_device *zotac = urb->context;
+	struct zotac_gamepad *gamepad;
+	int i;
+
+	if (!zotac || !zotac->gamepad)
+		return;
+
+	gamepad = zotac->gamepad;
+
+	if (urb->status)
+		dev_dbg(&urb->dev->dev, "FF urb status %d\n", urb->status);
+
+	for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+		if (gamepad->ff_urbs[i] == urb) {
+			gamepad->ff_urbs[i]->transfer_flags &=
+				~URB_NO_TRANSFER_DMA_MAP;
+			atomic_set(&gamepad->ff_active[i], 0);
+			break;
+		}
+	}
+}
+
+static int zotac_gamepad_play_effect(struct input_dev *dev, void *data,
+				     struct ff_effect *effect)
+{
+	struct zotac_device *zotac = input_get_drvdata(dev);
+	struct zotac_gamepad *gamepad = zotac->gamepad;
+	int retval = -EBUSY, i;
+	u16 strong, weak;
+
+	if (!gamepad || READ_ONCE(gamepad->disconnect))
+		return -ENODEV;
+	if (effect->type != FF_RUMBLE)
+		return 0;
+
+	strong = effect->u.rumble.strong_magnitude;
+	weak = effect->u.rumble.weak_magnitude;
+
+	for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+		if (atomic_read(&gamepad->ff_active[i]) == 0) {
+			gamepad->ff_data[i][0] = ZOTAC_FF_REPORT_ID;
+			gamepad->ff_data[i][1] = 0x08;
+			gamepad->ff_data[i][2] = 0x00;
+			gamepad->ff_data[i][3] = strong / 256;
+			gamepad->ff_data[i][4] = weak / 256;
+			gamepad->ff_data[i][5] = 0x00;
+			gamepad->ff_data[i][6] = 0x00;
+			gamepad->ff_data[i][7] = 0x00;
+
+			/* Use atomic compare-and-swap to claim this URB */
+			if (atomic_cmpxchg(&gamepad->ff_active[i], 0, 1) == 0) {
+				retval = usb_submit_urb(gamepad->ff_urbs[i],
+							GFP_ATOMIC);
+				if (retval) {
+					dev_err(&zotac->hdev->dev,
+						"usb_submit_urb(ff) failed: %d\n",
+						retval);
+					atomic_set(&gamepad->ff_active[i], 0);
+				}
+				break;
+			}
+		}
+	}
+	return retval;
+}
+
+void zotac_process_gamepad_report(struct zotac_device *zotac, u8 *data,
+				  int size)
+{
+	struct zotac_gamepad *gamepad = zotac->gamepad;
+	struct input_dev *input_dev;
+
+	if (!gamepad || size < 14 || !(input_dev = gamepad->dev) ||
+	    data[0] != 0x00)
+		return;
+
+	input_report_abs(input_dev, ABS_HAT0X,
+			 !!(data[2] & 0x08) - !!(data[2] & 0x04));
+	input_report_abs(input_dev, ABS_HAT0Y,
+			 !!(data[2] & 0x02) - !!(data[2] & 0x01));
+	input_report_key(input_dev, BTN_START, data[2] & BIT(4));
+	input_report_key(input_dev, BTN_SELECT, data[2] & BIT(5));
+	input_report_key(input_dev, BTN_THUMBL, data[2] & BIT(6));
+	input_report_key(input_dev, BTN_THUMBR, data[2] & BIT(7));
+	input_report_key(input_dev, BTN_A, data[3] & BIT(4));
+	input_report_key(input_dev, BTN_B, data[3] & BIT(5));
+	input_report_key(input_dev, BTN_X, data[3] & BIT(6));
+	input_report_key(input_dev, BTN_Y, data[3] & BIT(7));
+	input_report_key(input_dev, BTN_TL, data[3] & BIT(0));
+	input_report_key(input_dev, BTN_TR, data[3] & BIT(1));
+	input_report_key(input_dev, BTN_MODE, data[3] & BIT(2));
+	input_report_abs(input_dev, ABS_X,
+			 (__s16)le16_to_cpup((__le16 *)(data + 6)));
+	input_report_abs(input_dev, ABS_Y,
+			 ~(__s16)le16_to_cpup((__le16 *)(data + 8)));
+	input_report_abs(input_dev, ABS_RX,
+			 (__s16)le16_to_cpup((__le16 *)(data + 10)));
+	input_report_abs(input_dev, ABS_RY,
+			 ~(__s16)le16_to_cpup((__le16 *)(data + 12)));
+	input_report_abs(input_dev, ABS_Z, data[4]);
+	input_report_abs(input_dev, ABS_RZ, data[5]);
+	input_sync(input_dev);
+}
+
+static void zotac_button_work_func(struct work_struct *work)
+{
+	struct zotac_gamepad *gamepad = container_of(
+		to_delayed_work(work), struct zotac_gamepad, button_work);
+	unsigned int button2, button;
+	bool qam_update;
+
+	if (READ_ONCE(gamepad->disconnect) || !gamepad->dev)
+		return;
+
+	/* Access these values atomically without a spinlock */
+	/* We copy them locally to avoid races */
+	button = READ_ONCE(gamepad->button_to_press);
+	button2 = READ_ONCE(gamepad->button_to_press2);
+	qam_update = READ_ONCE(gamepad->update_qam);
+
+	/* Update the state variables */
+	WRITE_ONCE(gamepad->update_qam, false);
+	WRITE_ONCE(gamepad->button_to_press, 0);
+	WRITE_ONCE(gamepad->button_to_press2, 0);
+
+	/* Memory barrier to ensure these writes complete before proceeding */
+	smp_wmb();
+
+	if (qam_update) {
+		input_report_key(gamepad->dev, button, 1);
+		input_sync(gamepad->dev);
+		msleep(150);
+		input_report_key(gamepad->dev, button2, 1);
+		input_sync(gamepad->dev);
+		input_report_key(gamepad->dev, button2, 0);
+		input_sync(gamepad->dev);
+		input_report_key(gamepad->dev, button, 0);
+		input_sync(gamepad->dev);
+	} else if (button) {
+		input_report_key(gamepad->dev, button, 1);
+		input_sync(gamepad->dev);
+		input_report_key(gamepad->dev, button, 0);
+		input_sync(gamepad->dev);
+	}
+
+	/* Release the button press lock so others can schedule button presses */
+	atomic_set(&gamepad->button_press_in_progress, 0);
+}
+
+void zotac_gamepad_send_button(struct zotac_device *zotac, int buttons[],
+			       int num_buttons)
+{
+	struct zotac_gamepad *gamepad;
+
+	if (!zotac || !zotac->gamepad || !zotac->gamepad->dev ||
+	    READ_ONCE(zotac->gamepad->disconnect))
+		return;
+
+	gamepad = zotac->gamepad;
+
+	/* Try to atomically take the button press lock */
+	if (atomic_cmpxchg(&gamepad->button_press_in_progress, 0, 1) == 0) {
+		/* We got the lock, update button values */
+
+		/* Reset button state first */
+		WRITE_ONCE(gamepad->button_to_press, 0);
+		WRITE_ONCE(gamepad->button_to_press2, 0);
+		WRITE_ONCE(gamepad->update_qam, false);
+
+		/* Set new button values */
+		if (num_buttons == 1) {
+			WRITE_ONCE(gamepad->button_to_press, buttons[0]);
+		} else if (num_buttons == 2) {
+			WRITE_ONCE(gamepad->update_qam, true);
+			WRITE_ONCE(gamepad->button_to_press, buttons[0]);
+			WRITE_ONCE(gamepad->button_to_press2, buttons[1]);
+		} else {
+			/* No buttons to press, release the lock */
+			atomic_set(&gamepad->button_press_in_progress, 0);
+			return;
+		}
+
+		/* Memory barrier to ensure writes complete before scheduling work */
+		smp_wmb();
+
+		/* Schedule the work */
+		schedule_delayed_work(&gamepad->button_work,
+				      msecs_to_jiffies(5));
+	}
+}
+
+static void zotac_find_endpoints(struct usb_interface *intf,
+				 struct usb_endpoint_descriptor **ep_in,
+				 struct usb_endpoint_descriptor **ep_out)
+{
+	struct usb_host_interface *host_interface = intf->cur_altsetting;
+	int i;
+
+	*ep_in = *ep_out = NULL;
+	for (i = 0; i < host_interface->desc.bNumEndpoints; i++) {
+		struct usb_endpoint_descriptor *ep =
+			&host_interface->endpoint[i].desc;
+		if (usb_endpoint_is_int_in(ep))
+			*ep_in = ep;
+		else if (usb_endpoint_is_int_out(ep))
+			*ep_out = ep;
+	}
+}
+
+int zotac_init_gamepad(struct zotac_device *zotac, struct usb_interface *intf)
+{
+	struct usb_endpoint_descriptor *ep_in = NULL, *ep_out = NULL;
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct hid_device *hdev = zotac->hdev;
+	int pipe, maxp, interval, ret = 0, i;
+
+	struct usb_interface *gamepad_intf;
+	struct zotac_gamepad *gamepad;
+	struct input_dev *input_dev;
+
+	if (!(gamepad = kzalloc(sizeof(*gamepad), GFP_KERNEL)))
+		return -ENOMEM;
+
+	WRITE_ONCE(gamepad->disconnect, false);
+	gamepad->zotac = zotac;
+	zotac->gamepad = gamepad;
+	zotac->udev = udev;
+
+	if (!(gamepad_intf = usb_ifnum_to_if(udev, ZOTAC_GAMEPAD_INTERFACE))) {
+		ret = -ENODEV;
+		goto err_free_gamepad;
+	}
+
+	zotac_find_endpoints(gamepad_intf, &ep_in, &ep_out);
+	if (!ep_in) {
+		ret = -ENODEV;
+		goto err_free_gamepad;
+	}
+
+	gamepad->qam_mode = QAM_MODE_STEAM;
+	gamepad->ep_in = ep_in;
+	gamepad->ep_out = ep_out;
+
+	if (!(input_dev = input_allocate_device())) {
+		ret = -ENOMEM;
+		goto err_free_gamepad;
+	}
+
+	gamepad->dev = input_dev;
+	zotac_init_input_device(input_dev, hdev, "ZOTAC Gaming Zone Gamepad");
+	input_set_drvdata(input_dev, zotac);
+
+	input_set_abs_params(input_dev, ABS_X, -32768, 32767, 16, 128);
+	input_set_abs_params(input_dev, ABS_Y, -32768, 32767, 16, 128);
+	input_set_abs_params(input_dev, ABS_RX, -32768, 32767, 16, 128);
+	input_set_abs_params(input_dev, ABS_RY, -32768, 32767, 16, 128);
+	input_set_abs_params(input_dev, ABS_Z, 0, 255, 0, 0);
+	input_set_abs_params(input_dev, ABS_RZ, 0, 255, 0, 0);
+	input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0);
+	input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0);
+
+	input_set_capability(input_dev, EV_KEY, BTN_A);
+	input_set_capability(input_dev, EV_KEY, BTN_B);
+	input_set_capability(input_dev, EV_KEY, BTN_X);
+	input_set_capability(input_dev, EV_KEY, BTN_Y);
+	input_set_capability(input_dev, EV_KEY, BTN_TL);
+	input_set_capability(input_dev, EV_KEY, BTN_TR);
+	input_set_capability(input_dev, EV_KEY, BTN_MODE);
+	input_set_capability(input_dev, EV_KEY, BTN_START);
+	input_set_capability(input_dev, EV_KEY, BTN_SELECT);
+	input_set_capability(input_dev, EV_KEY, BTN_THUMBL);
+	input_set_capability(input_dev, EV_KEY, BTN_THUMBR);
+
+	/* Allow the gamepad to emit these events for screenface buttons */
+	input_set_capability(input_dev, EV_KEY, KEY_F14);
+	input_set_capability(input_dev, EV_KEY, KEY_F15);
+	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_capability(input_dev, EV_KEY, KEY_F19);
+
+	input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY1);
+	input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY2);
+	input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY3);
+	input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY4);
+	input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY5);
+	input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY6);
+
+	pipe = usb_rcvintpipe(udev, gamepad->ep_in->bEndpointAddress);
+	if (!(maxp = usb_maxpacket(udev, pipe))) {
+		ret = -EINVAL;
+		goto err_free_input;
+	}
+
+	interval = gamepad->ep_in->bInterval ? gamepad->ep_in->bInterval :
+					       ZOTAC_GAMEPAD_URB_INTERVAL;
+
+	for (i = 0; i < ZOTAC_NUM_URBS; i++) {
+		if (!(gamepad->urb_buf[i] =
+			      kzalloc(ZOTAC_GAMEPAD_REPORT_SIZE, GFP_KERNEL))) {
+			ret = -ENOMEM;
+			goto err_free_urbs;
+		}
+		if (!(gamepad->urbs[i] = usb_alloc_urb(0, GFP_KERNEL))) {
+			ret = -ENOMEM;
+			goto err_free_urbs;
+		}
+		usb_fill_int_urb(gamepad->urbs[i], udev, pipe,
+				 gamepad->urb_buf[i], ZOTAC_GAMEPAD_REPORT_SIZE,
+				 zotac_gamepad_urb_irq, zotac, interval);
+	}
+
+	INIT_DELAYED_WORK(&gamepad->button_work, zotac_button_work_func);
+
+	/* Initialize atomic variables */
+	atomic_set(&gamepad->button_press_in_progress, 0);
+
+	if (gamepad->ep_out) {
+		input_set_capability(input_dev, EV_FF, FF_RUMBLE);
+
+		for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+			/* Initialize atomic ff_active */
+			atomic_set(&gamepad->ff_active[i], 0);
+
+			if (!(gamepad->ff_data[i] = usb_alloc_coherent(
+				      udev, ZOTAC_FF_REPORT_LEN, GFP_KERNEL,
+				      &gamepad->ff_dma[i]))) {
+				ret = -ENOMEM;
+				goto err_free_ff_data;
+			}
+			if (!(gamepad->ff_urbs[i] =
+				      usb_alloc_urb(0, GFP_KERNEL))) {
+				ret = -ENOMEM;
+				goto err_free_ff_urbs;
+			}
+			usb_fill_int_urb(
+				gamepad->ff_urbs[i], udev,
+				usb_sndintpipe(
+					udev,
+					gamepad->ep_out->bEndpointAddress),
+				gamepad->ff_data[i], ZOTAC_FF_REPORT_LEN,
+				zotac_gamepad_ff_urb_complete, zotac,
+				gamepad->ep_out->bInterval);
+			gamepad->ff_urbs[i]->transfer_dma = gamepad->ff_dma[i];
+			gamepad->ff_urbs[i]->transfer_flags |=
+				URB_NO_TRANSFER_DMA_MAP;
+		}
+
+		if ((ret = input_ff_create_memless(
+			     input_dev, NULL, zotac_gamepad_play_effect))) {
+			dev_err(&zotac->hdev->dev,
+				"Failed to create FF device: %d\n", ret);
+			goto err_free_ff_urbs;
+		}
+	}
+
+	if ((ret = input_register_device(input_dev))) {
+		dev_err(&zotac->hdev->dev,
+			"Failed to register input device: %d\n", ret);
+		goto err_free_ff_urbs;
+	}
+
+	for (i = 0; i < ZOTAC_NUM_URBS; i++) {
+		if ((ret = usb_submit_urb(gamepad->urbs[i], GFP_KERNEL))) {
+			dev_err(&zotac->hdev->dev,
+				"Failed to submit URB %d: %d\n", i, ret);
+			while (--i >= 0)
+				usb_kill_urb(gamepad->urbs[i]);
+			input_unregister_device(input_dev);
+			gamepad->dev = NULL;
+			goto err_free_ff_urbs;
+		}
+	}
+	return 0;
+
+err_free_ff_urbs:
+	for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+		if (gamepad->ff_urbs[i]) {
+			usb_free_urb(gamepad->ff_urbs[i]);
+			gamepad->ff_urbs[i] = NULL;
+		}
+	}
+
+err_free_ff_data:
+	for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+		if (gamepad->ff_data[i]) {
+			usb_free_coherent(udev, ZOTAC_FF_REPORT_LEN,
+					  gamepad->ff_data[i],
+					  gamepad->ff_dma[i]);
+			gamepad->ff_data[i] = NULL;
+		}
+	}
+
+err_free_urbs:
+	for (i = 0; i < ZOTAC_NUM_URBS; i++) {
+		if (gamepad->urbs[i]) {
+			usb_free_urb(gamepad->urbs[i]);
+			gamepad->urbs[i] = NULL;
+		}
+		if (gamepad->urb_buf[i]) {
+			kfree(gamepad->urb_buf[i]);
+			gamepad->urb_buf[i] = NULL;
+		}
+	}
+
+err_free_input:
+	if (gamepad->dev) {
+		input_free_device(gamepad->dev);
+		gamepad->dev = NULL;
+	}
+
+err_free_gamepad:
+	zotac->gamepad = NULL;
+	kfree(gamepad);
+	return ret;
+}
+
+void zotac_cleanup_gamepad(struct zotac_device *zotac)
+{
+	struct zotac_gamepad *gamepad;
+	int i;
+
+	if (!zotac || !zotac->gamepad)
+		return;
+
+	gamepad = zotac->gamepad;
+
+	/* Set disconnect first, use WRITE_ONCE and memory barrier to ensure visibility */
+	WRITE_ONCE(gamepad->disconnect, true);
+	smp_wmb();
+
+	cancel_delayed_work_sync(&gamepad->button_work);
+
+	for (i = 0; i < ZOTAC_NUM_URBS; i++) {
+		if (gamepad->urbs[i]) {
+			usb_kill_urb(gamepad->urbs[i]);
+			usb_free_urb(gamepad->urbs[i]);
+			gamepad->urbs[i] = NULL;
+		}
+	}
+
+	for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) {
+		if (gamepad->ff_urbs[i]) {
+			usb_kill_urb(gamepad->ff_urbs[i]);
+			usb_free_urb(gamepad->ff_urbs[i]);
+			gamepad->ff_urbs[i] = NULL;
+		}
+	}
+
+	if (gamepad->dev) {
+		input_unregister_device(gamepad->dev);
+		gamepad->dev = NULL;
+	}
+
+	zotac->gamepad = NULL;
+}
diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c
new file mode 100644
index 000000000000..0305de2bd549
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c
@@ -0,0 +1,717 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+	* HID driver for ZOTAC Gaming Zone Controller - RGB LED control
+	*
+	* Copyright (c) 2025 Luke D. Jones <luke@ljones.dev>
+	*/
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/usb.h>
+
+#include "zotac-zone.h"
+
+#define SETTING_COLOR 0x00
+#define SETTING_SPEED 0x01
+#define SETTING_EFFECT 0x02
+#define SETTING_BRIGHTNESS 0x03
+#define SETTING_REAL_TIME 0x04
+
+#define EFFECT_RAINBOW 0x00
+#define EFFECT_BREATHE 0x01
+#define EFFECT_STARS 0x02
+#define EFFECT_FADE 0x03
+#define EFFECT_DANCE 0x04
+#define EFFECT_OFF 0xF0
+
+#define SPEED_SLOW 0x00
+#define SPEED_NORMAL 0x01
+#define SPEED_FAST 0x02
+
+#define BRIGHTNESS_OFF 0x00 /* 0% */
+#define BRIGHTNESS_LOW 0x19 /* 25% */
+#define BRIGHTNESS_MED 0x32 /* 50% */
+#define BRIGHTNESS_HIGH 0x4B /* 75% */
+#define BRIGHTNESS_MAX 0x64 /* 100% */
+
+static void zotac_rgb_set_default_colors(struct zotac_device *zotac,
+					 struct zotac_rgb_dev *led_rgb,
+					 int zone_idx)
+{
+	int j, led_index;
+
+	for (j = 0; j < ZOTAC_RGB_LEDS_PER_ZONE; j++) {
+		led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + j;
+
+		led_rgb->red[led_index] = 128;
+		led_rgb->green[led_index] = 128;
+		led_rgb->blue[led_index] = 128;
+
+		zotac->led_rgb_data.zone[zone_idx].red[j] = 128;
+		zotac->led_rgb_data.zone[zone_idx].green[j] = 128;
+		zotac->led_rgb_data.zone[zone_idx].blue[j] = 128;
+	}
+}
+
+static int zotac_rgb_read_zone_colors(struct zotac_device *zotac,
+				      struct zotac_rgb_dev *led_rgb,
+				      u8 zone_idx)
+{
+	size_t expected_len = 1 + ZOTAC_RGB_LEDS_PER_ZONE * 3;
+	u8 zone_rgb_data[1 + ZOTAC_RGB_LEDS_PER_ZONE * 3];
+	size_t data_len = sizeof(zone_rgb_data);
+	int ret, j, led_index, red_idx, green_idx, blue_idx;
+	u8 red, green, blue;
+
+	zone_rgb_data[0] = zone_idx;
+
+	ret = zotac_send_get_command(zotac, CMD_GET_RGB, SETTING_COLOR,
+				     &zone_idx, 1, zone_rgb_data, &data_len);
+
+	if (ret < 0) {
+		hid_err(zotac->hdev, "Failed to read RGB data for zone %d\n",
+			zone_idx);
+		zotac_rgb_set_default_colors(zotac, led_rgb, zone_idx);
+		return ret;
+	}
+
+	if (data_len < expected_len) {
+		hid_warn(zotac->hdev,
+			 "Incomplete RGB data for zone %d: %zu bytes\n",
+			 zone_idx, data_len);
+		zotac_rgb_set_default_colors(zotac, led_rgb, zone_idx);
+		return 0;
+	}
+
+	for (j = 0; j < ZOTAC_RGB_LEDS_PER_ZONE; j++) {
+		led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + j;
+		red_idx = 1 + (j * 3);
+		green_idx = red_idx + 1;
+		blue_idx = red_idx + 2;
+
+		if (red_idx >= data_len || green_idx >= data_len ||
+		    blue_idx >= data_len) {
+			hid_warn(zotac->hdev,
+				 "Index out of bounds for zone %d, LED %d\n",
+				 zone_idx, j);
+			break;
+		}
+
+		red = zone_rgb_data[red_idx];
+		green = zone_rgb_data[green_idx];
+		blue = zone_rgb_data[blue_idx];
+
+		if (led_index <
+		    ZOTAC_RGB_ZONE_COUNT * ZOTAC_RGB_LEDS_PER_ZONE) {
+			led_rgb->red[led_index] = red;
+			led_rgb->green[led_index] = green;
+			led_rgb->blue[led_index] = blue;
+
+			zotac->led_rgb_data.zone[zone_idx].red[j] = red;
+			zotac->led_rgb_data.zone[zone_idx].green[j] = green;
+			zotac->led_rgb_data.zone[zone_idx].blue[j] = blue;
+		} else {
+			hid_warn(
+				zotac->hdev,
+				"Output index out of bounds for zone %d, LED %d\n",
+				zone_idx, j);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int zotac_rgb_set_globals(struct zotac_device *zotac)
+{
+	int effect, speed, brightness;
+
+	effect = zotac_send_get_byte(zotac, CMD_GET_RGB, SETTING_EFFECT, NULL,
+				     0);
+	if (effect < 0) {
+		hid_warn(
+			zotac->hdev,
+			"Could not read effect from device, using default: %d\n",
+			EFFECT_RAINBOW);
+		effect = EFFECT_RAINBOW;
+	}
+
+	speed = zotac_send_get_byte(zotac, CMD_GET_RGB, SETTING_SPEED, NULL, 0);
+	if (speed < 0) {
+		hid_warn(
+			zotac->hdev,
+			"Could not read speed from device, using default: %d\n",
+			SPEED_NORMAL);
+		speed = SPEED_NORMAL;
+	}
+
+	/* This brightness is firmware level, not LED class level */
+	brightness = zotac_send_get_byte(zotac, CMD_GET_RGB, SETTING_BRIGHTNESS,
+					 NULL, 0);
+	if (brightness < 0) {
+		hid_warn(
+			zotac->hdev,
+			"Could not read brightness from device, using default: %d\n",
+			BRIGHTNESS_MED);
+		brightness = BRIGHTNESS_MED;
+	}
+
+	zotac->led_rgb_data.effect = effect;
+	zotac->led_rgb_data.speed = speed;
+	zotac->led_rgb_data.brightness = brightness;
+
+	return 0;
+}
+
+static void zotac_rgb_schedule_work(struct zotac_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);
+}
+
+static void zotac_rgb_do_work(struct work_struct *work)
+{
+	struct zotac_rgb_dev *led =
+		container_of(work, struct zotac_rgb_dev, work);
+	struct zotac_device *zotac = led->zotac;
+	u8 zone_idx = led - zotac->led_rgb_dev;
+	u8 zone_data[3 + ZOTAC_RGB_LEDS_PER_ZONE * 3];
+	unsigned long flags;
+	int j, led_index;
+
+	spin_lock_irqsave(&led->lock, flags);
+	if (!led->update_rgb) {
+		spin_unlock_irqrestore(&led->lock, flags);
+		return;
+	}
+	led->update_rgb = false;
+
+	zone_data[0] = zone_idx;
+
+	// [0] = zone number, [1..2] = blank, [3..] = data
+	for (j = 0; j < ZOTAC_RGB_LEDS_PER_ZONE; j++) {
+		led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + j;
+		zone_data[3 + (j * 3)] = led->red[led_index];
+		zone_data[4 + (j * 3)] = led->green[led_index];
+		zone_data[5 + (j * 3)] = led->blue[led_index];
+	}
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_COLOR, zone_data,
+			       sizeof(zone_data));
+
+	if (zone_idx == 0) {
+		zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_EFFECT,
+				       &zotac->led_rgb_data.effect, 1);
+		zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_SPEED,
+				       &zotac->led_rgb_data.speed, 1);
+		zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_BRIGHTNESS,
+				       &led->brightness, 1);
+	}
+
+	zotac_send_set_command(zotac, CMD_SAVE_CONFIG, 0, NULL, 0);
+}
+
+static void zotac_rgb_set_brightness(struct led_classdev *cdev,
+				     enum led_brightness brightness)
+{
+	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+	struct zotac_rgb_dev *led =
+		container_of(mc_cdev, struct zotac_rgb_dev, led_rgb_dev);
+	struct zotac_device *zotac = led->zotac;
+	u8 zone_idx = led - zotac->led_rgb_dev;
+	unsigned long flags;
+	int i, led_index, intensity, bright;
+
+	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 (i = 0; i < ZOTAC_RGB_LEDS_PER_ZONE; i++) {
+		led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + i;
+		intensity = mc_cdev->subled_info[i].intensity;
+		led->red[led_index] = (((intensity >> 16) & 0xFF) * bright) / 255;
+		led->green[led_index] = (((intensity >> 8) & 0xFF) * bright) / 255;
+		led->blue[led_index] = ((intensity & 0xFF) * bright) / 255;
+
+		zotac->led_rgb_data.zone[zone_idx].red[i] = led->red[led_index];
+		zotac->led_rgb_data.zone[zone_idx].green[i] = led->green[led_index];
+		zotac->led_rgb_data.zone[zone_idx].blue[i] = led->blue[led_index];
+	}
+
+	zotac->led_rgb_data.zone[zone_idx].brightness = bright;
+	zotac->led_rgb_data.initialized = true;
+	spin_unlock_irqrestore(&led->lock, flags);
+
+	zotac_rgb_schedule_work(led);
+}
+
+static void zotac_rgb_store_settings(struct zotac_device *zotac)
+{
+	struct zotac_rgb_dev *led_rgb;
+	int i, arr_size = ZOTAC_RGB_LEDS_PER_ZONE;
+
+	for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) {
+		led_rgb = &zotac->led_rgb_dev[i];
+
+		zotac->led_rgb_data.zone[i].brightness =
+			led_rgb->led_rgb_dev.led_cdev.brightness;
+
+		memcpy(zotac->led_rgb_data.zone[i].red,
+		       led_rgb->red + (i * ZOTAC_RGB_LEDS_PER_ZONE), arr_size);
+		memcpy(zotac->led_rgb_data.zone[i].green,
+		       led_rgb->green + (i * ZOTAC_RGB_LEDS_PER_ZONE),
+		       arr_size);
+		memcpy(zotac->led_rgb_data.zone[i].blue,
+		       led_rgb->blue + (i * ZOTAC_RGB_LEDS_PER_ZONE), arr_size);
+	}
+}
+
+static void zotac_rgb_restore_settings(struct zotac_rgb_dev *led_rgb,
+				       struct led_classdev *led_cdev,
+				       struct mc_subled *mc_led_info)
+{
+	struct zotac_device *zotac = led_rgb->zotac;
+	u8 zone_idx = led_rgb - zotac->led_rgb_dev;
+	int i, offset = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE;
+	int arr_size = ZOTAC_RGB_LEDS_PER_ZONE;
+
+	memcpy(led_rgb->red + offset, zotac->led_rgb_data.zone[zone_idx].red,
+	       arr_size);
+	memcpy(led_rgb->green + offset,
+	       zotac->led_rgb_data.zone[zone_idx].green, arr_size);
+	memcpy(led_rgb->blue + offset, zotac->led_rgb_data.zone[zone_idx].blue,
+	       arr_size);
+
+	for (i = 0; i < ZOTAC_RGB_LEDS_PER_ZONE; i++) {
+		mc_led_info[i].intensity =
+			(zotac->led_rgb_data.zone[zone_idx].red[i] << 16) |
+			(zotac->led_rgb_data.zone[zone_idx].green[i] << 8) |
+			zotac->led_rgb_data.zone[zone_idx].blue[i];
+	}
+
+	led_cdev->brightness = zotac->led_rgb_data.zone[zone_idx].brightness;
+}
+
+static ssize_t rgb_effect_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	int effect;
+
+	if (!zotac.cfg_data)
+		return -ENODEV;
+	effect = zotac_send_get_byte(&zotac, CMD_GET_RGB, SETTING_EFFECT, NULL,
+				     0);
+	if (effect < 0)
+		return effect;
+
+	return sysfs_emit(buf, "%d\n", effect);
+}
+
+static ssize_t rgb_effect_store(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	int effect, ret;
+	u8 effect_val;
+
+	if (!zotac.cfg_data)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 10, &effect);
+	if (ret)
+		return ret;
+
+	switch (effect) {
+	case EFFECT_RAINBOW:
+	case EFFECT_BREATHE:
+	case EFFECT_STARS:
+	case EFFECT_FADE:
+	case EFFECT_DANCE:
+	case EFFECT_OFF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	effect_val = (u8)effect;
+	ret = zotac_send_set_command(&zotac, CMD_SET_RGB, SETTING_EFFECT,
+				     &effect_val, 1);
+	if (ret < 0)
+		return ret;
+
+	zotac.led_rgb_data.effect = effect_val;
+
+	return count;
+}
+static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect");
+
+static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	int speed;
+
+	if (!zotac.cfg_data)
+		return -ENODEV;
+
+	speed = zotac_send_get_byte(&zotac, CMD_GET_RGB, SETTING_SPEED, NULL, 0);
+	if (speed < 0)
+		return speed;
+
+	return sysfs_emit(buf, "%d\n", speed);
+}
+
+static ssize_t rgb_speed_store(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	int speed, ret;
+	u8 speed_val;
+
+	if (!zotac.cfg_data)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 10, &speed);
+	if (ret) {
+		dev_err(dev, "Invalid speed value format\n");
+		return ret;
+	}
+
+	switch (speed) {
+	case SPEED_SLOW:
+	case SPEED_NORMAL:
+	case SPEED_FAST:
+		break;
+	default:
+		dev_err(dev, "Invalid speed value: %d (valid: 0-2)\n", speed);
+		return -EINVAL;
+	}
+
+	speed_val = (u8)speed;
+	ret = zotac_send_set_command(&zotac, CMD_SET_RGB, SETTING_SPEED,
+				     &speed_val, 1);
+	if (ret < 0) {
+		dev_err(dev, "Failed to set RGB speed: %d\n", ret);
+		return ret;
+	}
+
+	zotac.led_rgb_data.speed = speed_val;
+
+	return count;
+}
+static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed");
+
+static u8 brightness_level_to_value(unsigned int level)
+{
+	switch (level) {
+	case 0:
+		return BRIGHTNESS_OFF;
+	case 1:
+		return BRIGHTNESS_LOW;
+	case 2:
+		return BRIGHTNESS_MED;
+	case 3:
+		return BRIGHTNESS_HIGH;
+	case 4:
+		return BRIGHTNESS_MAX;
+	default:
+		return BRIGHTNESS_MED;
+	}
+}
+
+static unsigned int brightness_value_to_level(u8 value)
+{
+	if (value <= (BRIGHTNESS_OFF + BRIGHTNESS_LOW) / 2)
+		return 0;
+	else if (value <= (BRIGHTNESS_LOW + BRIGHTNESS_MED) / 2)
+		return 1;
+	else if (value <= (BRIGHTNESS_MED + BRIGHTNESS_HIGH) / 2)
+		return 2;
+	else if (value <= (BRIGHTNESS_HIGH + BRIGHTNESS_MAX) / 2)
+		return 3;
+	else
+		return 4;
+}
+
+static ssize_t rgb_brightness_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int brightness = 0;
+	unsigned int level;
+
+	if (!zotac.cfg_data)
+		return -ENODEV;
+
+	brightness = zotac_send_get_byte(&zotac, CMD_GET_RGB, SETTING_BRIGHTNESS,
+					 NULL, 0);
+	if (brightness < 0)
+		return brightness;
+
+	level = brightness_value_to_level(brightness);
+
+	return sysfs_emit(buf, "%u\n", level);
+}
+
+static ssize_t rgb_brightness_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int level, ret;
+	u8 brightness;
+
+	if (!zotac.cfg_data)
+		return -ENODEV;
+
+	ret = kstrtoint(buf, 10, &level);
+	if (ret)
+		return ret;
+
+	if (level > 4)
+		return -EINVAL;
+
+	brightness = brightness_level_to_value(level);
+
+	ret = zotac_send_set_command(&zotac, CMD_SET_RGB, SETTING_BRIGHTNESS,
+				     &brightness, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = zotac_send_set_command(&zotac, CMD_SAVE_CONFIG, 0, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	zotac.led_rgb_data.brightness = brightness;
+
+	return count;
+}
+static DEVICE_ATTR_RW_NAMED(rgb_brightness, "brightness");
+
+static struct attribute *zotac_rgb_attrs[] = { &dev_attr_rgb_effect.attr,
+					       &dev_attr_rgb_speed.attr,
+					       &dev_attr_rgb_brightness.attr,
+					       NULL };
+
+static const struct attribute_group zotac_rgb_attr_group = {
+	.name = "rgb",
+	.attrs = zotac_rgb_attrs,
+};
+
+/**
+* zotac_rgb_resume - Restore RGB LED settings after system resume
+* @zotac: Pointer to the zotac device structure
+*
+* Restores previously saved RGB settings after the system resumes from
+* suspend. This includes effect, speed, and color settings for all zones.
+*/
+void zotac_rgb_resume(struct zotac_device *zotac)
+{
+	struct zotac_rgb_dev *led_rgb;
+	struct led_classdev *led_cdev;
+	struct mc_subled *mc_led_info;
+	int i;
+
+	if (!zotac->led_rgb_dev)
+		return;
+
+	if (!zotac->led_rgb_data.initialized) {
+		hid_warn(
+			zotac->hdev,
+			"RGB data not initialized, skipping resume restoration\n");
+		return;
+	}
+
+	zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_EFFECT,
+			       &zotac->led_rgb_data.effect, 1);
+	zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_SPEED,
+			       &zotac->led_rgb_data.speed, 1);
+	zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_BRIGHTNESS,
+			       &zotac->led_rgb_data.brightness, 1);
+
+	for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) {
+		led_rgb = &zotac->led_rgb_dev[i];
+		led_cdev = &led_rgb->led_rgb_dev.led_cdev;
+		mc_led_info = led_rgb->led_rgb_dev.subled_info;
+
+		zotac_rgb_restore_settings(led_rgb, led_cdev, mc_led_info);
+		led_rgb->update_rgb = true;
+		zotac_rgb_schedule_work(led_rgb);
+	}
+}
+
+/**
+* zotac_rgb_suspend - Save RGB LED settings before system suspend
+* @zotac: Pointer to the zotac device structure
+*
+* Stores current RGB settings before the system suspends so they
+* can be restored when the system resumes.
+*/
+void zotac_rgb_suspend(struct zotac_device *zotac)
+{
+	if (!zotac->led_rgb_dev)
+		return;
+
+	zotac_rgb_store_settings(zotac);
+}
+
+static int zotac_rgb_register_zone(struct hid_device *hdev,
+				   struct zotac_rgb_dev *led_rgb,
+				   int zone_index)
+{
+	struct mc_subled *mc_led_info;
+	struct led_classdev *led_cdev;
+	char name[32];
+	int i, err;
+
+	snprintf(name, sizeof(name), "zotac:rgb:spectra_zone_%d", zone_index);
+
+	mc_led_info = devm_kmalloc_array(&hdev->dev, ZOTAC_RGB_LEDS_PER_ZONE,
+					 sizeof(*mc_led_info),
+					 GFP_KERNEL | __GFP_ZERO);
+	if (!mc_led_info)
+		return -ENOMEM;
+
+	for (i = 0; i < ZOTAC_RGB_LEDS_PER_ZONE; i++) {
+		mc_led_info[i].color_index = LED_COLOR_ID_RGB;
+	}
+
+	led_rgb->led_rgb_dev.subled_info = mc_led_info;
+	led_rgb->led_rgb_dev.num_colors = ZOTAC_RGB_LEDS_PER_ZONE;
+
+	led_cdev = &led_rgb->led_rgb_dev.led_cdev;
+	led_cdev->brightness = 128;
+	led_cdev->name = kstrdup(name, GFP_KERNEL);
+	if (!led_cdev->name){
+		devm_kfree(&hdev->dev, mc_led_info);
+		return -ENOMEM;
+	}
+
+	led_cdev->max_brightness = 255;
+	led_cdev->brightness_set = zotac_rgb_set_brightness;
+
+	err = devm_led_classdev_multicolor_register(&hdev->dev,
+						    &led_rgb->led_rgb_dev);
+	if (err) {
+		kfree(led_cdev->name);
+		return err;
+	}
+
+	err = sysfs_create_group(&led_cdev->dev->kobj, &zotac_rgb_attr_group);
+	if (err) {
+		return err;
+	}
+
+	return 0;
+}
+
+static int zotac_rgb_init_zone(struct zotac_device *zotac, int zone_idx)
+{
+	struct zotac_rgb_dev *led_rgb = &zotac->led_rgb_dev[zone_idx];
+	int ret;
+
+	led_rgb->hdev = zotac->hdev;
+	led_rgb->zotac = zotac;
+	led_rgb->removed = false;
+	led_rgb->brightness = 128;
+
+	zotac->led_rgb_data.zone[zone_idx].brightness =
+		zotac->led_rgb_data.brightness;
+
+	zotac_rgb_read_zone_colors(zotac, led_rgb, zone_idx);
+
+	INIT_WORK(&led_rgb->work, zotac_rgb_do_work);
+	led_rgb->output_worker_initialized = true;
+	spin_lock_init(&led_rgb->lock);
+
+	ret = zotac_rgb_register_zone(zotac->hdev, led_rgb, zone_idx);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/**
+	* zotac_rgb_cleanup - Clean up resources used by RGB LED subsystem
+	* @zotac: Pointer to the zotac device structure
+	*
+	* Releases resources allocated for RGB LED control, including sysfs
+	* attributes, scheduled work, and allocated memory.
+	*/
+void zotac_rgb_cleanup(struct zotac_device *zotac)
+{
+	struct zotac_rgb_dev *led_rgb;
+	unsigned long flags;
+	int i;
+
+	if (!zotac->led_rgb_dev)
+		return;
+
+	sysfs_remove_group(&zotac->led_rgb_dev->led_rgb_dev.led_cdev.dev->kobj, &zotac_rgb_attr_group);
+
+	for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) {
+		led_rgb = &zotac->led_rgb_dev[i];
+
+		if (led_rgb->removed)
+			continue;
+
+		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);
+	}
+
+	zotac->led_rgb_dev = NULL;
+}
+
+/**
+* zotac_rgb_init - Initialize RGB LED subsystem
+* @zotac: Pointer to the zotac device structure
+*
+* Initializes the RGB LED subsystem by allocating memory for LEDs,
+* fetching current settings from the device, registering LED zones,
+* and creating sysfs attributes for RGB control.
+*
+* Return: 0 on success, negative error code on failure
+*/
+int zotac_rgb_init(struct zotac_device *zotac)
+{
+	struct zotac_rgb_dev *led_rgb;
+	int ret, i;
+
+	led_rgb = devm_kcalloc(&zotac->hdev->dev, ZOTAC_RGB_ZONE_COUNT,
+			       sizeof(*led_rgb), GFP_KERNEL);
+	if (!led_rgb)
+		return -ENOMEM;
+
+	zotac->led_rgb_dev = led_rgb;
+
+	zotac_rgb_set_globals(zotac);
+
+	for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) {
+		ret = zotac_rgb_init_zone(zotac, i);
+		if (ret < 0) {
+			zotac_rgb_cleanup(zotac);
+			return ret;
+		}
+	}
+
+	zotac->led_rgb_data.initialized = true;
+
+	for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) {
+		led_rgb = &zotac->led_rgb_dev[i];
+		led_rgb->update_rgb = true;
+		zotac_rgb_schedule_work(led_rgb);
+	}
+
+	return 0;
+}
diff --git a/drivers/hid/zotac-zone-hid/zotac-zone.h b/drivers/hid/zotac-zone-hid/zotac-zone.h
new file mode 100644
index 000000000000..31188cee80f8
--- /dev/null
+++ b/drivers/hid/zotac-zone-hid/zotac-zone.h
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for ZOTAC Gaming Zone Controller - RGB LED control
+ *
+ * Copyright (c) 2025 Luke D. Jones <luke@ljones.dev>
+ */
+
+#ifndef __HID_ZOTAC_ZONE_H
+#define __HID_ZOTAC_ZONE_H
+
+#include <linux/hid.h>
+#include <linux/usb.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+
+#define ZOTAC_VERSION "0.1.4"
+
+#define ZOTAC_VENDOR_ID 0x1ee9
+#define ZOTAC_ALT_VENDOR_ID 0x1e19
+#define ZOTAC_PRODUCT_ID 0x1590
+
+#define ZOTAC_GAMEPAD_INTERFACE 0
+#define ZOTAC_DIAL_INTERFACE 1
+#define ZOTAC_REPORT_INTERFACE 2
+#define ZOTAC_COMMAND_INTERFACE 3
+
+#define ZOTAC_FF_REPORT_ID 0x00
+#define ZOTAC_FF_REPORT_LEN 8
+#define ZOTAC_NUM_URBS 3
+#define ZOTAC_NUM_FF_URBS 2
+
+#define CMD_SAVE_CONFIG 0xFB
+#define CMD_SET_RGB 0xAD
+#define CMD_GET_RGB 0xAE
+
+#define ZOTAC_RGB_ZONE_COUNT 2 /* Number of physical zones (0 and 1) */
+#define ZOTAC_RGB_LEDS_PER_ZONE 10 /* Number of LEDs in each zone */
+
+#define SENSITIVITY_POINT_COUNT 4
+
+/* Command codes for button mapping */
+#define CMD_SET_BUTTON_MAPPING 0xA1
+#define CMD_GET_BUTTON_MAPPING 0xA2
+
+#define BUTTON_MAX 0x18
+#define MAX_GAMEPAD_BUTTONS 14
+#define MAX_KEYBOARD_KEYS 6
+#define MAX_MOUSE_BUTTONS 3
+
+#define DEVICE_ATTR_RO_NAMED(_name, _attr_name)               \
+	struct device_attribute dev_attr_##_name = {   \
+		.attr = { .name = _attr_name, .mode = 0444 }, \
+		.show = _name##_show,                         \
+	}
+
+#define DEVICE_ATTR_WO_NAMED(_name, _attr_name)               \
+	struct device_attribute dev_attr_##_name = {   \
+		.attr = { .name = _attr_name, .mode = 0200 }, \
+		.store = _name##_store,                       \
+	}
+
+#define DEVICE_ATTR_RW_NAMED(_name, _attr_name)               \
+	struct device_attribute dev_attr_##_name = {   \
+		.attr = { .name = _attr_name, .mode = 0644 }, \
+		.show = _name##_show,                         \
+		.store = _name##_store,                       \
+	}
+
+enum qam_mode {
+	QAM_MODE_KEYBOARD = 0,
+	QAM_MODE_STEAM,
+	QAM_MODE_CUSTOM,
+	QAM_MODE_LENGTH,
+};
+
+struct zotac_gamepad {
+	struct input_dev *dev; /* input device interface */
+	struct zotac_device *zotac; /* back-pointer to parent zotac device */
+	bool disconnect; /* set when device disconnected */
+
+	struct usb_endpoint_descriptor *ep_in;
+	struct usb_endpoint_descriptor *ep_out;
+
+	struct urb *urbs[ZOTAC_NUM_URBS];
+	unsigned char *urb_buf[ZOTAC_NUM_URBS];
+
+	struct urb *ff_urbs[ZOTAC_NUM_FF_URBS];
+	unsigned char *ff_data[ZOTAC_NUM_FF_URBS];
+	dma_addr_t ff_dma[ZOTAC_NUM_FF_URBS];
+	atomic_t ff_active[ZOTAC_NUM_FF_URBS];
+
+	atomic_t button_press_in_progress;
+
+	struct delayed_work button_work;
+	unsigned int button_to_press;
+	unsigned int button_to_press2;
+	bool update_qam;
+	enum qam_mode qam_mode;
+};
+
+struct zotac_rgb_dev {
+	struct zotac_device *zotac;
+	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[ZOTAC_RGB_LEDS_PER_ZONE];
+	uint8_t green[ZOTAC_RGB_LEDS_PER_ZONE];
+	uint8_t blue[ZOTAC_RGB_LEDS_PER_ZONE];
+	uint8_t brightness;
+};
+
+struct zotac_rgb_data {
+	struct {
+		uint8_t red[ZOTAC_RGB_LEDS_PER_ZONE];
+		uint8_t green[ZOTAC_RGB_LEDS_PER_ZONE];
+		uint8_t blue[ZOTAC_RGB_LEDS_PER_ZONE];
+		uint8_t brightness; // MC LED class level brightness
+	} zone[ZOTAC_RGB_ZONE_COUNT];
+	uint8_t effect; // Global effect
+	uint8_t speed; // Global speed
+	uint8_t brightness; // Global brightness
+	bool initialized;
+};
+
+struct stick_sensitivity {
+	/* X1, Y1, X2, Y2, X3, Y3, X4, Y4 */
+	u8 values[SENSITIVITY_POINT_COUNT * 2];
+};
+
+struct deadzone {
+	u8 inner;
+	u8 outer;
+};
+
+struct button_mapping {
+	u32 target_gamepad_buttons; /* Bit field for controller buttons */
+	u8 target_modifier_keys; /* Bit field for modifier keys */
+	u8 target_keyboard_keys[MAX_KEYBOARD_KEYS]; /* Array of keyboard key codes */
+	u8 target_mouse_buttons; /* Bit field for mouse buttons */
+};
+
+struct zotac_cfg_data {
+	struct mutex command_mutex;
+	u8 sequence_num;
+	/* 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
+	struct stick_sensitivity left_stick_sensitivity;
+	struct stick_sensitivity right_stick_sensitivity;
+	u8 button_turbo;
+	/* Indexed by the button number */
+	struct button_mapping button_mappings[BUTTON_MAX+1];
+};
+
+struct zotac_device {
+	struct hid_device *hdev;
+	struct input_dev *wheel_input;
+	struct input_dev *mouse_input;
+	struct zotac_gamepad *gamepad;
+	struct usb_device *udev;
+	struct zotac_cfg_data *cfg_data;
+	struct zotac_rgb_dev *led_rgb_dev;
+	struct zotac_rgb_data led_rgb_data;
+};
+extern struct zotac_device zotac;
+
+void zotac_init_input_device(struct input_dev *input_dev,
+			     struct hid_device *hdev, const char *name);
+
+struct usb_interface *zotac_get_usb_interface(struct hid_device *hdev);
+
+int zotac_init_gamepad(struct zotac_device *zotac, struct usb_interface *intf);
+
+void zotac_process_gamepad_report(struct zotac_device *zotac, u8 *data,
+				  int size);
+
+void zotac_cleanup_gamepad(struct zotac_device *zotac);
+
+void zotac_gamepad_send_button(struct zotac_device *zotac, int buttons[],
+			       int num_buttons);
+
+int zotac_cfg_init(struct zotac_device *zotac);
+
+void zotac_cfg_cleanup(struct zotac_device *zotac);
+
+int zotac_register_sysfs(struct zotac_device *zotac);
+
+void zotac_unregister_sysfs(struct zotac_device *zotac);
+
+int zotac_send_get_command(struct zotac_device *zotac, u8 cmd_code, u8 setting,
+			   const u8 *req_data, size_t req_data_len,
+			   u8 *output_data, size_t *output_len);
+
+int zotac_send_set_command(struct zotac_device *zotac, u8 cmd_code, u8 setting,
+			   const u8 *data, size_t data_len);
+
+int zotac_send_get_byte(struct zotac_device *zotac, u8 cmd_code, u8 setting,
+			const u8 *req_data, size_t req_data_len);
+
+/* RGB LED functions */
+int zotac_rgb_init(struct zotac_device *zotac);
+void zotac_rgb_cleanup(struct zotac_device *zotac);
+void zotac_rgb_resume(struct zotac_device *zotac);
+void zotac_rgb_suspend(struct zotac_device *zotac);
+
+#endif /* __HID_ZOTAC_ZONE_H */
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index f91f713b0105..4d645de56bec 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2110,6 +2110,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 766c652ef22b..d669fb4a8218 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -212,6 +212,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/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index 1008858f78e2..8e3cf2a55bea 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -353,7 +353,9 @@ static const struct xpad_device {
 	{ 0x1bad, 0xfa01, "MadCatz GamePad", 0, XTYPE_XBOX360 },
 	{ 0x1bad, 0xfd00, "Razer Onza TE", 0, XTYPE_XBOX360 },
 	{ 0x1bad, 0xfd01, "Razer Onza", 0, XTYPE_XBOX360 },
+	#if !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID)
 	{ 0x1ee9, 0x1590, "ZOTAC Gaming Zone", 0, XTYPE_XBOX360 },
+	#endif /* !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID) */
 	{ 0x20d6, 0x2001, "BDA Xbox Series X Wired Controller", 0, XTYPE_XBOXONE },
 	{ 0x20d6, 0x2009, "PowerA Enhanced Wired Controller for Xbox Series X|S", 0, XTYPE_XBOXONE },
 	{ 0x20d6, 0x2064, "PowerA Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
@@ -550,7 +552,9 @@ static const struct usb_device_id xpad_table[] = {
 	XPAD_XBOX360_VENDOR(0x1949),		/* Amazon controllers */
 	XPAD_XBOX360_VENDOR(0x1a86),		/* Nanjing Qinheng Microelectronics (WCH) */
 	XPAD_XBOX360_VENDOR(0x1bad),		/* Harmonix Rock Band guitar and drums */
+	#if !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID)
 	XPAD_XBOX360_VENDOR(0x1ee9),		/* ZOTAC Technology Limited */
+	#endif /* !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID) */
 	XPAD_XBOX360_VENDOR(0x20d6),		/* PowerA controllers */
 	XPAD_XBOXONE_VENDOR(0x20d6),		/* PowerA controllers */
 	XPAD_XBOX360_VENDOR(0x2345),		/* Machenike Controllers */
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a104cbb0a001..6fd7921006f9 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -1013,6 +1013,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 2f170d69dcbf..10bf84080e28 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -85,6 +85,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..3d56ec4403cd
--- /dev/null
+++ b/drivers/leds/leds-steamdeck.c
@@ -0,0 +1,123 @@
+// 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 ssize_t led_brightness_multiplier_show(struct device *dev,
+					      struct device_attribute *attr,
+					      char *buf)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led,
+						cdev);
+	unsigned long long led_brightness_multiplier;
+
+	if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle,
+					       "GLDM",
+					       NULL,
+					       &led_brightness_multiplier)))
+		return -EIO;
+
+
+	return sprintf(buf, "%llu", led_brightness_multiplier);
+}
+
+static ssize_t led_brightness_multiplier_store(struct device *dev,
+					       struct device_attribute *attr,
+					       const char *buf, size_t count)
+{
+	struct led_classdev *cdev = dev_get_drvdata(dev);
+	struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led,
+						cdev);
+	unsigned long value;
+
+	if (kstrtoul(buf, 10, &value) || value > 100)
+		return -EINVAL;
+
+
+	if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
+						    "SLDM", value)))
+		return -EIO;
+
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(led_brightness_multiplier);
+
+static struct attribute *steamdeck_led_attrs[] = {
+	&dev_attr_led_brightness_multiplier.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(steamdeck_led);
+
+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;
+	sd->cdev.groups = steamdeck_led_groups;
+
+	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 22b936310039..a788bb645c95 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2422,5 +2422,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 948cbdf42a18..e14823b233e3 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -289,4 +289,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 0281ce6fb717..e76a020d4fbb 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 731d18308cb6..6507dffc2aa4 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -482,6 +482,47 @@ config IBM_RTL
 	 state = 0 (BIOS SMIs on)
 	 state = 1 (BIOS SMIs off)
 
+config LENOVO_WMI_EVENTS
+	tristate
+	depends on ACPI_WMI
+
+config LENOVO_WMI_HELPERS
+	tristate
+	depends on ACPI_WMI
+
+config LENOVO_WMI_GAMEZONE
+	tristate "Lenovo GameZone WMI Driver"
+	depends on ACPI_WMI
+	depends on DMI
+	select ACPI_PLATFORM_PROFILE
+	select LENOVO_WMI_EVENTS
+	select LENOVO_WMI_HELPERS
+	help
+	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+	  platform-profile firmware interface to manage power usage.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo-wmi-gamezone.
+
+config LENOVO_WMI_DATA01
+	tristate
+	depends on ACPI_WMI
+
+config LENOVO_WMI_TUNING
+	tristate "Lenovo Other Mode WMI Driver"
+	depends on ACPI_WMI
+	select FW_ATTR_CLASS
+	select LENOVO_WMI_DATA01
+	select LENOVO_WMI_EVENTS
+	select LENOVO_WMI_HELPERS
+	help
+	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
+	  firmware_attributes API to control various tunable settings typically exposed by
+	  Lenovo software in Windows.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called lenovo-wmi-other.
+
 config IDEAPAD_LAPTOP
 	tristate "Lenovo IdeaPad Laptop Extras"
 	depends on ACPI
@@ -761,8 +802,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.
@@ -1224,6 +1268,16 @@ config SEL3350_PLATFORM
 	  To compile this driver as a module, choose M here: the module
 	  will be called sel3350-platform.
 
+config ZOTAC_ZONE_PLATFORM
+	tristate "Zotac Zone platform driver"
+	select FW_ATTR_CLASS
+	select ACPI_PLATFORM_PROFILE
+	help
+	  Support for fans and PPT on Zotac Zone devices.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called zotac-zone.
+
 endif # X86_PLATFORM_DEVICES
 
 config P2SB
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 3a0085f941d9..14f22d19bda0 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -70,6 +70,11 @@ obj-$(CONFIG_THINKPAD_LMI)	+= think-lmi.o
 obj-$(CONFIG_YOGABOOK)		+= lenovo-yogabook.o
 obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
 obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
+obj-$(CONFIG_LENOVO_WMI_DATA01)	+= lenovo-wmi-capdata01.o
+obj-$(CONFIG_LENOVO_WMI_EVENTS)	+= lenovo-wmi-events.o
+obj-$(CONFIG_LENOVO_WMI_GAMEZONE)	+= lenovo-wmi-gamezone.o
+obj-$(CONFIG_LENOVO_WMI_HELPERS)	+= lenovo-wmi-helpers.o
+obj-$(CONFIG_LENOVO_WMI_TUNING)	+= lenovo-wmi-other.o
 
 # Intel
 obj-y				+= intel/
@@ -155,3 +160,5 @@ obj-$(CONFIG_WINMATE_FM07_KEYS)		+= winmate-fm07-keys.o
 
 # SEL
 obj-$(CONFIG_SEL3350_PLATFORM)		+= sel3350-platform.o
+
+obj-$(CONFIG_ZOTAC_ZONE_PLATFORM)		+= zotac-zone-platform.o
diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
new file mode 100644
index 000000000000..2bd862a75c71
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Capability Data 01 WMI Data Block driver.
+ *
+ * Lenovo Capability Data 01 provides information on tunable attributes used by
+ * the "Other Mode" WMI interface. The data includes if the attribute is
+ * supported by the hardware, the default_value, max_value, min_value, and step
+ * increment. Each attribute has multiple pages, one for each of the thermal
+ * modes managed by the Gamezone interface.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mutex_types.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-capdata01.h"
+
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+#define ACPI_AC_CLASS "ac_adapter"
+#define ACPI_AC_NOTIFY_STATUS 0x80
+
+struct lwmi_cd01_priv {
+	struct notifier_block acpi_nb; /* ACPI events */
+	struct wmi_device *wdev;
+	struct cd01_list *list;
+};
+
+struct cd01_list {
+	struct mutex list_mutex; /* list R/W mutex */
+	u8 count;
+	struct capdata01 data[];
+};
+
+/**
+ * lwmi_cd01_component_bind() - Bind component to master device.
+ * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device.
+ * @om_dev: Pointer to the lenovo-wmi-other driver parent device.
+ * @data: capdata01_list object pointer used to return the capability data.
+ *
+ * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01
+ * list. This is used to call lwmi_cd01_get_data to look up attribute data
+ * from the lenovo-wmi-other driver.
+ *
+ * Return: 0
+ */
+static int lwmi_cd01_component_bind(struct device *cd01_dev,
+				    struct device *om_dev, void *data)
+{
+	struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
+	struct cd01_list **cd01_list = data;
+
+	*cd01_list = priv->list;
+
+	return 0;
+}
+
+static const struct component_ops lwmi_cd01_component_ops = {
+	.bind = lwmi_cd01_component_bind,
+};
+
+/**
+ * lwmi_cd01_get_data - Get the data of the specified attribute
+ * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct.
+ * @attribute_id: The capdata attribute ID to be found.
+ * @output: Pointer to a capdata01 struct to return the data.
+ *
+ * Retrieves the capability data 01 struct pointer for the given
+ * attribute for its specified thermal mode.
+ *
+ * Return: 0 on success, or -EINVAL.
+ */
+int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output)
+{
+	u8 idx;
+
+	guard(mutex)(&list->list_mutex);
+	for (idx = 0; idx < list->count; idx++) {
+		if (list->data[idx].id != attribute_id)
+			continue;
+		memcpy(output, &list->data[idx], sizeof(list->data[idx]));
+		return 0;
+	};
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01");
+
+/**
+ * lwmi_cd01_cache() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Loop through each WMI data block and cache the data.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv)
+{
+	int idx;
+
+	guard(mutex)(&priv->list->list_mutex);
+	for (idx = 0; idx < priv->list->count; idx++) {
+		union acpi_object *ret_obj __free(kfree) = NULL;
+
+		ret_obj = wmidev_block_query(priv->wdev, idx);
+		if (!ret_obj)
+			return -ENODEV;
+
+		if (ret_obj->type != ACPI_TYPE_BUFFER ||
+		    ret_obj->buffer.length < sizeof(priv->list->data[idx]))
+			continue;
+
+		memcpy(&priv->list->data[idx], ret_obj->buffer.pointer,
+		       ret_obj->buffer.length);
+	}
+
+	return 0;
+}
+
+/**
+ * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface.
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv)
+{
+	struct cd01_list *list;
+	size_t list_size;
+	int count, ret;
+
+	count = wmidev_instance_count(priv->wdev);
+	list_size = struct_size(list, data, count);
+
+	list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL);
+	if (!list)
+		return -ENOMEM;
+
+	ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex);
+	if (ret)
+		return ret;
+
+	list->count = count;
+	priv->list = list;
+
+	return 0;
+}
+
+/**
+ * lwmi_cd01_setup() - Cache all WMI data block information
+ * @priv: lenovo-wmi-capdata01 driver data.
+ *
+ * Allocate a cd01_list struct large enough to contain data from all WMI data
+ * blocks provided by the interface. Then loop through each data block and
+ * cache the data.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv)
+{
+	int ret;
+
+	ret = lwmi_cd01_alloc(priv);
+	if (ret)
+		return ret;
+
+	return lwmi_cd01_cache(priv);
+}
+
+/**
+ * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @action: Unused.
+ * @data: The ACPI event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long action,
+				   void *data)
+{
+	struct acpi_bus_event *event = data;
+	struct lwmi_cd01_priv *priv;
+	int ret;
+
+	if (strcmp(event->device_class, ACPI_AC_CLASS) != 0)
+		return NOTIFY_DONE;
+
+	priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb);
+
+	switch (event->type) {
+	case ACPI_AC_NOTIFY_STATUS:
+		ret = lwmi_cd01_cache(priv);
+		if (ret)
+			return NOTIFY_BAD;
+
+		return NOTIFY_OK;
+	default:
+		return NOTIFY_DONE;
+	}
+}
+
+/**
+ * lwmi_cd01_unregister() - Unregister the cd01 ACPI notifier_block.
+ * @data: The ACPI event notifier_block to unregister.
+ */
+static void lwmi_cd01_unregister(void *data)
+{
+	struct notifier_block *acpi_nb = data;
+
+	unregister_acpi_notifier(acpi_nb);
+}
+
+static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context)
+
+{
+	struct lwmi_cd01_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	ret = lwmi_cd01_setup(priv);
+	if (ret)
+		return ret;
+
+	priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call;
+
+	ret = register_acpi_notifier(&priv->acpi_nb);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb);
+	if (ret)
+		return ret;
+
+	return component_add(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static void lwmi_cd01_remove(struct wmi_device *wdev)
+{
+	component_del(&wdev->dev, &lwmi_cd01_component_ops);
+}
+
+static const struct wmi_device_id lwmi_cd01_id_table[] = {
+	{ LENOVO_CAPABILITY_DATA_01_GUID, NULL },
+	{}
+};
+
+static struct wmi_driver lwmi_cd01_driver = {
+	.driver = {
+		.name = "lenovo_wmi_cd01",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_cd01_id_table,
+	.probe = lwmi_cd01_probe,
+	.remove = lwmi_cd01_remove,
+	.no_singleton = true,
+};
+
+/**
+ * lwmi_cd01_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data 01 parent device.
+ * @data: Unused void pointer for passing match criteria.
+ *
+ * Return: int.
+ */
+int lwmi_cd01_match(struct device *dev, void *data)
+{
+	return dev->driver == &lwmi_cd01_driver.driver;
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01");
+
+module_wmi_driver(lwmi_cd01_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.h b/drivers/platform/x86/lenovo-wmi-capdata01.h
new file mode 100644
index 000000000000..bd06c5751f68
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_CAPDATA01_H_
+#define _LENOVO_WMI_CAPDATA01_H_
+
+#include <linux/types.h>
+
+struct device;
+struct cd01_list;
+
+struct capdata01 {
+	u32 id;
+	u32 supported;
+	u32 default_value;
+	u32 step;
+	u32 min_value;
+	u32 max_value;
+};
+
+int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output);
+int lwmi_cd01_match(struct device *dev, void *data);
+
+#endif /* !_LENOVO_WMI_CAPDATA01_H_ */
diff --git a/drivers/platform/x86/lenovo-wmi-events.c b/drivers/platform/x86/lenovo-wmi-events.c
new file mode 100644
index 000000000000..2e0bbda5b976
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-events.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo WMI Events driver. Lenovo WMI interfaces provide various
+ * hardware triggered events that many drivers need to have propagated.
+ * This driver provides a uniform entrypoint for these events so that
+ * any driver that needs to respond to these events can subscribe to a
+ * notifier chain.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-events.h"
+#include "lenovo-wmi-gamezone.h"
+
+#define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
+
+#define LWMI_EVENT_DEVICE(guid, type)                        \
+	.guid_string = (guid), .context = &(enum lwmi_events_type) \
+	{                                                          \
+		type                                               \
+	}
+
+static BLOCKING_NOTIFIER_HEAD(events_chain_head);
+
+struct lwmi_events_priv {
+	struct wmi_device *wdev;
+	enum lwmi_events_type type;
+};
+
+/**
+ * lwmi_events_register_notifier() - Add a notifier to the notifier chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_register to register the notifier block to the
+ * lenovo-wmi-events driver blocking notifier chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+int lwmi_events_register_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&events_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
+
+/**
+ * lwmi_events_unregister_notifier() - Remove a notifier from the notifier
+ * chain.
+ * @nb: The notifier_block struct to unregister
+ *
+ * Call blocking_notifier_chain_unregister to unregister the notifier block
+ * from the lenovo-wmi-events driver blocking notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+int lwmi_events_unregister_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&events_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_events_unregister_notifier, "LENOVO_WMI_EVENTS");
+
+/**
+ * devm_lwmi_events_unregister_notifier() - Remove a notifier from the notifier
+ * chain.
+ * @data: Void pointer to the notifier_block struct to unregister.
+ *
+ * Call lwmi_events_unregister_notifier to unregister the notifier block from
+ * the lenovo-wmi-events driver blocking notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_events_unregister_notifier(void *data)
+{
+	struct notifier_block *nb = data;
+
+	lwmi_events_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_events_register_notifier() - Add a notifier to the notifier chain.
+ * @dev: The parent device of the notifier_block struct.
+ * @nb: The notifier_block struct to register
+ *
+ * Call lwmi_events_register_notifier to register the notifier block to the
+ * lenovo-wmi-events driver blocking notifier chain. Then add, as a device
+ * managed action, unregister_notifier to automatically unregister the
+ * notifier block upon its parent device removal.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int devm_lwmi_events_register_notifier(struct device *dev,
+				       struct notifier_block *nb)
+{
+	int ret;
+
+	ret = lwmi_events_register_notifier(nb);
+	if (ret < 0)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_lwmi_events_unregister_notifier, nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_events_register_notifier, "LENOVO_WMI_EVENTS");
+
+/**
+ * lwmi_events_notify() - Call functions for the notifier call chain.
+ * @wdev: The parent WMI device of the driver.
+ * @obj: ACPI object passed by the registered WMI Event.
+ *
+ * Validate WMI event data and notify all registered drivers of the event and
+ * its output.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static void lwmi_events_notify(struct wmi_device *wdev, union acpi_object *obj)
+{
+	struct lwmi_events_priv *priv = dev_get_drvdata(&wdev->dev);
+	int sel_prof;
+	int ret;
+
+	switch (priv->type) {
+	case LWMI_EVENT_THERMAL_MODE:
+		if (obj->type != ACPI_TYPE_INTEGER)
+			return;
+
+		sel_prof = obj->integer.value;
+
+		switch (sel_prof) {
+		case LWMI_GZ_THERMAL_MODE_QUIET:
+		case LWMI_GZ_THERMAL_MODE_BALANCED:
+		case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
+		case LWMI_GZ_THERMAL_MODE_EXTREME:
+		case LWMI_GZ_THERMAL_MODE_CUSTOM:
+			ret = blocking_notifier_call_chain(&events_chain_head,
+							   LWMI_EVENT_THERMAL_MODE,
+							   &sel_prof);
+			if (ret == NOTIFY_BAD)
+				dev_err(&wdev->dev,
+					"Failed to send notification to call chain for WMI Events\n");
+			return;
+		default:
+			dev_err(&wdev->dev, "Got invalid thermal mode: %x",
+				sel_prof);
+			return;
+		}
+		break;
+	default:
+		return;
+	}
+}
+
+static int lwmi_events_probe(struct wmi_device *wdev, const void *context)
+{
+	struct lwmi_events_priv *priv;
+
+	if (!context)
+		return -EINVAL;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wdev = wdev;
+	priv->type = *(enum lwmi_events_type *)context;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	return 0;
+}
+
+static const struct wmi_device_id lwmi_events_id_table[] = {
+	{ LWMI_EVENT_DEVICE(THERMAL_MODE_EVENT_GUID, LWMI_EVENT_THERMAL_MODE) },
+	{}
+};
+
+static struct wmi_driver lwmi_events_driver = {
+	.driver = {
+		.name = "lenovo_wmi_events",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_events_id_table,
+	.probe = lwmi_events_probe,
+	.notify = lwmi_events_notify,
+	.no_singleton = true,
+};
+
+module_wmi_driver(lwmi_events_driver);
+
+MODULE_DEVICE_TABLE(wmi, lwmi_events_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Events Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-events.h b/drivers/platform/x86/lenovo-wmi-events.h
new file mode 100644
index 000000000000..cd34e886912c
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-events.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_EVENTS_H_
+#define _LENOVO_WMI_EVENTS_H_
+
+struct device;
+struct notifier_block;
+
+enum lwmi_events_type {
+	LWMI_EVENT_THERMAL_MODE = 1,
+};
+
+int lwmi_events_register_notifier(struct notifier_block *nb);
+int lwmi_events_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_events_register_notifier(struct device *dev,
+				       struct notifier_block *nb);
+
+#endif /* !_LENOVO_WMI_EVENTS_H_ */
diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.c b/drivers/platform/x86/lenovo-wmi-gamezone.c
new file mode 100644
index 000000000000..261284de22b6
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo GameZone WMI interface driver.
+ *
+ * The GameZone WMI interface provides platform profile and fan curve settings
+ * for devices that fall under the "Gaming Series" of Lenovo Legion devices.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/spinlock.h>
+#include <linux/spinlock_types.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-events.h"
+#include "lenovo-wmi-gamezone.h"
+#include "lenovo-wmi-helpers.h"
+#include "lenovo-wmi-other.h"
+
+#define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+
+#define LWMI_GZ_METHOD_ID_SMARTFAN_SUP 43
+#define LWMI_GZ_METHOD_ID_SMARTFAN_SET 44
+#define LWMI_GZ_METHOD_ID_SMARTFAN_GET 45
+
+static BLOCKING_NOTIFIER_HEAD(gz_chain_head);
+
+struct lwmi_gz_priv {
+	enum thermal_mode current_mode;
+	struct notifier_block event_nb;
+	struct notifier_block mode_nb;
+	spinlock_t gz_mode_lock; /* current_mode lock */
+	struct wmi_device *wdev;
+	int extreme_supported;
+	struct device *ppdev;
+};
+
+struct quirk_entry {
+	bool extreme_supported;
+};
+
+static struct quirk_entry quirk_no_extreme_bug = {
+	.extreme_supported = false,
+};
+
+/**
+ * lwmi_gz_mode_call() - Call method for lenovo-wmi-other driver notifier.
+ *
+ * @nb: The notifier_block registered to lenovo-wmi-other driver.
+ * @cmd: The event type.
+ * @data: Thermal mode enum pointer pointer for returning the thermal mode.
+ *
+ * For LWMI_GZ_GET_THERMAL_MODE, retrieve the current thermal mode.
+ *
+ * Return: Notifier_block status.
+ */
+static int lwmi_gz_mode_call(struct notifier_block *nb, unsigned long cmd,
+			     void *data)
+{
+	enum thermal_mode **mode = data;
+	struct lwmi_gz_priv *priv;
+
+	priv = container_of(nb, struct lwmi_gz_priv, mode_nb);
+
+	switch (cmd) {
+	case LWMI_GZ_GET_THERMAL_MODE:
+		scoped_guard(spinlock, &priv->gz_mode_lock) {
+			**mode = priv->current_mode;
+		}
+		return NOTIFY_OK;
+	default:
+		return NOTIFY_DONE;
+	}
+}
+
+/**
+ * lwmi_gz_event_call() - Call method for lenovo-wmi-events driver notifier.
+ * block call chain.
+ * @nb: The notifier_block registered to lenovo-wmi-events driver.
+ * @cmd: The event type.
+ * @data: The data to be updated by the event.
+ *
+ * For LWMI_EVENT_THERMAL_MODE, set current_mode and notify platform_profile
+ * of a change.
+ *
+ * Return: notifier_block status.
+ */
+static int lwmi_gz_event_call(struct notifier_block *nb, unsigned long cmd,
+			      void *data)
+{
+	enum thermal_mode *mode = data;
+	struct lwmi_gz_priv *priv;
+
+	priv = container_of(nb, struct lwmi_gz_priv, event_nb);
+
+	switch (cmd) {
+	case LWMI_EVENT_THERMAL_MODE:
+		scoped_guard(spinlock, &priv->gz_mode_lock) {
+			priv->current_mode = *mode;
+		}
+		platform_profile_notify(priv->ppdev);
+		return NOTIFY_STOP;
+	default:
+		return NOTIFY_DONE;
+	}
+}
+
+/**
+ * lwmi_gz_thermal_mode_supported() - Get the version of the WMI
+ * interface to determine the support level.
+ * @wdev: The Gamezone WMI device.
+ * @supported: Pointer to return the support level with.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_thermal_mode_supported(struct wmi_device *wdev,
+					  int *supported)
+{
+	return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_SUP,
+				     NULL, 0, supported);
+}
+
+/**
+ * lwmi_gz_thermal_mode_get() - Get the current thermal mode.
+ * @wdev: The Gamezone interface WMI device.
+ * @mode: Pointer to return the thermal mode with.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_thermal_mode_get(struct wmi_device *wdev,
+				    enum thermal_mode *mode)
+{
+	return lwmi_dev_evaluate_int(wdev, 0x0, LWMI_GZ_METHOD_ID_SMARTFAN_GET,
+				     NULL, 0, mode);
+}
+
+/**
+ * lwmi_gz_profile_get() - Get the current platform profile.
+ * @dev: the Gamezone interface parent device.
+ * @profile: Pointer to provide the current platform profile with.
+ *
+ * Call lwmi_gz_thermal_mode_get and convert the thermal mode into a platform
+ * profile based on the support level of the interface.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_profile_get(struct device *dev,
+			       enum platform_profile_option *profile)
+{
+	struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
+	enum thermal_mode mode;
+	int ret;
+
+	ret = lwmi_gz_thermal_mode_get(priv->wdev, &mode);
+	if (ret)
+		return ret;
+
+	switch (mode) {
+	case LWMI_GZ_THERMAL_MODE_QUIET:
+		*profile = PLATFORM_PROFILE_LOW_POWER;
+		break;
+	case LWMI_GZ_THERMAL_MODE_BALANCED:
+		*profile = PLATFORM_PROFILE_BALANCED;
+		break;
+	case LWMI_GZ_THERMAL_MODE_PERFORMANCE:
+		if (priv->extreme_supported) {
+			*profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+			break;
+		}
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		break;
+	case LWMI_GZ_THERMAL_MODE_EXTREME:
+		*profile = PLATFORM_PROFILE_PERFORMANCE;
+		break;
+	case LWMI_GZ_THERMAL_MODE_CUSTOM:
+		*profile = PLATFORM_PROFILE_CUSTOM;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	guard(spinlock)(&priv->gz_mode_lock);
+	priv->current_mode = mode;
+
+	return 0;
+}
+
+/**
+ * lwmi_gz_profile_set() - Set the current platform profile.
+ * @dev: The Gamezone interface parent device.
+ * @profile: Pointer to the desired platform profile.
+ *
+ * Convert the given platform profile into a thermal mode based on the support
+ * level of the interface, then call the WMI method to set the thermal mode.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_profile_set(struct device *dev,
+			       enum platform_profile_option profile)
+{
+	struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
+	struct wmi_method_args_32 args;
+	enum thermal_mode mode;
+	int ret;
+
+	switch (profile) {
+	case PLATFORM_PROFILE_LOW_POWER:
+		mode = LWMI_GZ_THERMAL_MODE_QUIET;
+		break;
+	case PLATFORM_PROFILE_BALANCED:
+		mode = LWMI_GZ_THERMAL_MODE_BALANCED;
+		break;
+	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
+		mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+		break;
+	case PLATFORM_PROFILE_PERFORMANCE:
+		if (priv->extreme_supported) {
+			mode = LWMI_GZ_THERMAL_MODE_EXTREME;
+			break;
+		}
+		mode = LWMI_GZ_THERMAL_MODE_PERFORMANCE;
+		break;
+	case PLATFORM_PROFILE_CUSTOM:
+		mode = LWMI_GZ_THERMAL_MODE_CUSTOM;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	args.arg0 = mode;
+
+	ret = lwmi_dev_evaluate_int(priv->wdev, 0x0,
+				    LWMI_GZ_METHOD_ID_SMARTFAN_SET,
+				    (u8 *)&args, sizeof(args), NULL);
+	if (ret)
+		return ret;
+
+	guard(spinlock)(&priv->gz_mode_lock);
+	priv->current_mode = mode;
+
+	return 0;
+}
+
+static const struct dmi_system_id fwbug_list[] = {
+	{
+		.ident = "Legion Go 8APU1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go 8APU1"),
+		},
+		.driver_data = &quirk_no_extreme_bug,
+	},
+	{
+		.ident = "Legion Go S 8APU1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8APU1"),
+		},
+		.driver_data = &quirk_no_extreme_bug,
+	},
+	{
+		.ident = "Legion Go S 8ARP1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "Legion Go S 8ARP1"),
+		},
+		.driver_data = &quirk_no_extreme_bug,
+	},
+	{},
+
+};
+
+/**
+ * lwmi_gz_extreme_supported() - Evaluate if a device supports extreme thermal mode.
+ * @profile_support_ver: Version of the WMI interface.
+ *
+ * Determine if the extreme thermal mode is supported by the hardware.
+ * Anything version 5 or lower does not. For devices with a version 6 or
+ * greater do a DMI check, as some devices report a version that supports
+ * extreme mode but have an incomplete entry in the BIOS. To ensure this
+ * cannot be set, quirk them to prevent assignment.
+ *
+ * Return: bool.
+ */
+static bool lwmi_gz_extreme_supported(int profile_support_ver)
+{
+	const struct dmi_system_id *dmi_id;
+	struct quirk_entry *quirks;
+
+	if (profile_support_ver < 6)
+		return false;
+
+	dmi_id = dmi_first_match(fwbug_list);
+	if (!dmi_id)
+		return true;
+
+	quirks = dmi_id->driver_data;
+
+	return quirks->extreme_supported;
+}
+
+/**
+ * lwmi_gz_platform_profile_probe - Enable and set up the platform profile
+ * device.
+ * @drvdata: Driver data for the interface.
+ * @choices: Container for enabled platform profiles.
+ *
+ * Determine if thermal mode is supported, and if so to what feature level.
+ * Then enable all supported platform profiles.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_gz_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+	struct lwmi_gz_priv *priv = drvdata;
+	int profile_support_ver;
+	int ret;
+
+	ret = lwmi_gz_thermal_mode_supported(priv->wdev, &profile_support_ver);
+	if (ret)
+		return ret;
+
+	if (profile_support_ver < 1)
+		return -ENODEV;
+
+	set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+	set_bit(PLATFORM_PROFILE_BALANCED, choices);
+	set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+	set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+
+	priv->extreme_supported = lwmi_gz_extreme_supported(profile_support_ver);
+	if (priv->extreme_supported)
+		set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+
+	return 0;
+}
+
+static const struct platform_profile_ops lwmi_gz_platform_profile_ops = {
+	.probe = lwmi_gz_platform_profile_probe,
+	.profile_get = lwmi_gz_profile_get,
+	.profile_set = lwmi_gz_profile_set,
+};
+
+static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
+{
+	struct lwmi_gz_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	priv->ppdev = devm_platform_profile_register(&wdev->dev, "lenovo-wmi-gamezone",
+						     priv, &lwmi_gz_platform_profile_ops);
+	if (IS_ERR(priv->ppdev))
+		return -ENODEV;
+
+	spin_lock_init(&priv->gz_mode_lock);
+
+	ret = lwmi_gz_thermal_mode_get(wdev, &priv->current_mode);
+	if (ret)
+		return ret;
+
+	priv->event_nb.notifier_call = lwmi_gz_event_call;
+	ret = devm_lwmi_events_register_notifier(&wdev->dev, &priv->event_nb);
+	if (ret)
+		return ret;
+
+	priv->mode_nb.notifier_call = lwmi_gz_mode_call;
+	return devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
+}
+
+static const struct wmi_device_id lwmi_gz_id_table[] = {
+	{ LENOVO_GAMEZONE_GUID, NULL },
+	{}
+};
+
+static struct wmi_driver lwmi_gz_driver = {
+	.driver = {
+		.name = "lenovo_wmi_gamezone",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_gz_id_table,
+	.probe = lwmi_gz_probe,
+	.no_singleton = true,
+};
+
+module_wmi_driver(lwmi_gz_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
+MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-gamezone.h b/drivers/platform/x86/lenovo-wmi-gamezone.h
new file mode 100644
index 000000000000..6b163a5eeb95
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-gamezone.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_GAMEZONE_H_
+#define _LENOVO_WMI_GAMEZONE_H_
+
+enum gamezone_events_type {
+	LWMI_GZ_GET_THERMAL_MODE = 1,
+};
+
+enum thermal_mode {
+	LWMI_GZ_THERMAL_MODE_QUIET =	   0x01,
+	LWMI_GZ_THERMAL_MODE_BALANCED =	   0x02,
+	LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
+	LWMI_GZ_THERMAL_MODE_EXTREME =	   0xE0, /* Ver 6+ */
+	LWMI_GZ_THERMAL_MODE_CUSTOM =	   0xFF,
+};
+
+#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
diff --git a/drivers/platform/x86/lenovo-wmi-helpers.c b/drivers/platform/x86/lenovo-wmi-helpers.c
new file mode 100644
index 000000000000..4a194aad1ed0
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-helpers.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Legion WMI helpers driver.
+ *
+ * The Lenovo Legion WMI interface is broken up into multiple GUID interfaces
+ * that require cross-references between GUID's for some functionality. The
+ * "Custom Mode" interface is a legacy interface for managing and displaying
+ * CPU & GPU power and hwmon settings and readings. The "Other Mode" interface
+ * is a modern interface that replaces or extends the "Custom Mode" interface
+ * methods. The "Gamezone" interface adds advanced features such as fan
+ * profiles and overclocking. The "Lighting" interface adds control of various
+ * status lights related to different hardware components. Each of these
+ * drivers uses a common procedure to get data from the WMI interface,
+ * enumerated here.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-helpers.h"
+
+/**
+ * lwmi_dev_evaluate_int() - Helper function for calling WMI methods that
+ * return an integer.
+ * @wdev: Pointer to the WMI device to be called.
+ * @instance: Instance of the called method.
+ * @method_id: WMI Method ID for the method to be called.
+ * @buf: Buffer of all arguments for the given method_id.
+ * @size: Length of the buffer.
+ * @retval: Pointer for the return value to be assigned.
+ *
+ * Calls wmidev_evaluate_method for Lenovo WMI devices that return an ACPI
+ * integer. Validates the return value type and assigns the value to the
+ * retval pointer.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
+			  unsigned char *buf, size_t size, u32 *retval)
+{
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *ret_obj __free(kfree) = NULL;
+	struct acpi_buffer input = { size, buf };
+	acpi_status status;
+
+	status = wmidev_evaluate_method(wdev, instance, method_id, &input,
+					&output);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	if (retval) {
+		ret_obj = output.pointer;
+		if (!ret_obj)
+			return -ENODATA;
+
+		if (ret_obj->type != ACPI_TYPE_INTEGER)
+			return -ENXIO;
+
+		*retval = (u32)ret_obj->integer.value;
+	}
+
+	return 0;
+};
+EXPORT_SYMBOL_NS_GPL(lwmi_dev_evaluate_int, "LENOVO_WMI_HELPERS");
+
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-helpers.h b/drivers/platform/x86/lenovo-wmi-helpers.h
new file mode 100644
index 000000000000..20fd21749803
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-helpers.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_HELPERS_H_
+#define _LENOVO_WMI_HELPERS_H_
+
+#include <linux/types.h>
+
+struct wmi_device;
+
+struct wmi_method_args_32 {
+	u32 arg0;
+	u32 arg1;
+};
+
+int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
+			  unsigned char *buf, size_t size, u32 *retval);
+
+#endif /* !_LENOVO_WMI_HELPERS_H_ */
diff --git a/drivers/platform/x86/lenovo-wmi-other.c b/drivers/platform/x86/lenovo-wmi-other.c
new file mode 100644
index 000000000000..299fc87189ec
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Other Mode WMI interface driver.
+ *
+ * This driver uses the fw_attributes class to expose the various WMI functions
+ * provided by the "Other Mode" WMI interface. This enables CPU and GPU power
+ * limit as well as various other attributes for devices that fall under the
+ * "Gaming Series" of Lenovo laptop devices. Each attribute exposed by the
+ * "Other Mode" interface has a corresponding Capability Data struct that
+ * allows the driver to probe details about the attribute such as if it is
+ * supported by the hardware, the default_value, max_value, min_value, and step
+ * increment.
+ *
+ * These attributes typically don't fit anywhere else in the sysfs and are set
+ * in Windows using one of Lenovo's multiple user applications.
+ *
+ * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gfp_types.h>
+#include <linux/idr.h>
+#include <linux/kdev_t.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_profile.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+
+#include "lenovo-wmi-capdata01.h"
+#include "lenovo-wmi-events.h"
+#include "lenovo-wmi-gamezone.h"
+#include "lenovo-wmi-helpers.h"
+#include "lenovo-wmi-other.h"
+#include "firmware_attributes_class.h"
+
+#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+
+#define LWMI_DEVICE_ID_CPU 0x01
+
+#define LWMI_FEATURE_ID_CPU_SPPT 0x01
+#define LWMI_FEATURE_ID_CPU_SPL 0x02
+#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+
+#define LWMI_TYPE_ID_NONE 0x00
+
+#define LWMI_FEATURE_VALUE_GET 17
+#define LWMI_FEATURE_VALUE_SET 18
+
+#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24)
+#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16)
+#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
+#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+
+#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+
+static BLOCKING_NOTIFIER_HEAD(om_chain_head);
+static DEFINE_IDA(lwmi_om_ida);
+
+enum attribute_property {
+	DEFAULT_VAL,
+	MAX_VAL,
+	MIN_VAL,
+	STEP_VAL,
+	SUPPORTED,
+};
+
+struct lwmi_om_priv {
+	struct component_master_ops *ops;
+	struct cd01_list *cd01_list; /* only valid after capdata01 bind */
+	struct device *fw_attr_dev;
+	struct kset *fw_attr_kset;
+	struct notifier_block nb;
+	struct wmi_device *wdev;
+	int ida_id;
+};
+
+struct tunable_attr_01 {
+	struct capdata01 *capdata;
+	struct device *dev;
+	u32 feature_id;
+	u32 device_id;
+	u32 type_id;
+};
+
+static struct tunable_attr_01 ppt_pl1_spl = {
+	.device_id = LWMI_DEVICE_ID_CPU,
+	.feature_id = LWMI_FEATURE_ID_CPU_SPL,
+	.type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl2_sppt = {
+	.device_id = LWMI_DEVICE_ID_CPU,
+	.feature_id = LWMI_FEATURE_ID_CPU_SPPT,
+	.type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl3_fppt = {
+	.device_id = LWMI_DEVICE_ID_CPU,
+	.feature_id = LWMI_FEATURE_ID_CPU_FPPT,
+	.type_id = LWMI_TYPE_ID_NONE,
+};
+
+struct capdata01_attr_group {
+	const struct attribute_group *attr_group;
+	struct tunable_attr_01 *tunable_attr;
+};
+
+/**
+ * lwmi_om_register_notifier() - Add a notifier to the blocking notifier chain
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_register to register the notifier block to the
+ * lenovo-wmi-other driver notifier chain.
+ *
+ * Return: 0 on success, %-EEXIST on error.
+ */
+int lwmi_om_register_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_register(&om_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
+
+/**
+ * lwmi_om_unregister_notifier() - Remove a notifier from the blocking notifier
+ * chain.
+ * @nb: The notifier_block struct to register
+ *
+ * Call blocking_notifier_chain_unregister to unregister the notifier block from the
+ * lenovo-wmi-other driver notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+int lwmi_om_unregister_notifier(struct notifier_block *nb)
+{
+	return blocking_notifier_chain_unregister(&om_chain_head, nb);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_om_unregister_notifier, "LENOVO_WMI_OTHER");
+
+/**
+ * devm_lwmi_om_unregister_notifier() - Remove a notifier from the blocking
+ * notifier chain.
+ * @data: Void pointer to the notifier_block struct to register.
+ *
+ * Call lwmi_om_unregister_notifier to unregister the notifier block from the
+ * lenovo-wmi-other driver notifier chain.
+ *
+ * Return: 0 on success, %-ENOENT on error.
+ */
+static void devm_lwmi_om_unregister_notifier(void *data)
+{
+	struct notifier_block *nb = data;
+
+	lwmi_om_unregister_notifier(nb);
+}
+
+/**
+ * devm_lwmi_om_register_notifier() - Add a notifier to the blocking notifier
+ * chain.
+ * @dev: The parent device of the notifier_block struct.
+ * @nb: The notifier_block struct to register
+ *
+ * Call lwmi_om_register_notifier to register the notifier block to the
+ * lenovo-wmi-other driver notifier chain. Then add devm_lwmi_om_unregister_notifier
+ * as a device managed action to automatically unregister the notifier block
+ * upon parent device removal.
+ *
+ * Return: 0 on success, or an error code.
+ */
+int devm_lwmi_om_register_notifier(struct device *dev,
+				   struct notifier_block *nb)
+{
+	int ret;
+
+	ret = lwmi_om_register_notifier(nb);
+	if (ret < 0)
+		return ret;
+
+	return devm_add_action_or_reset(dev, devm_lwmi_om_unregister_notifier,
+					nb);
+}
+EXPORT_SYMBOL_NS_GPL(devm_lwmi_om_register_notifier, "LENOVO_WMI_OTHER");
+
+/**
+ * lwmi_om_notifier_call() - Call functions for the notifier call chain.
+ * @mode: Pointer to a thermal mode enum to retrieve the data from.
+ *
+ * Call blocking_notifier_call_chain to retrieve the thermal mode from the
+ * lenovo-wmi-gamezone driver.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_notifier_call(enum thermal_mode *mode)
+{
+	int ret;
+
+	ret = blocking_notifier_call_chain(&om_chain_head,
+					   LWMI_GZ_GET_THERMAL_MODE, &mode);
+	if ((ret & ~NOTIFY_STOP_MASK) != NOTIFY_OK)
+		return -EINVAL;
+
+	return 0;
+}
+
+/* Attribute Methods */
+
+/**
+ * int_type_show() - Emit the data type for an integer attribute
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ *
+ * Return: Number of characters written to buf.
+ */
+static ssize_t int_type_show(struct kobject *kobj, struct kobj_attribute *kattr,
+			     char *buf)
+{
+	return sysfs_emit(buf, "integer\n");
+}
+
+/**
+ * attr_capdata01_show() - Get the value of the specified attribute property
+ *
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ * @tunable_attr: The attribute to be read.
+ * @prop: The property of this attribute to be read.
+ *
+ * Retrieves the given property from the capability data 01 struct for the
+ * specified attribute's "custom" thermal mode. This function is intended
+ * to be generic so it can be called from any integer attributes "_show"
+ * function.
+ *
+ * If the WMI is success the sysfs attribute is notified.
+ *
+ * Return: Either number of characters written to buf, or an error code.
+ */
+static ssize_t attr_capdata01_show(struct kobject *kobj,
+				   struct kobj_attribute *kattr, char *buf,
+				   struct tunable_attr_01 *tunable_attr,
+				   enum attribute_property prop)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+	struct capdata01 capdata;
+	u32 attribute_id;
+	int value, ret;
+
+	attribute_id =
+		FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+		FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+		FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
+			   LWMI_GZ_THERMAL_MODE_CUSTOM) |
+		FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+	ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
+	if (ret)
+		return ret;
+
+	switch (prop) {
+	case DEFAULT_VAL:
+		value = capdata.default_value;
+		break;
+	case MAX_VAL:
+		value = capdata.max_value;
+		break;
+	case MIN_VAL:
+		value = capdata.min_value;
+		break;
+	case STEP_VAL:
+		value = capdata.step;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return sysfs_emit(buf, "%d\n", value);
+}
+
+/**
+ * attr_current_value_store() - Set the current value of the given attribute
+ * @kobj: Pointer to the driver object.
+ * @kattr: 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.
+ * @tunable_attr: The attribute to be stored.
+ *
+ * Sets the value of the given attribute when operating under the "custom"
+ * smartfan profile. The current smartfan profile is retrieved from the
+ * lenovo-wmi-gamezone driver and error is returned if the result is not
+ * "custom". This function is intended to be generic so it can be called from
+ * any integer attribute's "_store" function. The integer to be sent to the WMI
+ * method is range checked and an error code is returned if out of range.
+ *
+ * If the value is valid and WMI is success, then the sysfs attribute is
+ * notified.
+ *
+ * Return: Either count, or an error code.
+ */
+static ssize_t attr_current_value_store(struct kobject *kobj,
+					struct kobj_attribute *kattr,
+					const char *buf, size_t count,
+					struct tunable_attr_01 *tunable_attr)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+	struct wmi_method_args_32 args;
+	struct capdata01 capdata;
+	enum thermal_mode mode;
+	u32 attribute_id;
+	u32 value;
+	int ret;
+
+	ret = lwmi_om_notifier_call(&mode);
+	if (ret)
+		return ret;
+
+	if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
+		return -EBUSY;
+
+	attribute_id =
+		FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+		FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+		FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
+		FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+	ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
+	if (ret)
+		return ret;
+
+	ret = kstrtouint(buf, 10, &value);
+	if (ret)
+		return ret;
+
+	if (value < capdata.min_value || value > capdata.max_value)
+		return -EINVAL;
+
+	args.arg0 = attribute_id;
+	args.arg1 = value;
+
+	ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
+				    (unsigned char *)&args, sizeof(args), NULL);
+	if (ret)
+		return ret;
+
+	return count;
+};
+
+/**
+ * attr_current_value_show() - Get the current value of the given attribute
+ * @kobj: Pointer to the driver object.
+ * @kattr: Pointer to the attribute calling this function.
+ * @buf: The buffer to write to.
+ * @tunable_attr: The attribute to be read.
+ *
+ * Retrieves the value of the given attribute for the current smartfan profile.
+ * The current smartfan profile is retrieved from the lenovo-wmi-gamezone driver.
+ * This function is intended to be generic so it can be called from any integer
+ * attribute's "_show" function.
+ *
+ * If the WMI is success the sysfs attribute is notified.
+ *
+ * Return: Either number of characters written to buf, or an error code.
+ */
+static ssize_t attr_current_value_show(struct kobject *kobj,
+				       struct kobj_attribute *kattr, char *buf,
+				       struct tunable_attr_01 *tunable_attr)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+	struct wmi_method_args_32 args;
+	enum thermal_mode mode;
+	u32 attribute_id;
+	int retval;
+	int ret;
+
+	ret = lwmi_om_notifier_call(&mode);
+	if (ret)
+		return ret;
+
+	attribute_id =
+		FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
+		FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
+		FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
+		FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+
+	args.arg0 = attribute_id;
+
+	ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+				    (unsigned char *)&args, sizeof(args),
+				    &retval);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%d\n", retval);
+}
+
+/* Lenovo WMI Other Mode Attribute macros */
+#define __LWMI_ATTR_RO(_func, _name)                                  \
+	{                                                             \
+		.attr = { .name = __stringify(_name), .mode = 0444 }, \
+		.show = _func##_##_name##_show,                       \
+	}
+
+#define __LWMI_ATTR_RO_AS(_name, _show)                               \
+	{                                                             \
+		.attr = { .name = __stringify(_name), .mode = 0444 }, \
+		.show = _show,                                        \
+	}
+
+#define __LWMI_ATTR_RW(_func, _name) \
+	__ATTR(_name, 0644, _func##_##_name##_show, _func##_##_name##_store)
+
+/* Shows a formatted static variable */
+#define __LWMI_ATTR_SHOW_FMT(_prop, _attrname, _fmt, _val)                     \
+	static ssize_t _attrname##_##_prop##_show(                             \
+		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+	{                                                                      \
+		return sysfs_emit(buf, _fmt, _val);                            \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_##_prop =              \
+		__LWMI_ATTR_RO(_attrname, _prop)
+
+/* Attribute current value read/write */
+#define __LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname)                          \
+	static ssize_t _attrname##_current_value_store(                        \
+		struct kobject *kobj, struct kobj_attribute *kattr,            \
+		const char *buf, size_t count)                                 \
+	{                                                                      \
+		return attr_current_value_store(kobj, kattr, buf, count,       \
+						&_attrname);                   \
+	}                                                                      \
+	static ssize_t _attrname##_current_value_show(                         \
+		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+	{                                                                      \
+		return attr_current_value_show(kobj, kattr, buf, &_attrname);  \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_current_value =        \
+		__LWMI_ATTR_RW(_attrname, current_value)
+
+/* Attribute property read only */
+#define __LWMI_TUNABLE_RO_CAP01(_prop, _attrname, _prop_type)                  \
+	static ssize_t _attrname##_##_prop##_show(                             \
+		struct kobject *kobj, struct kobj_attribute *kattr, char *buf) \
+	{                                                                      \
+		return attr_capdata01_show(kobj, kattr, buf, &_attrname,       \
+					   _prop_type);                        \
+	}                                                                      \
+	static struct kobj_attribute attr_##_attrname##_##_prop =              \
+		__LWMI_ATTR_RO(_attrname, _prop)
+
+#define LWMI_ATTR_GROUP_TUNABLE_CAP01(_attrname, _fsname, _dispname)      \
+	__LWMI_TUNABLE_CURRENT_VALUE_CAP01(_attrname);                    \
+	__LWMI_TUNABLE_RO_CAP01(default_value, _attrname, DEFAULT_VAL);   \
+	__LWMI_ATTR_SHOW_FMT(display_name, _attrname, "%s\n", _dispname); \
+	__LWMI_TUNABLE_RO_CAP01(max_value, _attrname, MAX_VAL);           \
+	__LWMI_TUNABLE_RO_CAP01(min_value, _attrname, MIN_VAL);           \
+	__LWMI_TUNABLE_RO_CAP01(scalar_increment, _attrname, STEP_VAL);   \
+	static struct kobj_attribute attr_##_attrname##_type =            \
+		__LWMI_ATTR_RO_AS(type, int_type_show);                   \
+	static struct attribute *_attrname##_attrs[] = {                  \
+		&attr_##_attrname##_current_value.attr,                   \
+		&attr_##_attrname##_default_value.attr,                   \
+		&attr_##_attrname##_display_name.attr,                    \
+		&attr_##_attrname##_max_value.attr,                       \
+		&attr_##_attrname##_min_value.attr,                       \
+		&attr_##_attrname##_scalar_increment.attr,                \
+		&attr_##_attrname##_type.attr,                            \
+		NULL,                                                     \
+	};                                                                \
+	static const struct attribute_group _attrname##_attr_group = {    \
+		.name = _fsname, .attrs = _attrname##_attrs               \
+	}
+
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
+			      "Set the CPU sustained power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
+			      "Set the CPU slow package power tracking limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
+			      "Set the CPU fast package power tracking limit");
+
+static struct capdata01_attr_group cd01_attr_groups[] = {
+	{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
+	{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
+	{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
+	{},
+};
+
+/**
+ * lwmi_om_fw_attr_add() - Register all firmware_attributes_class members
+ * @priv: The Other Mode driver data.
+ *
+ * Return: Either 0, or an error code.
+ */
+static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
+{
+	unsigned int i;
+	int err;
+
+	priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
+	if (priv->ida_id < 0)
+		return priv->ida_id;
+
+	priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
+					  MKDEV(0, 0), NULL, "%s-%u",
+					  LWMI_OM_FW_ATTR_BASE_PATH,
+					  priv->ida_id);
+	if (IS_ERR(priv->fw_attr_dev)) {
+		err = PTR_ERR(priv->fw_attr_dev);
+		goto err_free_ida;
+	}
+
+	priv->fw_attr_kset = kset_create_and_add("attributes", NULL,
+						 &priv->fw_attr_dev->kobj);
+	if (!priv->fw_attr_kset) {
+		err = -ENOMEM;
+		goto err_destroy_classdev;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
+		err = sysfs_create_group(&priv->fw_attr_kset->kobj,
+					 cd01_attr_groups[i].attr_group);
+		if (err)
+			goto err_remove_groups;
+
+		cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
+	}
+	return 0;
+
+err_remove_groups:
+	while (i--)
+		sysfs_remove_group(&priv->fw_attr_kset->kobj,
+				   cd01_attr_groups[i].attr_group);
+
+	kset_unregister(priv->fw_attr_kset);
+
+err_destroy_classdev:
+	device_unregister(priv->fw_attr_dev);
+
+err_free_ida:
+	ida_free(&lwmi_om_ida, priv->ida_id);
+	return err;
+}
+
+/**
+ * lwmi_om_fw_attr_remove() - Unregister all capability data attribute groups
+ * @priv: the lenovo-wmi-other driver data.
+ */
+static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
+{
+	for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
+		sysfs_remove_group(&priv->fw_attr_kset->kobj,
+				   cd01_attr_groups[i].attr_group);
+
+	kset_unregister(priv->fw_attr_kset);
+	device_unregister(priv->fw_attr_dev);
+}
+
+/**
+ * lwmi_om_master_bind() - Bind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device.
+ *
+ * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the
+ * lenovo-wmi-other master driver. On success, assign the capability data 01
+ * list pointer to the driver data struct for later access. This pointer
+ * is only valid while the capdata01 interface exists. Finally, register all
+ * firmware attribute groups.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_master_bind(struct device *dev)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+	struct cd01_list *tmp_list;
+	int ret;
+
+	ret = component_bind_all(dev, &tmp_list);
+	if (ret)
+		return ret;
+
+	priv->cd01_list = tmp_list;
+	if (!priv->cd01_list)
+		return -ENODEV;
+
+	return lwmi_om_fw_attr_add(priv);
+}
+
+/**
+ * lwmi_om_master_unbind() - Unbind all components of the other mode driver
+ * @dev: The lenovo-wmi-other driver basic device
+ *
+ * Unregister all capability data attribute groups. Then call
+ * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the
+ * lenovo-wmi-other master driver. Finally, free the IDA for this device.
+ */
+static void lwmi_om_master_unbind(struct device *dev)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(dev);
+
+	lwmi_om_fw_attr_remove(priv);
+	component_unbind_all(dev, NULL);
+}
+
+static const struct component_master_ops lwmi_om_master_ops = {
+	.bind = lwmi_om_master_bind,
+	.unbind = lwmi_om_master_unbind,
+};
+
+static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
+{
+	struct component_match *master_match = NULL;
+	struct lwmi_om_priv *priv;
+
+	priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->wdev = wdev;
+	dev_set_drvdata(&wdev->dev, priv);
+
+	component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL);
+	if (IS_ERR(master_match))
+		return PTR_ERR(master_match);
+
+	return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
+					       master_match);
+}
+
+static void lwmi_other_remove(struct wmi_device *wdev)
+{
+	struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
+
+	component_master_del(&wdev->dev, &lwmi_om_master_ops);
+	ida_free(&lwmi_om_ida, priv->ida_id);
+}
+
+static const struct wmi_device_id lwmi_other_id_table[] = {
+	{ LENOVO_OTHER_MODE_GUID, NULL },
+	{}
+};
+
+static struct wmi_driver lwmi_other_driver = {
+	.driver = {
+		.name = "lenovo_wmi_other",
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = lwmi_other_id_table,
+	.probe = lwmi_other_probe,
+	.remove = lwmi_other_remove,
+	.no_singleton = true,
+};
+
+module_wmi_driver(lwmi_other_driver);
+
+MODULE_IMPORT_NS("LENOVO_WMI_CD01");
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
+MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo-wmi-other.h b/drivers/platform/x86/lenovo-wmi-other.h
new file mode 100644
index 000000000000..8ebf5602bb99
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-other.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com> */
+
+#ifndef _LENOVO_WMI_OTHER_H_
+#define _LENOVO_WMI_OTHER_H_
+
+struct device;
+struct notifier_block;
+
+int lwmi_om_register_notifier(struct notifier_block *nb);
+int lwmi_om_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_om_register_notifier(struct device *dev,
+				   struct notifier_block *nb);
+
+#endif /* !_LENOVO_WMI_OTHER_H_ */
diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c
index dc5e9878cb68..b00b2b52e0f2 100644
--- a/drivers/platform/x86/msi-wmi-platform.c
+++ b/drivers/platform/x86/msi-wmi-platform.c
@@ -1,8 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 /*
- * Linux driver for WMI platform features on MSI notebooks.
+ * Linux driver for WMI platform features on MSI notebooks and handhelds.
  *
- * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>
+ * Copyright (C) 2024-2025 Armin Wolf <W_Armin@gmx.de>
+ * Copyright (C) 2025 Antheas Kapenekakis <lkml@antheas.dev>
  */
 
 #define pr_format(fmt) KBUILD_MODNAME ": " fmt
@@ -14,27 +15,40 @@
 #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
 
+/* Get_WMI() WMI method */
 #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)
@@ -42,6 +56,33 @@
 #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
+
 static bool force;
 module_param_unsafe(force, bool, 0);
 MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
@@ -78,9 +119,68 @@ 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 mutex wmi_lock;	/* Necessary when calling WMI methods */
+	struct device *ppdev;
+	struct msi_wmi_platform_factory_curves factory_curves;
+	struct acpi_battery_hook battery_hook;
+	struct device *fw_attrs_dev;
+	struct kset *fw_attrs_kset;
+};
+
+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 {
@@ -123,6 +223,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)
@@ -139,45 +286,332 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz
 	return 0;
 }
 
-static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
-				  enum msi_wmi_platform_method method, u8 *input,
-				  size_t input_length, u8 *output, size_t output_length)
+static int msi_wmi_platform_query_unlocked(struct msi_wmi_platform_data *data,
+				  enum msi_wmi_platform_method method, u8 *buffer,
+				  size_t length)
 {
 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
 	struct acpi_buffer in = {
-		.length = input_length,
-		.pointer = input
+		.length = length,
+		.pointer = buffer
 	};
 	union acpi_object *obj;
 	acpi_status status;
 	int ret;
 
-	if (!input_length || !output_length)
+	if (!length)
 		return -EINVAL;
 
+	status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	obj = out.pointer;
+	if (!obj)
+		return -ENODATA;
+
+	ret = msi_wmi_platform_parse_buffer(obj, buffer, length);
+	kfree(obj);
+
+	return ret;
+}
+
+static int msi_wmi_platform_query(struct msi_wmi_platform_data *data,
+				  enum msi_wmi_platform_method method, u8 *buffer,
+				  size_t length)
+{
 	/*
 	 * The ACPI control method responsible for handling the WMI method calls
 	 * is not thread-safe. Because of this we have to do the locking ourself.
 	 */
 	scoped_guard(mutex, &data->wmi_lock) {
-		status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out);
-		if (ACPI_FAILURE(status))
-			return -EIO;
+		return msi_wmi_platform_query_unlocked(data, method, buffer, length);
 	}
+}
 
-	obj = out.pointer;
-	if (!obj)
-		return -ENODATA;
+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_parse_buffer(obj, output, output_length);
-	kfree(obj);
+	ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer));
+	if (ret < 0)
+		return ret;
 
-	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->wmi_lock);
+
+	ret = msi_wmi_platform_query_unlocked(data, 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_unlocked(data, 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, 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->wmi_lock);
+
+	ret = msi_wmi_platform_query_unlocked(data, 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_unlocked(data, 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_unlocked(
+		data, 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_unlocked(
+		data, 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_unlocked(
+		data, 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_unlocked(
+		data, 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_unlocked(data, 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_unlocked(data, 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_unlocked(
+		data, 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_unlocked(
+		data, 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;
 }
 
@@ -185,28 +619,114 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ
 				 int channel, long *val)
 {
 	struct msi_wmi_platform_data *data = dev_get_drvdata(dev);
-	u8 input[32] = { 0 };
-	u8 output[32];
+	u8 buffer[32] = { 0 };
 	u16 value;
+	u8 flags;
 	int ret;
 
-	ret = msi_wmi_platform_query(data, 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, 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, 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;
+			else
+				*val = 2;
+
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
 
-	value = get_unaligned_be16(&output[channel * 2 + 1]);
-	if (!value)
-		*val = 0;
-	else
-		*val = 480000 / value;
+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;
+	guard(mutex)(&data->wmi_lock);
+
+	switch (type) {
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+			buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE;
+			ret = msi_wmi_platform_query_unlocked(
+				data, 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_unlocked(
+				data, 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[] = {
@@ -216,6 +736,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
 };
 
@@ -224,8 +748,497 @@ 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);
+	int ret;
+
+	u8 buffer[32] = { };
+
+	buffer[0] = MSI_PLATFORM_SHIFT_ADDR;
+
+	ret = msi_wmi_platform_query(data, 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, 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]);
+
+	ret = msi_wmi_platform_query(data, 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, 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 int msi_platform_psy_ext_get_prop(struct power_supply *psy,
+					 const struct power_supply_ext *ext,
+					 void *data,
+					 enum power_supply_property psp,
+					 union power_supply_propval *val)
+{
+	struct msi_wmi_platform_data *msi = data;
+	u8 buffer[32] = { 0 };
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		buffer[0] = MSI_PLATFORM_BAT_ADDR;
+		ret = msi_wmi_platform_query(msi, MSI_PLATFORM_GET_DATA,
+					     buffer, sizeof(buffer));
+		if (ret)
+			return ret;
+
+		val->intval = buffer[1] & ~BIT(7);
+		if (val->intval > 100)
+			return -EINVAL;
+
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int msi_platform_psy_ext_set_prop(struct power_supply *psy,
+					 const struct power_supply_ext *ext,
+					 void *data,
+					 enum power_supply_property psp,
+					 const union power_supply_propval *val)
+{
+	struct msi_wmi_platform_data *msi = data;
+	u8 buffer[32] = { 0 };
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		if (val->intval > 100)
+			return -EINVAL;
+		buffer[0] = MSI_PLATFORM_BAT_ADDR;
+		buffer[1] = val->intval | BIT(7);
+		return msi_wmi_platform_query(msi, MSI_PLATFORM_SET_DATA,
+					      buffer, sizeof(buffer));
+	default:
+		return -EINVAL;
+	}
+}
+
+static int
+msi_platform_psy_prop_is_writeable(struct power_supply *psy,
+				   const struct power_supply_ext *ext,
+				   void *data, enum power_supply_property psp)
+{
+	return true;
+}
+
+static const enum power_supply_property oxp_psy_ext_props[] = {
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_ext msi_platform_psy_ext = {
+	.name			= "msi-platform-charge-control",
+	.properties		= oxp_psy_ext_props,
+	.num_properties		= ARRAY_SIZE(oxp_psy_ext_props),
+	.get_property		= msi_platform_psy_ext_get_prop,
+	.set_property		= msi_platform_psy_ext_set_prop,
+	.property_is_writeable	= msi_platform_psy_prop_is_writeable,
+};
+
+static int msi_wmi_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);
+
+	return power_supply_register_extension(battery, &msi_platform_psy_ext,
+					       &data->wdev->dev, data);
+}
+
+static int msi_wmi_platform_battery_remove(struct power_supply *battery,
+					   struct acpi_battery_hook *hook)
+{
+	power_supply_unregister_extension(battery, &msi_platform_psy_ext);
+	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;
@@ -245,17 +1258,21 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input,
 		return ret;
 
 	down_write(&data->buffer_lock);
-	ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer,
+	ret = msi_wmi_platform_query(data->data, data->method, data->buffer,
 				     data->length);
 	up_write(&data->buffer_lock);
 
 	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;
@@ -267,19 +1284,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,
 };
@@ -340,31 +1357,32 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data)
 {
 	struct device *hdev;
 
-	hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data,
-						    &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 msi_wmi_platform_data *data)
 {
-	u8 input[32] = { 0 };
-	u8 output[32];
+	u8 buffer[32] = { 0 };
 	u8 flags;
 	int ret;
 
-	ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
-				     sizeof(output));
+	ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, buffer, sizeof(buffer));
 	if (ret < 0)
 		return ret;
 
-	flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
+	flags = buffer[MSI_PLATFORM_EC_FLAGS_OFFSET];
 
 	dev_dbg(&data->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(&data->wdev->dev, "EC firmware version %.28s\n",
-		&output[MSI_PLATFORM_EC_VERSION_OFFSET]);
+		&buffer[MSI_PLATFORM_EC_VERSION_OFFSET]);
 
 	if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
 		if (!force)
@@ -378,35 +1396,46 @@ static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data)
 
 static int msi_wmi_platform_init(struct msi_wmi_platform_data *data)
 {
-	u8 input[32] = { 0 };
-	u8 output[32];
+	u8 buffer[32] = { 0 };
 	int ret;
 
-	ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
-				     sizeof(output));
+	ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, buffer, sizeof(buffer));
 	if (ret < 0)
 		return ret;
 
 	dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n",
-		output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
-		output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
+		buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET],
+		buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]);
 
-	if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
+	if (buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
 		if (!force)
 			return -ENODEV;
 
 		dev_warn(&data->wdev->dev,
 			 "Loading despite unsupported WMI interface version (%u.%u)\n",
-			 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
-			 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
+			 buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET],
+			 buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]);
 	}
 
 	return 0;
 }
 
+static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data)
+{
+	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);
+
+	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;
 
 	data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
@@ -416,6 +1445,12 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
 	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->wmi_lock);
 	if (ret < 0)
 		return ret;
@@ -428,11 +1463,44 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
 	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_wmi_platform_battery_add;
+		data->battery_hook.remove_battery = msi_wmi_platform_battery_remove;
+		battery_hook_register(&data->battery_hook);
+	}
+
 	msi_wmi_platform_debugfs_init(data);
 
+	msi_wmi_platform_profile_setup(data);
+
+	if (data->quirks->restore_curves) {
+		guard(mutex)(&data->wmi_lock);
+		ret = msi_wmi_platform_curves_save(data);
+		if (ret < 0)
+			return ret;
+	}
+
 	return msi_wmi_platform_hwmon_init(data);
 }
 
+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->wmi_lock);
+		msi_wmi_platform_curves_load(data);
+	}
+}
+
 static const struct wmi_device_id msi_wmi_platform_id_table[] = {
 	{ MSI_PLATFORM_GUID, NULL },
 	{ }
@@ -446,6 +1514,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/platform/x86/zotac-zone-platform.c b/drivers/platform/x86/zotac-zone-platform.c
new file mode 100644
index 000000000000..d097f7b9c5a0
--- /dev/null
+++ b/drivers/platform/x86/zotac-zone-platform.c
@@ -0,0 +1,1122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Zotac Handheld Platform Driver
+ *
+ * Copyright (C) 2025 Luke D. Jones
+ */
+
+#include "linux/mod_devicetable.h"
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mutex.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/acpi.h>
+#include <linux/ioport.h>
+#include <linux/jiffies.h>
+#include <linux/platform_profile.h>
+#include <linux/wmi.h>
+
+#include "firmware_attributes_class.h"
+
+#define DRIVER_NAME "zotac_zone_platform"
+
+#define EC_COMMAND_PORT 0x4E
+#define EC_DATA_PORT 0x4F
+
+#define EC_FAN_CTRL_ADDR 0x44A
+#define EC_FAN_DUTY_ADDR 0x44B
+#define EC_FAN_SPEED_UPPER_ADDR 0x476
+#define EC_FAN_SPEED_LOWER_ADDR 0x477
+#define EC_CPU_TEMP_ADDR 0x462
+
+/* Internal values */
+#define EC_FAN_MODE_AUTO 0
+#define EC_FAN_MODE_MANUAL 1
+/* Follow standard convention for userspace */
+#define PWM_ENABLE_OFF    0
+#define PWM_ENABLE_MANUAL 1
+#define PWM_ENABLE_AUTO   2  /* Automatic control (EC control) */
+#define PWM_ENABLE_CURVE  3  /* Custom curve control */
+
+#define PWM_MIN 0
+#define PWM_MAX 255
+
+#define FAN_CURVE_POINTS 9 /* 9 points for 10-90°C like in the Zotac C# code */
+
+/* AMD APU WMI DPTC constants */
+#define AMD_APU_WMI_METHODS_GUID "1f72b0f1-bfea-4472-9877-6e62937ab616"
+#define AMD_APU_WMI_DATA_GUID "05901221-d566-11d1-b2f0-00a0c9062910"
+
+/* DPTC command IDs */
+#define DPTC_STAPM_TIME_CONSTANT  1
+#define DPTC_SUSTAINED_POWER      5
+#define DPTC_FAST_POWER_1         6
+#define DPTC_FAST_POWER_2         7
+#define DPTC_SLOW_PPT_CONSTANT    8
+#define DPTC_P3T_LIMIT            0x32
+
+/* DPTC power limits in milliwatts */
+#define DPTC_MIN_POWER            5000
+#define DPTC_MAX_POWER            28000
+
+#define PPT_PL1_SPL_MIN		8
+#define PPT_PL1_SPL_MAX		28
+#define PPT_PL2_SPPT_MIN	8
+#define PPT_PL2_SPPT_MAX	28
+#define PPT_PL3_FPPT_MIN	8
+#define PPT_PL3_FPPT_MAX	28
+
+struct power_limits {
+	u8 ppt_pl1_spl;
+	u8 ppt_pl2_sppt;
+	u8 ppt_pl3_fppt;
+};
+
+static const struct power_limits ppt_quiet_profile = {
+	.ppt_pl1_spl = 5,
+	.ppt_pl2_sppt = 10,
+	.ppt_pl3_fppt = 15,
+};
+
+static const struct power_limits ppt_balanced_profile = {
+	.ppt_pl1_spl = 12,
+	.ppt_pl2_sppt = 19,
+	.ppt_pl3_fppt = 26,
+};
+
+static const struct power_limits ppt_performance_profile = {
+	.ppt_pl1_spl = 28,
+	.ppt_pl2_sppt = 35,
+	.ppt_pl3_fppt = 45,
+};
+
+static struct timer_list fan_curve_timer;
+
+struct zotac_platform_data {
+	struct device *hwmon_dev;
+	struct mutex update_lock;
+	unsigned int fan_rpm;
+	unsigned int pwm;
+	unsigned int pwm_enable;
+	unsigned int temp;
+	unsigned long last_updated;
+	bool valid;
+	bool curve_enabled;
+
+	/* Fan curve points */
+	unsigned int curve_temp[FAN_CURVE_POINTS]; /* Temperature points */
+	unsigned int curve_pwm[FAN_CURVE_POINTS]; /* PWM/duty points */
+
+	/* DPTC values */
+	struct device *ppdev;
+	struct device *fw_attr_dev;
+	struct kset *fw_attr_kset;
+
+	bool wmi_dptc_supported;
+	struct power_limits current_power_limits;
+	enum platform_profile_option current_profile;
+	/* TODO: hacking - must be removed later */
+	unsigned int ppt_pl1_stapm_time_const;
+	unsigned int ppt_pl2_sppt_time_const;
+	unsigned int ppt_platform_sppt;
+};
+
+static struct platform_device *zotac_platform_device;
+static DEFINE_MUTEX(ec_mutex);
+static struct resource ec_io_ports[] = {
+	{
+		.start = EC_COMMAND_PORT,
+		.end = EC_COMMAND_PORT,
+		.name = "ec-command",
+		.flags = IORESOURCE_IO,
+	},
+	{
+		.start = EC_DATA_PORT,
+		.end = EC_DATA_PORT,
+		.name = "ec-data",
+		.flags = IORESOURCE_IO,
+	},
+};
+
+static u8 ec_read_byte(u16 addr)
+{
+	u8 addr_upper = (addr >> 8) & 0xFF;
+	u8 addr_lower = addr & 0xFF;
+	u8 value;
+
+	mutex_lock(&ec_mutex);
+
+	/* Select upper byte address */
+	outb(0x2E, EC_COMMAND_PORT);
+	outb(0x11, EC_DATA_PORT);
+	outb(0x2F, EC_COMMAND_PORT);
+	outb(addr_upper, EC_DATA_PORT);
+
+	/* Select lower byte address */
+	outb(0x2E, EC_COMMAND_PORT);
+	outb(0x10, EC_DATA_PORT);
+	outb(0x2F, EC_COMMAND_PORT);
+	outb(addr_lower, EC_DATA_PORT);
+
+	/* Read data */
+	outb(0x2E, EC_COMMAND_PORT);
+	outb(0x12, EC_DATA_PORT);
+	outb(0x2F, EC_COMMAND_PORT);
+	value = inb(EC_DATA_PORT);
+
+	mutex_unlock(&ec_mutex);
+
+	return value;
+}
+
+static int ec_write_byte(u16 addr, u8 value)
+{
+	u8 addr_upper = (addr >> 8) & 0xFF;
+	u8 addr_lower = addr & 0xFF;
+
+	mutex_lock(&ec_mutex);
+
+	/* Select upper byte address */
+	outb(0x2E, EC_COMMAND_PORT);
+	outb(0x11, EC_DATA_PORT);
+	outb(0x2F, EC_COMMAND_PORT);
+	outb(addr_upper, EC_DATA_PORT);
+
+	/* Select lower byte address */
+	outb(0x2E, EC_COMMAND_PORT);
+	outb(0x10, EC_DATA_PORT);
+	outb(0x2F, EC_COMMAND_PORT);
+	outb(addr_lower, EC_DATA_PORT);
+
+	/* Write data */
+	outb(0x2E, EC_COMMAND_PORT);
+	outb(0x12, EC_DATA_PORT);
+	outb(0x2F, EC_COMMAND_PORT);
+	outb(value, EC_DATA_PORT);
+
+	mutex_unlock(&ec_mutex);
+
+	return 0;
+}
+
+static int send_dptc_cmd(u8 cmd_id, u32 value)
+{
+	struct acpi_buffer input = { 0, NULL };
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	u8 *buffer;
+	acpi_status status;
+	int ret;
+
+	buffer = kzalloc(8, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = cmd_id;
+	*(u32 *)(buffer + 4) = value;
+
+	input.length = 8;
+	input.pointer = buffer;
+
+	status = wmi_evaluate_method(AMD_APU_WMI_METHODS_GUID, 0, 9,
+				&input, &output);
+
+	ret = ACPI_SUCCESS(status) ? 0 : -EIO;
+
+	kfree(buffer);
+	if (output.pointer)
+		kfree(output.pointer);
+
+	return ret;
+}
+
+static struct zotac_platform_data *zotac_platform_update_device(struct device *dev)
+{
+	struct zotac_platform_data *data = dev_get_drvdata(dev);
+	unsigned long current_time = jiffies;
+
+	if (time_after(current_time, data->last_updated + HZ) || !data->valid) {
+		mutex_lock(&data->update_lock);
+
+		data->pwm_enable = ec_read_byte(EC_FAN_CTRL_ADDR);
+		data->pwm = ec_read_byte(EC_FAN_DUTY_ADDR);
+
+		u32 upper = ec_read_byte(EC_FAN_SPEED_UPPER_ADDR);
+		u32 lower = ec_read_byte(EC_FAN_SPEED_LOWER_ADDR);
+		data->fan_rpm = (upper << 8) | lower;
+
+		data->temp = ec_read_byte(EC_CPU_TEMP_ADDR);
+
+		data->last_updated = current_time;
+		data->valid = true;
+
+		mutex_unlock(&data->update_lock);
+	}
+
+	return data;
+}
+
+/* Internal version doesn't acquire the lock */
+static int set_fan_duty_internal(unsigned int duty_percent)
+{
+	u8 duty_val;
+
+	if (duty_percent > 100)
+		return -EINVAL;
+
+	duty_val =
+		(duty_percent * (PWM_MAX - PWM_MIN)) / 100 +
+		PWM_MIN;
+	return ec_write_byte(EC_FAN_DUTY_ADDR, duty_val);
+}
+
+static void fan_curve_function(struct timer_list *t)
+{
+	struct zotac_platform_data *data = platform_get_drvdata(zotac_platform_device);
+	unsigned int current_temp;
+	unsigned int pwm = 0;
+	int i;
+
+	if (!data || !data->curve_enabled) {
+		if (data && data->curve_enabled)
+			mod_timer(&fan_curve_timer, jiffies + HZ);
+		return;
+	}
+
+	mutex_lock(&data->update_lock);
+
+	current_temp = ec_read_byte(EC_CPU_TEMP_ADDR);
+	data->temp = current_temp;
+
+	pwm = data->curve_pwm[0];
+
+	if (current_temp >= data->curve_temp[FAN_CURVE_POINTS - 1]) {
+		/* Above highest temperature point - use maximum PWM */
+		pwm = data->curve_pwm[FAN_CURVE_POINTS - 1];
+	} else {
+		/* Find the temperature range and interpolate */
+		for (i = 0; i < FAN_CURVE_POINTS - 1; i++) {
+			if (current_temp >= data->curve_temp[i] &&
+			    current_temp < data->curve_temp[i + 1]) {
+				/* Linear interpolation between points */
+				int temp_range = data->curve_temp[i + 1] -
+						 data->curve_temp[i];
+				int pwm_range = data->curve_pwm[i + 1] -
+						data->curve_pwm[i];
+				int temp_offset =
+					current_temp - data->curve_temp[i];
+
+				if (temp_range > 0) {
+					pwm = data->curve_pwm[i] +
+					      (pwm_range * temp_offset) /
+						      temp_range;
+				} else {
+					pwm = data->curve_pwm[i];
+				}
+
+				break;
+			}
+		}
+	}
+
+	set_fan_duty_internal(pwm);
+	mutex_unlock(&data->update_lock);
+
+	mod_timer(&fan_curve_timer, jiffies + HZ);
+}
+
+/* Fan speed RPM */
+static ssize_t fan1_input_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct zotac_platform_data *data = zotac_platform_update_device(dev);
+	return sprintf(buf, "%u\n", data->fan_rpm);
+}
+static DEVICE_ATTR_RO(fan1_input);
+
+/* Fan mode */
+
+static int set_pwm_enable(struct device *dev, u8 mode)
+{
+	struct zotac_platform_data *data = dev_get_drvdata(dev);
+	int err = 0;
+	u8 ec_mode;
+
+	/* Convert from standard modes to EC-specific modes */
+	switch (mode) {
+	case PWM_ENABLE_OFF:
+		/* If supported by EC, turn fan off */
+		return -EOPNOTSUPP; /* If EC doesn't support OFF mode */
+	case PWM_ENABLE_MANUAL:
+		ec_mode =
+			EC_FAN_MODE_MANUAL; /* Assuming this is your actual EC value */
+		data->curve_enabled = false;
+		if (data->curve_enabled) {
+			data->curve_enabled = false;
+			timer_delete(&fan_curve_timer);
+		}
+		break;
+	case PWM_ENABLE_AUTO:
+		ec_mode =
+			EC_FAN_MODE_AUTO; /* Assuming this is your actual EC value */
+		data->curve_enabled = false;
+		if (data->curve_enabled) {
+			data->curve_enabled = false;
+			timer_delete(&fan_curve_timer);
+		}
+		break;
+	case PWM_ENABLE_CURVE:
+		ec_mode =
+			EC_FAN_MODE_MANUAL; /* We'll control manually but via the curve */
+		data->curve_enabled = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Set mode to EC if needed */
+	if (!data->curve_enabled || mode != PWM_ENABLE_CURVE) {
+		err = ec_write_byte(EC_FAN_CTRL_ADDR, ec_mode);
+	}
+
+	if (err == 0) {
+		data->pwm_enable = mode;
+		if (mode == PWM_ENABLE_CURVE)
+			mod_timer(&fan_curve_timer, jiffies + HZ);
+	}
+
+	return err;
+}
+
+/* Replace fan1_duty with pwm1 and scale to 0-255 */
+static int set_pwm(struct device *dev, u8 pwm_value)
+{
+	struct zotac_platform_data *data = dev_get_drvdata(dev);
+	int err;
+
+	if (pwm_value > PWM_MAX)
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+	err = ec_write_byte(EC_FAN_DUTY_ADDR, pwm_value);
+	if (err == 0)
+		data->pwm = pwm_value;
+	mutex_unlock(&data->update_lock);
+
+	return err;
+}
+
+static ssize_t pwm1_enable_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct zotac_platform_data *data = zotac_platform_update_device(dev);
+	return sprintf(buf, "%u\n", data->pwm_enable);
+}
+
+static ssize_t pwm1_enable_store(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	unsigned long mode;
+	int err;
+
+	err = kstrtoul(buf, 10, &mode);
+	if (err)
+		return err;
+
+	if (mode > PWM_ENABLE_CURVE)
+		return -EINVAL;
+
+	err = set_pwm_enable(dev, mode);
+	if (err)
+		return err;
+
+	return count;
+}
+static DEVICE_ATTR_RW(pwm1_enable);
+
+/* Fan duty cycle (percent) */
+static ssize_t pwm1_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	struct zotac_platform_data *data = zotac_platform_update_device(dev);
+	return sprintf(buf, "%u\n", data->pwm);
+}
+
+static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	unsigned long pwm_value;
+	int err;
+
+	err = kstrtoul(buf, 10, &pwm_value);
+	if (err)
+		return err;
+
+	if (pwm_value > PWM_MAX)
+		return -EINVAL;
+
+	err = set_pwm(dev, pwm_value);
+	if (err)
+		return err;
+
+	return count;
+}
+static DEVICE_ATTR_RW(pwm1);
+
+/* Macro to generate temperature point attributes */
+#define CURVE_TEMP_ATTR(index)                                                \
+	static ssize_t pwm1_auto_point##index##_temp_show(                    \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		struct zotac_platform_data *data = dev_get_drvdata(dev);      \
+		return sprintf(buf, "%u\n", data->curve_temp[index - 1]);     \
+	}                                                                     \
+                                                                              \
+	static ssize_t pwm1_auto_point##index##_temp_store(                   \
+		struct device *dev, struct device_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		struct zotac_platform_data *data = dev_get_drvdata(dev);      \
+		unsigned long temp;                                           \
+		int err;                                                      \
+                                                                              \
+		err = kstrtoul(buf, 10, &temp);                               \
+		if (err)                                                      \
+			return err;                                           \
+                                                                              \
+		mutex_lock(&data->update_lock);                               \
+		data->curve_temp[index - 1] = temp;                           \
+		mutex_unlock(&data->update_lock);                             \
+                                                                              \
+		return count;                                                 \
+	}                                                                     \
+	static DEVICE_ATTR_RW(pwm1_auto_point##index##_temp)
+
+/* Macro to generate PWM point attributes */
+#define CURVE_PWM_ATTR(index)                                                 \
+	static ssize_t pwm1_auto_point##index##_pwm_show(                     \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		struct zotac_platform_data *data = dev_get_drvdata(dev);      \
+		return sprintf(buf, "%u\n", data->curve_pwm[index - 1]);      \
+	}                                                                     \
+                                                                              \
+	static ssize_t pwm1_auto_point##index##_pwm_store(                    \
+		struct device *dev, struct device_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		struct zotac_platform_data *data = dev_get_drvdata(dev);      \
+		unsigned long pwm;                                            \
+		int err;                                                      \
+                                                                              \
+		err = kstrtoul(buf, 10, &pwm);                                \
+		if (err)                                                      \
+			return err;                                           \
+                                                                              \
+		if (pwm > 100)                                                \
+			return -EINVAL;                                       \
+                                                                              \
+		mutex_lock(&data->update_lock);                               \
+		data->curve_pwm[index - 1] = pwm;                             \
+		mutex_unlock(&data->update_lock);                             \
+                                                                              \
+		return count;                                                 \
+	}                                                                     \
+	static DEVICE_ATTR_RW(pwm1_auto_point##index##_pwm)
+
+/* Generate attributes for each point */
+CURVE_TEMP_ATTR(1);
+CURVE_PWM_ATTR(1);
+CURVE_TEMP_ATTR(2);
+CURVE_PWM_ATTR(2);
+CURVE_TEMP_ATTR(3);
+CURVE_PWM_ATTR(3);
+CURVE_TEMP_ATTR(4);
+CURVE_PWM_ATTR(4);
+CURVE_TEMP_ATTR(5);
+CURVE_PWM_ATTR(5);
+CURVE_TEMP_ATTR(6);
+CURVE_PWM_ATTR(6);
+CURVE_TEMP_ATTR(7);
+CURVE_PWM_ATTR(7);
+CURVE_TEMP_ATTR(8);
+CURVE_PWM_ATTR(8);
+CURVE_TEMP_ATTR(9);
+CURVE_PWM_ATTR(9);
+
+/* Temperature reading */
+static ssize_t temp1_input_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct zotac_platform_data *data = zotac_platform_update_device(dev);
+	return sprintf(buf, "%u\n",
+		       data->temp * 1000); /* Convert to milli-degrees */
+}
+static DEVICE_ATTR_RO(temp1_input);
+
+static struct attribute *zotac_platform_hwmon_attrs[] = {
+	&dev_attr_fan1_input.attr,
+	&dev_attr_pwm1_enable.attr,
+	&dev_attr_pwm1.attr,
+	&dev_attr_temp1_input.attr,
+	&dev_attr_pwm1_auto_point1_temp.attr,
+	&dev_attr_pwm1_auto_point1_pwm.attr,
+	&dev_attr_pwm1_auto_point2_temp.attr,
+	&dev_attr_pwm1_auto_point2_pwm.attr,
+	&dev_attr_pwm1_auto_point3_temp.attr,
+	&dev_attr_pwm1_auto_point3_pwm.attr,
+	&dev_attr_pwm1_auto_point4_temp.attr,
+	&dev_attr_pwm1_auto_point4_pwm.attr,
+	&dev_attr_pwm1_auto_point5_temp.attr,
+	&dev_attr_pwm1_auto_point5_pwm.attr,
+	&dev_attr_pwm1_auto_point6_temp.attr,
+	&dev_attr_pwm1_auto_point6_pwm.attr,
+	&dev_attr_pwm1_auto_point7_temp.attr,
+	&dev_attr_pwm1_auto_point7_pwm.attr,
+	&dev_attr_pwm1_auto_point8_temp.attr,
+	&dev_attr_pwm1_auto_point8_pwm.attr,
+	&dev_attr_pwm1_auto_point9_temp.attr,
+	&dev_attr_pwm1_auto_point9_pwm.attr,
+	NULL
+};
+
+/* DPTC attributes */
+#define DPTC_ATTR(display_name, cmd_id, min_val, max_val)                     \
+	static ssize_t display_name##_show(                                   \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		struct zotac_platform_data *data = dev_get_drvdata(dev);      \
+		return sprintf(buf, "%u\n", data->display_name);              \
+	}                                                                     \
+                                                                              \
+	static ssize_t display_name##_store(struct device *dev,               \
+					    struct device_attribute *attr,    \
+					    const char *buf, size_t count)    \
+	{                                                                     \
+		struct zotac_platform_data *data = dev_get_drvdata(dev);      \
+		unsigned long val;                                            \
+		int err;                                                      \
+                                                                              \
+		if (!data->wmi_dptc_supported)                                \
+			return -ENODEV;                                       \
+                                                                              \
+		err = kstrtoul(buf, 10, &val);                                \
+		if (err)                                                      \
+			return err;                                           \
+                                                                              \
+		if (val < min_val || val > max_val)                           \
+			return -EINVAL;                                       \
+                                                                              \
+		mutex_lock(&data->update_lock);                               \
+		err = send_dptc_cmd(cmd_id, val);                             \
+		if (err == 0)                                                 \
+			data->display_name = val;                             \
+		mutex_unlock(&data->update_lock);                             \
+                                                                              \
+		return err ? err : count;                                     \
+	}                                                                     \
+	static DEVICE_ATTR_RW(display_name)
+
+/* Generate DPTC attributes with AMD-specific naming */
+DPTC_ATTR(ppt_pl1_stapm_time_const, DPTC_STAPM_TIME_CONSTANT, 1, 10000);
+DPTC_ATTR(ppt_pl2_sppt_time_const, DPTC_SLOW_PPT_CONSTANT, 1, 0xFF);
+DPTC_ATTR(ppt_platform_sppt, DPTC_P3T_LIMIT, DPTC_MIN_POWER, DPTC_MAX_POWER);
+
+static struct attribute *zotac_platform_dptc_attrs[] = {
+	&dev_attr_ppt_pl1_stapm_time_const.attr,
+	&dev_attr_ppt_pl2_sppt_time_const.attr,
+	&dev_attr_ppt_platform_sppt.attr,
+	NULL
+};
+
+static const struct attribute_group zotac_platform_hwmon_group = {
+	.attrs = zotac_platform_hwmon_attrs,
+};
+
+static const struct attribute_group zotac_platform_dptc_group = {
+	.name = "dptc",
+	.attrs = zotac_platform_dptc_attrs,
+};
+
+static const struct attribute_group *zotac_platform_hwmon_groups[] = {
+	&zotac_platform_hwmon_group,
+	NULL
+};
+
+/* Helper function to show a simple integer attribute */
+static ssize_t show_int_attr(struct device *dev,
+                             struct device_attribute *attr,
+                             char *buf, int value)
+{
+	return sprintf(buf, "%d\n", value);
+}
+
+/* Helper function to show a simple string attribute */
+static ssize_t show_string_attr(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf, const char *value)
+{
+	return sprintf(buf, "%s\n", value);
+}
+
+#define PPT_ATTR_RO(_name, _attr_name)                        \
+	struct device_attribute dev_attr_##_name = {          \
+		.attr = { .name = _attr_name, .mode = 0444 }, \
+		.show = _name##_show,                         \
+	}
+
+#define PPT_ATTR_RW(_name, _attr_name)                        \
+	struct device_attribute dev_attr_##_name = {          \
+		.attr = { .name = _attr_name, .mode = 0644 }, \
+		.show = _name##_show,                         \
+		.store = _name##_store,                       \
+	}
+
+/* Macro that creates a complete set of attributes for a power limit */
+#define DEFINE_POWER_LIMIT_ATTRS(attr_name, cmd_id, min, max, desc)           \
+	static ssize_t attr_name##_current_value_show(                        \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		struct zotac_platform_data *data =                            \
+			platform_get_drvdata(zotac_platform_device);          \
+		return show_int_attr(dev, attr, buf,                          \
+				     data->current_power_limits.attr_name);   \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_current_value_store(                       \
+		struct device *dev, struct device_attribute *attr,            \
+		const char *buf, size_t count)                                \
+	{                                                                     \
+		struct zotac_platform_data *data =                            \
+			platform_get_drvdata(zotac_platform_device);          \
+		unsigned long val;                                            \
+		int err;                                                      \
+                                                                              \
+		if (!data->wmi_dptc_supported)                                \
+			return -ENODEV;                                       \
+                                                                              \
+		err = kstrtoul(buf, 10, &val);                                \
+		if (err)                                                      \
+			return err;                                           \
+                                                                              \
+		if (val < min || val > max)                                   \
+			return -EINVAL;                                       \
+                                                                              \
+		mutex_lock(&data->update_lock);                               \
+		data->current_power_limits.attr_name = val;                   \
+		data->current_profile = PLATFORM_PROFILE_CUSTOM;              \
+                                                                              \
+		err = send_dptc_cmd(cmd_id, val * 1000);                      \
+		mutex_unlock(&data->update_lock);                             \
+                                                                              \
+		return err ? err : count;                                     \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_min_value_show(                            \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return show_int_attr(dev, attr, buf, min);                    \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_max_value_show(                            \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return show_int_attr(dev, attr, buf, max);                    \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_default_value_show(                        \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		struct zotac_platform_data *data =                            \
+			platform_get_drvdata(zotac_platform_device);          \
+		int default_value;                                            \
+                                                                              \
+		if (data->current_profile == PLATFORM_PROFILE_CUSTOM) {       \
+			default_value = ppt_balanced_profile.attr_name;       \
+		} else {                                                      \
+			const struct power_limits *profile;                   \
+                                                                              \
+			switch (data->current_profile) {                      \
+			case PLATFORM_PROFILE_LOW_POWER:                      \
+				profile = &ppt_quiet_profile;                 \
+				break;                                        \
+			case PLATFORM_PROFILE_BALANCED:                       \
+				profile = &ppt_balanced_profile;              \
+				break;                                        \
+			case PLATFORM_PROFILE_PERFORMANCE:                    \
+				profile = &ppt_performance_profile;           \
+				break;                                        \
+			default:                                              \
+				profile = &ppt_balanced_profile;              \
+				break;                                        \
+			}                                                     \
+                                                                              \
+			default_value = profile->attr_name;                   \
+		}                                                             \
+                                                                              \
+		return show_int_attr(dev, attr, buf, default_value);          \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_scalar_increment_show(                     \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return show_int_attr(dev, attr, buf, 1);                      \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_display_name_show(                         \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return show_string_attr(dev, attr, buf, desc);                \
+	}                                                                     \
+                                                                              \
+	static ssize_t attr_name##_type_show(                                 \
+		struct device *dev, struct device_attribute *attr, char *buf) \
+	{                                                                     \
+		return show_string_attr(dev, attr, buf, "int");               \
+	}                                                                     \
+                                                                              \
+	static PPT_ATTR_RW(attr_name##_current_value, "current_value");       \
+	static PPT_ATTR_RO(attr_name##_min_value, "min_value");               \
+	static PPT_ATTR_RO(attr_name##_max_value, "max_value");               \
+	static PPT_ATTR_RO(attr_name##_default_value, "default_value");       \
+	static PPT_ATTR_RO(attr_name##_scalar_increment, "scalar_increment"); \
+	static PPT_ATTR_RO(attr_name##_display_name, "display_name");         \
+	static PPT_ATTR_RO(attr_name##_type, "type");                         \
+                                                                              \
+	static struct attribute *attr_name##_attrs[] = {                      \
+		&dev_attr_##attr_name##_current_value.attr,                   \
+		&dev_attr_##attr_name##_min_value.attr,                       \
+		&dev_attr_##attr_name##_max_value.attr,                       \
+		&dev_attr_##attr_name##_default_value.attr,                   \
+		&dev_attr_##attr_name##_scalar_increment.attr,                \
+		&dev_attr_##attr_name##_display_name.attr,                    \
+		&dev_attr_##attr_name##_type.attr,                            \
+		NULL                                                          \
+	};                                                                    \
+                                                                              \
+	static const struct attribute_group attr_name##_attr_group = {        \
+		.name = #attr_name, .attrs = attr_name##_attrs                \
+	}
+
+/* Define the power limit attribute groups */
+DEFINE_POWER_LIMIT_ATTRS(ppt_pl1_spl, DPTC_SUSTAINED_POWER, PPT_PL1_SPL_MIN,
+			 PPT_PL1_SPL_MAX,
+			 "CPU Sustained Power Limit (PL1/SPL)");
+
+DEFINE_POWER_LIMIT_ATTRS(ppt_pl2_sppt, DPTC_FAST_POWER_1, PPT_PL2_SPPT_MIN,
+			 PPT_PL2_SPPT_MAX,
+			 "CPU Short Term Power Limit (PL2/SPPT)");
+
+DEFINE_POWER_LIMIT_ATTRS(ppt_pl3_fppt, DPTC_FAST_POWER_2, PPT_PL3_FPPT_MIN,
+			 PPT_PL3_FPPT_MAX, "CPU Fast Power Limit (PL3/FPPT)");
+
+/* Combine all power attribute groups */
+static const struct attribute_group *zotac_platform_power_groups[] = {
+	&ppt_pl1_spl_attr_group, &ppt_pl2_sppt_attr_group,
+	&ppt_pl3_fppt_attr_group, NULL
+};
+
+/* Helper function to create all power attribute groups */
+static int create_power_attributes(struct platform_device *pdev, struct zotac_platform_data *data)
+{
+	int i, ret = 0;
+
+	if (!data->wmi_dptc_supported)
+		return 0;
+
+	data->fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
+						NULL, "%s", DRIVER_NAME);
+	if (IS_ERR(data->fw_attr_dev)) {
+		ret = PTR_ERR(data->fw_attr_dev);
+		goto fail_class_get;
+	}
+
+	data->fw_attr_kset = kset_create_and_add("attributes", NULL,
+						&data->fw_attr_dev->kobj);
+	if (!data->fw_attr_kset) {
+		ret = -ENOMEM;
+		goto err_destroy_kset;
+	}
+
+	for (i = 0; zotac_platform_power_groups[i]; i++) {
+		ret = sysfs_create_group(&data->fw_attr_kset->kobj,
+					 zotac_platform_power_groups[i]);
+		if (ret) {
+			dev_warn(&pdev->dev,
+				 "Failed to create power limit group %d\n", i);
+			goto error;
+		}
+	}
+
+	return 0;
+
+error:
+	while (--i >= 0)
+		sysfs_remove_group(&data->fw_attr_kset->kobj,
+				   zotac_platform_power_groups[i]);
+err_destroy_kset:
+	kset_unregister(data->fw_attr_kset);
+fail_class_get:
+	device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+	return ret;
+}
+
+/* Helper function to remove all power attribute groups */
+static void remove_power_attributes(struct platform_device *pdev)
+{
+	struct zotac_platform_data *data = platform_get_drvdata(pdev);
+	int i;
+
+	for (i = 0; zotac_platform_power_groups[i]; i++)
+		sysfs_remove_group(&data->fw_attr_kset->kobj,
+				   zotac_platform_power_groups[i]);
+
+	kset_unregister(data->fw_attr_kset);
+	device_destroy(&firmware_attributes_class, MKDEV(0, 0));
+}
+
+static int zotac_platform_profile_get(struct device *dev,
+				     enum platform_profile_option *profile)
+{
+	struct zotac_platform_data *data = dev_get_drvdata(dev);
+	*profile = data->current_profile;
+	return 0;
+}
+
+static int zotac_platform_profile_set(struct device *dev,
+				     enum platform_profile_option profile)
+{
+	struct zotac_platform_data *data = dev_get_drvdata(dev);
+	const struct power_limits *limits;
+	int ret = 0;
+
+	if (!data->wmi_dptc_supported)
+		return -ENODEV;
+
+	switch (profile) {
+	case PLATFORM_PROFILE_PERFORMANCE:
+		limits = &ppt_performance_profile;
+		break;
+	case PLATFORM_PROFILE_BALANCED:
+		limits = &ppt_balanced_profile;
+		break;
+	case PLATFORM_PROFILE_LOW_POWER:
+		limits = &ppt_quiet_profile;
+		break;
+	case PLATFORM_PROFILE_CUSTOM:
+		limits = &data->current_power_limits;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	mutex_lock(&data->update_lock);
+	ret = send_dptc_cmd(DPTC_SUSTAINED_POWER, limits->ppt_pl1_spl * 1000);
+	if (ret == 0)
+		ret = send_dptc_cmd(DPTC_FAST_POWER_1, limits->ppt_pl2_sppt * 1000);
+	if (ret == 0)
+		ret = send_dptc_cmd(DPTC_FAST_POWER_2, limits->ppt_pl3_fppt * 1000);
+	if (ret == 0) {
+		data->current_profile = profile;
+		if (profile != PLATFORM_PROFILE_CUSTOM)
+			memcpy(&data->current_power_limits, limits, sizeof(struct power_limits));
+	}
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+static int zotac_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_PERFORMANCE, choices);
+	set_bit(PLATFORM_PROFILE_CUSTOM, choices);
+	return 0;
+}
+
+static const struct platform_profile_ops zotac_platform_profile_ops = {
+	.probe = zotac_platform_profile_probe,
+	.profile_get = zotac_platform_profile_get,
+	.profile_set = zotac_platform_profile_set,
+};
+
+static int platform_profile_setup(struct zotac_platform_data *data)
+{
+	struct device *dev = &zotac_platform_device->dev;
+
+	if (!data->wmi_dptc_supported)
+		return 0;
+
+	data->ppdev = devm_platform_profile_register(dev, DRIVER_NAME, data,
+						     &zotac_platform_profile_ops);
+	if (IS_ERR(data->ppdev)) {
+		dev_err(dev, "Failed to register platform_profile device\n");
+		return PTR_ERR(data->ppdev);
+	}
+
+	/* Set default profile */
+	zotac_platform_profile_set(dev, PLATFORM_PROFILE_BALANCED);
+
+	return 0;
+}
+
+static int zotac_platform_probe(struct platform_device *pdev)
+{
+	struct zotac_platform_data *data;
+	struct device *hwmon_dev;
+	int i, ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(struct zotac_platform_data),
+			    GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->wmi_dptc_supported = wmi_has_guid(AMD_APU_WMI_METHODS_GUID);
+	data->valid = false;
+	data->curve_enabled = false;
+	mutex_init(&data->update_lock);
+
+	for (i = 0; i < FAN_CURVE_POINTS; i++) {
+		/* Set default temperature points from 10°C to 90°C */
+		data->curve_temp[i] = 10 + (i * 10);
+		/* Set default PWM values - simple linear curve from 20% to 100% */
+		data->curve_pwm[i] = 20 + (i * 10);
+		if (data->curve_pwm[i] > 100)
+			data->curve_pwm[i] = 100;
+	}
+
+	data->current_profile = PLATFORM_PROFILE_BALANCED;
+	memcpy(&data->current_power_limits, &ppt_balanced_profile, sizeof(struct power_limits));
+	data->ppt_pl1_stapm_time_const = 300;
+	data->ppt_pl2_sppt_time_const = 0x11;
+	data->ppt_platform_sppt = 13000;
+
+	hwmon_dev = devm_hwmon_device_register_with_groups(
+		&pdev->dev, "zotac_platform", data, zotac_platform_hwmon_groups);
+	if (IS_ERR(hwmon_dev))
+		return PTR_ERR(hwmon_dev);
+
+	data->hwmon_dev = hwmon_dev;
+
+	if (data->wmi_dptc_supported) {
+		ret = sysfs_create_group(&pdev->dev.kobj, &zotac_platform_dptc_group);
+		if (ret)
+			dev_warn(&pdev->dev, "Failed to create DPTC sysfs group\n");
+	}
+
+	platform_set_drvdata(pdev, data);
+
+	timer_setup(&fan_curve_timer, fan_curve_function, 0);
+
+	zotac_platform_update_device(&pdev->dev);
+
+	ret = platform_profile_setup(data);
+	if (ret)
+		dev_warn(&pdev->dev, "Failed to setup platform profile\n");
+
+	ret = create_power_attributes(pdev, data);
+	if (ret)
+		dev_warn(&pdev->dev, "Failed to setup firmware attributes\n");
+
+	return 0;
+}
+
+static void zotac_platform_remove(struct platform_device *pdev)
+{
+	struct zotac_platform_data *data = platform_get_drvdata(pdev);
+
+	if (data && data->wmi_dptc_supported) {
+		sysfs_remove_group(&pdev->dev.kobj, &zotac_platform_dptc_group);
+		remove_power_attributes(pdev);
+	}
+}
+
+static struct platform_driver zotac_platform_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe = zotac_platform_probe,
+	.remove = zotac_platform_remove,
+};
+
+static const struct dmi_system_id zotac_platform_dmi_table[] __initconst = {
+    {
+        .ident = "Zotac Gaming Handheld",
+        .matches = {
+            DMI_MATCH(DMI_SYS_VENDOR, "ZOTAC"),
+            DMI_MATCH(DMI_BOARD_NAME, "G0A1W"),
+        },
+    },
+    {
+        .ident = "Zotac ZONE",
+        .matches = {
+            DMI_MATCH(DMI_SYS_VENDOR, "ZOTAC"),
+            DMI_MATCH(DMI_PRODUCT_NAME, "ZOTAC GAMING ZONE"),
+        },
+    },
+    {}  /* Terminate list */
+};
+MODULE_DEVICE_TABLE(dmi, zotac_platform_dmi_table);
+
+static int __init zotac_platform_init(void)
+{
+	int err;
+
+	if (!dmi_check_system(zotac_platform_dmi_table)) {
+		pr_info("No compatible Zotac hardware found\n");
+		return -ENODEV;
+	}
+
+	/* Request I/O regions */
+	if (!request_region(EC_COMMAND_PORT, 1, "zotac_platform_ec") ||
+	    !request_region(EC_DATA_PORT, 1, "zotac_platform_ec")) {
+		pr_err("Failed to request EC I/O ports\n");
+		err = -EBUSY;
+		goto err_release;
+	}
+
+	zotac_platform_device = platform_device_register_simple(
+		DRIVER_NAME, -1, ec_io_ports, ARRAY_SIZE(ec_io_ports));
+	if (IS_ERR(zotac_platform_device)) {
+		err = PTR_ERR(zotac_platform_device);
+		goto err_release;
+	}
+
+	err = platform_driver_register(&zotac_platform_driver);
+	if (err)
+		goto err_device_unregister;
+
+	return 0;
+
+err_device_unregister:
+	platform_device_unregister(zotac_platform_device);
+err_release:
+	release_region(EC_COMMAND_PORT, 1);
+	release_region(EC_DATA_PORT, 1);
+	return err;
+}
+
+static void __exit zotac_platform_exit(void)
+{
+	timer_delete_sync(&fan_curve_timer);
+
+	platform_driver_unregister(&zotac_platform_driver);
+	platform_device_unregister(zotac_platform_device);
+	release_region(EC_COMMAND_PORT, 1);
+	release_region(EC_DATA_PORT, 1);
+}
+
+module_init(zotac_platform_init);
+module_exit(zotac_platform_exit);
+
+MODULE_AUTHOR("Luke D. Jones");
+MODULE_DESCRIPTION("Zotac Handheld Platform Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index e70fba9f55d6..7297d790b664 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -765,6 +765,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 				 ctrlreq->wValue, vdev->rhport);
 
 			vdev->udev = usb_get_dev(urb->dev);
+			dev_pm_syscore_device(&vdev->udev->dev, true);
 			usb_put_dev(old);
 
 			spin_lock(&vdev->ud.lock);
@@ -785,6 +786,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 					"Not yet?:Get_Descriptor to device 0 (get max pipe size)\n");
 
 			vdev->udev = usb_get_dev(urb->dev);
+			dev_pm_syscore_device(&vdev->udev->dev, true);
 			usb_put_dev(old);
 			goto out;
 
@@ -1419,11 +1421,6 @@ static void vhci_hcd_remove(struct platform_device *pdev)
 static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state)
 {
 	struct usb_hcd *hcd;
-	struct vhci *vhci;
-	int rhport;
-	int connected = 0;
-	int ret = 0;
-	unsigned long flags;
 
 	dev_dbg(&pdev->dev, "%s\n", __func__);
 
@@ -1431,33 +1428,9 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state)
 	if (!hcd)
 		return 0;
 
-	vhci = *((void **)dev_get_platdata(hcd->self.controller));
-
-	spin_lock_irqsave(&vhci->lock, flags);
-
-	for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
-		if (vhci->vhci_hcd_hs->port_status[rhport] &
-		    USB_PORT_STAT_CONNECTION)
-			connected += 1;
-
-		if (vhci->vhci_hcd_ss->port_status[rhport] &
-		    USB_PORT_STAT_CONNECTION)
-			connected += 1;
-	}
+	clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
 
-	spin_unlock_irqrestore(&vhci->lock, flags);
-
-	if (connected > 0) {
-		dev_info(&pdev->dev,
-			 "We have %d active connection%s. Do not suspend.\n",
-			 connected, str_plural(connected));
-		ret =  -EBUSY;
-	} else {
-		dev_info(&pdev->dev, "suspend vhci_hcd");
-		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
-	}
-
-	return ret;
+	return 0;
 }
 
 static int vhci_hcd_resume(struct platform_device *pdev)
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/include/linux/hid.h b/include/linux/hid.h
index 98aad4880f84..d3793f9b196b 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -669,6 +669,7 @@ struct hid_device {
 	char name[128];							/* Device name */
 	char phys[64];							/* Device physical location */
 	char uniq[64];							/* Device unique identifier (serial #) */
+	u64 firmware_version;						/* Firmware version */
 
 	void *driver_data;
 
@@ -681,6 +682,7 @@ struct hid_device {
 	void (*hiddev_hid_event) (struct hid_device *, struct hid_field *field,
 				  struct hid_usage *, __s32);
 	void (*hiddev_report_event) (struct hid_device *, struct hid_report *);
+	int (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
 
 	/* debugging support via debugfs */
 	unsigned short debug;
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.50.0.145.g83014dc05f

