Update lenovo-legion-laptop.patch

This commit is contained in:
ferrreo 2024-04-04 19:20:17 +01:00 committed by GitHub
parent 8580833649
commit 1f88b2fc25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,14 +1,14 @@
From cc20a3c74424b7fd78a650803bc06b822e8b1e56 Mon Sep 17 00:00:00 2001
From 26077d270f462eaf3da592ed047956df3436ed36 Mon Sep 17 00:00:00 2001
From: John Martens <john.martens4@proton.me>
Date: Wed, 30 Aug 2023 15:42:09 +0000
Subject: [PATCH] Add legion-laptop v0.0.7
Date: Fri, 29 Mar 2024 20:18:47 +0000
Subject: [PATCH] Add legion-laptop v0.0.12
Add extra support for Lenovo Legion laptops.
---
drivers/platform/x86/Kconfig | 10 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/legion-laptop.c | 5858 ++++++++++++++++++++++++++
3 files changed, 5869 insertions(+)
drivers/platform/x86/legion-laptop.c | 6089 ++++++++++++++++++++++++++
3 files changed, 6100 insertions(+)
create mode 100644 drivers/platform/x86/legion-laptop.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
@ -46,10 +46,10 @@ index 52dfdf574..5f32dd9df 100644
# Intel
diff --git a/drivers/platform/x86/legion-laptop.c b/drivers/platform/x86/legion-laptop.c
new file mode 100644
index 000000000..727510507
index 000000000..5ec0a518f
--- /dev/null
+++ b/drivers/platform/x86/legion-laptop.c
@@ -0,0 +1,5858 @@
@@ -0,0 +1,6089 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * legion-laptop.c - Extra Lenovo Legion laptop support, in
@ -64,14 +64,14 @@ index 000000000..727510507
+ * data doesn't match the model AND FIRMWARE.
+ *
+ * Support for other hardware of this model is already partially
+ * provided by the module ideapd-laptop.
+ * provided by the module ideapad-laptop.
+ *
+ * The development page for this driver is located at
+ * https://github.com/johnfanv2/LenovoLegionLinux
+ *
+ * This driver exports the files:
+ * - /sys/kernel/debug/legion/fancurve (ro)
+ * The fan curve in the form stored in the firmware in an
+ * The fan curve stored in the firmware in the form of a
+ * human readable table.
+ *
+ * - /sys/module/legion_laptop/drivers/platform\:legion/PNP0C09\:00/powermode (rw)
@ -176,13 +176,13 @@ index 000000000..727510507
+ * to part of it (first 0xFF bytes?)
+ *
+ * In later models the firmware directly exposes ACPI methods to
+ * set the fan curve direclty, without writing to EC RAM. This
+ * set the fan curve directly, without writing to EC RAM. This
+ * is done inside the ACPI method.
+ */
+
+/**
+ * Offsets for interseting values inside the EC RAM (0 = start of
+ * EC RAM. These might change depending on the software inside of
+ * Offsets for interesting values inside the EC RAM (0 = start of
+ * EC RAM) These might change depending on the software inside of
+ * the EC, which can be updated by a BIOS update from Lenovo.
+ */
+// TODO: same order as in initialization
@ -192,7 +192,7 @@ index 000000000..727510507
+ // General Control (GCTRL)
+ // (see EC Interface Registers and 6.2 Plug and Play Configuration (PNPCFG)) in datasheet
+ // note: these are in two places saved
+ // in EC Interface Registers and in super io configuraion registers
+ // in EC Interface Registers and in super io configuration registers
+ // Chip ID
+ u16 ECHIPID1;
+ u16 ECHIPID2;
@ -225,9 +225,9 @@ index 000000000..727510507
+ u16 EXT_POWERMODE;
+ u16 EXT_MINIFANCURVE_ON_COOL;
+ // values
+ // 0x04: enable mini fan curve if very long on cool level
+ // 0x04: enable mini fan curve if left for too long on cool level
+ // - this might be due to potential temp failure
+ // - or just because really so cool
+ // - or just because of really cool temps
+ // 0xA0: disable it
+ u16 EXT_LOCKFANCONTROLLER;
+ u16 EXT_MAXIMUMFANSPEED;
@ -245,6 +245,7 @@ index 000000000..727510507
+ ACCESS_METHOD_WMI2 = 4,
+ ACCESS_METHOD_WMI3 = 5,
+ ACCESS_METHOD_EC2 = 10, // ideapad fancurve method
+ ACCESS_METHOD_EC3 = 11, // loq
+};
+
+struct model_config {
@ -413,6 +414,39 @@ index 000000000..727510507
+ .EXT_WHITE_KEYBOARD_BACKLIGHT = 0xC5a0 // not found yet
+};
+
+static const struct ec_register_offsets ec_register_offsets_loq_v0 = {
+ .ECHIPID1 = 0x2000,
+ .ECHIPID2 = 0x2001,
+ .ECHIPVER = 0x2002,
+ .ECDEBUG = 0x2003,
+ .EXT_FAN_CUR_POINT = 0xC5a0,
+ .EXT_FAN_POINTS_SIZE = 0xC5a0, // constant 0
+ .EXT_FAN1_BASE = 0xC530,
+ .EXT_FAN2_BASE = 0xC530, // same rpm as cpu
+ .EXT_FAN_ACC_BASE = 0xC5a0, // not found yet
+ .EXT_FAN_DEC_BASE = 0xC5a0, // not found yet
+ .EXT_CPU_TEMP = 0xC52F,
+ .EXT_CPU_TEMP_HYST = 0xC5a0, // not found yet
+ .EXT_GPU_TEMP = 0xC531,
+ .EXT_GPU_TEMP_HYST = 0xC5a0, // not found yet
+ .EXT_VRM_TEMP = 0xC5a0, // not found yet
+ .EXT_VRM_TEMP_HYST = 0xC5a0, // not found yet
+ .EXT_FAN1_RPM_LSB = 0xC5a0, // not found yet
+ .EXT_FAN1_RPM_MSB = 0xC5a0, // not found yet
+ .EXT_FAN2_RPM_LSB = 0xC5a0, // not found yet
+ .EXT_FAN2_RPM_MSB = 0xC5a0, // not found yet
+ .EXT_MINIFANCURVE_ON_COOL = 0xC5a0, // not found yet
+ .EXT_LOCKFANCONTROLLER = 0xC5a0, // not found yet
+ .EXT_CPU_TEMP_INPUT = 0xC5a0, // not found yet
+ .EXT_GPU_TEMP_INPUT = 0xC5a0, // not found yet
+ .EXT_IC_TEMP_INPUT = 0xC5a0, // not found yet
+ .EXT_POWERMODE = 0xc41D,
+ .EXT_FAN1_TARGET_RPM = 0xC5a0, // not found yet
+ .EXT_FAN2_TARGET_RPM = 0xC5a0, // not found yet
+ .EXT_MAXIMUMFANSPEED = 0xC5a0, // not found yet
+ .EXT_WHITE_KEYBOARD_BACKLIGHT = 0xC5a0 // not found yet
+};
+
+static const struct model_config model_v0 = {
+ .registers = &ec_register_offsets_v0,
+ .check_embedded_controller_id = true,
@ -565,13 +599,70 @@ index 000000000..727510507
+ .ramio_size = 0x600
+};
+
+static const struct model_config model_m0cn = {
+ .registers = &ec_register_offsets_v0,
+ .check_embedded_controller_id = true,
+ .embedded_controller_id = 0x5507,
+ .memoryio_physical_ec_start = 0xC400,
+ .memoryio_size = 0x300,
+ .has_minifancurve = true,
+ .has_custom_powermode = true,
+ .access_method_powermode = ACCESS_METHOD_WMI,
+ .access_method_keyboard = ACCESS_METHOD_WMI,
+ .access_method_fanspeed = ACCESS_METHOD_WMI3,
+ .access_method_temperature = ACCESS_METHOD_WMI3,
+ .access_method_fancurve = ACCESS_METHOD_WMI3,
+ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
+ .acpi_check_dev = false,
+ .ramio_physical_start = 0xFE0B0400,
+ .ramio_size = 0x600
+};
+
+static const struct model_config model_m1cn = {
+ .registers = &ec_register_offsets_v0,
+ .check_embedded_controller_id = true,
+ .embedded_controller_id = 0x5507,
+ .memoryio_physical_ec_start = 0xC400,
+ .memoryio_size = 0x300,
+ .has_minifancurve = true,
+ .has_custom_powermode = true,
+ .access_method_powermode = ACCESS_METHOD_WMI,
+ .access_method_keyboard = ACCESS_METHOD_WMI,
+ .access_method_fanspeed = ACCESS_METHOD_WMI3,
+ .access_method_temperature = ACCESS_METHOD_WMI3,
+ .access_method_fancurve = ACCESS_METHOD_WMI3,
+ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
+ .acpi_check_dev = false,
+ .ramio_physical_start = 0xFE0B0400,
+ .ramio_size = 0x600
+};
+
+static const struct model_config model_m2cn = {
+ .registers = &ec_register_offsets_v0,
+ .check_embedded_controller_id = true,
+ .embedded_controller_id = 0x8227,
+ .memoryio_physical_ec_start = 0xC400,
+ .memoryio_size = 0x300,
+ .has_minifancurve = false,
+ .has_minifancurve = true,
+ .has_custom_powermode = true,
+ .access_method_powermode = ACCESS_METHOD_WMI,
+ .access_method_keyboard = ACCESS_METHOD_WMI,
+ .access_method_fanspeed = ACCESS_METHOD_WMI3,
+ .access_method_temperature = ACCESS_METHOD_WMI3,
+ .access_method_fancurve = ACCESS_METHOD_WMI3,
+ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
+ .acpi_check_dev = false,
+ .ramio_physical_start = 0xFE0B0400,
+ .ramio_size = 0x600
+};
+
+static const struct model_config model_m6cn = {
+ .registers = &ec_register_offsets_v0,
+ .check_embedded_controller_id = true,
+ .embedded_controller_id = 0x8227,
+ .memoryio_physical_ec_start = 0xC400,
+ .memoryio_size = 0x300,
+ .has_minifancurve = true,
+ .has_custom_powermode = true,
+ .access_method_powermode = ACCESS_METHOD_WMI,
+ .access_method_keyboard = ACCESS_METHOD_WMI,
@ -863,14 +954,33 @@ index 000000000..727510507
+ .ramio_size = 0x600
+};
+
+// LOQ Model
+static const struct model_config model_lzcn = {
+ .registers = &ec_register_offsets_loq_v0,
+ .check_embedded_controller_id = true,
+ .embedded_controller_id = 0x8227,
+ .memoryio_physical_ec_start = 0xC400,
+ .memoryio_size = 0x300,
+ .has_minifancurve = true,
+ .has_custom_powermode = true,
+ .access_method_powermode = ACCESS_METHOD_WMI,
+ .access_method_keyboard = ACCESS_METHOD_WMI2,
+ .access_method_fanspeed = ACCESS_METHOD_WMI3,
+ .access_method_temperature = ACCESS_METHOD_WMI3,
+ .access_method_fancurve = ACCESS_METHOD_EC3,
+ .access_method_fanfullspeed = ACCESS_METHOD_WMI3,
+ .acpi_check_dev = false,
+ .ramio_physical_start = 0xFE0B0400,
+ .ramio_size = 0x600
+};
+
+static const struct dmi_system_id denylist[] = { {} };
+
+static const struct dmi_system_id optimistic_allowlist[] = {
+ {
+ // modelyear: 2021
+ // generation: 6
+ // name: Legion 5, Legion 5 pro, Legion 7
+ // Release year: 2021
+ // Generation: 6
+ // Name: Legion 5, Legion 5 pro, Legion 7
+ // Family: Legion 5 15ACH6H, ...
+ .ident = "GKCN",
+ .matches = {
@ -880,7 +990,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2020
+ // Release year: 2020
+ .ident = "EUCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -889,7 +999,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_eucn
+ },
+ {
+ // modelyear: 2020
+ // Release year: 2020
+ .ident = "EFCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -898,7 +1008,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2020
+ // Release year: 2020
+ .ident = "FSCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -907,7 +1017,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2021
+ // Release year: 2021
+ .ident = "HHCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -916,7 +1026,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2022
+ // Release year: 2022
+ .ident = "H1CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -925,7 +1035,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2022
+ // Release year: 2022
+ .ident = "J2CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -934,7 +1044,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2022
+ // Release year: 2022
+ .ident = "JUCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -943,7 +1053,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2022
+ // Release year: 2022
+ .ident = "KFCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -952,7 +1062,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_kfcn
+ },
+ {
+ // modelyear: 2021
+ // Release year: 2021
+ .ident = "HACN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -961,7 +1071,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_hacn
+ },
+ {
+ // modelyear: 2021
+ // Release year: 2021
+ .ident = "G9CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -970,7 +1080,7 @@ index 000000000..727510507
+ .driver_data = (void *)&model_v0
+ },
+ {
+ // modelyear: 2022
+ // Release year: 2022
+ .ident = "K9CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
@ -988,6 +1098,15 @@ index 000000000..727510507
+ .driver_data = (void *)&model_fccn
+ },
+ {
+ // e.g. IdeaPad Gaming 3 15ARH05 (8K21)
+ .ident = "H4CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "H4CN"),
+ },
+ .driver_data = (void *)&model_fccn
+ },
+ {
+ // e.g. Ideapad Gaming 3 15ACH6
+ .ident = "H3CN",
+ .matches = {
@ -1123,6 +1242,24 @@ index 000000000..727510507
+ .driver_data = (void *)&model_j1cn
+ },
+ {
+ // e.g. Legion Slim 7 16IRH8 (2023) with RTX 4070
+ .ident = "M0CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "M0CN"),
+ },
+ .driver_data = (void *)&model_m0cn
+ },
+ {
+ // e.g. Legion Slim 7 16IRH8 (2023) AMD Ryzen 7 7840HS with RTX 4060
+ .ident = "M1CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "M1CN"),
+ },
+ .driver_data = (void *)&model_m1cn
+ },
+ {
+ // e.g. Legion Slim 5 16IRH8 (2023) with RTX 4070
+ .ident = "M2CN",
+ .matches = {
@ -1132,6 +1269,15 @@ index 000000000..727510507
+ .driver_data = (void *)&model_m2cn
+ },
+ {
+ // e.g. Lenovo Yoga Slim 7 gen 8 (2023)
+ .ident = "M6CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "M6CN"),
+ },
+ .driver_data = (void *)&model_m6cn
+ },
+ {
+ // e.g. Yoga Slim 7-14ARE05
+ .ident = "DMCN",
+ .matches = {
@ -1149,6 +1295,15 @@ index 000000000..727510507
+ },
+ .driver_data = (void *)&model_khcn
+ },
+ {
+ // e.g. LOQ 15IRH8
+ .ident = "LZCN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "LZCN"),
+ },
+ .driver_data = (void *)&model_lzcn
+ },
+ {}
+};
+
@ -1235,8 +1390,10 @@ index 000000000..727510507
+ error = -AE_ERROR;
+ goto err;
+ }
+ pr_info("ACPI result for %s:%d: ACPI buffer length: %u\n", id_name,
+ id_nr, out->buffer.length);
+
+// Reduced verbosity (only printing when ACPI result have bad parameters)
+// pr_info("ACPI result for %s:%d: ACPI buffer length: %u\n", id_name,
+// id_nr, out->buffer.length);
+
+ for (i = 0; i < ressize; ++i)
+ res[i] = out->buffer.pointer[i];
@ -1278,7 +1435,7 @@ index 000000000..727510507
+{
+ acpi_status status;
+ struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
+ // seto to NULL call kfree on NULL if next function call fails
+ // set to NULL and call kfree on NULL if next function call fails
+ union acpi_object *out = NULL;
+ int error = 0;
+
@ -1356,8 +1513,8 @@ index 000000000..727510507
+#define WMI_METHOD_ID_ISSUPPORTGPUOC 4
+
+// Fan speed
+// only completely implemented only for some models here
+// often implemted also in other class and other method
+// only fully implemented for some models here
+// often implemented in other classes and methods too
+// below
+#define WMI_METHOD_ID_GETFAN1SPEED 8
+#define WMI_METHOD_ID_GETFAN2SPEED 9
@ -1367,8 +1524,8 @@ index 000000000..727510507
+// Does it support CPU overclock?
+#define WMI_METHOD_ID_ISSUPPORTCPUOC 14
+// Temperatures
+// only completely implemented only for some models here
+// often implemted also in other class and other method
+// only fully implemented for some models here
+// often implemented in other classes and methods too
+// below
+#define WMI_METHOD_ID_GETCPUTEMP 18
+#define WMI_METHOD_ID_GETGPUTEMP 19
@ -1376,17 +1533,17 @@ index 000000000..727510507
+// two state keyboard light
+#define WMI_METHOD_ID_GETKEYBOARDLIGHT 37
+#define WMI_METHOD_ID_SETKEYBOARDLIGHT 36
+// disable win key
+// toggle win key
+// 0 = win key enabled; 1 = win key disabled
+#define WMI_METHOD_ID_ISSUPPORTDISABLEWINKEY 21
+#define WMI_METHOD_ID_GETWINKEYSTATUS 23
+#define WMI_METHOD_ID_SETWINKEYSTATUS 22
+// disable touchpad
+// toggle touchpad
+//0 = touchpad enabled; 1 = touchpad disabled
+#define WMI_METHOD_ID_ISSUPPORTDISABLETP 24
+#define WMI_METHOD_ID_GETTPSTATUS 26
+#define WMI_METHOD_ID_SETTPSTATUS 25
+// gSync
+// GSync
+#define WMI_METHOD_ID_ISSUPPORTGSYNC 40
+#define WMI_METHOD_ID_GETGSYNCSTATUS 41
+#define WMI_METHOD_ID_SETGSYNCSTATUS 42
@ -1572,7 +1729,7 @@ index 000000000..727510507
+ ec_memoryio->physical_start = physical_start;
+ ec_memoryio->physical_ec_start = physical_ec_start;
+ ec_memoryio->size = size;
+ pr_info("Succeffuly mapped embedded controller: 0x%llx (in RAM)/0x%llx (in EC) to virtual 0x%p\n",
+ pr_info("Successfully mapped embedded controller: 0x%llx (in RAM)/0x%llx (in EC) to virtual 0x%p\n",
+ ec_memoryio->physical_start,
+ ec_memoryio->physical_ec_start,
+ ec_memoryio->virtual_start);
@ -1660,8 +1817,8 @@ index 000000000..727510507
+// Number of used ports
+#define ECRAM_PORTIO_PORTS_SIZE 2
+// Port used to specify address in EC RAM to read/write
+// 0x4E/0x4F is the usual port for IO super controler
+// 0x2E/0x2F also common (ITE can also be configure to use these)
+// 0x4E/0x4F is the usual port for IO super controller
+// 0x2E/0x2F also common (ITE can also be configured to use these)
+#define ECRAM_PORTIO_ADDR_PORT 0x4E
+// Port to send/receive the value to write/read
+#define ECRAM_PORTIO_DATA_PORT 0x4F
@ -1802,7 +1959,7 @@ index 000000000..727510507
+
+ err = ecram_portio_read(&ecram->portio, ecram_offset, &value);
+ if (err)
+ pr_info("Error reading EC RAM at 0x%x\n", ecram_offset);
+ pr_info("Error reading EC RAM at 0x%x.\n", ecram_offset);
+ return value;
+}
+
@ -1811,13 +1968,13 @@ index 000000000..727510507
+ int err;
+
+ if (ec_readonly) {
+ pr_info("Skipping writing EC RAM at 0x%x because readonly.\n",
+ pr_info("Skipping writing EC RAM to 0x%x: Read-Only.\n",
+ ecram_offset);
+ return;
+ }
+ err = ecram_portio_write(&ecram->portio, ecram_offset, value);
+ if (err)
+ pr_info("Error writing EC RAM at 0x%x\n", ecram_offset);
+ pr_info("Error writing EC RAM to 0x%x: Read-Only.\n", ecram_offset);
+}
+
+/* =============================== */
@ -1874,24 +2031,24 @@ index 000000000..727510507
+ u8 rpm1_raw;
+ // rpm2 devided by 100
+ u8 rpm2_raw;
+ // >=2 , <=5 (lower is faster); must be increasing by level
+ // >=2 , <=5 (lower is faster); must increase by level
+ u8 accel;
+ // >=2 , <=5 (lower is faster); must be increasing by level
+ // >=2 , <=5 (lower is faster); must increase by level
+ u8 decel;
+
+ // min must be lower or equal than max
+ // min must be lower than or equal to max
+ // last level max must be 127
+ // <=127 cpu max temp for this level; must be increasing by level
+ // <=127 cpu max temp for this level; must increase by level
+ u8 cpu_max_temp_celsius;
+ // <=127 cpu min temp for this level; must be increasing by level
+ // <=127 cpu min temp for this level; must increase by level
+ u8 cpu_min_temp_celsius;
+ // <=127 gpu min temp for this level; must be increasing by level
+ // <=127 gpu min temp for this level; must increase by level
+ u8 gpu_max_temp_celsius;
+ // <=127 gpu max temp for this level; must be increasing by level
+ // <=127 gpu max temp for this level; must increase by level
+ u8 gpu_min_temp_celsius;
+ // <=127 ic max temp for this level; must be increasing by level
+ // <=127 ic max temp for this level; must increase by level
+ u8 ic_max_temp_celsius;
+ // <=127 ic max temp for this level; must be increasing by level
+ // <=127 ic max temp for this level; must increase by level
+ u8 ic_min_temp_celsius;
+};
+
@ -1918,7 +2075,7 @@ index 000000000..727510507
+ struct fancurve_point points[MAXFANCURVESIZE];
+ // number of points used; must be <= MAXFANCURVESIZE
+ size_t size;
+ // the point that at which fans are run currently
+ // the point at which fans are run currently
+ size_t current_point_i;
+};
+
@ -2047,7 +2204,7 @@ index 000000000..727510507
+ if (!valid)
+ return false;
+ if (init_values && size < fancurve->size) {
+ // fancurve size is decreased, but last etnry alwasy needs 127 temperatures
+ // fancurve size is decreased, but last entry always needs 127 temperatures
+ // Note: size >=1
+ fancurve->points[size - 1].cpu_max_temp_celsius = 127;
+ fancurve->points[size - 1].ic_max_temp_celsius = 127;
@ -2100,8 +2257,8 @@ index 000000000..727510507
+/* Global and shared data between */
+/* all calls to this module */
+/* ============================= */
+// Implemented like ideapad-laptop.c but currenlty still
+// wihtout dynamic memory allocation (instead global _priv)
+// Implemented like ideapad-laptop.c but currently still
+// without dynamic memory allocation (instead global _priv)
+struct legion_private {
+ struct platform_device *platform_device;
+ // TODO: remove or keep? init?
@ -2109,10 +2266,10 @@ index 000000000..727510507
+
+ // Method to access ECRAM
+ struct ecram ecram;
+ // Configuration with registers an ECRAM access method
+ // Configuration with registers and ECRAM access method
+ const struct model_config *conf;
+
+ // TODO: maybe refactor an keep only local to each function
+ // TODO: maybe refactor and keep only local to each function
+ // last known fan curve
+ struct fancurve fancurve;
+ // configured fan curve from user space
@ -2137,7 +2294,7 @@ index 000000000..727510507
+ struct ecram_memoryio ec_memoryio;
+};
+
+// shared between different drivers: WMI, platform and proteced by mutex
+// shared between different drivers: WMI, platform and protected by mutex
+static struct legion_private *legion_shared;
+static struct legion_private _priv;
+static DEFINE_MUTEX(legion_shared_mutex);
@ -2238,7 +2395,7 @@ index 000000000..727510507
+}
+
+/* ============================= */
+/* Sensor values reading/writing */
+/* Sensor value reading/writing */
+/* ============================= */
+
+static int ec_read_sensor_values(struct ecram *ecram,
@ -2673,7 +2830,7 @@ index 000000000..727510507
+/* Read the fan curve from the EC.
+ *
+ * In newer models (>=2022) there is an ACPI/WMI to read fan curve as
+ * a whole. So read/write fan table as a whole to use
+ * a whole. So read/write fan table as a whole to use the
+ * same interface for both cases.
+ *
+ * It reads all points from EC memory, even if stored fancurve is smaller, so
@ -2711,7 +2868,7 @@ index 000000000..727510507
+ ecram, model->registers->EXT_VRM_TEMP_HYST + i);
+ }
+
+ // Do not trust that hardware; It might suddendly report
+ // Do not trust that hardware; It might suddenly report
+ // a larger size, so clamp it.
+ fancurve->size =
+ ecram_read(ecram, model->registers->EXT_FAN_POINTS_SIZE);
@ -2731,9 +2888,6 @@ index 000000000..727510507
+{
+ size_t i;
+
+ //TODO: remove again
+ pr_info("Set fancurve\n");
+
+ // Reset fan update counters (try to avoid any race conditions)
+ ecram_write(ecram, 0xC5FE, 0);
+ ecram_write(ecram, 0xC5FF, 0);
@ -2816,7 +2970,7 @@ index 000000000..727510507
+ point->ic_min_temp_celsius = 0;
+ }
+
+ // Do not trust that hardware; It might suddendly report
+ // Do not trust that hardware; It might suddenly report
+ // a larger size, so clamp it.
+ fancurve->size = FANCURVESIZE_IDEAPDAD;
+ fancurve->current_point_i =
@ -2835,7 +2989,7 @@ index 000000000..727510507
+ int valr2;
+
+ // add this later: maybe other addresses needed
+ // therefore, fan curve might not be effective immediatley but
+ // therefore, fan curve might not be effective immediately but
+ // only after temp change
+ // Reset fan update counters (try to avoid any race conditions)
+ ecram_write(ecram, 0xC5FE, 0);
@ -2877,7 +3031,7 @@ index 000000000..727510507
+ }
+
+ // add this later: maybe other addresses needed
+ // therefore, fan curve might not be effective immediatley but
+ // therefore, fan curve might not be effective immediately but
+ // only after temp change
+ // // Reset current fan level to 0, so algorithm in EC
+ // // selects fan curve point again and resetting hysterisis
@ -2892,6 +3046,77 @@ index 000000000..727510507
+ return 0;
+}
+
+#define FANCURVESIZE_LOQ 10
+
+static int ec_read_fancurve_loq(struct ecram *ecram,
+ const struct model_config *model,
+ struct fancurve *fancurve)
+{
+ size_t i = 0;
+ size_t struct_offset = 3; // {cpu_temp: u8, rpm: u8, gpu_temp?: u8}
+
+ for (i = 0; i < FANCURVESIZE_LOQ; ++i) {
+ struct fancurve_point *point = &fancurve->points[i];
+
+ point->rpm1_raw =
+ ecram_read(ecram, model->registers->EXT_FAN1_BASE + (i * struct_offset));
+ point->rpm2_raw =
+ ecram_read(ecram, model->registers->EXT_FAN2_BASE + (i * struct_offset));
+
+ point->accel = 0;
+ point->decel = 0;
+ point->cpu_max_temp_celsius =
+ ecram_read(ecram, model->registers->EXT_CPU_TEMP + (i * struct_offset));
+ point->gpu_max_temp_celsius =
+ ecram_read(ecram, model->registers->EXT_GPU_TEMP + (i * struct_offset));
+ point->cpu_min_temp_celsius = 0;
+ point->gpu_min_temp_celsius = 0;
+ point->ic_max_temp_celsius = 0;
+ point->ic_min_temp_celsius = 0;
+ }
+
+ fancurve->size = FANCURVESIZE_LOQ;
+ fancurve->current_point_i =
+ ecram_read(ecram, model->registers->EXT_FAN_CUR_POINT);
+ fancurve->current_point_i =
+ min(fancurve->current_point_i, fancurve->size);
+ return 0;
+}
+
+static int ec_write_fancurve_loq(struct ecram *ecram,
+ const struct model_config *model,
+ const struct fancurve *fancurve)
+{
+ size_t i;
+ int valr1;
+ int valr2;
+ size_t struct_offset = 3; // {cpu_temp: u8, rpm: u8, gpu_temp?: u8}
+
+ for (i = 0; i < FANCURVESIZE_LOQ; ++i) {
+ const struct fancurve_point *point = &fancurve->points[i];
+
+ ecram_write(ecram, model->registers->EXT_FAN1_BASE + (i * struct_offset),
+ point->rpm1_raw);
+ valr1 = ecram_read(ecram, model->registers->EXT_FAN1_BASE + (i * struct_offset));
+ ecram_write(ecram, model->registers->EXT_FAN2_BASE + (i * struct_offset),
+ point->rpm2_raw);
+ valr2 = ecram_read(ecram, model->registers->EXT_FAN2_BASE + (i * struct_offset));
+ pr_info("Writing fan1: %d; reading fan1: %d\n", point->rpm1_raw,
+ valr1);
+ pr_info("Writing fan2: %d; reading fan2: %d\n", point->rpm2_raw,
+ valr2);
+
+ // write to memory and repeat 8 bytes later again
+ ecram_write(ecram, model->registers->EXT_CPU_TEMP + (i * struct_offset),
+ point->cpu_max_temp_celsius);
+ // write to memory and repeat 8 bytes later again
+ ecram_write(ecram, model->registers->EXT_GPU_TEMP + (i * struct_offset),
+ point->gpu_max_temp_celsius);
+ }
+
+ return 0;
+}
+
+static int read_fancurve(struct legion_private *priv, struct fancurve *fancurve)
+{
+ // TODO: use enums or function pointers?
@ -2902,6 +3127,9 @@ index 000000000..727510507
+ case ACCESS_METHOD_EC2:
+ return ec_read_fancurve_ideapad(&priv->ecram, priv->conf,
+ fancurve);
+ case ACCESS_METHOD_EC3:
+ return ec_read_fancurve_loq(&priv->ecram, priv->conf,
+ fancurve);
+ case ACCESS_METHOD_WMI3:
+ return wmi_read_fancurve_custom(priv->conf, fancurve);
+ default:
@ -2922,6 +3150,9 @@ index 000000000..727510507
+ case ACCESS_METHOD_EC2:
+ return ec_write_fancurve_ideapad(&priv->ecram, priv->conf,
+ fancurve);
+ case ACCESS_METHOD_EC3:
+ return ec_write_fancurve_loq(&priv->ecram, priv->conf,
+ fancurve);
+ case ACCESS_METHOD_WMI3:
+ return wmi_write_fancurve_custom(priv->conf, fancurve);
+ default:
@ -3150,7 +3381,7 @@ index 000000000..727510507
+ unsigned long acpi_powermode;
+ int err;
+
+ // spmo method not alwasy available
+ // spmo method not always available
+ // \_SB.PCI0.LPC0.EC0.SPMO
+ err = eval_spmo(priv->adev->handle, &acpi_powermode);
+ *powermode = (int)acpi_powermode;
@ -3255,7 +3486,7 @@ index 000000000..727510507
+ unsigned long result;
+ int err;
+
+ //also works? what is better?
+ //also works? which one is better?
+ /*
+ * err = eval_qcho(adev->handle, &result);
+ * if (err)
@ -3543,7 +3774,7 @@ index 000000000..727510507
+ seq_printf(s, "WMI light IO port: %d\n",
+ legion_wmi_light_get(priv, LIGHT_ID_IOPORT, 0, 4));
+
+ seq_printf(s, "WMI light y logo/lid: %d\n",
+ seq_printf(s, "WMI light Y logo/lid: %d\n",
+ legion_wmi_light_get(priv, LIGHT_ID_YLOGO, 0, 4));
+
+ seq_printf(s, "EC minifancurve feature enabled: %d\n",
@ -3591,7 +3822,7 @@ index 000000000..727510507
+ struct dentry *dir;
+
+ // TODO: remove this note
+ // Note: as other kernel modules, do not catch errors here
+ // Note: like other kernel modules, do not catch errors here
+ // because if kernel is build without debugfs this
+ // will return an error but module still has to
+ // work, just without debugfs
@ -3655,7 +3886,7 @@ index 000000000..727510507
+ struct legion_private *priv = dev_get_drvdata(dev);
+
+ if (ressize > ARRAY_SIZE(res)) {
+ pr_info("Buffer to small for WMI result\n");
+ pr_info("Buffer too small for WMI result\n");
+ return -EINVAL;
+ }
+ if (i >= ressize) {
@ -4402,8 +4633,8 @@ index 000000000..727510507
+unlock:
+ mutex_unlock(&legion_shared_mutex);
+ // todo; fix that!
+ // problem: we get a event just before the powermode change (from the key?),
+ // so if we notify to early, it will read the old power mode/platform profile
+ // problem: we get an event just before the powermode change (from the key?),
+ // so if we notify too early, it will read the old power mode/platform profile
+ msleep(500);
+ legion_platform_profile_notify();
+}
@ -4752,11 +4983,11 @@ index 000000000..727510507
+ mutex_unlock(&priv->fancurve_mutex);
+
+ if (err) {
+ pr_info("Reading fancurve failed\n");
+ pr_info("Failed to read fancurve\n");
+ return -EOPNOTSUPP;
+ }
+ if (!(point_id >= 0 && point_id < MAXFANCURVESIZE)) {
+ pr_info("Reading fancurve failed due to wrong point id: %d\n",
+ pr_info("Failed to read fancurve due to wrong point id: %d\n",
+ point_id);
+ return -EOPNOTSUPP;
+ }
@ -4796,7 +5027,7 @@ index 000000000..727510507
+ value = fancurve.size;
+ break;
+ default:
+ pr_info("Reading fancurve failed due to wrong attribute id: %d\n",
+ pr_info("Failed to read fancurve due to wrong attribute id: %d\n",
+ fancurve_attr_id);
+ return -EOPNOTSUPP;
+ }
@ -4818,7 +5049,7 @@ index 000000000..727510507
+ bool write_fancurve_size = false;
+
+ if (!(point_id >= 0 && point_id < MAXFANCURVESIZE)) {
+ pr_info("Reading fancurve failed due to wrong point id: %d\n",
+ pr_info("Failed to read fancurve due to wrong point id: %d\n",
+ point_id);
+ err = -EOPNOTSUPP;
+ goto error;
@ -4826,7 +5057,7 @@ index 000000000..727510507
+
+ err = kstrtoint(buf, 0, &value);
+ if (err) {
+ pr_info("Parse for hwmon store is not succesful: error:%d; point_id: %d; fancurve_attr_id: %d\\n",
+ pr_info("Parsing hwmon store failed: error: %d; point_id: %d; fancurve_attr_id: %d\\n",
+ err, point_id, fancurve_attr_id);
+ goto error;
+ }
@ -4835,7 +5066,7 @@ index 000000000..727510507
+ err = read_fancurve(priv, &fancurve);
+
+ if (err) {
+ pr_info("Reading fancurve failed\n");
+ pr_info("Failed to read fancurve\n");
+ err = -EOPNOTSUPP;
+ goto error_mutex;
+ }
@ -4876,7 +5107,7 @@ index 000000000..727510507
+ write_fancurve_size = true;
+ break;
+ default:
+ pr_info("Writing fancurve failed due to wrong attribute id: %d\n",
+ pr_info("Failed to write fancurve due to wrong attribute id: %d\n",
+ fancurve_attr_id);
+ err = -EOPNOTSUPP;
+ goto error_mutex;
@ -4891,7 +5122,7 @@ index 000000000..727510507
+
+ err = write_fancurve(priv, &fancurve, write_fancurve_size);
+ if (err) {
+ pr_info("Writing fancurve failed for accessing hwmon at point_id: %d\n",
+ pr_info("Failed to write fancurve for accessing hwmon at point_id: %d\n",
+ point_id);
+ err = -EOPNOTSUPP;
+ goto error_mutex;
@ -5130,7 +5361,7 @@ index 000000000..727510507
+ err = ec_read_minifancurve(&priv->ecram, priv->conf, &value);
+ if (err) {
+ err = -1;
+ pr_info("Reading minifancurve not succesful\n");
+ pr_info("Failed to read minifancurve\n");
+ goto error_unlock;
+ }
+ mutex_unlock(&priv->fancurve_mutex);
@ -5152,7 +5383,7 @@ index 000000000..727510507
+ err = kstrtoint(buf, 0, &value);
+ if (err) {
+ err = -1;
+ pr_info("Parse for hwmon store is not succesful: error:%d\n",
+ pr_info("Parsing hwmon store failed: error:%d\n",
+ err);
+ goto error;
+ }
@ -5161,7 +5392,7 @@ index 000000000..727510507
+ err = ec_write_minifancurve(&priv->ecram, priv->conf, value);
+ if (err) {
+ err = -1;
+ pr_info("Writing minifancurve not succesful\n");
+ pr_info("Failed to write minifancurve\n");
+ goto error_unlock;
+ }
+ mutex_unlock(&priv->fancurve_mutex);
@ -5186,7 +5417,7 @@ index 000000000..727510507
+ err = ec_read_fanfullspeed(&priv->ecram, priv->conf, &value);
+ if (err) {
+ err = -1;
+ pr_info("Reading pwm1_mode/maximumfanspeed not succesful\n");
+ pr_info("Failed to pwm1_mode/maximumfanspeed\n");
+ goto error_unlock;
+ }
+ mutex_unlock(&priv->fancurve_mutex);
@ -5210,7 +5441,7 @@ index 000000000..727510507
+ err = kstrtoint(buf, 0, &value);
+ if (err) {
+ err = -1;
+ pr_info("Parse for hwmon store is not succesful: error:%d\n",
+ pr_info("Parsing hwmon store failed: error:%d\n",
+ err);
+ goto error;
+ }
@ -5221,7 +5452,7 @@ index 000000000..727510507
+ is_maximumfanspeed);
+ if (err) {
+ err = -1;
+ pr_info("Writing pwm1_mode/maximumfanspeed not succesful\n");
+ pr_info("Failed to write pwm1_mode/maximumfanspeed\n");
+ goto error_unlock;
+ }
+ mutex_unlock(&priv->fancurve_mutex);
@ -5480,7 +5711,7 @@ index 000000000..727510507
+
+ brightness = legion_kbd_bl_brightness_get(priv);
+ if (brightness < 0) {
+ pr_info("Error reading keyboard brighntess\n");
+ pr_info("Error reading keyboard brightness\n");
+ return brightness;
+ }
+
@ -5568,7 +5799,7 @@ index 000000000..727510507
+ light_ins->lower_limit,
+ light_ins->upper_limit);
+ if (brightness < 0) {
+ pr_info("Error reading brighntess for light: %u\n",
+ pr_info("Error reading brightness for light: %u\n",
+ light_ins->light_id);
+ return brightness;
+ }
@ -5663,7 +5894,7 @@ index 000000000..727510507
+ if (!(do_load)) {
+ dev_info(
+ &pdev->dev,
+ "Module not useable for this laptop because it is not in allowlist. Notify maintainer if you want to add your device or force load with param force.\n");
+ "Module not usable for this laptop because it is not in allowlist. Notify the maintainer if you want to add your device or force load with param force.\n");
+ err = -ENOMEM;
+ goto err_model_mismtach;
+ }
@ -5674,7 +5905,7 @@ index 000000000..727510507
+ if (!do_load_by_list && do_load) {
+ dev_info(
+ &pdev->dev,
+ "legion_laptop is forced to load and would otherwise be not loaded\n");
+ "legion_laptop is forced to load and would otherwise not be loaded\n");
+ }
+
+ // if forced and no module found, use config for first model
@ -5729,13 +5960,13 @@ index 000000000..727510507
+ "Skipped checking embedded controller id\n");
+ }
+
+ dev_info(&pdev->dev, "Creating debugfs inteface\n");
+ dev_info(&pdev->dev, "Creating debugfs interface\n");
+ legion_debugfs_init(priv);
+
+ pr_info("Creating sysfs inteface\n");
+ pr_info("Creating sysfs interface\n");
+ err = legion_sysfs_init(priv);
+ if (err) {
+ dev_info(&pdev->dev, "Creating sysfs interface failed: %d\n",
+ dev_info(&pdev->dev, "Failed to create sysfs interface: %d\n",
+ err);
+ goto err_sysfs_init;
+ }
@ -5743,7 +5974,7 @@ index 000000000..727510507
+ pr_info("Creating hwmon interface");
+ err = legion_hwmon_init(priv);
+ if (err) {
+ dev_info(&pdev->dev, "Creating hwmon interface failed: %d\n",
+ dev_info(&pdev->dev, "Failed to create hwmon interface: %d\n",
+ err);
+ goto err_hwmon_init;
+ }
@ -5751,7 +5982,7 @@ index 000000000..727510507
+ pr_info("Creating platform profile support\n");
+ err = legion_platform_profile_init(priv);
+ if (err) {
+ dev_info(&pdev->dev, "Creating platform profile failed: %d\n",
+ dev_info(&pdev->dev, "Failed to create platform profile: %d\n",
+ err);
+ goto err_platform_profile;
+ }
@ -5759,7 +5990,7 @@ index 000000000..727510507
+ pr_info("Init WMI driver support\n");
+ err = legion_wmi_init();
+ if (err) {
+ dev_info(&pdev->dev, "Init WMI driver failed: %d\n", err);
+ dev_info(&pdev->dev, "Failed to init WMI driver: %d\n", err);
+ goto err_wmi;
+ }
+
@ -5768,7 +5999,7 @@ index 000000000..727510507
+ if (err) {
+ dev_info(
+ &pdev->dev,
+ "Init keyboard backlight LED driver failed. Skipping ...\n");
+ "Failed to init keyboard backlight LED driver. Skipping ...\n");
+ }
+
+ pr_info("Init Y-Logo LED driver\n");
@ -5776,7 +6007,7 @@ index 000000000..727510507
+ "platform::ylogo");
+ if (err) {
+ dev_info(&pdev->dev,
+ "Init Y-Logo LED driver failed. Skipping ...\n");
+ "Failed to init Y-Logo LED driver. Skipping ...\n");
+ }
+
+ pr_info("Init IO-Port LED driver\n");
@ -5784,7 +6015,7 @@ index 000000000..727510507
+ "platform::ioport");
+ if (err) {
+ dev_info(&pdev->dev,
+ "Init IO-Port LED driver failed. Skipping ...\n");
+ "Failed to init IO-Port LED driver. Skipping ...\n");
+ }
+
+ dev_info(&pdev->dev, "legion_laptop loaded for this device\n");
@ -5889,7 +6120,7 @@ index 000000000..727510507
+{
+ int err;
+
+ pr_info("legion_laptop starts loading\n");
+ pr_info("Loading legion_laptop\n");
+ err = platform_driver_register(&legion_driver);
+ if (err) {
+ pr_info("legion_laptop: platform_driver_register failed\n");
@ -5909,5 +6140,4 @@ index 000000000..727510507
+
+module_exit(legion_exit);
--
2.41.0
2.43.2