From 1f88b2fc2584ab6049d50bbb0edde97ca44c70b5 Mon Sep 17 00:00:00 2001 From: ferrreo Date: Thu, 4 Apr 2024 19:20:17 +0100 Subject: [PATCH] Update lenovo-legion-laptop.patch --- patches/nobara/lenovo-legion-laptop.patch | 466 ++++++++++++++++------ 1 file changed, 348 insertions(+), 118 deletions(-) diff --git a/patches/nobara/lenovo-legion-laptop.patch b/patches/nobara/lenovo-legion-laptop.patch index bfc51fb..a2bb5c8 100644 --- a/patches/nobara/lenovo-legion-laptop.patch +++ b/patches/nobara/lenovo-legion-laptop.patch @@ -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 -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; + @@ -1355,9 +1512,9 @@ index 000000000..727510507 +// GPU over clock +#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 ++// Fan speed ++// 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,10 +1817,10 @@ 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 ++// Port to send/receive the value to write/read +#define ECRAM_PORTIO_DATA_PORT 0x4F +// Name used to request ports +#define ECRAM_PORTIO_NAME "legion" @@ -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,10 +3127,13 @@ 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: -+ pr_info("No access method for fancurve:%d\n", ++ pr_info("No access method for fancurve: %d\n", + priv->conf->access_method_fancurve); + return -EINVAL; + } @@ -2922,10 +3150,13 @@ 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: -+ pr_info("No access method for fancurve:%d\n", ++ pr_info("No access method for fancurve: %d\n", + priv->conf->access_method_fancurve); + return -EINVAL; + } @@ -2948,7 +3179,7 @@ index 000000000..727510507 + *state = false; + break; + default: -+ pr_info("Unexpected value in MINIFANCURVE register:%d\n", ++ pr_info("Unexpected value in MINIFANCURVE register: %d\n", + value); + return -1; + } @@ -2992,7 +3223,7 @@ index 000000000..727510507 + *state = false; + break; + default: -+ pr_info("Unexpected value in lockfanspeed register:%d\n", ++ pr_info("Unexpected value in lockfanspeed register: %d\n", + value); + return -1; + } @@ -3015,7 +3246,7 @@ index 000000000..727510507 + *state = false; + break; + default: -+ pr_info("Unexpected value in maximumfanspeed register:%d\n", ++ pr_info("Unexpected value in maximumfanspeed register: %d\n", + value); + return -1; + } @@ -3072,7 +3303,7 @@ index 000000000..727510507 + case ACCESS_METHOD_WMI: + return wmi_write_fanfullspeed(priv, state); + default: -+ pr_info("No access method for fan full speed:%d\n", ++ pr_info("No access method for fan full speed: %d\n", + priv->conf->access_method_fanfullspeed); + return -EINVAL; + } @@ -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; @@ -3197,7 +3428,7 @@ index 000000000..727510507 + case ACCESS_METHOD_WMI: + return wmi_read_powermode(powermode); + default: -+ pr_info("No access method for powermode:%d\n", ++ pr_info("No access method for powermode: %d\n", + priv->conf->access_method_powermode); + return -EINVAL; + } @@ -3218,7 +3449,7 @@ index 000000000..727510507 + case ACCESS_METHOD_WMI: + return wmi_write_powermode(value); + default: -+ pr_info("No access method for powermode:%d\n", ++ pr_info("No access method for powermode: %d\n", + priv->conf->access_method_powermode); + return -EINVAL; + } @@ -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