summaryrefslogtreecommitdiff
path: root/SOURCES/asus-linux.patch
diff options
context:
space:
mode:
authorJan200101 <sentrycraft123@gmail.com>2023-07-22 20:34:19 +0200
committerJan200101 <sentrycraft123@gmail.com>2023-07-22 20:34:19 +0200
commite57ae98adb2244cb95c2570e6fc73b039b98f918 (patch)
tree6ad89940f61b24642c42fdadfe59f16cfebab8ee /SOURCES/asus-linux.patch
parentb8b93206c3cd36b7003b72c7cfa17582f9f3ef3a (diff)
downloadkernel-fsync-e57ae98adb2244cb95c2570e6fc73b039b98f918.tar.gz
kernel-fsync-e57ae98adb2244cb95c2570e6fc73b039b98f918.zip
kernel 6.4.4
Diffstat (limited to 'SOURCES/asus-linux.patch')
-rw-r--r--SOURCES/asus-linux.patch1839
1 files changed, 1515 insertions, 324 deletions
diff --git a/SOURCES/asus-linux.patch b/SOURCES/asus-linux.patch
index 11e3e80..ed6254a 100644
--- a/SOURCES/asus-linux.patch
+++ b/SOURCES/asus-linux.patch
@@ -1,312 +1,1342 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jan200101 <sentrycraft123@gmail.com>
-Date: Fri, 19 May 2023 17:45:47 +0200
-Subject: [PATCH] asus_linux
+From 5195a8ad57e5076ce520f725a49f595fe617b6d2 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Sun, 4 Jun 2023 18:48:11 +1200
+Subject: [PATCH v2 1/8] platform/x86: asus-wmi: add support for showing
+ charger mode
-Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
+Expose a WMI method in sysfs platform for showing which connected
+charger the laptop is currently using.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
---
- drivers/acpi/resource.c | 14 ++
- drivers/hid/amd-sfh-hid/amd_sfh_client.c | 2 +
- drivers/hid/amd-sfh-hid/amd_sfh_hid.h | 2 +-
- drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 4 +
- drivers/hid/amd-sfh-hid/amd_sfh_pcie.h | 1 +
- .../hid_descriptor/amd_sfh_hid_desc.c | 27 ++++
- .../hid_descriptor/amd_sfh_hid_desc.h | 7 +
- .../hid_descriptor/amd_sfh_hid_report_desc.h | 21 +++
- drivers/hid/hid-asus.c | 42 +++---
- drivers/hid/hid-ids.h | 1 +
- drivers/platform/x86/asus-wmi.c | 131 ++++++++++++++++++
- drivers/platform/x86/asus-wmi.h | 1 +
- include/linux/platform_data/x86/asus-wmi.h | 4 +
- 14 files changed, 250 insertions(+), 27 deletions(-)
-
-diff --git a/drivers/acpi/resource.c b/drivers/acpi/resource.c
-index e8492b3a393a..01a7befe9625 100644
---- a/drivers/acpi/resource.c
-+++ b/drivers/acpi/resource.c
-@@ -467,6 +467,20 @@ static const struct dmi_system_id asus_laptop[] = {
- DMI_MATCH(DMI_BOARD_NAME, "B2502CBA"),
- },
- },
-+ {
-+ .ident = "Asus TUF Gaming A15 FA507NV",
-+ .matches = {
-+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
-+ DMI_MATCH(DMI_BOARD_NAME, "FA507NV"),
-+ },
-+ },
-+ {
-+ .ident = "ASUS TUF Gaming A16 FA617NS",
-+ .matches = {
-+ DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
-+ DMI_MATCH(DMI_BOARD_NAME, "FA617NS"),
-+ },
-+ },
- { }
- };
+ .../ABI/testing/sysfs-platform-asus-wmi | 10 +++++++++
+ drivers/platform/x86/asus-wmi.c | 21 +++++++++++++++++++
+ include/linux/platform_data/x86/asus-wmi.h | 3 +++
+ 3 files changed, 34 insertions(+)
+
+diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+index a77a004a1baa..eb29e3023c7b 100644
+--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+@@ -98,3 +98,13 @@ Description:
+ Enable an LCD response-time boost to reduce or remove ghosting:
+ * 0 - Disable,
+ * 1 - Enable
++
++What: /sys/devices/platform/<platform>/charge_mode
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Get the current charging mode being used:
++ * 1 - Barrel connected charger,
++ * 2 - USB-C charging
++ * 3 - Both connected, barrel used for charging
+\ No newline at end of file
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index 1038dfdcdd32..f23375d5fb82 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -237,6 +237,7 @@ struct asus_wmi {
+ u8 fan_boost_mode_mask;
+ u8 fan_boost_mode;
-diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
-index c751d12f5df8..690be680e392 100644
---- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c
-+++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
-@@ -146,6 +146,8 @@ static const char *get_sensor_name(int idx)
- return "gyroscope";
- case mag_idx:
- return "magnetometer";
-+ case tms_idx:
-+ return "tablet-mode-switch";
- case als_idx:
- return "ALS";
- case HPD_IDX:
-diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
-index 528036892c9d..97296f587bc7 100644
---- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
-+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
-@@ -11,7 +11,7 @@
- #ifndef AMDSFH_HID_H
- #define AMDSFH_HID_H
-
--#define MAX_HID_DEVICES 5
-+#define MAX_HID_DEVICES 6
- #define AMD_SFH_HID_VENDOR 0x1022
- #define AMD_SFH_HID_PRODUCT 0x0001
-
-diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
-index c936d6a51c0c..c33f3f202ff7 100644
---- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
-+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c
-@@ -27,6 +27,7 @@
- #define ACEL_EN BIT(0)
- #define GYRO_EN BIT(1)
- #define MAGNO_EN BIT(2)
-+#define TMS_EN BIT(15)
- #define HPD_EN BIT(16)
- #define ALS_EN BIT(19)
-
-@@ -227,6 +228,9 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id)
- if (MAGNO_EN & activestatus)
- sensor_id[num_of_sensors++] = mag_idx;
-
-+ if (TMS_EN & activestatus)
-+ sensor_id[num_of_sensors++] = tms_idx;
-+
- if (ALS_EN & activestatus)
- sensor_id[num_of_sensors++] = als_idx;
-
-diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
-index dfb7cabd82ef..e18ceee9e5db 100644
---- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
-+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h
-@@ -78,6 +78,7 @@ enum sensor_idx {
- accel_idx = 0,
- gyro_idx = 1,
- mag_idx = 2,
-+ tms_idx = 15,
- als_idx = 19
- };
++ bool charge_mode_available;
+ bool egpu_enable_available;
+ bool dgpu_disable_available;
+ bool gpu_mux_mode_available;
+@@ -586,6 +587,22 @@ static void asus_wmi_tablet_mode_get_state(struct asus_wmi *asus)
+ asus_wmi_tablet_sw_report(asus, result);
+ }
-diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
-index f9a8c02d5a7b..181973f35f05 100644
---- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
-+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c
-@@ -47,6 +47,11 @@ static int get_report_descriptor(int sensor_idx, u8 *rep_desc)
- memcpy(rep_desc, comp3_report_descriptor,
- sizeof(comp3_report_descriptor));
- break;
-+ case tms_idx: /* tablet mode switch */
-+ memset(rep_desc, 0, sizeof(tms_report_descriptor));
-+ memcpy(rep_desc, tms_report_descriptor,
-+ sizeof(tms_report_descriptor));
-+ break;
- case als_idx: /* ambient light sensor */
- memset(rep_desc, 0, sizeof(als_report_descriptor));
- memcpy(rep_desc, als_report_descriptor,
-@@ -96,6 +101,16 @@ static u32 get_descr_sz(int sensor_idx, int descriptor_name)
- return sizeof(struct magno_feature_report);
- }
- break;
-+ case tms_idx:
-+ switch (descriptor_name) {
-+ case descr_size:
-+ return sizeof(tms_report_descriptor);
-+ case input_size:
-+ return sizeof(struct tms_input_report);
-+ case feature_size:
-+ return sizeof(struct tms_feature_report);
-+ }
-+ break;
- case als_idx:
- switch (descriptor_name) {
- case descr_size:
-@@ -138,6 +153,7 @@ static u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report)
- struct accel3_feature_report acc_feature;
- struct gyro_feature_report gyro_feature;
- struct magno_feature_report magno_feature;
-+ struct tms_feature_report tms_feature;
- struct hpd_feature_report hpd_feature;
- struct als_feature_report als_feature;
- u8 report_size = 0;
-@@ -173,6 +189,11 @@ static u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report)
- memcpy(feature_report, &magno_feature, sizeof(magno_feature));
- report_size = sizeof(magno_feature);
- break;
-+ case tms_idx: /* tablet mode switch */
-+ get_common_features(&tms_feature.common_property, report_id);
-+ memcpy(feature_report, &tms_feature, sizeof(tms_feature));
-+ report_size = sizeof(tms_feature);
++/* Charging mode, 1=Barrel, 2=USB ******************************************/
++static ssize_t charge_mode_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++ int result, value;
++
++ result = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CHARGE_MODE, &value);
++ if (result < 0)
++ return result;
++
++ return sysfs_emit(buf, "%d\n", value & 0xff);
++}
++
++static DEVICE_ATTR_RO(charge_mode);
++
+ /* dGPU ********************************************************************/
+ static ssize_t dgpu_disable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+@@ -3462,6 +3479,7 @@ static struct attribute *platform_attributes[] = {
+ &dev_attr_camera.attr,
+ &dev_attr_cardr.attr,
+ &dev_attr_touchpad.attr,
++ &dev_attr_charge_mode.attr,
+ &dev_attr_egpu_enable.attr,
+ &dev_attr_dgpu_disable.attr,
+ &dev_attr_gpu_mux_mode.attr,
+@@ -3491,6 +3509,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+ devid = ASUS_WMI_DEVID_LID_RESUME;
+ else if (attr == &dev_attr_als_enable.attr)
+ devid = ASUS_WMI_DEVID_ALS_ENABLE;
++ else if (attr == &dev_attr_charge_mode.attr)
++ ok = asus->charge_mode_available;
+ else if (attr == &dev_attr_egpu_enable.attr)
+ ok = asus->egpu_enable_available;
+ else if (attr == &dev_attr_dgpu_disable.attr)
+@@ -3757,6 +3777,7 @@ static int asus_wmi_add(struct platform_device *pdev)
+ if (err)
+ goto fail_platform;
+
++ asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE);
+ asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
+ asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
+ asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
+diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
+index 28234dc9fa6a..f90cafe26af1 100644
+--- a/include/linux/platform_data/x86/asus-wmi.h
++++ b/include/linux/platform_data/x86/asus-wmi.h
+@@ -95,6 +95,9 @@
+ /* Keyboard dock */
+ #define ASUS_WMI_DEVID_KBD_DOCK 0x00120063
+
++/* Charging mode - 1=Barrel, 2=USB */
++#define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C
++
+ /* dgpu on/off */
+ #define ASUS_WMI_DEVID_EGPU 0x00090019
+
+--
+2.41.0
+
+From dcc493d84f202bbdf97eb48660179724f744c76d Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Sun, 4 Jun 2023 19:07:31 +1200
+Subject: [PATCH v2 2/8] platform/x86: asus-wmi: add support for showing middle
+ fan RPM
+
+Some newer ASUS ROG laptops now have a middle/center fan in addition
+to the CPU and GPU fans. This new fan typically blows across the
+heatpipes and VRMs betweent eh CPU and GPU.
+
+This commit exposes that fan to PWM control plus showing RPM.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ drivers/platform/x86/asus-wmi.c | 91 ++++++++++++++++++++++
+ include/linux/platform_data/x86/asus-wmi.h | 1 +
+ 2 files changed, 92 insertions(+)
+
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index f23375d5fb82..375d25ae0aca 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -72,6 +72,7 @@ module_param(fnlock_default, bool, 0444);
+
+ #define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0)
+
++#define ASUS_MID_FAN_DESC "mid_fan"
+ #define ASUS_GPU_FAN_DESC "gpu_fan"
+ #define ASUS_FAN_DESC "cpu_fan"
+ #define ASUS_FAN_MFUN 0x13
+@@ -229,8 +230,10 @@ struct asus_wmi {
+
+ enum fan_type fan_type;
+ enum fan_type gpu_fan_type;
++ enum fan_type mid_fan_type;
+ int fan_pwm_mode;
+ int gpu_fan_pwm_mode;
++ int mid_fan_pwm_mode;
+ int agfn_pwm;
+
+ bool fan_boost_mode_available;
+@@ -2129,6 +2132,31 @@ static ssize_t fan2_label_show(struct device *dev,
+ return sysfs_emit(buf, "%s\n", ASUS_GPU_FAN_DESC);
+ }
+
++/* Middle/Center fan on modern ROG laptops */
++static ssize_t fan3_input_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++ int value;
++ int ret;
++
++ ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_MID_FAN_CTRL, &value);
++ if (ret < 0)
++ return ret;
++
++ value &= 0xffff;
++
++ return sysfs_emit(buf, "%d\n", value * 100);
++}
++
++static ssize_t fan3_label_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return sysfs_emit(buf, "%s\n", ASUS_MID_FAN_DESC);
++}
++
+ static ssize_t pwm2_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+@@ -2175,6 +2203,52 @@ static ssize_t pwm2_enable_store(struct device *dev,
+ return count;
+ }
+
++static ssize_t pwm3_enable_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ return sysfs_emit(buf, "%d\n", asus->mid_fan_pwm_mode);
++}
++
++static ssize_t pwm3_enable_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++ int state;
++ int value;
++ int ret;
++ u32 retval;
++
++ ret = kstrtouint(buf, 10, &state);
++ if (ret)
++ return ret;
++
++ switch (state) { /* standard documented hwmon values */
++ case ASUS_FAN_CTRL_FULLSPEED:
++ value = 1;
+ break;
- case als_idx: /* ambient light sensor */
- get_common_features(&als_feature.common_property, report_id);
- als_feature.als_change_sesnitivity = HID_DEFAULT_SENSITIVITY;
-@@ -211,6 +232,7 @@ static u8 get_input_report(u8 current_index, int sensor_idx, int report_id,
- struct accel3_input_report acc_input;
- struct gyro_input_report gyro_input;
- struct hpd_input_report hpd_input;
-+ struct tms_input_report tms_input;
- struct als_input_report als_input;
- struct hpd_status hpdstatus;
- u8 report_size = 0;
-@@ -244,6 +266,11 @@ static u8 get_input_report(u8 current_index, int sensor_idx, int report_id,
- memcpy(input_report, &magno_input, sizeof(magno_input));
- report_size = sizeof(magno_input);
- break;
-+ case tms_idx: /* tablet mode switch */
-+ get_common_inputs(&tms_input.common_property, report_id);
-+ report_size = sizeof(tms_input);
-+ memcpy(input_report, &tms_input, sizeof(tms_input));
++ case ASUS_FAN_CTRL_AUTO:
++ value = 0;
+ break;
- case als_idx: /* Als */
- get_common_inputs(&als_input.common_property, report_id);
- /* For ALS ,V2 Platforms uses C2P_MSG5 register instead of DRAM access method */
-diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h
-index ebd55675eb62..b22068a47429 100644
---- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h
-+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h
-@@ -111,4 +111,11 @@ struct hpd_input_report {
- u8 human_presence;
- } __packed;
-
-+struct tms_feature_report {
-+ struct common_feature_property common_property;
-+} __packed;
-+
-+struct tms_input_report {
-+ struct common_input_property common_property;
-+} __packed;
- #endif
-diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h
-index 697f2791ea9c..96cbc1e5b9a7 100644
---- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h
-+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h
-@@ -644,6 +644,27 @@ static const u8 als_report_descriptor[] = {
- 0xC0 /* HID end collection */
- };
++ default:
++ return -EINVAL;
++ }
++
++ ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_MID_FAN_CTRL,
++ value, &retval);
++ if (ret)
++ return ret;
++
++ if (retval != 1)
++ return -EIO;
++
++ asus->mid_fan_pwm_mode = state;
++ return count;
++}
++
+ /* Fan1 */
+ static DEVICE_ATTR_RW(pwm1);
+ static DEVICE_ATTR_RW(pwm1_enable);
+@@ -2184,6 +2258,10 @@ static DEVICE_ATTR_RO(fan1_label);
+ static DEVICE_ATTR_RW(pwm2_enable);
+ static DEVICE_ATTR_RO(fan2_input);
+ static DEVICE_ATTR_RO(fan2_label);
++/* Fan3 - Middle/center fan */
++static DEVICE_ATTR_RW(pwm3_enable);
++static DEVICE_ATTR_RO(fan3_input);
++static DEVICE_ATTR_RO(fan3_label);
+
+ /* Temperature */
+ static DEVICE_ATTR(temp1_input, S_IRUGO, asus_hwmon_temp1, NULL);
+@@ -2192,10 +2270,13 @@ static struct attribute *hwmon_attributes[] = {
+ &dev_attr_pwm1.attr,
+ &dev_attr_pwm1_enable.attr,
+ &dev_attr_pwm2_enable.attr,
++ &dev_attr_pwm3_enable.attr,
+ &dev_attr_fan1_input.attr,
+ &dev_attr_fan1_label.attr,
+ &dev_attr_fan2_input.attr,
+ &dev_attr_fan2_label.attr,
++ &dev_attr_fan3_input.attr,
++ &dev_attr_fan3_label.attr,
+
+ &dev_attr_temp1_input.attr,
+ NULL
+@@ -2221,6 +2302,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj,
+ || attr == &dev_attr_pwm2_enable.attr) {
+ if (asus->gpu_fan_type == FAN_TYPE_NONE)
+ return 0;
++ } else if (attr == &dev_attr_fan3_input.attr
++ || attr == &dev_attr_fan3_label.attr
++ || attr == &dev_attr_pwm3_enable.attr) {
++ if (asus->mid_fan_type == FAN_TYPE_NONE)
++ return 0;
+ } else if (attr == &dev_attr_temp1_input.attr) {
+ int err = asus_wmi_get_devstate(asus,
+ ASUS_WMI_DEVID_THERMAL_CTRL,
+@@ -2264,6 +2350,7 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus)
+ static int asus_wmi_fan_init(struct asus_wmi *asus)
+ {
+ asus->gpu_fan_type = FAN_TYPE_NONE;
++ asus->mid_fan_type = FAN_TYPE_NONE;
+ asus->fan_type = FAN_TYPE_NONE;
+ asus->agfn_pwm = -1;
+@@ -2278,6 +2365,10 @@ static int asus_wmi_fan_init(struct asus_wmi *asus)
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_FAN_CTRL))
+ asus->gpu_fan_type = FAN_TYPE_SPEC83;
+
++ /* Some models also have a center/middle fan */
++ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MID_FAN_CTRL))
++ asus->mid_fan_type = FAN_TYPE_SPEC83;
+
-+/* TABLET MODE SWITCH */
-+__maybe_unused // Used by sfh1.0, but not yet implemented in sfh1.1
-+static const u8 tms_report_descriptor[] = {
-+0x06, 0x43, 0xFF, // Usage Page (Vendor Defined 0xFF43)
-+0x0A, 0x02, 0x02, // Usage (0x0202)
-+0xA1, 0x01, // Collection (Application)
-+0x85, 0x11, // Report ID (17)
-+0x15, 0x00, // Logical Minimum (0)
-+0x25, 0x01, // Logical Maximum (1)
-+0x35, 0x00, // Physical Minimum (0)
-+0x45, 0x01, // Physical Maximum (1)
-+0x65, 0x00, // Unit (None)
-+0x55, 0x00, // Unit Exponent (0)
-+0x75, 0x01, // Report Size (1)
-+0x95, 0x98, // Report Count (-104)
-+0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
-+0x91, 0x03, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
-+0xC1, 0x00, // End Collection
-+};
+ if (asus->fan_type == FAN_TYPE_NONE)
+ return -ENODEV;
+
+diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
+index f90cafe26af1..2c03bda7703f 100644
+--- a/include/linux/platform_data/x86/asus-wmi.h
++++ b/include/linux/platform_data/x86/asus-wmi.h
+@@ -80,6 +80,7 @@
+ #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 /* deprecated */
+ #define ASUS_WMI_DEVID_CPU_FAN_CTRL 0x00110013
+ #define ASUS_WMI_DEVID_GPU_FAN_CTRL 0x00110014
++#define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031
+ #define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024
+ #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
+
+--
+2.41.0
+
+From 3b638cb413bfa9f50433676fdc82bf63ef0fb3d9 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Sun, 4 Jun 2023 19:37:34 +1200
+Subject: [PATCH v2 3/8] platform/x86: asus-wmi: support middle fan custom
+ curves
+
+Adds support for fan curves defined for the middle fan which
+is available on some ASUS ROG laptops.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ drivers/platform/x86/asus-wmi.c | 77 +++++++++++++++++++++-
+ include/linux/platform_data/x86/asus-wmi.h | 1 +
+ 2 files changed, 76 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index 375d25ae0aca..fb27218e51cf 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -113,6 +113,7 @@ module_param(fnlock_default, bool, 0444);
+ #define FAN_CURVE_BUF_LEN 32
+ #define FAN_CURVE_DEV_CPU 0x00
+ #define FAN_CURVE_DEV_GPU 0x01
++#define FAN_CURVE_DEV_MID 0x02
+ /* Mask to determine if setting temperature or percentage */
+ #define FAN_CURVE_PWM_MASK 0x04
+
+@@ -253,7 +254,8 @@ struct asus_wmi {
+
+ bool cpu_fan_curve_available;
+ bool gpu_fan_curve_available;
+- struct fan_curve_data custom_fan_curves[2];
++ bool mid_fan_curve_available;
++ struct fan_curve_data custom_fan_curves[3];
+
+ struct platform_profile_handler platform_profile_handler;
+ bool platform_profile_support;
+@@ -2080,6 +2082,8 @@ static ssize_t pwm1_enable_store(struct device *dev,
+ asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
+ if (asus->gpu_fan_curve_available)
+ asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
++ if (asus->mid_fan_curve_available)
++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false;
+
+ return count;
+ }
+@@ -2531,6 +2535,9 @@ static int fan_curve_get_factory_default(struct asus_wmi *asus, u32 fan_dev)
+ if (fan_dev == ASUS_WMI_DEVID_GPU_FAN_CURVE)
+ fan_idx = FAN_CURVE_DEV_GPU;
+
++ if (fan_dev == ASUS_WMI_DEVID_MID_FAN_CURVE)
++ fan_idx = FAN_CURVE_DEV_MID;
+
- /* BIOMETRIC PRESENCE*/
- static const u8 hpd_report_descriptor[] = {
- 0x05, 0x20, /* Usage page */
-diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
-index d1094bb1aa42..2bc14e076739 100644
---- a/drivers/hid/hid-asus.c
-+++ b/drivers/hid/hid-asus.c
-@@ -883,33 +883,20 @@ static int asus_input_mapping(struct hid_device *hdev,
- case 0xb5: asus_map_key_clear(KEY_CALC); break;
- case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP); break;
- case 0xc5: asus_map_key_clear(KEY_KBDILLUMDOWN); break;
-+ case 0xc7: asus_map_key_clear(KEY_KBDILLUMTOGGLE); break;
-
-- /* ASUS touchpad toggle */
-- case 0x6b: asus_map_key_clear(KEY_F21); break;
-+ case 0x6b: asus_map_key_clear(KEY_F21); break; /* ASUS touchpad toggle */
-+ case 0x38: asus_map_key_clear(KEY_PROG1); break; /* ROG key */
-+ case 0xba: asus_map_key_clear(KEY_PROG2); break; /* Fn+C ASUS Splendid */
-+ case 0x5c: asus_map_key_clear(KEY_PROG3); break; /* Fn+Space Power4Gear */
-+ case 0x99: asus_map_key_clear(KEY_PROG4); break; /* Fn+F5 "fan" symbol */
-+ case 0xae: asus_map_key_clear(KEY_PROG4); break; /* Fn+F5 "fan" symbol */
-+ case 0x92: asus_map_key_clear(KEY_CALC); break; /* Fn+Ret "Calc" symbol */
-+ case 0xb2: asus_map_key_clear(KEY_PROG2); break; /* Fn+Left previous aura */
-+ case 0xb3: asus_map_key_clear(KEY_PROG3); break; /* Fn+Left next aura */
-+ case 0x6a: asus_map_key_clear(KEY_F13); break; /* Screenpad toggle */
-+ case 0x4b: asus_map_key_clear(KEY_F14); break; /* Arrows/Pg-Up/Dn toggle */
-
-- /* ROG key */
-- case 0x38: asus_map_key_clear(KEY_PROG1); break;
--
-- /* Fn+C ASUS Splendid */
-- case 0xba: asus_map_key_clear(KEY_PROG2); break;
--
-- /* Fn+Space Power4Gear Hybrid */
-- case 0x5c: asus_map_key_clear(KEY_PROG3); break;
--
-- /* Fn+F5 "fan" symbol on FX503VD */
-- case 0x99: asus_map_key_clear(KEY_PROG4); break;
--
-- /* Fn+F5 "fan" symbol on N-Key keyboard */
-- case 0xae: asus_map_key_clear(KEY_PROG4); break;
--
-- /* Fn+Ret "Calc" symbol on N-Key keyboard */
-- case 0x92: asus_map_key_clear(KEY_CALC); break;
--
-- /* Fn+Left Aura mode previous on N-Key keyboard */
-- case 0xb2: asus_map_key_clear(KEY_PROG2); break;
--
-- /* Fn+Right Aura mode next on N-Key keyboard */
-- case 0xb3: asus_map_key_clear(KEY_PROG3); break;
-
- default:
- /* ASUS lazily declares 256 usages, ignore the rest,
-@@ -1267,6 +1254,9 @@ static const struct hid_device_id asus_devices[] = {
- { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
- USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
- QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
-+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
-+ USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
-+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
- { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
- USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
- QUIRK_ROG_CLAYMORE_II_KEYBOARD },
-@@ -1309,4 +1299,4 @@ static struct hid_driver asus_driver = {
+ curves = &asus->custom_fan_curves[fan_idx];
+ err = asus_wmi_evaluate_method_buf(asus->dsts_id, fan_dev, mode, buf,
+ FAN_CURVE_BUF_LEN);
+@@ -2819,6 +2826,42 @@ static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, fan_curve,
+ static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, fan_curve,
+ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7);
+
++/* MID */
++static SENSOR_DEVICE_ATTR_RW(pwm3_enable, fan_curve_enable, FAN_CURVE_DEV_GPU);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 0);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 1);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 2);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 3);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 4);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 5);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 6);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, fan_curve,
++ FAN_CURVE_DEV_GPU, 7);
++
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 0);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 1);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 2);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 3);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 4);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 5);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 6);
++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_pwm, fan_curve,
++ FAN_CURVE_DEV_GPU | FAN_CURVE_PWM_MASK, 7);
++
+ static struct attribute *asus_fan_curve_attr[] = {
+ /* CPU */
+ &sensor_dev_attr_pwm1_enable.dev_attr.attr,
+@@ -2856,6 +2899,24 @@ static struct attribute *asus_fan_curve_attr[] = {
+ &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point7_pwm.dev_attr.attr,
+ &sensor_dev_attr_pwm2_auto_point8_pwm.dev_attr.attr,
++ /* MID */
++ &sensor_dev_attr_pwm3_enable.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point5_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point6_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point7_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point8_temp.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point6_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point7_pwm.dev_attr.attr,
++ &sensor_dev_attr_pwm3_auto_point8_pwm.dev_attr.attr,
+ NULL
};
- module_hid_driver(asus_driver);
--MODULE_LICENSE("GPL");
-+MODULE_LICENSE("GPL");
+@@ -2875,6 +2936,9 @@ static umode_t asus_fan_curve_is_visible(struct kobject *kobj,
+ if (asus->gpu_fan_curve_available && attr->name[3] == '2')
+ return 0644;
+
++ if (asus->mid_fan_curve_available && attr->name[3] == '3')
++ return 0644;
++
+ return 0;
+ }
+
+@@ -2904,7 +2968,14 @@ static int asus_wmi_custom_fan_curve_init(struct asus_wmi *asus)
+ if (err)
+ return err;
+
+- if (!asus->cpu_fan_curve_available && !asus->gpu_fan_curve_available)
++ err = fan_curve_check_present(asus, &asus->mid_fan_curve_available,
++ ASUS_WMI_DEVID_MID_FAN_CURVE);
++ if (err)
++ return err;
++
++ if (!asus->cpu_fan_curve_available
++ && !asus->gpu_fan_curve_available
++ && !asus->mid_fan_curve_available)
+ return 0;
+
+ hwmon = devm_hwmon_device_register_with_groups(
+@@ -2973,6 +3044,8 @@ static int throttle_thermal_policy_write(struct asus_wmi *asus)
+ asus->custom_fan_curves[FAN_CURVE_DEV_CPU].enabled = false;
+ if (asus->gpu_fan_curve_available)
+ asus->custom_fan_curves[FAN_CURVE_DEV_GPU].enabled = false;
++ if (asus->mid_fan_curve_available)
++ asus->custom_fan_curves[FAN_CURVE_DEV_MID].enabled = false;
+
+ return 0;
+ }
+diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
+index 2c03bda7703f..329efc086993 100644
+--- a/include/linux/platform_data/x86/asus-wmi.h
++++ b/include/linux/platform_data/x86/asus-wmi.h
+@@ -83,6 +83,7 @@
+ #define ASUS_WMI_DEVID_MID_FAN_CTRL 0x00110031
+ #define ASUS_WMI_DEVID_CPU_FAN_CURVE 0x00110024
+ #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
++#define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032
+
+ /* Power */
+ #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012
+--
+2.41.0
+
+From c43a477f701c2c97e13f1a2ee6e1304beeddbf56 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Sun, 4 Jun 2023 20:01:57 +1200
+Subject: [PATCH v2 4/8] platform/x86: asus-wmi: add WMI method to show if egpu
+ connected
+
+Exposes the WMI method which tells if the eGPU is properly connected
+on the devices that support it.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ .../ABI/testing/sysfs-platform-asus-wmi | 11 +++++++++-
+ drivers/platform/x86/asus-wmi.c | 21 +++++++++++++++++++
+ include/linux/platform_data/x86/asus-wmi.h | 4 +++-
+ 3 files changed, 34 insertions(+), 2 deletions(-)
+
+diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+index eb29e3023c7b..878daf7c2036 100644
+--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+@@ -107,4 +107,13 @@ Description:
+ Get the current charging mode being used:
+ * 1 - Barrel connected charger,
+ * 2 - USB-C charging
+- * 3 - Both connected, barrel used for charging
\ No newline at end of file
-diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
-index c2e9b6d1fd7d..513290a2e91c 100644
---- a/drivers/hid/hid-ids.h
-+++ b/drivers/hid/hid-ids.h
-@@ -207,6 +207,7 @@
- #define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
- #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
- #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
-+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30
- #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b
- #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
++ * 3 - Both connected, barrel used for charging
++
++What: /sys/devices/platform/<platform>/egpu_connected
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Show if the egpu (XG Mobile) is correctly connected:
++ * 0 - False,
++ * 1 - True
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index fb27218e51cf..0c8a4a46b121 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -243,6 +243,7 @@ struct asus_wmi {
+
+ bool charge_mode_available;
+ bool egpu_enable_available;
++ bool egpu_connect_available;
+ bool dgpu_disable_available;
+ bool gpu_mux_mode_available;
+
+@@ -709,6 +710,22 @@ static ssize_t egpu_enable_store(struct device *dev,
+ }
+ static DEVICE_ATTR_RW(egpu_enable);
+
++/* Is eGPU connected? *********************************************************/
++static ssize_t egpu_connected_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++ int result;
++
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
++ if (result < 0)
++ return result;
++
++ return sysfs_emit(buf, "%d\n", result);
++}
++
++static DEVICE_ATTR_RO(egpu_connected);
++
+ /* gpu mux switch *************************************************************/
+ static ssize_t gpu_mux_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+@@ -3645,6 +3662,7 @@ static struct attribute *platform_attributes[] = {
+ &dev_attr_touchpad.attr,
+ &dev_attr_charge_mode.attr,
+ &dev_attr_egpu_enable.attr,
++ &dev_attr_egpu_connected.attr,
+ &dev_attr_dgpu_disable.attr,
+ &dev_attr_gpu_mux_mode.attr,
+ &dev_attr_lid_resume.attr,
+@@ -3677,6 +3695,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+ ok = asus->charge_mode_available;
+ else if (attr == &dev_attr_egpu_enable.attr)
+ ok = asus->egpu_enable_available;
++ else if (attr == &dev_attr_egpu_connected.attr)
++ ok = asus->egpu_connect_available;
+ else if (attr == &dev_attr_dgpu_disable.attr)
+ ok = asus->dgpu_disable_available;
+ else if (attr == &dev_attr_gpu_mux_mode.attr)
+@@ -3943,6 +3963,7 @@ static int asus_wmi_add(struct platform_device *pdev)
+
+ asus->charge_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CHARGE_MODE);
+ asus->egpu_enable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU);
++ asus->egpu_connect_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
+ asus->dgpu_disable_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_DGPU);
+ asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
+ asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
+diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
+index 329efc086993..2034648f8cdf 100644
+--- a/include/linux/platform_data/x86/asus-wmi.h
++++ b/include/linux/platform_data/x86/asus-wmi.h
+@@ -100,7 +100,9 @@
+ /* Charging mode - 1=Barrel, 2=USB */
+ #define ASUS_WMI_DEVID_CHARGE_MODE 0x0012006C
+
+-/* dgpu on/off */
++/* epu is connected? 1 == true */
++#define ASUS_WMI_DEVID_EGPU_CONNECTED 0x00090018
++/* egpu on/off */
+ #define ASUS_WMI_DEVID_EGPU 0x00090019
+
+ /* dgpu on/off */
+--
+2.41.0
+
+From ba1fcbaa4037e2523c40a7aaa0cab9d75bf75e10 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Tue, 20 Jun 2023 12:26:51 +1200
+Subject: [PATCH v2 5/8] platform/x86: asus-wmi: don't allow eGPU switching if
+ eGPU not connected
+
+Check the ASUS_WMI_DEVID_EGPU_CONNECTED method for eGPU connection
+before allowing the ASUS_WMI_DEVID_EGPU method to run.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ drivers/platform/x86/asus-wmi.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index 0c8a4a46b121..821addb284d7 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -693,6 +693,15 @@ static ssize_t egpu_enable_store(struct device *dev,
+ if (enable > 1)
+ return -EINVAL;
+
++ err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
++ if (err < 0)
++ return err;
++ if (err < 1) {
++ err = -ENODEV;
++ pr_warn("Failed to set egpu disable: %d\n", err);
++ return err;
++ }
++
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result);
+ if (err) {
+ pr_warn("Failed to set egpu disable: %d\n", err);
+--
+2.41.0
+
+From 391b0757f19890d67ec0ade558a255421588047e Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Tue, 20 Jun 2023 12:48:31 +1200
+Subject: [PATCH v2 6/8] platform/x86: asus-wmi: add safety checks to gpu
+ switching
+
+Add safety checking to dgpu_disable, egpu_enable, gpu_mux_mode.
+
+These checks prevent users from doing such things as:
+- disabling the dGPU while is muxed to drive the internal screen
+- enabling the eGPU which also disables the dGPU, while muxed to
+ the internal screen
+- switching the MUX to dGPU while the dGPU is disabled
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ drivers/platform/x86/asus-wmi.c | 50 ++++++++++++++++++++++++++++++++-
+ 1 file changed, 49 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index 821addb284d7..602426a7fb41 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -645,6 +645,18 @@ static ssize_t dgpu_disable_store(struct device *dev,
+ if (disable > 1)
+ return -EINVAL;
+
++ if (asus->gpu_mux_mode_available) {
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
++ if (result < 0)
++ /* An error here may signal greater failure of GPU handling */
++ return result;
++ if (!result && disable) {
++ err = -ENODEV;
++ pr_warn("Can not disable dGPU when the MUX is in dGPU mode: %d\n", err);
++ return err;
++ }
++ }
++
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_DGPU, disable, &result);
+ if (err) {
+ pr_warn("Failed to set dgpu disable: %d\n", err);
+@@ -693,7 +705,7 @@ static ssize_t egpu_enable_store(struct device *dev,
+ if (enable > 1)
+ return -EINVAL;
+
+- err = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU_CONNECTED);
+ if (err < 0)
+ return err;
+ if (err < 1) {
+@@ -702,6 +714,18 @@ static ssize_t egpu_enable_store(struct device *dev,
+ return err;
+ }
+
++ if (asus->gpu_mux_mode_available) {
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_GPU_MUX);
++ if (result < 0)
++ /* An error here may signal greater failure of GPU handling */
++ return result;
++ if (!result && enable) {
++ err = -ENODEV;
++ pr_warn("Can not enable eGPU when the MUX is in dGPU mode: %d\n", err);
++ return err;
++ }
++ }
++
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_EGPU, enable, &result);
+ if (err) {
+ pr_warn("Failed to set egpu disable: %d\n", err);
+@@ -764,6 +788,30 @@ static ssize_t gpu_mux_mode_store(struct device *dev,
+ if (optimus > 1)
+ return -EINVAL;
+
++ if (asus->dgpu_disable_available) {
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_DGPU);
++ if (result < 0)
++ /* An error here may signal greater failure of GPU handling */
++ return result;
++ if (result && !optimus) {
++ err = -ENODEV;
++ pr_warn("Can not switch MUX to dGPU mode when dGPU is disabled: %d\n", err);
++ return err;
++ }
++ }
++
++ if (asus->egpu_enable_available) {
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_EGPU);
++ if (result < 0)
++ /* An error here may signal greater failure of GPU handling */
++ return result;
++ if (result && !optimus) {
++ err = -ENODEV;
++ pr_warn("Can not switch MUX to dGPU mode when eGPU is enabled: %d\n", err);
++ return err;
++ }
++ }
++
+ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_GPU_MUX, optimus, &result);
+ if (err) {
+ dev_err(dev, "Failed to set GPU MUX mode: %d\n", err);
+--
+2.41.0
+
+From 7e181ab671dc4c63eb2b2df5cc50dd3218d9b37d Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Sun, 4 Jun 2023 20:21:10 +1200
+Subject: [PATCH v2 7/8] platform/x86: asus-wmi: support setting mini-LED mode
+
+Support changing the mini-LED mode on some of the newer ASUS laptops.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ .../ABI/testing/sysfs-platform-asus-wmi | 9 ++++
+ drivers/platform/x86/asus-wmi.c | 53 +++++++++++++++++++
+ include/linux/platform_data/x86/asus-wmi.h | 1 +
+ 3 files changed, 63 insertions(+)
+
+diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+index 878daf7c2036..5624bdef49cb 100644
+--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+@@ -117,3 +117,12 @@ Description:
+ Show if the egpu (XG Mobile) is correctly connected:
+ * 0 - False,
+ * 1 - True
++
++What: /sys/devices/platform/<platform>/mini_led_mode
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Change the mini-LED mode:
++ * 0 - Single-zone,
++ * 1 - Multi-zone
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index 602426a7fb41..1fc9e8afc2f3 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -265,6 +265,7 @@ struct asus_wmi {
+ bool battery_rsoc_available;
+
+ bool panel_overdrive_available;
++ bool mini_led_mode_available;
+
+ struct hotplug_slot hotplug_slot;
+ struct mutex hotplug_lock;
+@@ -1820,6 +1821,54 @@ static ssize_t panel_od_store(struct device *dev,
+ }
+ static DEVICE_ATTR_RW(panel_od);
+
++/* Mini-LED mode **************************************************************/
++static ssize_t mini_led_mode_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++ int result;
++
++ result = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
++ if (result < 0)
++ return result;
++
++ return sysfs_emit(buf, "%d\n", result);
++}
++
++static ssize_t mini_led_mode_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 mode;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &mode);
++ if (result)
++ return result;
++
++ if (mode > 1)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MINI_LED_MODE, mode, &result);
++
++ if (err) {
++ pr_warn("Failed to set mini-LED: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set mini-LED mode (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "mini_led_mode");
++
++ return count;
++}
++static DEVICE_ATTR_RW(mini_led_mode);
++
+ /* Quirks *********************************************************************/
+
+ static void asus_wmi_set_xusb2pr(struct asus_wmi *asus)
+@@ -3727,6 +3776,7 @@ static struct attribute *platform_attributes[] = {
+ &dev_attr_fan_boost_mode.attr,
+ &dev_attr_throttle_thermal_policy.attr,
+ &dev_attr_panel_od.attr,
++ &dev_attr_mini_led_mode.attr,
+ NULL
+ };
+
+@@ -3764,6 +3814,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+ ok = asus->throttle_thermal_policy_available;
+ else if (attr == &dev_attr_panel_od.attr)
+ ok = asus->panel_overdrive_available;
++ else if (attr == &dev_attr_mini_led_mode.attr)
++ ok = asus->mini_led_mode_available;
+
+ if (devid != -1)
+ ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0);
+@@ -4026,6 +4078,7 @@ static int asus_wmi_add(struct platform_device *pdev)
+ asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
+ asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
+ asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
++ asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
+
+ err = fan_boost_mode_check_present(asus);
+ if (err)
+diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
+index 2034648f8cdf..ea80361ac6c7 100644
+--- a/include/linux/platform_data/x86/asus-wmi.h
++++ b/include/linux/platform_data/x86/asus-wmi.h
+@@ -66,6 +66,7 @@
+ #define ASUS_WMI_DEVID_CAMERA 0x00060013
+ #define ASUS_WMI_DEVID_LID_FLIP 0x00060062
+ #define ASUS_WMI_DEVID_LID_FLIP_ROG 0x00060077
++#define ASUS_WMI_DEVID_MINI_LED_MODE 0x0005001E
+
+ /* Storage */
+ #define ASUS_WMI_DEVID_CARDREADER 0x00080013
+--
+2.41.0
+
+From b297ff51a7a10ed07f176cc8ba8ea7a653c5b725 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Tue, 6 Jun 2023 15:05:01 +1200
+Subject: [PATCH v2 8/8] platform/x86: asus-wmi: expose dGPU and CPU tunables
+ for ROG
+
+Expose various CPU and dGPU tunables that are available on many ASUS
+ROG laptops. The tunables shown in sysfs will vary depending on the CPU
+and dGPU vendor.
+
+All of these variables are write only and there is no easy way to find
+what the defaults are. In general they seem to default to the max value
+the vendor sets for the CPU and dGPU package - this is not the same as
+the min/max writable value. Values written to these variables that are
+beyond the capabilities of the CPU are ignored by the laptop.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ .../ABI/testing/sysfs-platform-asus-wmi | 58 ++++
+ drivers/platform/x86/asus-wmi.c | 285 ++++++++++++++++++
+ include/linux/platform_data/x86/asus-wmi.h | 9 +
+ 3 files changed, 352 insertions(+)
+
+diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+index 5624bdef49cb..caaccd28fabf 100644
+--- a/Documentation/ABI/testing/sysfs-platform-asus-wmi
++++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi
+@@ -126,3 +126,61 @@ Description:
+ Change the mini-LED mode:
+ * 0 - Single-zone,
+ * 1 - Multi-zone
++
++What: /sys/devices/platform/<platform>/ppt_pl1_spl
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the Package Power Target total of CPU: PL1 on Intel, SPL on AMD.
++ Shown on Intel+Nvidia or AMD+Nvidia based systems.
++ * min=5, max=250
++
++What: /sys/devices/platform/<platform>/ppt_pl2_sppt
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the Slow Package Power Tracking Limit of CPU: PL2 on Intel, SPPT,
++ on AMD. Shown on Intel+Nvidia or AMD+Nvidia based systems.
++ * min=5, max=250
++
++What: /sys/devices/platform/<platform>/ppt_fppt
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the Fast Package Power Tracking Limit of CPU. AMD+Nvidia only.
++ * min=5, max=250
++
++What: /sys/devices/platform/<platform>/ppt_apu_sppt
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the APU SPPT limit. Shown on full AMD systems only.
++ * min=5, max=130
++
++What: /sys/devices/platform/<platform>/ppt_platform_sppt
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the platform SPPT limit. Shown on full AMD systems only.
++ * min=5, max=130
++
++What: /sys/devices/platform/<platform>/nv_dynamic_boost
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the dynamic boost limit of the Nvidia dGPU:
++ * min=5, max=25
++
++What: /sys/devices/platform/<platform>/nv_temp_target
++Date: Jun 2023
++KernelVersion: 6.5
++Contact: "Luke Jones" <luke@ljones.dev>
++Description:
++ Set the target temperature limit of the Nvidia dGPU:
++ * min=75, max=87
+diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
+index 1fc9e8afc2f3..d9a353081f91 100644
+--- a/drivers/platform/x86/asus-wmi.c
++++ b/drivers/platform/x86/asus-wmi.c
+@@ -117,6 +117,16 @@ module_param(fnlock_default, bool, 0444);
+ /* Mask to determine if setting temperature or percentage */
+ #define FAN_CURVE_PWM_MASK 0x04
+
++/* Limits for tunables available on ASUS ROG laptops */
++#define PPT_TOTAL_MIN 5
++#define PPT_TOTAL_MAX 250
++#define PPT_CPU_MIN 5
++#define PPT_CPU_MAX 130
++#define NVIDIA_BOOST_MIN 5
++#define NVIDIA_BOOST_MAX 25
++#define NVIDIA_TEMP_MIN 75
++#define NVIDIA_TEMP_MAX 87
++
+ static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL };
+
+ static int throttle_thermal_policy_write(struct asus_wmi *);
+@@ -247,6 +257,15 @@ struct asus_wmi {
+ bool dgpu_disable_available;
+ bool gpu_mux_mode_available;
+
++ /* Tunables provided by ASUS for gaming laptops */
++ bool ppt_pl2_sppt_available;
++ bool ppt_pl1_spl_available;
++ bool ppt_apu_sppt_available;
++ bool ppt_plat_sppt_available;
++ bool ppt_fppt_available;
++ bool nv_dyn_boost_available;
++ bool nv_temp_tgt_available;
++
+ bool kbd_rgb_mode_available;
+ bool kbd_rgb_state_available;
+
+@@ -946,6 +965,244 @@ static const struct attribute_group *kbd_rgb_mode_groups[] = {
+ NULL,
+ };
+
++/* Tunable: PPT: Intel=PL1, AMD=SPPT *****************************************/
++static ssize_t ppt_pl2_sppt_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL2_SPPT, value, &result);
++ if (err) {
++ pr_warn("Failed to set ppt_pl2_sppt: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set ppt_pl2_sppt (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl2_sppt");
++
++ return count;
++}
++static DEVICE_ATTR_WO(ppt_pl2_sppt);
++
++/* Tunable: PPT, Intel=PL1, AMD=SPL ******************************************/
++static ssize_t ppt_pl1_spl_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PL1_SPL, value, &result);
++ if (err) {
++ pr_warn("Failed to set ppt_pl1_spl: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set ppt_pl1_spl (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_pl1_spl");
++
++ return count;
++}
++static DEVICE_ATTR_WO(ppt_pl1_spl);
++
++/* Tunable: PPT APU FPPT ******************************************************/
++static ssize_t ppt_fppt_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < PPT_TOTAL_MIN || value > PPT_TOTAL_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_FPPT, value, &result);
++ if (err) {
++ pr_warn("Failed to set ppt_fppt: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set ppt_fppt (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_fpu_sppt");
++
++ return count;
++}
++static DEVICE_ATTR_WO(ppt_fppt);
++
++/* Tunable: PPT APU SPPT *****************************************************/
++static ssize_t ppt_apu_sppt_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_APU_SPPT, value, &result);
++ if (err) {
++ pr_warn("Failed to set ppt_apu_sppt: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set ppt_apu_sppt (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_apu_sppt");
++
++ return count;
++}
++static DEVICE_ATTR_WO(ppt_apu_sppt);
++
++/* Tunable: PPT platform SPPT ************************************************/
++static ssize_t ppt_platform_sppt_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < PPT_CPU_MIN || value > PPT_CPU_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_PPT_PLAT_SPPT, value, &result);
++ if (err) {
++ pr_warn("Failed to set ppt_platform_sppt: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set ppt_platform_sppt (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "ppt_platform_sppt");
++
++ return count;
++}
++static DEVICE_ATTR_WO(ppt_platform_sppt);
++
++/* Tunable: NVIDIA dynamic boost *********************************************/
++static ssize_t nv_dynamic_boost_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < NVIDIA_BOOST_MIN || value > NVIDIA_BOOST_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_DYN_BOOST, value, &result);
++ if (err) {
++ pr_warn("Failed to set nv_dynamic_boost: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set nv_dynamic_boost (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_dynamic_boost");
++
++ return count;
++}
++static DEVICE_ATTR_WO(nv_dynamic_boost);
++
++/* Tunable: NVIDIA temperature target ****************************************/
++static ssize_t nv_temp_target_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ int result, err;
++ u32 value;
++
++ struct asus_wmi *asus = dev_get_drvdata(dev);
++
++ result = kstrtou32(buf, 10, &value);
++ if (result)
++ return result;
++
++ if (value < NVIDIA_TEMP_MIN || value > NVIDIA_TEMP_MAX)
++ return -EINVAL;
++
++ err = asus_wmi_set_devstate(ASUS_WMI_DEVID_NV_THERM_TARGET, value, &result);
++ if (err) {
++ pr_warn("Failed to set nv_temp_target: %d\n", err);
++ return err;
++ }
++
++ if (result > 1) {
++ pr_warn("Failed to set nv_temp_target (result): 0x%x\n", result);
++ return -EIO;
++ }
++
++ sysfs_notify(&asus->platform_device->dev.kobj, NULL, "nv_temp_target");
++
++ return count;
++}
++static DEVICE_ATTR_WO(nv_temp_target);
++
+ /* Battery ********************************************************************/
+
+ /* The battery maximum charging percentage */
+@@ -3775,6 +4032,13 @@ static struct attribute *platform_attributes[] = {
+ &dev_attr_als_enable.attr,
+ &dev_attr_fan_boost_mode.attr,
+ &dev_attr_throttle_thermal_policy.attr,
++ &dev_attr_ppt_pl2_sppt.attr,
++ &dev_attr_ppt_pl1_spl.attr,
++ &dev_attr_ppt_fppt.attr,
++ &dev_attr_ppt_apu_sppt.attr,
++ &dev_attr_ppt_platform_sppt.attr,
++ &dev_attr_nv_dynamic_boost.attr,
++ &dev_attr_nv_temp_target.attr,
+ &dev_attr_panel_od.attr,
+ &dev_attr_mini_led_mode.attr,
+ NULL
+@@ -3812,6 +4076,20 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj,
+ ok = asus->fan_boost_mode_available;
+ else if (attr == &dev_attr_throttle_thermal_policy.attr)
+ ok = asus->throttle_thermal_policy_available;
++ else if (attr == &dev_attr_ppt_pl2_sppt.attr)
++ ok = asus->ppt_pl2_sppt_available;
++ else if (attr == &dev_attr_ppt_pl1_spl.attr)
++ ok = asus->ppt_pl1_spl_available;
++ else if (attr == &dev_attr_ppt_fppt.attr)
++ ok = asus->ppt_fppt_available;
++ else if (attr == &dev_attr_ppt_apu_sppt.attr)
++ ok = asus->ppt_apu_sppt_available;
++ else if (attr == &dev_attr_ppt_platform_sppt.attr)
++ ok = asus->ppt_plat_sppt_available;
++ else if (attr == &dev_attr_nv_dynamic_boost.attr)
++ ok = asus->nv_dyn_boost_available;
++ else if (attr == &dev_attr_nv_temp_target.attr)
++ ok = asus->nv_temp_tgt_available;
+ else if (attr == &dev_attr_panel_od.attr)
+ ok = asus->panel_overdrive_available;
+ else if (attr == &dev_attr_mini_led_mode.attr)
+@@ -4077,6 +4355,13 @@ static int asus_wmi_add(struct platform_device *pdev)
+ asus->gpu_mux_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_GPU_MUX);
+ asus->kbd_rgb_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_MODE);
+ asus->kbd_rgb_state_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_TUF_RGB_STATE);
++ asus->ppt_pl2_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL2_SPPT);
++ asus->ppt_pl1_spl_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PL1_SPL);
++ asus->ppt_fppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_FPPT);
++ asus->ppt_apu_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_APU_SPPT);
++ asus->ppt_plat_sppt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PPT_PLAT_SPPT);
++ asus->nv_dyn_boost_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_DYN_BOOST);
++ asus->nv_temp_tgt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_THERM_TARGET);
+ asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD);
+ asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE);
+
+diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
+index ea80361ac6c7..16e99a1c37fc 100644
+--- a/include/linux/platform_data/x86/asus-wmi.h
++++ b/include/linux/platform_data/x86/asus-wmi.h
+@@ -86,6 +86,15 @@
+ #define ASUS_WMI_DEVID_GPU_FAN_CURVE 0x00110025
+ #define ASUS_WMI_DEVID_MID_FAN_CURVE 0x00110032
+
++/* Tunables for AUS ROG laptops */
++#define ASUS_WMI_DEVID_PPT_PL2_SPPT 0x001200A0
++#define ASUS_WMI_DEVID_PPT_PL1_SPL 0x001200A3
++#define ASUS_WMI_DEVID_PPT_APU_SPPT 0x001200B0
++#define ASUS_WMI_DEVID_PPT_PLAT_SPPT 0x001200B1
++#define ASUS_WMI_DEVID_PPT_FPPT 0x001200C1
++#define ASUS_WMI_DEVID_NV_DYN_BOOST 0x001200C0
++#define ASUS_WMI_DEVID_NV_THERM_TARGET 0x001200C2
++
+ /* Power */
+ #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012
+--
+2.41.0
+
+From 43d8c3f0139b08cd3c0c31ed463d7ae57fcf552c Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Sun, 30 Apr 2023 10:56:34 +1200
+Subject: [PATCH v4 1/1] platform/x86: asus-wmi: add support for ASUS screenpad
+
+Add support for the WMI methods used to turn off and adjust the
+brightness of the secondary "screenpad" device found on some high-end
+ASUS laptops like the GX650P series and others.
+
+These methods are utilised in a new backlight device named asus_screenpad.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ drivers/platform/x86/asus-wmi.c | 128 +++++++++++++++++++++
+ drivers/platform/x86/asus-wmi.h | 1 +
+ include/linux/platform_data/x86/asus-wmi.h | 4 +
+ 3 files changed, 133 insertions(+)
+
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
-index 1038dfdcdd32..14a32e08f900 100644
+index 62cee13f5576..967c92ceb041 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -25,6 +25,7 @@
@@ -317,7 +1347,7 @@ index 1038dfdcdd32..14a32e08f900 100644
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
-@@ -200,6 +201,7 @@ struct asus_wmi {
+@@ -212,6 +213,7 @@ struct asus_wmi {
struct input_dev *inputdev;
struct backlight_device *backlight_device;
@@ -325,7 +1355,7 @@ index 1038dfdcdd32..14a32e08f900 100644
struct platform_device *platform_device;
struct led_classdev wlan_led;
-@@ -3208,6 +3210,127 @@ static int is_display_toggle(int code)
+@@ -3839,6 +3841,123 @@ static int is_display_toggle(int code)
return 0;
}
@@ -333,8 +1363,9 @@ index 1038dfdcdd32..14a32e08f900 100644
+
+static int read_screenpad_backlight_power(struct asus_wmi *asus)
+{
-+ int ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
++ int ret;
+
++ ret = asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_SCREENPAD_POWER);
+ if (ret < 0)
+ return ret;
+ /* 1 == powered */
@@ -368,9 +1399,7 @@ index 1038dfdcdd32..14a32e08f900 100644
+ u32 ctrl_param;
+
+ power = read_screenpad_backlight_power(asus);
-+ if (power == -ENODEV)
-+ return err;
-+ else if (power < 0)
++ if (power < 0)
+ return power;
+
+ if (bd->props.power != power) {
@@ -404,14 +1433,22 @@ index 1038dfdcdd32..14a32e08f900 100644
+{
+ struct backlight_device *bd;
+ struct backlight_properties props;
-+ int power, brightness;
++ int err, power;
++ int brightness = 0;
+
+ power = read_screenpad_backlight_power(asus);
-+ if (power == -ENODEV)
-+ power = FB_BLANK_UNBLANK;
-+ else if (power < 0)
++ if (power < 0)
+ return power;
+
++ if (power != FB_BLANK_POWERDOWN) {
++ err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT, &brightness);
++ if (err < 0)
++ return err;
++ }
++ /* default to an acceptable min brightness on boot if too low */
++ if (brightness < 60)
++ brightness = 60;
++
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_RAW; /* ensure this bd is last to be picked */
+ props.max_brightness = 255;
@@ -424,17 +1461,6 @@ index 1038dfdcdd32..14a32e08f900 100644
+ }
+
+ asus->screenpad_backlight_device = bd;
-+
-+ brightness = read_screenpad_brightness(bd);
-+ if (brightness < 0)
-+ return brightness;
-+ /*
-+ * Counter an odd behaviour where default is set to < 13 if it was 0 on boot.
-+ * 60 is subjective, but accepted as a good compromise to retain visibility.
-+ */
-+ if (brightness < 60)
-+ brightness = 60;
-+
+ asus->driver->screenpad_brightness = brightness;
+ bd->props.brightness = brightness;
+ bd->props.power = power;
@@ -453,28 +1479,36 @@ index 1038dfdcdd32..14a32e08f900 100644
/* Fn-lock ********************************************************************/
static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus)
-@@ -3823,6 +3946,13 @@ static int asus_wmi_add(struct platform_device *pdev)
+@@ -4504,6 +4623,12 @@ static int asus_wmi_add(struct platform_device *pdev)
} else if (asus->driver->quirks->wmi_backlight_set_devstate)
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL);
+ if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT)) {
-+ pr_warn("Begin asus_screenpad_init");
+ err = asus_screenpad_init(asus);
+ if (err && err != -ENODEV)
-+ goto fail_backlight;
++ goto fail_screenpad;
+ }
+
if (asus_wmi_has_fnlock_key(asus)) {
asus->fnlock_locked = fnlock_default;
asus_wmi_fnlock_update(asus);
-@@ -3844,6 +3974,7 @@ static int asus_wmi_add(struct platform_device *pdev)
-
- fail_wmi_handler:
+@@ -4527,6 +4652,8 @@ static int asus_wmi_add(struct platform_device *pdev)
asus_wmi_backlight_exit(asus);
-+ asus_screenpad_exit(asus);
fail_backlight:
asus_wmi_rfkill_exit(asus);
++fail_screenpad:
++ asus_screenpad_exit(asus);
fail_rfkill:
+ asus_wmi_led_exit(asus);
+ fail_leds:
+@@ -4553,6 +4680,7 @@ static int asus_wmi_remove(struct platform_device *device)
+ asus = platform_get_drvdata(device);
+ wmi_remove_notify_handler(asus->driver->event_guid);
+ asus_wmi_backlight_exit(asus);
++ asus_screenpad_exit(asus);
+ asus_wmi_input_exit(asus);
+ asus_wmi_led_exit(asus);
+ asus_wmi_rfkill_exit(asus);
diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h
index a478ebfd34df..5fbdd0eafa02 100644
--- a/drivers/platform/x86/asus-wmi.h
@@ -488,7 +1522,7 @@ index a478ebfd34df..5fbdd0eafa02 100644
const char *name;
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
-index 28234dc9fa6a..a2d94adb5c80 100644
+index d17ae2eb0f8d..61ba70b32846 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -58,6 +58,10 @@
@@ -502,3 +1536,160 @@ index 28234dc9fa6a..a2d94adb5c80 100644
#define ASUS_WMI_DEVID_FAN_BOOST_MODE 0x00110018
#define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075
+--
+2.41.0
+
+From 3f62172395b381b53e76dd8bcb8f202f8bad3720 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Fri, 30 Jun 2023 16:24:05 +1200
+Subject: [PATCH v2 1/5] ALSA: hda/realtek: Add quirk for ASUS ROG GX650P
+
+Adds the required quirk to enable the Cirrus amp and correct pins
+on the ASUS ROG GV601V series which uses an I2C connected Cirrus amp.
+
+While this works if the related _DSD properties are made available, these
+aren't included in the ACPI of these laptops (yet).
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ sound/pci/hda/patch_realtek.c | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+
+diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
+index afe8253f9a4f..b41fdf22157c 100644
+--- a/sound/pci/hda/patch_realtek.c
++++ b/sound/pci/hda/patch_realtek.c
+@@ -7068,6 +7068,8 @@ enum {
+ ALC285_FIXUP_SPEAKER2_TO_DAC1,
+ ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1,
+ ALC285_FIXUP_ASUS_HEADSET_MIC,
++ ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1,
++ ALC285_FIXUP_ASUS_I2C_HEADSET_MIC,
+ ALC280_FIXUP_HP_HEADSET_MIC,
+ ALC221_FIXUP_HP_FRONT_MIC,
+ ALC292_FIXUP_TPT460,
+@@ -8058,6 +8060,22 @@ static const struct hda_fixup alc269_fixups[] = {
+ .chained = true,
+ .chain_id = ALC285_FIXUP_ASUS_SPEAKER2_TO_DAC1
+ },
++ [ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1] = {
++ .type = HDA_FIXUP_FUNC,
++ .v.func = alc285_fixup_speaker2_to_dac1,
++ .chained = true,
++ .chain_id = ALC287_FIXUP_CS35L41_I2C_2
++ },
++ [ALC285_FIXUP_ASUS_I2C_HEADSET_MIC] = {
++ .type = HDA_FIXUP_PINS,
++ .v.pins = (const struct hda_pintbl[]) {
++ { 0x19, 0x03a11050 },
++ { 0x1b, 0x03a11c30 },
++ { }
++ },
++ .chained = true,
++ .chain_id = ALC285_FIXUP_ASUS_I2C_SPEAKER2_TO_DAC1
++ },
+ [ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+@@ -9573,6 +9591,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE),
+ SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK),
++ SND_PCI_QUIRK(0x1043, 0x1433, "ASUS GX650P", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1473, "ASUS GU604V", ALC285_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1483, "ASUS GU603V", ALC285_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1493, "ASUS GV601V", ALC285_FIXUP_ASUS_HEADSET_MIC),
+--
+2.41.0
+
+From c0a696a80313e239f092dcda8c24f26c93eb9167 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Fri, 30 Jun 2023 16:25:02 +1200
+Subject: [PATCH v2 2/5] ALSA: hda/realtek: Add quirk for ASUS ROG GA402X
+
+Adds the required quirk to enable the Cirrus amp and correct pins
+on the ASUS ROG GA402X series which uses an I2C connected Cirrus amp.
+
+While this works if the related _DSD properties are made available, these
+aren't included in the ACPI of these laptops (yet).
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ sound/pci/hda/patch_realtek.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
+index b41fdf22157c..1fae8e8b1234 100644
+--- a/sound/pci/hda/patch_realtek.c
++++ b/sound/pci/hda/patch_realtek.c
+@@ -9592,6 +9592,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK),
+ SND_PCI_QUIRK(0x1043, 0x1433, "ASUS GX650P", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC),
++ SND_PCI_QUIRK(0x1043, 0x1463, "Asus GA402X", ALC285_FIXUP_ASUS_I2C_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1473, "ASUS GU604V", ALC285_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1483, "ASUS GU603V", ALC285_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1493, "ASUS GV601V", ALC285_FIXUP_ASUS_HEADSET_MIC),
+--
+2.41.0
+
+From 64411d8f62ef78bb45167fb7f10f87c662148a7c Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Tue, 4 Jul 2023 16:06:46 +1200
+Subject: [PATCH v2 4/5] ALSA: hda/realtek: Add quirk for ASUS ROG G614Jx
+
+Adds the required quirk to enable the Cirrus amp and correct pins
+on the ASUS ROG G614J series which uses an SPI connected Cirrus amp.
+
+While this works if the related _DSD properties are made available, these
+aren't included in the ACPI of these laptops (yet).
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ sound/pci/hda/patch_realtek.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
+index 50becdc86daa..ba3c113f0be1 100644
+--- a/sound/pci/hda/patch_realtek.c
++++ b/sound/pci/hda/patch_realtek.c
+@@ -9573,6 +9573,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = {
+ SND_PCI_QUIRK(0x1043, 0x1c23, "Asus X55U", ALC269_FIXUP_LIMIT_INT_MIC_BOOST),
+ SND_PCI_QUIRK(0x1043, 0x1c62, "ASUS GU603", ALC289_FIXUP_ASUS_GA401),
+ SND_PCI_QUIRK(0x1043, 0x1c92, "ASUS ROG Strix G15", ALC285_FIXUP_ASUS_G533Z_PINS),
++ SND_PCI_QUIRK(0x1043, 0x1c9f, "ASUS G614JI", ALC285_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1caf, "ASUS G634JYR/JZR", ALC285_FIXUP_ASUS_HEADSET_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC),
+ SND_PCI_QUIRK(0x1043, 0x1d42, "ASUS Zephyrus G14 2022", ALC289_FIXUP_ASUS_GA401),
+--
+2.41.0
+
+From fb720ee87adcf7ab507b46299652ebd8f0f04f12 Mon Sep 17 00:00:00 2001
+From: "Luke D. Jones" <luke@ljones.dev>
+Date: Tue, 4 Jul 2023 16:34:23 +1200
+Subject: [PATCH v2 5/5] Fixes: 31278997add6 (ALSA: hda/realtek - Add headset
+ quirk for Dell DT)
+
+Remove an erroneous whitespace.
+
+Signed-off-by: Luke D. Jones <luke@ljones.dev>
+---
+ sound/pci/hda/patch_realtek.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
+index ba3c113f0be1..1547c40dc7e9 100644
+--- a/sound/pci/hda/patch_realtek.c
++++ b/sound/pci/hda/patch_realtek.c
+@@ -5883,7 +5883,7 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec,
+ struct alc_spec *spec = codec->spec;
+ spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
+ alc255_set_default_jack_type(codec);
+- }
++ }
+ else
+ alc_fixup_headset_mode(codec, fix, action);
+ }
+--
+2.41.0
+