aboutsummaryrefslogtreecommitdiff
path: root/SOURCES/lenovo-legion-laptop.patch
diff options
context:
space:
mode:
Diffstat (limited to 'SOURCES/lenovo-legion-laptop.patch')
-rw-r--r--SOURCES/lenovo-legion-laptop.patch4707
1 files changed, 3768 insertions, 939 deletions
diff --git a/SOURCES/lenovo-legion-laptop.patch b/SOURCES/lenovo-legion-laptop.patch
index 7c1cbda..bfc51fb 100644
--- a/SOURCES/lenovo-legion-laptop.patch
+++ b/SOURCES/lenovo-legion-laptop.patch
@@ -1,21 +1,21 @@
-From cc665be20697f44218f895e596fec00025965d5b Mon Sep 17 00:00:00 2001
+From cc20a3c74424b7fd78a650803bc06b822e8b1e56 Mon Sep 17 00:00:00 2001
From: John Martens <john.martens4@proton.me>
-Date: Fri, 27 Jan 2023 10:54:22 +0000
-Subject: [PATCH] Add legion-laptop v0.1
+Date: Wed, 30 Aug 2023 15:42:09 +0000
+Subject: [PATCH] Add legion-laptop v0.0.7
Add extra support for Lenovo Legion laptops.
---
drivers/platform/x86/Kconfig | 10 +
drivers/platform/x86/Makefile | 1 +
- drivers/platform/x86/legion-laptop.c | 3029 ++++++++++++++++++++++++++
- 3 files changed, 3040 insertions(+)
+ drivers/platform/x86/legion-laptop.c | 5858 ++++++++++++++++++++++++++
+ 3 files changed, 5869 insertions(+)
create mode 100644 drivers/platform/x86/legion-laptop.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
-index f5312f51d..74e24e5ab 100644
+index 49c2c4cd8..b7d70c20e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
-@@ -642,6 +642,16 @@ config THINKPAD_LMI
+@@ -643,6 +643,16 @@ config THINKPAD_LMI
To compile this driver as a module, choose M here: the module will
be called think-lmi.
@@ -33,23 +33,23 @@ index f5312f51d..74e24e5ab 100644
config MSI_EC
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
-index 5a428caa6..cfd07f9f3 100644
+index 52dfdf574..5f32dd9df 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
-@@ -68,6 +68,7 @@ obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
+@@ -65,6 +65,7 @@ obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
+obj-$(CONFIG_LEGION_LAPTOP) += legion-laptop.o
+ obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
# Intel
- obj-y += intel/
diff --git a/drivers/platform/x86/legion-laptop.c b/drivers/platform/x86/legion-laptop.c
new file mode 100644
-index 000000000..6edfeffcc
+index 000000000..727510507
--- /dev/null
+++ b/drivers/platform/x86/legion-laptop.c
-@@ -0,0 +1,3029 @@
+@@ -0,0 +1,5858 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * legion-laptop.c - Extra Lenovo Legion laptop support, in
@@ -109,12 +109,14 @@ index 000000000..6edfeffcc
+ * and commincation method with EC via ports
+ * - 0x1F9F1: additional reverse engineering for complete fan curve
+ */
++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <asm/io.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/dmi.h>
++#include <linux/leds.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/kernel.h>
@@ -141,8 +143,12 @@ index 000000000..6edfeffcc
+ ec_readonly,
+ "Only read from embedded controller but do not write or change settings.");
+
-+//TODO: remove this, kernel modules do not have versions
-+#define MODULEVERSION "0.1"
++static bool enable_platformprofile = true;
++module_param(enable_platformprofile, bool, 0440);
++MODULE_PARM_DESC(
++ enable_platformprofile,
++ "Enable the platform profile sysfs API to read and write the power mode.");
++
+#define LEGIONFEATURES \
+ "fancurve powermode platformprofile platformprofilenotify minifancurve"
+
@@ -152,6 +158,8 @@ index 000000000..6edfeffcc
+#define LEGION_DRVR_SHORTNAME "legion"
+#define LEGION_HWMON_NAME LEGION_DRVR_SHORTNAME "_hwmon"
+
++struct legion_private;
++
+/* =============================== */
+/* Embedded Controller Description */
+/* =============================== */
@@ -179,309 +187,683 @@ index 000000000..6edfeffcc
+ */
+// TODO: same order as in initialization
+struct ec_register_offsets {
-+ u16 ECINDAR0;
-+ u16 ECINDAR1;
-+ u16 ECINDAR2;
-+ u16 ECINDAR3;
-+ u16 ECINDDR;
-+ u16 GPDRA;
-+ u16 GPCRA0;
-+ u16 GPCRA1;
-+ u16 GPCRA2;
-+ u16 GPCRA3;
-+ u16 GPCRA4;
-+ u16 GPCRA5;
-+ u16 GPCRA6;
-+ u16 GPCRA7;
-+ u16 GPOTA;
-+ u16 GPDMRA;
-+ u16 DCR0;
-+ u16 DCR1;
-+ u16 DCR2;
-+ u16 DCR3;
-+ u16 DCR4;
-+ u16 DCR5;
-+ u16 DCR6;
-+ u16 DCR7;
-+ u16 CTR2;
++ // Super I/O Configuration Registers
++ // 7.15 General Control (GCTRL)
++ // 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
++ // Chip ID
+ u16 ECHIPID1;
+ u16 ECHIPID2;
++ // Chip Version
+ u16 ECHIPVER;
+ u16 ECDEBUG;
-+ u16 EADDR;
-+ u16 EDAT;
-+ u16 ECNT;
-+ u16 ESTS;
-+ u16 FW_VER;
-+ u16 FAN_CUR_POINT;
-+ u16 FAN_POINTS_SIZE;
-+ u16 FAN1_BASE;
-+ u16 FAN2_BASE;
-+ u16 FAN_ACC_BASE;
-+ u16 FAN_DEC_BASE;
-+ u16 CPU_TEMP;
-+ u16 CPU_TEMP_HYST;
-+ u16 GPU_TEMP;
-+ u16 GPU_TEMP_HYST;
-+ u16 VRM_TEMP;
-+ u16 VRM_TEMP_HYST;
-+ u16 CPU_TEMP_EN;
-+ u16 GPU_TEMP_EN;
-+ u16 VRM_TEMP_EN;
-+ u16 FAN1_ACC_TIMER;
-+ u16 FAN2_ACC_TIMER;
-+ u16 FAN1_CUR_ACC;
-+ u16 FAN1_CUR_DEC;
-+ u16 FAN2_CUR_ACC;
-+ u16 FAN2_CUR_DEC;
-+ u16 FAN1_RPM_LSB;
-+ u16 FAN1_RPM_MSB;
-+ u16 FAN2_RPM_LSB;
-+ u16 FAN2_RPM_MSB;
-+
-+ u16 F1TLRR;
-+ u16 F1TMRR;
-+ u16 F2TLRR;
-+ u16 F2TMRR;
-+ u16 CTR1;
-+ u16 CTR3;
-+ u16 FAN1CNF;
-+ u16 FAN2CNF;
-+
-+ // altnerive regsisters
-+ // TODO: decide on one version
-+ u16 FAN1_TARGET_RPM;
-+ u16 FAN2_TARGET_RPM;
-+ u16 ALT_CPU_TEMP;
-+ u16 ALT_GPU_TEMP;
-+ u16 ALT_POWERMODE;
-+
-+ u16 ALT_FAN1_RPM;
-+ u16 ALT_FAN2_RPM;
-+ u16 ALT_CPU_TEMP2;
-+ u16 ALT_GPU_TEMP2;
-+ u16 ALT_IC_TEMP2;
-+
-+ u16 MINIFANCURVE_ON_COOL;
-+ u16 LOCKFANCONTROLLER;
-+ u16 MAXIMUMFANSPEED;
-+};
-+
-+enum ECRAM_ACCESS { ECRAM_ACCESS_PORTIO, ECRAM_ACCESS_MEMORYIO };
-+
-+enum CONTROL_METHOD {
-+ // control EC by readin/writing to EC memory
-+ CONTROL_METHOD_ECRAM,
-+ // control EC only by ACPI calls
-+ CONTROL_METHOD_ACPI
++
++ // Lenovo Custom OEM extension
++ // Firmware of ITE can be extended by
++ // custom program using its own "variables"
++ // These are the offsets to these "variables"
++ u16 EXT_FAN_CUR_POINT;
++ u16 EXT_FAN_POINTS_SIZE;
++ u16 EXT_FAN1_BASE;
++ u16 EXT_FAN2_BASE;
++ u16 EXT_FAN_ACC_BASE;
++ u16 EXT_FAN_DEC_BASE;
++ u16 EXT_CPU_TEMP;
++ u16 EXT_CPU_TEMP_HYST;
++ u16 EXT_GPU_TEMP;
++ u16 EXT_GPU_TEMP_HYST;
++ u16 EXT_VRM_TEMP;
++ u16 EXT_VRM_TEMP_HYST;
++ u16 EXT_FAN1_RPM_LSB;
++ u16 EXT_FAN1_RPM_MSB;
++ u16 EXT_FAN2_RPM_LSB;
++ u16 EXT_FAN2_RPM_MSB;
++ u16 EXT_FAN1_TARGET_RPM;
++ u16 EXT_FAN2_TARGET_RPM;
++ u16 EXT_POWERMODE;
++ u16 EXT_MINIFANCURVE_ON_COOL;
++ // values
++ // 0x04: enable mini fan curve if very long on cool level
++ // - this might be due to potential temp failure
++ // - or just because really so cool
++ // 0xA0: disable it
++ u16 EXT_LOCKFANCONTROLLER;
++ u16 EXT_MAXIMUMFANSPEED;
++ u16 EXT_WHITE_KEYBOARD_BACKLIGHT;
++ u16 EXT_IC_TEMP_INPUT;
++ u16 EXT_CPU_TEMP_INPUT;
++ u16 EXT_GPU_TEMP_INPUT;
++};
++
++enum access_method {
++ ACCESS_METHOD_NO_ACCESS = 0,
++ ACCESS_METHOD_EC = 1,
++ ACCESS_METHOD_ACPI = 2,
++ ACCESS_METHOD_WMI = 3,
++ ACCESS_METHOD_WMI2 = 4,
++ ACCESS_METHOD_WMI3 = 5,
++ ACCESS_METHOD_EC2 = 10, // ideapad fancurve method
+};
+
+struct model_config {
+ const struct ec_register_offsets *registers;
+ bool check_embedded_controller_id;
+ u16 embedded_controller_id;
-+ // how should the EC be acesses?
-+ enum CONTROL_METHOD access_method;
-+
-+ // if EC is accessed by RAM, how sould it be access
-+ enum ECRAM_ACCESS ecram_access_method;
+
-+ // if EC is accessed by memory mapped, what is its address
-+ phys_addr_t memoryio_physical_start;
++ // first addr in EC we access/scan
+ phys_addr_t memoryio_physical_ec_start;
+ size_t memoryio_size;
++
++ // TODO: maybe use bitfield
++ bool has_minifancurve;
++ bool has_custom_powermode;
++ enum access_method access_method_powermode;
++
++ enum access_method access_method_keyboard;
++ enum access_method access_method_temperature;
++ enum access_method access_method_fanspeed;
++ enum access_method access_method_fancurve;
++ enum access_method access_method_fanfullspeed;
++ bool three_state_keyboard;
++
++ bool acpi_check_dev;
++
++ phys_addr_t ramio_physical_start;
++ size_t ramio_size;
+};
+
+/* =================================== */
-+/* Coinfiguration for different models */
++/* Configuration for different models */
+/* =================================== */
+
+// Idea by SmokelesssCPU (modified)
+// - all default names and register addresses are supported by datasheet
+// - register addresses for custom firmware by SmokelesssCPU
+static const struct ec_register_offsets ec_register_offsets_v0 = {
-+ // 6.3 Shared Memory Flash Interface Bridge (SMFI)
-+ // "The SMFI provides an HLPC interface between the host bus a
-+ // and the M bus. The flash is mapped into the
-+ // host memory address space for host accesses. The flash is also
-+ // mapped into the EC memory address space for EC accesses"
-+ .ECINDAR0 = 0x103B,
-+ .ECINDAR1 = 0x103C,
-+ .ECINDAR2 = 0x103D,
-+ .ECINDAR3 = 0x103E,
-+ .ECINDDR = 0x103F,
-+
-+ // 7.5 General Purpose I/O Port (GPIO)
-+ // I/O pins controlled by registers.
-+ .GPDRA = 0x1601,
-+ // port data, i.e. data to output to pins
-+ // or data read from pins
-+ .GPCRA0 = 0x1610,
-+ // control register for each pin,
-+ // set as input, output, ...
-+ .GPCRA1 = 0x1611,
-+ .GPCRA2 = 0x1612,
-+ .GPCRA3 = 0x1613,
-+ .GPCRA4 = 0x1614,
-+ .GPCRA5 = 0x1615,
-+ .GPCRA6 = 0x1616,
-+ .GPCRA7 = 0x1617,
-+ .GPOTA = 0x1671,
-+ .GPDMRA = 0x1661,
++ .ECHIPID1 = 0x2000,
++ .ECHIPID2 = 0x2001,
++ .ECHIPVER = 0x2002,
++ .ECDEBUG = 0x2003,
++ .EXT_FAN_CUR_POINT = 0xC534,
++ .EXT_FAN_POINTS_SIZE = 0xC535,
++ .EXT_FAN1_BASE = 0xC540,
++ .EXT_FAN2_BASE = 0xC550,
++ .EXT_FAN_ACC_BASE = 0xC560,
++ .EXT_FAN_DEC_BASE = 0xC570,
++ .EXT_CPU_TEMP = 0xC580,
++ .EXT_CPU_TEMP_HYST = 0xC590,
++ .EXT_GPU_TEMP = 0xC5A0,
++ .EXT_GPU_TEMP_HYST = 0xC5B0,
++ .EXT_VRM_TEMP = 0xC5C0,
++ .EXT_VRM_TEMP_HYST = 0xC5D0,
++ .EXT_FAN1_RPM_LSB = 0xC5E0,
++ .EXT_FAN1_RPM_MSB = 0xC5E1,
++ .EXT_FAN2_RPM_LSB = 0xC5E2,
++ .EXT_FAN2_RPM_MSB = 0xC5E3,
++ .EXT_MINIFANCURVE_ON_COOL = 0xC536,
++ .EXT_LOCKFANCONTROLLER = 0xc4AB,
++ .EXT_CPU_TEMP_INPUT = 0xc538,
++ .EXT_GPU_TEMP_INPUT = 0xc539,
++ .EXT_IC_TEMP_INPUT = 0xC5E8,
++ .EXT_POWERMODE = 0xc420,
++ .EXT_FAN1_TARGET_RPM = 0xc600,
++ .EXT_FAN2_TARGET_RPM = 0xc601,
++ .EXT_MAXIMUMFANSPEED = 0xBD,
++ .EXT_WHITE_KEYBOARD_BACKLIGHT = (0x3B + 0xC400)
++};
+
-+ // Super I/O Configuration Registers
-+ // 7.15 General Control (GCTRL)
-+ // 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
-+ // Chip ID
-+ .ECHIPID1 = 0x2000, // 0x20
-+ .ECHIPID2 = 0x2001, // 0x21
-+ // Chip Version
-+ .ECHIPVER = 0x2002, // 0x22
-+ .ECDEBUG = 0x2003, //0x23 SIOCTRL (super io control)
-+
-+ // External GPIO Controller (EGPC)
-+ // 7.16 External GPIO Controller (EGPC)
-+ // Communication with an external GPIO chip
-+ // (IT8301)
-+ // Address
-+ .EADDR = 0x2100,
-+ // Data
-+ .EDAT = 0x2101,
-+ // Control
-+ .ECNT = 0x2102,
-+ // Status
-+ .ESTS = 0x2103,
-+
-+ // FAN/PWM control by ITE
-+ // 7.11 PWM
-+ // - lower powered ITEs just come with PWM
-+ // control
-+ // - higher powered ITEs, e.g. ITE8511, come
-+ // from ITE with a fan control software
-+ // in ROM with 3 (or 4) fan curve points
-+ // called SmartAuto Fan Control
-+ // - in Lenovo Legion Laptop the SmartAuto
-+ // is not used, but the fan is controlled
-+ // by a custom program flashed on the ITE
-+ // chip
-+
-+ // duty cycle of each PWM output
-+ .DCR0 = 0x1802,
-+ .DCR1 = 0x1803,
-+ .DCR2 = 0x1804,
-+ .DCR3 = 0x1805,
-+ .DCR4 = 0x1806,
-+ .DCR5 = 0x1807,
-+ .DCR6 = 0x1808,
-+ .DCR7 = 0x1809,
-+ // FAN1 tachometer (least, most signficant byte)
-+ .F1TLRR = 0x181E,
-+ .F1TMRR = 0x181F,
-+ // FAN1 tachometer (least, most signficant byte)
-+ .F2TLRR = 0x1820,
-+ .F2TLRR = 0x1821,
-+ // cycle time, i.e. clock prescaler for PWM signal
-+ .CTR1 = 0x1842,
-+ .CTR2 = 0x1842,
-+ .CTR3 = 0x1842,
-+
-+ // bits 7-6 (higest bit)
-+ // 00: SmartAuto mode 0 (temperature controlled)
-+ // 01: SmartAuto mode 1 (temperaute replaced by a register value)
-+ // 10: manual mode
-+ // bits: 4-2
-+ // PWM output channel used for ouput (0-7 by 3 bits)
-+ .FAN1CNF = 0x1810,
-+ // spin up time (duty cycle = 100% for this time when fan stopped)
-+ // 00: 0
-+ // 01: 250ms
-+ // 10: 500ms
-+ // 11: 1000ms
-+ .FAN2CNF = 0x1811,
++static const struct ec_register_offsets ec_register_offsets_v1 = {
++ .ECHIPID1 = 0x2000,
++ .ECHIPID2 = 0x2001,
++ .ECHIPVER = 0x2002,
++ .ECDEBUG = 0x2003,
++ .EXT_FAN_CUR_POINT = 0xC534,
++ .EXT_FAN_POINTS_SIZE = 0xC535,
++ .EXT_FAN1_BASE = 0xC540,
++ .EXT_FAN2_BASE = 0xC550,
++ .EXT_FAN_ACC_BASE = 0xC560,
++ .EXT_FAN_DEC_BASE = 0xC570,
++ .EXT_CPU_TEMP = 0xC580,
++ .EXT_CPU_TEMP_HYST = 0xC590,
++ .EXT_GPU_TEMP = 0xC5A0,
++ .EXT_GPU_TEMP_HYST = 0xC5B0,
++ .EXT_VRM_TEMP = 0xC5C0,
++ .EXT_VRM_TEMP_HYST = 0xC5D0,
++ .EXT_FAN1_RPM_LSB = 0xC5E0,
++ .EXT_FAN1_RPM_MSB = 0xC5E1,
++ .EXT_FAN2_RPM_LSB = 0xC5E2,
++ .EXT_FAN2_RPM_MSB = 0xC5E3,
++ .EXT_MINIFANCURVE_ON_COOL = 0xC536,
++ .EXT_LOCKFANCONTROLLER = 0xc4AB,
++ .EXT_CPU_TEMP_INPUT = 0xc538,
++ .EXT_GPU_TEMP_INPUT = 0xc539,
++ .EXT_IC_TEMP_INPUT = 0xC5E8,
++ .EXT_POWERMODE = 0xc41D,
++ .EXT_FAN1_TARGET_RPM = 0xc600,
++ .EXT_FAN2_TARGET_RPM = 0xc601,
++ .EXT_MAXIMUMFANSPEED = 0xBD,
++ .EXT_WHITE_KEYBOARD_BACKLIGHT = (0x3B + 0xC400)
++};
+
-+ // Lenovo Custom OEM extension
-+ // Firmware of ITE can be extended by
-+ // custom program using its own "variables"
-+ // These are the offsets to these "variables"
-+ .FW_VER = 0xC2C7,
-+ .FAN_CUR_POINT = 0xC534,
-+ .FAN_POINTS_SIZE = 0xC535,
-+ .FAN1_BASE = 0xC540,
-+ .FAN2_BASE = 0xC550,
-+ .FAN_ACC_BASE = 0xC560,
-+ .FAN_DEC_BASE = 0xC570,
-+ .CPU_TEMP = 0xC580,
-+ .CPU_TEMP_HYST = 0xC590,
-+ .GPU_TEMP = 0xC5A0,
-+ .GPU_TEMP_HYST = 0xC5B0,
-+ .VRM_TEMP = 0xC5C0,
-+ .VRM_TEMP_HYST = 0xC5D0,
-+ .CPU_TEMP_EN = 0xC631,
-+ .GPU_TEMP_EN = 0xC632,
-+ .VRM_TEMP_EN = 0xC633,
-+ .FAN1_ACC_TIMER = 0xC3DA,
-+ .FAN2_ACC_TIMER = 0xC3DB,
-+ .FAN1_CUR_ACC = 0xC3DC,
-+ .FAN1_CUR_DEC = 0xC3DD,
-+ .FAN2_CUR_ACC = 0xC3DE,
-+ .FAN2_CUR_DEC = 0xC3DF,
-+ .FAN1_RPM_LSB = 0xC5E0,
-+ .FAN1_RPM_MSB = 0xC5E1,
-+ .FAN2_RPM_LSB = 0xC5E2,
-+ .FAN2_RPM_MSB = 0xC5E3,
++static const struct ec_register_offsets ec_register_offsets_ideapad_v0 = {
++ .ECHIPID1 = 0x2000,
++ .ECHIPID2 = 0x2001,
++ .ECHIPVER = 0x2002,
++ .ECDEBUG = 0x2003,
++ .EXT_FAN_CUR_POINT = 0xC5a0, // not found yet
++ .EXT_FAN_POINTS_SIZE = 0xC5a0, // constant 0
++ .EXT_FAN1_BASE = 0xC5a0,
++ .EXT_FAN2_BASE = 0xC5a8,
++ .EXT_FAN_ACC_BASE = 0xC5a0, // not found yet
++ .EXT_FAN_DEC_BASE = 0xC5a0, // not found yet
++ .EXT_CPU_TEMP = 0xC550, // and repeated after 8 bytes
++ .EXT_CPU_TEMP_HYST = 0xC590, // and repeated after 8 bytes
++ .EXT_GPU_TEMP = 0xC5C0, // and repeated after 8 bytes
++ .EXT_GPU_TEMP_HYST = 0xC5D0, // and repeated after 8 bytes
++ .EXT_VRM_TEMP = 0xC5a0, // does not exists or not found
++ .EXT_VRM_TEMP_HYST = 0xC5a0, // does not exists ot 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, // does not exists or not found
++ .EXT_LOCKFANCONTROLLER = 0xC5a0, // does not exists or not found
++ .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 = 0xC5a0, // not found yet
++ .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
++};
+
-+ // values
-+ // 0x04: enable mini fan curve if very long on cool level
-+ // - this might be due to potential temp failure
-+ // - or just because really so cool
-+ // 0xA0: disable it
-+ .MINIFANCURVE_ON_COOL = 0xC536,
++static const struct ec_register_offsets ec_register_offsets_ideapad_v1 = {
++ .ECHIPID1 = 0x2000,
++ .ECHIPID2 = 0x2001,
++ .ECHIPVER = 0x2002,
++ .ECDEBUG = 0x2003,
++ .EXT_FAN_CUR_POINT = 0xC5a0, // not found yet
++ .EXT_FAN_POINTS_SIZE = 0xC5a0, // constant 0
++ .EXT_FAN1_BASE = 0xC5a0,
++ .EXT_FAN2_BASE = 0xC5a8,
++ .EXT_FAN_ACC_BASE = 0xC5a0, // not found yet
++ .EXT_FAN_DEC_BASE = 0xC5a0, // not found yet
++ .EXT_CPU_TEMP = 0xC550, // and repeated after 8 bytes
++ .EXT_CPU_TEMP_HYST = 0xC590, // and repeated after 8 bytes
++ .EXT_GPU_TEMP = 0xC5C0, // and repeated after 8 bytes
++ .EXT_GPU_TEMP_HYST = 0xC5D0, // and repeated after 8 bytes
++ .EXT_VRM_TEMP = 0xC5a0, // does not exists or not found
++ .EXT_VRM_TEMP_HYST = 0xC5a0, // does not exists ot 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, // does not exists or not found
++ .EXT_LOCKFANCONTROLLER = 0xC5a0, // does not exists or not found
++ .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 = 0xC5a0, // not found yet
++ .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,
++ .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,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
+
-+ .LOCKFANCONTROLLER = 0xc4AB,
++static const struct model_config model_j2cn = {
++ .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,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
+
-+ .ALT_CPU_TEMP = 0xc538,
-+ .ALT_GPU_TEMP = 0xc539,
-+ .ALT_POWERMODE = 0xc420,
++static const struct model_config model_9vcn = {
++ .registers = &ec_register_offsets_ideapad_v1,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x8226,
++ .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_WMI,
++ .access_method_temperature = ACCESS_METHOD_WMI,
++ .access_method_fancurve = ACCESS_METHOD_EC2,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = false,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
+
-+ .FAN1_TARGET_RPM = 0xc600,
-+ .FAN2_TARGET_RPM = 0xc601,
-+ .ALT_FAN1_RPM = 0xC406,
-+ .ALT_FAN2_RPM = 0xC4FE,
++static const struct model_config model_v2022 = {
++ .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,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
+
-+ .ALT_CPU_TEMP2 = 0xC5E6,
-+ .ALT_GPU_TEMP2 = 0xC5E7,
-+ .ALT_IC_TEMP2 = 0xC5E8,
++static const struct model_config model_4gcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x8226,
++ .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_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
+
-+ //enabled: 0x40
-+ //disabled: 0x00
-+ .MAXIMUMFANSPEED = 0xBD
++static const struct model_config model_bvcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = false,
++ .embedded_controller_id = 0x8226,
++ .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_WMI,
++ .access_method_temperature = ACCESS_METHOD_WMI,
++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = false,
++ .ramio_physical_start = 0xFC7E0800,
++ .ramio_size = 0x600
+};
+
-+static const struct model_config model_v0 = {
++static const struct model_config model_bhcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x8226,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = true,
++ .has_custom_powermode = false,
++ .access_method_powermode = ACCESS_METHOD_WMI,
++ .access_method_keyboard = ACCESS_METHOD_ACPI,
++ .access_method_fanspeed = ACCESS_METHOD_WMI,
++ .access_method_temperature = ACCESS_METHOD_ACPI,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFF00D400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_kwcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x5507,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .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 = true,
++ .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,
-+ .access_method = CONTROL_METHOD_ECRAM,
-+ .ecram_access_method = ECRAM_ACCESS_PORTIO,
-+ .memoryio_physical_start = 0xFE00D400,
+ .memoryio_physical_ec_start = 0xC400,
-+ .memoryio_size = 0x300
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .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_k1cn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x5263,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .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 = true,
++ .ramio_physical_start = 0xFE0B0400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_lpcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x5507,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .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 = true,
++ .ramio_physical_start = 0xFE0B0400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_kfcn = {
++ .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_custom_powermode = true,
++ .access_method_powermode = ACCESS_METHOD_WMI,
++ .access_method_keyboard = ACCESS_METHOD_WMI,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
+};
+
+static const struct model_config model_hacn = {
+ .registers = &ec_register_offsets_v0,
+ .check_embedded_controller_id = false,
+ .embedded_controller_id = 0x8227,
-+ .access_method = CONTROL_METHOD_ECRAM,
-+ .ecram_access_method = ECRAM_ACCESS_MEMORYIO,
-+ .memoryio_physical_start = 0xFE00D400,
+ .memoryio_physical_ec_start = 0xC400,
-+ .memoryio_size = 0x300
++ .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_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_k9cn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = false,
++ .embedded_controller_id = 0x8227,
++ .memoryio_physical_ec_start = 0xC400, // or replace 0xC400 by 0x0400 ?
++ .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_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_eucn = {
++ .registers = &ec_register_offsets_v1,
++ .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,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_fccn = {
++ .registers = &ec_register_offsets_ideapad_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x8227,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .has_custom_powermode = true,
++ .access_method_powermode = ACCESS_METHOD_WMI,
++ .access_method_keyboard = ACCESS_METHOD_WMI,
++ .access_method_fanspeed = ACCESS_METHOD_WMI,
++ .access_method_temperature = ACCESS_METHOD_ACPI,
++ .access_method_fancurve = ACCESS_METHOD_EC2,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_h3cn = {
++ //0xFE0B0800
++ .registers = &ec_register_offsets_v1,
++ .check_embedded_controller_id = false,
++ .embedded_controller_id = 0x8227,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .has_custom_powermode = false,
++ .access_method_powermode = ACCESS_METHOD_WMI,
++ // not implemented (properly) in WMI, RGB conrolled by USB
++ .access_method_keyboard = ACCESS_METHOD_NO_ACCESS,
++ // accessing fan speed is not implemented in ACPI
++ // a variable in the operation region (or not found)
++ // and not per WMI (methods returns constant 0)
++ .access_method_fanspeed = ACCESS_METHOD_NO_ACCESS,
++ .access_method_temperature = ACCESS_METHOD_WMI,
++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = false,
++ .ramio_physical_start = 0xFE0B0800,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_e9cn = {
++ //0xFE0B0800
++ .registers = &ec_register_offsets_v1,
++ .check_embedded_controller_id = false,
++ .embedded_controller_id = 0x8227,
++ .memoryio_physical_ec_start = 0xC400, //0xFC7E0800
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .has_custom_powermode = false,
++ .access_method_powermode = ACCESS_METHOD_WMI,
++ // not implemented (properly) in WMI, RGB conrolled by USB
++ .access_method_keyboard = ACCESS_METHOD_NO_ACCESS,
++ // accessing fan speed is not implemented in ACPI
++ // a variable in the operation region (or not found)
++ // and not per WMI (methods returns constant 0)
++ .access_method_fanspeed = ACCESS_METHOD_WMI,
++ .access_method_temperature = ACCESS_METHOD_WMI,
++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = false,
++ .ramio_physical_start = 0xFC7E0800,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_8jcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = true,
++ .embedded_controller_id = 0x8226,
++ .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_WMI,
++ .access_method_temperature = ACCESS_METHOD_WMI,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = false,
++ .ramio_physical_start = 0xFE00D400,
++ .ramio_size = 0x600
++};
++
++static const struct model_config model_jncn = {
++ .registers = &ec_register_offsets_v1,
++ .check_embedded_controller_id = false,
++ .embedded_controller_id = 0x8227,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = false,
++ .has_custom_powermode = false,
++ .access_method_powermode = ACCESS_METHOD_WMI,
++ .access_method_keyboard = ACCESS_METHOD_NO_ACCESS,
++ .access_method_fanspeed = ACCESS_METHOD_WMI,
++ .access_method_temperature = ACCESS_METHOD_WMI,
++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = false,
++ .ramio_physical_start = 0xFC7E0800,
++ .ramio_size = 0x600
++};
++
++// Yoga Model!
++static const struct model_config model_j1cn = {
++ .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,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE0B0400,
++ .ramio_size = 0x600
++};
++
++// Yoga Model!
++static const struct model_config model_dmcn = {
++ .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,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .acpi_check_dev = true,
++ .ramio_physical_start = 0xFE700D00,
++ .ramio_size = 0x600
++};
++
++// Yoga Model!
++static const struct model_config model_khcn = {
++ .registers = &ec_register_offsets_v0,
++ .check_embedded_controller_id = false,
++ .embedded_controller_id = 0x8227,
++ .memoryio_physical_ec_start = 0xC400,
++ .memoryio_size = 0x300,
++ .has_minifancurve = true,
++ .has_custom_powermode = true,
++ .access_method_powermode = ACCESS_METHOD_EC,
++ .access_method_keyboard = ACCESS_METHOD_WMI,
++ .access_method_fanspeed = ACCESS_METHOD_EC,
++ .access_method_temperature = ACCESS_METHOD_EC,
++ .access_method_fancurve = ACCESS_METHOD_EC,
++ .access_method_fanfullspeed = ACCESS_METHOD_WMI,
++ .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[] = {
@@ -504,7 +886,7 @@ index 000000000..6edfeffcc
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "EUCN"),
+ },
-+ .driver_data = (void *)&model_v0
++ .driver_data = (void *)&model_eucn
+ },
+ {
+ // modelyear: 2020
@@ -567,7 +949,7 @@ index 000000000..6edfeffcc
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_BIOS_VERSION, "KFCN"),
+ },
-+ .driver_data = (void *)&model_v0
++ .driver_data = (void *)&model_kfcn
+ },
+ {
+ // modelyear: 2021
@@ -578,22 +960,679 @@ index 000000000..6edfeffcc
+ },
+ .driver_data = (void *)&model_hacn
+ },
-+ {}
-+};
-+
-+static const struct dmi_system_id explicit_allowlist[] = {
+ {
-+ .ident = "GKCN58WW",
++ // modelyear: 2021
++ .ident = "G9CN",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
-+ DMI_MATCH(DMI_BIOS_VERSION, "GKCN58WW"),
++ DMI_MATCH(DMI_BIOS_VERSION, "G9CN"),
+ },
+ .driver_data = (void *)&model_v0
+ },
++ {
++ // modelyear: 2022
++ .ident = "K9CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "K9CN"),
++ },
++ .driver_data = (void *)&model_k9cn
++ },
++ {
++ // e.g. IdeaPad Gaming 3 15ARH05
++ .ident = "FCCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "FCCN"),
++ },
++ .driver_data = (void *)&model_fccn
++ },
++ {
++ // e.g. Ideapad Gaming 3 15ACH6
++ .ident = "H3CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "H3CN"),
++ },
++ .driver_data = (void *)&model_h3cn
++ },
++ {
++ // e.g. IdeaPad Gaming 3 15ARH7 (2022)
++ .ident = "JNCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "JNCN"),
++ },
++ .driver_data = (void *)&model_jncn
++ },
++ {
++ // 2020, seems very different in ACPI dissassembly
++ .ident = "E9CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "E9CN"),
++ },
++ .driver_data = (void *)&model_e9cn
++ },
++ {
++ // e.g. Legion Y7000 (older version)
++ .ident = "8JCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "8JCN"),
++ },
++ .driver_data = (void *)&model_8jcn
++ },
++ {
++ // e.g. Legion 7i Pro 2023
++ .ident = "KWCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "KWCN"),
++ },
++ .driver_data = (void *)&model_kwcn
++ },
++ {
++ // e.g. Legion Pro 5 2023 or R9000P
++ .ident = "LPCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "LPCN"),
++ },
++ .driver_data = (void *)&model_lpcn
++ },
++ {
++ // e.g. Lenovo Legion 5i/Y7000 2019 PG0
++ .ident = "BHCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "BHCN"),
++ },
++ .driver_data = (void *)&model_bhcn
++ },
++ {
++ // e.g. Lenovo 7 16IAX7
++ .ident = "K1CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "K1CN"),
++ },
++ .driver_data = (void *)&model_k1cn
++ },
++ {
++ // e.g. Legion Y720
++ .ident = "4GCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "4GCN"),
++ },
++ .driver_data = (void *)&model_4gcn
++ },
++ {
++ // e.g. Legion Slim 5 16APH8 2023
++ .ident = "M3CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "M3CN"),
++ },
++ .driver_data = (void *)&model_lpcn
++ },
++ {
++ // e.g. Legion Y7000p-1060
++ .ident = "9VCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "9VCN"),
++ },
++ .driver_data = (void *)&model_9vcn
++ },
++ {
++ // e.g. Legion Y9000X
++ .ident = "JYCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "JYCN"),
++ },
++ .driver_data = (void *)&model_v2022
++ },
++ {
++ // e.g. Legion Y740-15IRH, older model e.g. with GTX 1660
++ .ident = "BVCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "BVCN"),
++ },
++ .driver_data = (void *)&model_bvcn
++ },
++ {
++ // e.g. Legion 5 Pro 16IAH7H with a RTX 3070 Ti
++ .ident = "J2CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "J2CN"),
++ },
++ .driver_data = (void *)&model_j2cn
++ },
++ {
++ // e.g. Lenovo Yoga 7 16IAH7 with GPU Intel DG2 Arc A370M
++ .ident = "J1CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "J1CN"),
++ },
++ .driver_data = (void *)&model_j1cn
++ },
++ {
++ // e.g. Legion Slim 5 16IRH8 (2023) with RTX 4070
++ .ident = "M2CN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "M2CN"),
++ },
++ .driver_data = (void *)&model_m2cn
++ },
++ {
++ // e.g. Yoga Slim 7-14ARE05
++ .ident = "DMCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "DMCN"),
++ },
++ .driver_data = (void *)&model_dmcn
++ },
++ {
++ // e.g. Yoga Slim 7 Pro 14ARH7
++ .ident = "KHCN",
++ .matches = {
++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
++ DMI_MATCH(DMI_BIOS_VERSION, "KHCN"),
++ },
++ .driver_data = (void *)&model_khcn
++ },
+ {}
+};
+
+/* ================================= */
++/* ACPI and WMI access */
++/* ================================= */
++
++// function from ideapad-laptop.c
++static int eval_int(acpi_handle handle, const char *name, unsigned long *res)
++{
++ unsigned long long result;
++ acpi_status status;
++
++ status = acpi_evaluate_integer(handle, (char *)name, NULL, &result);
++ if (ACPI_FAILURE(status))
++ return -EIO;
++
++ *res = result;
++
++ return 0;
++}
++
++// function from ideapad-laptop.c
++static int exec_simple_method(acpi_handle handle, const char *name,
++ unsigned long arg)
++{
++ acpi_status status =
++ acpi_execute_simple_method(handle, (char *)name, arg);
++
++ return ACPI_FAILURE(status) ? -EIO : 0;
++}
++
++// function from ideapad-laptop.c
++static int exec_sbmc(acpi_handle handle, unsigned long arg)
++{
++ // \_SB.PCI0.LPC0.EC0.VPC0.SBMC
++ return exec_simple_method(handle, "VPC0.SBMC", arg);
++}
++
++//static int eval_qcho(acpi_handle handle, unsigned long *res)
++//{
++// // \_SB.PCI0.LPC0.EC0.QCHO
++// return eval_int(handle, "QCHO", res);
++//}
++
++static int eval_gbmd(acpi_handle handle, unsigned long *res)
++{
++ return eval_int(handle, "VPC0.GBMD", res);
++}
++
++static int eval_spmo(acpi_handle handle, unsigned long *res)
++{
++ // \_SB.PCI0.LPC0.EC0.QCHO
++ return eval_int(handle, "VPC0.BTSM", res);
++}
++
++static int acpi_process_buffer_to_ints(const char *id_name, int id_nr,
++ acpi_status status,
++ struct acpi_buffer *out_buffer, u8 *res,
++ size_t ressize)
++{
++ // seto to NULL call kfree on NULL if next function call fails
++ union acpi_object *out = NULL;
++ size_t i;
++ int error = 0;
++
++ if (ACPI_FAILURE(status)) {
++ pr_info("ACPI evaluation error for: %s:%d\n", id_name, id_nr);
++ error = -EFAULT;
++ goto err;
++ }
++
++ out = out_buffer->pointer;
++ if (!out) {
++ pr_info("Unexpected ACPI result for %s:%d\n", id_name, id_nr);
++ error = -AE_ERROR;
++ goto err;
++ }
++
++ if (out->type != ACPI_TYPE_BUFFER || out->buffer.length != ressize) {
++ pr_info("Unexpected ACPI result for %s:%d: expected type %d but got %d; expected length %lu but got %u;\n",
++ id_name, id_nr, ACPI_TYPE_BUFFER, out->type, ressize,
++ out->buffer.length);
++ error = -AE_ERROR;
++ goto err;
++ }
++ 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];
++ error = 0;
++
++err:
++ kfree(out);
++ return error;
++}
++
++//static int exec_ints(acpi_handle handle, const char *method_name,
++// struct acpi_object_list *params, u8 *res, size_t ressize)
++//{
++// acpi_status status;
++// struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
++
++// status = acpi_evaluate_object(handle, (acpi_string)method_name, params,
++// &out_buffer);
++
++// return acpi_process_buffer_to_ints(method_name, 0, status, &out_buffer,
++// res, ressize);
++//}
++
++static int wmi_exec_ints(const char *guid, u8 instance, u32 method_id,
++ const struct acpi_buffer *params, u8 *res,
++ size_t ressize)
++{
++ acpi_status status;
++ struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
++
++ status = wmi_evaluate_method(guid, instance, method_id, params,
++ &out_buffer);
++ return acpi_process_buffer_to_ints(guid, method_id, status, &out_buffer,
++ res, ressize);
++}
++
++static int wmi_exec_int(const char *guid, u8 instance, u32 method_id,
++ const struct acpi_buffer *params, unsigned long *res)
++{
++ acpi_status status;
++ struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
++ // seto to NULL call kfree on NULL if next function call fails
++ union acpi_object *out = NULL;
++ int error = 0;
++
++ status = wmi_evaluate_method(guid, instance, method_id, params,
++ &out_buffer);
++
++ if (ACPI_FAILURE(status)) {
++ pr_info("WMI evaluation error for: %s:%d\n", guid, method_id);
++ error = -EFAULT;
++ goto err;
++ }
++
++ out = out_buffer.pointer;
++ if (!out) {
++ pr_info("Unexpected ACPI result for %s:%d", guid, method_id);
++ error = -AE_ERROR;
++ goto err;
++ }
++
++ if (out->type != ACPI_TYPE_INTEGER) {
++ pr_info("Unexpected ACPI result for %s:%d: expected type %d but got %d\n",
++ guid, method_id, ACPI_TYPE_INTEGER, out->type);
++ error = -AE_ERROR;
++ goto err;
++ }
++
++ *res = out->integer.value;
++ error = 0;
++
++err:
++ kfree(out);
++ return error;
++}
++
++static int wmi_exec_noarg_int(const char *guid, u8 instance, u32 method_id,
++ unsigned long *res)
++{
++ struct acpi_buffer params;
++
++ params.length = 0;
++ params.pointer = NULL;
++ return wmi_exec_int(guid, instance, method_id, &params, res);
++}
++
++static int wmi_exec_noarg_ints(const char *guid, u8 instance, u32 method_id,
++ u8 *res, size_t ressize)
++{
++ struct acpi_buffer params;
++
++ params.length = 0;
++ params.pointer = NULL;
++ return wmi_exec_ints(guid, instance, method_id, &params, res, ressize);
++}
++
++static int wmi_exec_arg(const char *guid, u8 instance, u32 method_id, void *arg,
++ size_t arg_size)
++{
++ struct acpi_buffer params;
++ acpi_status status;
++
++ params.length = arg_size;
++ params.pointer = arg;
++ status = wmi_evaluate_method(guid, instance, method_id, &params, NULL);
++
++ if (ACPI_FAILURE(status))
++ return -EIO;
++ return 0;
++}
++
++/* ================================= */
++/* Lenovo WMI config */
++/* ================================= */
++#define LEGION_WMI_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
++// 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
++// below
++#define WMI_METHOD_ID_GETFAN1SPEED 8
++#define WMI_METHOD_ID_GETFAN2SPEED 9
++
++// Version of ACPI
++#define WMI_METHOD_ID_GETVERSION 11
++// 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
++// below
++#define WMI_METHOD_ID_GETCPUTEMP 18
++#define WMI_METHOD_ID_GETGPUTEMP 19
++
++// two state keyboard light
++#define WMI_METHOD_ID_GETKEYBOARDLIGHT 37
++#define WMI_METHOD_ID_SETKEYBOARDLIGHT 36
++// disable 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
++//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
++#define WMI_METHOD_ID_ISSUPPORTGSYNC 40
++#define WMI_METHOD_ID_GETGSYNCSTATUS 41
++#define WMI_METHOD_ID_SETGSYNCSTATUS 42
++//smartFanMode = powermode
++#define WMI_METHOD_ID_ISSUPPORTSMARTFAN 49
++#define WMI_METHOD_ID_GETSMARTFANMODE 45
++#define WMI_METHOD_ID_SETSMARTFANMODE 44
++// power charge mode
++#define WMI_METHOD_ID_GETPOWERCHARGEMODE 47
++// overdrive of display to reduce latency
++// 0=off, 1=on
++#define WMI_METHOD_ID_ISSUPPORTOD 49
++#define WMI_METHOD_ID_GETODSTATUS 50
++#define WMI_METHOD_ID_SETODSTATUS 51
++// thermal mode = power mode used for cooling
++#define WMI_METHOD_ID_GETTHERMALMODE 55
++// get max frequency of core 0
++#define WMI_METHOD_ID_GETCPUMAXFREQUENCY 60
++// check if AC adapter has enough power to overclock
++#define WMI_METHOD_ID_ISACFITFOROC 62
++// set iGPU (GPU packaged with CPU) state
++#define WMI_METHOD_ID_ISSUPPORTIGPUMODE 63
++#define WMI_METHOD_ID_GETIGPUMODESTATUS 64
++#define WMI_METHOD_ID_SETIGPUMODESTATUS 65
++#define WMI_METHOD_ID_NOTIFYDGPUSTATUS 66
++enum IGPUState {
++ IGPUState_default = 0,
++ IGPUState_iGPUOnly = 1,
++ IGPUState_auto = 2
++};
++
++#define WMI_GUID_LENOVO_CPU_METHOD "14afd777-106f-4c9b-b334-d388dc7809be"
++#define WMI_METHOD_ID_CPU_GET_SUPPORT_OC_STATUS 15
++#define WMI_METHOD_ID_CPU_GET_OC_STATUS 1
++#define WMI_METHOD_ID_CPU_SET_OC_STATUS 2
++
++// ppt limit slow
++#define WMI_METHOD_ID_CPU_GET_SHORTTERM_POWERLIMIT 3
++#define WMI_METHOD_ID_CPU_SET_SHORTTERM_POWERLIMIT 4
++// ppt stapm
++#define WMI_METHOD_ID_CPU_GET_LONGTERM_POWERLIMIT 5
++#define WMI_METHOD_ID_CPU_SET_LONGTERM_POWERLIMIT 6
++// default power limit
++#define WMI_METHOD_ID_CPU_GET_DEFAULT_POWERLIMIT 7
++// peak power limit
++#define WMI_METHOD_ID_CPU_GET_PEAK_POWERLIMIT 8
++#define WMI_METHOD_ID_CPU_SET_PEAK_POWERLIMIT 9
++// apu sppt powerlimit
++#define WMI_METHOD_ID_CPU_GET_APU_SPPT_POWERLIMIT 12
++#define WMI_METHOD_ID_CPU_SET_APU_SPPT_POWERLIMIT 13
++// cross loading powerlimit
++#define WMI_METHOD_ID_CPU_GET_CROSS_LOADING_POWERLIMIT 16
++#define WMI_METHOD_ID_CPU_SET_CROSS_LOADING_POWERLIMIT 17
++
++#define WMI_GUID_LENOVO_GPU_METHOD "da7547f1-824d-405f-be79-d9903e29ced7"
++// overclock GPU possible
++#define WMI_METHOD_ID_GPU_GET_OC_STATUS 1
++#define WMI_METHOD_ID_GPU_SET_OC_STATUS 2
++// dynamic boost power
++#define WMI_METHOD_ID_GPU_GET_PPAB_POWERLIMIT 3
++#define WMI_METHOD_ID_GPU_SET_PPAB_POWERLIMIT 4
++// configurable TGP (power)
++#define WMI_METHOD_ID_GPU_GET_CTGP_POWERLIMIT 5
++#define WMI_METHOD_ID_GPU_SET_CTGP_POWERLIMIT 6
++// ppab/ctgp powerlimit
++#define WMI_METHOD_ID_GPU_GET_DEFAULT_PPAB_CTGP_POWERLIMIT 7
++// temperature limit
++#define WMI_METHOD_ID_GPU_GET_TEMPERATURE_LIMIT 8
++#define WMI_METHOD_ID_GPU_SET_TEMPERATURE_LIMIT 9
++// boost clock
++#define WMI_METHOD_ID_GPU_GET_BOOST_CLOCK 10
++
++#define WMI_GUID_LENOVO_FAN_METHOD "92549549-4bde-4f06-ac04-ce8bf898dbaa"
++// set fan to maximal speed; dust cleaning mode
++// only works in custom power mode
++#define WMI_METHOD_ID_FAN_GET_FULLSPEED 1
++#define WMI_METHOD_ID_FAN_SET_FULLSPEED 2
++// max speed of fan
++#define WMI_METHOD_ID_FAN_GET_MAXSPEED 3
++#define WMI_METHOD_ID_FAN_SET_MAXSPEED 4
++// fan table in custom mode
++#define WMI_METHOD_ID_FAN_GET_TABLE 5
++#define WMI_METHOD_ID_FAN_SET_TABLE 6
++// get speed of fans
++#define WMI_METHOD_ID_FAN_GETCURRENTFANSPEED 7
++// get temperatures of CPU and GPU used for controlling cooling
++#define WMI_METHOD_ID_FAN_GETCURRENTSENSORTEMPERATURE 8
++
++// do not implement following
++// #define WMI_METHOD_ID_Fan_SetCurrentFanSpeed 9
++
++#define LEGION_WMI_KBBACKLIGHT_GUID "8C5B9127-ECD4-4657-980F-851019F99CA5"
++// access the keyboard backlight with 3 states
++#define WMI_METHOD_ID_KBBACKLIGHTGET 0x1
++#define WMI_METHOD_ID_KBBACKLIGHTSET 0x2
++
++// new method in newer methods to get or set most of the values
++// with the two methods GetFeatureValue or SetFeatureValue.
++// They are called like GetFeatureValue(feature_id) where
++// feature_id is a id for the feature
++#define LEGION_WMI_LENOVO_OTHER_METHOD_GUID \
++ "dc2a8805-3a8c-41ba-a6f7-092e0089cd3b"
++#define WMI_METHOD_ID_GET_FEATURE_VALUE 17
++#define WMI_METHOD_ID_SET_FEATURE_VALUE 18
++
++enum OtherMethodFeature {
++ OtherMethodFeature_U1 = 0x010000, //->PC00.LPCB.EC0.REJF
++ OtherMethodFeature_U2 = 0x0F0000, //->C00.PEG1.PXP._STA?
++ OtherMethodFeature_U3 = 0x030000, //->PC00.LPCB.EC0.FLBT?
++ OtherMethodFeature_CPU_SHORT_TERM_POWER_LIMIT = 0x01010000,
++ OtherMethodFeature_CPU_LONG_TERM_POWER_LIMIT = 0x01020000,
++ OtherMethodFeature_CPU_PEAK_POWER_LIMIT = 0x01030000,
++ OtherMethodFeature_CPU_TEMPERATURE_LIMIT = 0x01040000,
++
++ OtherMethodFeature_APU_PPT_POWER_LIMIT = 0x01050000,
++
++ OtherMethodFeature_CPU_CROSS_LOAD_POWER_LIMIT = 0x01060000,
++ OtherMethodFeature_CPU_L1_TAU = 0x01070000,
++
++ OtherMethodFeature_GPU_POWER_BOOST = 0x02010000,
++ OtherMethodFeature_GPU_cTGP = 0x02020000,
++ OtherMethodFeature_GPU_TEMPERATURE_LIMIT = 0x02030000,
++ OtherMethodFeature_GPU_POWER_TARGET_ON_AC_OFFSET_FROM_BASELINE =
++ 0x02040000,
++
++ OtherMethodFeature_FAN_SPEED_1 = 0x04030001,
++ OtherMethodFeature_FAN_SPEED_2 = 0x04030002,
++
++ OtherMethodFeature_C_U1 = 0x05010000,
++ OtherMethodFeature_TEMP_CPU = 0x05040000,
++ OtherMethodFeature_TEMP_GPU = 0x05050000,
++};
++
++static ssize_t wmi_other_method_get_value(enum OtherMethodFeature feature_id,
++ int *value)
++{
++ struct acpi_buffer params;
++ int error;
++ unsigned long res;
++ u32 param1 = feature_id;
++
++ params.length = sizeof(param1);
++ params.pointer = &param1;
++ error = wmi_exec_int(LEGION_WMI_LENOVO_OTHER_METHOD_GUID, 0,
++ WMI_METHOD_ID_GET_FEATURE_VALUE, &params, &res);
++ if (!error)
++ *value = res;
++ return error;
++}
++
++/* =================================== */
++/* EC RAM Access with memory mapped IO */
++/* =================================== */
++
++struct ecram_memoryio {
++ // TODO: start of remapped memory in EC RAM is assumed to be 0
++ // u16 ecram_start;
++
++ // physical address of remapped IO, depends on model and firmware
++ phys_addr_t physical_start;
++ // start adress of region in ec memory
++ phys_addr_t physical_ec_start;
++ // virtual address of remapped IO
++ u8 *virtual_start;
++ // size of remapped access
++ size_t size;
++};
++
++/**
++ * physical_start : corresponds to EC RAM 0 inside EC
++ * size: size of remapped region
++ *
++ * strong exception safety
++ */
++static ssize_t ecram_memoryio_init(struct ecram_memoryio *ec_memoryio,
++ phys_addr_t physical_start,
++ phys_addr_t physical_ec_start, size_t size)
++{
++ void *virtual_start = ioremap(physical_start, size);
++
++ if (!IS_ERR_OR_NULL(virtual_start)) {
++ ec_memoryio->virtual_start = virtual_start;
++ 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",
++ ec_memoryio->physical_start,
++ ec_memoryio->physical_ec_start,
++ ec_memoryio->virtual_start);
++ } else {
++ pr_info("Error mapping embedded controller memory at 0x%llx\n",
++ physical_start);
++ return -ENOMEM;
++ }
++ return 0;
++}
++
++static void ecram_memoryio_exit(struct ecram_memoryio *ec_memoryio)
++{
++ if (ec_memoryio->virtual_start != NULL) {
++ pr_info("Unmapping embedded controller memory at 0x%llx (in RAM)/0x%llx (in EC) at virtual 0x%p\n",
++ ec_memoryio->physical_start,
++ ec_memoryio->physical_ec_start,
++ ec_memoryio->virtual_start);
++ iounmap(ec_memoryio->virtual_start);
++ ec_memoryio->virtual_start = NULL;
++ }
++}
++
++/* Read a byte from the EC RAM.
++ *
++ * Return status because of commong signature for alle
++ * methods to access EC RAM.
++ */
++static ssize_t ecram_memoryio_read(const struct ecram_memoryio *ec_memoryio,
++ u16 ec_offset, u8 *value)
++{
++ if (ec_offset < ec_memoryio->physical_ec_start) {
++ pr_info("Unexpected read at offset %d into EC RAM\n",
++ ec_offset);
++ return -1;
++ }
++ *value = *(ec_memoryio->virtual_start +
++ (ec_offset - ec_memoryio->physical_ec_start));
++ return 0;
++}
++
++/* Write a byte to the EC RAM.
++ *
++ * Return status because of commong signature for alle
++ * methods to access EC RAM.
++ */
++ssize_t ecram_memoryio_write(const struct ecram_memoryio *ec_memoryio,
++ u16 ec_offset, u8 value)
++{
++ if (ec_offset < ec_memoryio->physical_ec_start) {
++ pr_info("Unexpected write at offset %d into EC RAM\n",
++ ec_offset);
++ return -1;
++ }
++ *(ec_memoryio->virtual_start +
++ (ec_offset - ec_memoryio->physical_ec_start)) = value;
++ return 0;
++}
++
++/* ================================= */
+/* EC RAM Access with port-mapped IO */
+/* ================================= */
+
@@ -638,7 +1677,7 @@ index 000000000..6edfeffcc
+ struct mutex io_port_mutex;
+};
+
-+ssize_t ecram_portio_init(struct ecram_portio *ec_portio)
++static ssize_t ecram_portio_init(struct ecram_portio *ec_portio)
+{
+ if (!request_region(ECRAM_PORTIO_START_PORT, ECRAM_PORTIO_PORTS_SIZE,
+ ECRAM_PORTIO_NAME)) {
@@ -651,32 +1690,18 @@ index 000000000..6edfeffcc
+ return 0;
+}
+
-+void ecram_portio_exit(struct ecram_portio *ec_portio)
++static void ecram_portio_exit(struct ecram_portio *ec_portio)
+{
+ release_region(ECRAM_PORTIO_START_PORT, ECRAM_PORTIO_PORTS_SIZE);
+}
+
-+//ssize_t ecram_portio_read_low(struct ecram_portio *ec_portio, u8 offset, u8 *value){
-+// mutex_lock(&ec_portio->io_port_mutex);
-+// outb(0x66, 0x80);
-+// outb(offset, ECRAM_PORTIO_DATA_PORT);
-+// *value = inb(ECRAM_PORTIO_DATA_PORT);
-+// mutex_unlock(&ec_portio->io_port_mutex);
-+//}
-+//ssize_t ecram_portio_write_low(struct ecram_portio *ec_portio, u8 offset, u8 value){
-+// mutex_lock(&ec_portio->io_port_mutex);
-+// outb(0x66, ECRAM_PORTIO_ADDR_PORT);
-+// outb(offset, ECRAM_PORTIO_DATA_PORT);
-+// outb(value, ECRAM_PORTIO_DATA_PORT);
-+// mutex_unlock(&ec_portio->io_port_mutex);
-+//}
-+
+/* Read a byte from the EC RAM.
+ *
+ * Return status because of commong signature for alle
+ * methods to access EC RAM.
+ */
-+ssize_t ecram_portio_read(struct ecram_portio *ec_portio, u16 offset, u8 *value)
++static ssize_t ecram_portio_read(struct ecram_portio *ec_portio, u16 offset,
++ u8 *value)
+{
+ mutex_lock(&ec_portio->io_port_mutex);
+
@@ -706,7 +1731,8 @@ index 000000000..6edfeffcc
+ * Return status because of commong signature for alle
+ * methods to access EC RAM.
+ */
-+ssize_t ecram_portio_write(struct ecram_portio *ec_portio, u16 offset, u8 value)
++static ssize_t ecram_portio_write(struct ecram_portio *ec_portio, u16 offset,
++ u8 value)
+{
+ mutex_lock(&ec_portio->io_port_mutex);
+
@@ -728,98 +1754,8 @@ index 000000000..6edfeffcc
+ outb(value, ECRAM_PORTIO_DATA_PORT);
+
+ mutex_unlock(&ec_portio->io_port_mutex);
-+ return 0;
-+}
-+
-+/* =================================== */
-+/* EC RAM Access with memory mapped IO */
-+/* =================================== */
-+
-+struct ecram_memoryio {
-+ // TODO: start of remapped memory in EC RAM is assumed to be 0
-+ // u16 ecram_start;
-+
-+ // physical address of remapped IO, depends on model and firmware
-+ phys_addr_t physical_start;
-+ // start adress of region in ec memory
-+ phys_addr_t physical_ec_start;
-+ // virtual address of remapped IO
-+ u8 *virtual_start;
-+ // size of remapped access
-+ size_t size;
-+};
-+
-+/**
-+ * physical_start : corresponds to EC RAM 0 inside EC
-+ * size: size of remapped region
-+ *
-+ * strong exception safety
-+ */
-+ssize_t ecram_memoryio_init(struct ecram_memoryio *ec_memoryio,
-+ phys_addr_t physical_start, phys_addr_t physical_ec_start, size_t size)
-+{
-+ void *virtual_start = ioremap(physical_start, size);
-+
-+ if (!IS_ERR_OR_NULL(virtual_start)) {
-+ ec_memoryio->virtual_start = virtual_start;
-+ 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",
-+ ec_memoryio->physical_start,
-+ ec_memoryio->physical_ec_start,
-+ ec_memoryio->virtual_start);
-+ } else {
-+ pr_info("Error mapping embedded controller memory at 0x%llx\n",
-+ physical_start);
-+ return -ENOMEM;
-+ }
-+ return 0;
-+}
-+
-+void ecram_memoryio_exit(struct ecram_memoryio *ec_memoryio)
-+{
-+ if (ec_memoryio->virtual_start != NULL) {
-+ pr_info("Unmapping embedded controller memory at 0x%llx (in RAM)/0x%llx (in EC) at virtual 0x%p\n",
-+ ec_memoryio->physical_start,
-+ ec_memoryio->physical_ec_start,
-+ ec_memoryio->virtual_start);
-+ iounmap(ec_memoryio->virtual_start);
-+ ec_memoryio->virtual_start = NULL;
-+ }
-+}
-+
-+/* Read a byte from the EC RAM.
-+ *
-+ * Return status because of commong signature for alle
-+ * methods to access EC RAM.
-+ */
-+ssize_t ecram_memoryio_read(const struct ecram_memoryio *ec_memoryio,
-+ u16 ec_offset, u8 *value)
-+{
-+ if (ec_offset < ec_memoryio->physical_ec_start) {
-+ pr_info("Unexpected read at offset %d into EC RAM\n",
-+ ec_offset);
-+ return -1;
-+ }
-+ *value = *(ec_memoryio->virtual_start + (ec_offset - ec_memoryio->physical_ec_start));
-+ return 0;
-+}
-+
-+/* Write a byte to the EC RAM.
-+ *
-+ * Return status because of commong signature for alle
-+ * methods to access EC RAM.
-+ */
-+ssize_t ecram_memoryio_write(const struct ecram_memoryio *ec_memoryio,
-+ u16 ec_offset, u8 value)
-+{
-+ if (ec_offset < ec_memoryio->physical_ec_start) {
-+ pr_info("Unexpected write at offset %d into EC RAM\n",
-+ ec_offset);
-+ return -1;
-+ }
-+ *(ec_memoryio->virtual_start + (ec_offset - ec_memoryio->physical_ec_start)) = value;
++ // TODO: remove this
++ //pr_info("Writing %d to addr %x\n", value, offset);
+ return 0;
+}
+
@@ -828,48 +1764,35 @@ index 000000000..6edfeffcc
+/* =================================== */
+
+struct ecram {
-+ struct ecram_memoryio memoryio;
+ struct ecram_portio portio;
-+ enum ECRAM_ACCESS access_method;
+};
+
-+ssize_t ecram_init(struct ecram *ecram, enum ECRAM_ACCESS access_method,
-+ phys_addr_t memoryio_physical_start, phys_addr_t memoryio_ec_physical_start, size_t region_size)
++static ssize_t ecram_init(struct ecram *ecram,
++ phys_addr_t memoryio_ec_physical_start,
++ size_t region_size)
+{
+ ssize_t err;
+
-+ err = ecram_memoryio_init(&ecram->memoryio, memoryio_physical_start,
-+ memoryio_ec_physical_start, region_size);
-+ if (err) {
-+ pr_info("Failed ecram_memoryio_init\n");
-+ goto err_ecram_memoryio_init;
-+ }
+ err = ecram_portio_init(&ecram->portio);
+ if (err) {
+ pr_info("Failed ecram_portio_init\n");
+ goto err_ecram_portio_init;
+ }
+
-+ ecram->access_method = access_method;
-+
+ return 0;
+
+err_ecram_portio_init:
-+ ecram_memoryio_exit(&ecram->memoryio);
-+err_ecram_memoryio_init:
-+
+ return err;
+}
+
-+void ecram_exit(struct ecram *ecram)
++static void ecram_exit(struct ecram *ecram)
+{
+ pr_info("Unloading legion ecram\n");
+ ecram_portio_exit(&ecram->portio);
-+ ecram_memoryio_exit(&ecram->memoryio);
+ pr_info("Unloading legion ecram done\n");
+}
+
-+/**
++/** Read from EC RAM
+ * ecram_offset address on the EC
+ */
+static u8 ecram_read(struct ecram *ecram, u16 ecram_offset)
@@ -877,17 +1800,7 @@ index 000000000..6edfeffcc
+ u8 value;
+ int err;
+
-+ switch (ecram->access_method) {
-+ case ECRAM_ACCESS_MEMORYIO:
-+ err = ecram_memoryio_read(&ecram->memoryio, ecram_offset,
-+ &value);
-+ break;
-+ case ECRAM_ACCESS_PORTIO:
-+ err = ecram_portio_read(&ecram->portio, ecram_offset, &value);
-+ break;
-+ default:
-+ break;
-+ }
++ err = ecram_portio_read(&ecram->portio, ecram_offset, &value);
+ if (err)
+ pr_info("Error reading EC RAM at 0x%x\n", ecram_offset);
+ return value;
@@ -902,18 +1815,7 @@ index 000000000..6edfeffcc
+ ecram_offset);
+ return;
+ }
-+
-+ switch (ecram->access_method) {
-+ case ECRAM_ACCESS_MEMORYIO:
-+ err = ecram_memoryio_write(&ecram->memoryio, ecram_offset,
-+ value);
-+ break;
-+ case ECRAM_ACCESS_PORTIO:
-+ err = ecram_portio_write(&ecram->portio, ecram_offset, value);
-+ break;
-+ default:
-+ break;
-+ }
++ err = ecram_portio_write(&ecram->portio, ecram_offset, value);
+ if (err)
+ pr_info("Error writing EC RAM at 0x%x\n", ecram_offset);
+}
@@ -922,7 +1824,7 @@ index 000000000..6edfeffcc
+/* Reads from EC */
+/* =============================== */
+
-+u16 read_ec_id(struct ecram *ecram, const struct model_config *model)
++static u16 read_ec_id(struct ecram *ecram, const struct model_config *model)
+{
+ u8 id1 = ecram_read(ecram, model->registers->ECHIPID1);
+ u8 id2 = ecram_read(ecram, model->registers->ECHIPID2);
@@ -930,7 +1832,8 @@ index 000000000..6edfeffcc
+ return (id1 << 8) + id2;
+}
+
-+u16 read_ec_version(struct ecram *ecram, const struct model_config *model)
++static u16 read_ec_version(struct ecram *ecram,
++ const struct model_config *model)
+{
+ u8 vers = ecram_read(ecram, model->registers->ECHIPVER);
+ u8 debug = ecram_read(ecram, model->registers->ECDEBUG);
@@ -940,7 +1843,7 @@ index 000000000..6edfeffcc
+
+/* ============================= */
+/* Data model for sensor values */
-+/* ============================ */
++/* ============================= */
+
+struct sensor_values {
+ u16 fan1_rpm; // current speed in rpm of fan 1
@@ -962,172 +1865,9 @@ index 000000000..6edfeffcc
+ SENSOR_FAN2_TARGET_RPM_ID = 7
+};
+
-+static int read_sensor_values(struct ecram *ecram,
-+ const struct model_config *model,
-+ struct sensor_values *values)
-+{
-+ values->fan1_target_rpm =
-+ 100 * ecram_read(ecram, model->registers->FAN1_TARGET_RPM);
-+ values->fan2_target_rpm =
-+ 100 * ecram_read(ecram, model->registers->FAN2_TARGET_RPM);
-+ // TODO: what source toc choose?
-+ // values->fan1_rpm = 100*ecram_read(ecram, model->registers->ALT_FAN1_RPM);
-+ // values->fan2_rpm = 100*ecram_read(ecram, model->registers->ALT_FAN2_RPM);
-+
-+ values->fan1_rpm =
-+ ecram_read(ecram, model->registers->FAN1_RPM_LSB) +
-+ (((int)ecram_read(ecram, model->registers->FAN1_RPM_MSB)) << 8);
-+ values->fan2_rpm =
-+ ecram_read(ecram, model->registers->FAN2_RPM_LSB) +
-+ (((int)ecram_read(ecram, model->registers->FAN2_RPM_MSB)) << 8);
-+
-+ values->cpu_temp_celsius =
-+ ecram_read(ecram, model->registers->ALT_CPU_TEMP);
-+ values->gpu_temp_celsius =
-+ ecram_read(ecram, model->registers->ALT_GPU_TEMP);
-+ values->ic_temp_celsius =
-+ ecram_read(ecram, model->registers->ALT_IC_TEMP2);
-+
-+ values->cpu_temp_celsius = ecram_read(ecram, 0xC5E6);
-+ values->gpu_temp_celsius = ecram_read(ecram, 0xC5E7);
-+ values->ic_temp_celsius = ecram_read(ecram, 0xC5E8);
-+
-+ return 0;
-+}
-+
-+/* =============================== */
-+/* Behaviour changing functions */
-+/* =============================== */
-+
-+int read_powermode(struct ecram *ecram, const struct model_config *model)
-+{
-+ return ecram_read(ecram, model->registers->ALT_POWERMODE);
-+}
-+
-+ssize_t write_powermode(struct ecram *ecram, const struct model_config *model,
-+ u8 value)
-+{
-+ if (!(value >= 0 && value <= 2)) {
-+ pr_info("Unexpected power mode value ignored: %d\n", value);
-+ return -ENOMEM;
-+ }
-+ ecram_write(ecram, model->registers->ALT_POWERMODE, value);
-+ return 0;
-+}
-+
-+/**
-+ * Shortly toggle powermode to a different mode
-+ * and switch back, e.g. to reset fan curve.
-+ */
-+void toggle_powermode(struct ecram *ecram, const struct model_config *model)
-+{
-+ int old_powermode = read_powermode(ecram, model);
-+ int next_powermode = old_powermode == 0 ? 1 : 0;
-+
-+ write_powermode(ecram, model, next_powermode);
-+ mdelay(1500);
-+ write_powermode(ecram, model, old_powermode);
-+}
-+
-+#define lockfancontroller_ON 8
-+#define lockfancontroller_OFF 0
-+
-+ssize_t write_lockfancontroller(struct ecram *ecram,
-+ const struct model_config *model, bool state)
-+{
-+ u8 val = state ? lockfancontroller_ON : lockfancontroller_OFF;
-+
-+ ecram_write(ecram, model->registers->LOCKFANCONTROLLER, val);
-+ return 0;
-+}
-+
-+int read_lockfancontroller(struct ecram *ecram,
-+ const struct model_config *model, bool *state)
-+{
-+ int value = ecram_read(ecram, model->registers->LOCKFANCONTROLLER);
-+
-+ switch (value) {
-+ case lockfancontroller_ON:
-+ *state = true;
-+ break;
-+ case lockfancontroller_OFF:
-+ *state = false;
-+ break;
-+ default:
-+ pr_info("Unexpected value in lockfanspeed register:%d\n",
-+ value);
-+ return -1;
-+ }
-+ return 0;
-+}
-+
-+#define MAXIMUMFANSPEED_ON 0x40
-+#define MAXIMUMFANSPEED_OFF 0x00
-+
-+int read_maximumfanspeed(struct ecram *ecram, const struct model_config *model,
-+ bool *state)
-+{
-+ int value = ecram_read(ecram, model->registers->MAXIMUMFANSPEED);
-+
-+ switch (value) {
-+ case MAXIMUMFANSPEED_ON:
-+ *state = true;
-+ break;
-+ case MAXIMUMFANSPEED_OFF:
-+ *state = false;
-+ break;
-+ default:
-+ pr_info("Unexpected value in maximumfanspeed register:%d\n",
-+ value);
-+ return -1;
-+ }
-+ return 0;
-+}
-+
-+ssize_t write_maximumfanspeed(struct ecram *ecram,
-+ const struct model_config *model, bool state)
-+{
-+ u8 val = state ? MAXIMUMFANSPEED_ON : MAXIMUMFANSPEED_OFF;
-+
-+ ecram_write(ecram, model->registers->MAXIMUMFANSPEED, val);
-+ return 0;
-+}
-+
-+#define MINIFANCUVE_ON_COOL_ON 0x04
-+#define MINIFANCUVE_ON_COOL_OFF 0xA0
-+
-+int read_minifancurve(struct ecram *ecram, const struct model_config *model,
-+ bool *state)
-+{
-+ int value = ecram_read(ecram, model->registers->MINIFANCURVE_ON_COOL);
-+
-+ switch (value) {
-+ case MINIFANCUVE_ON_COOL_ON:
-+ *state = true;
-+ break;
-+ case MINIFANCUVE_ON_COOL_OFF:
-+ *state = false;
-+ break;
-+ default:
-+ pr_info("Unexpected value in MINIFANCURVE register:%d\n",
-+ value);
-+ return -1;
-+ }
-+ return 0;
-+}
-+
-+ssize_t write_minifancurve(struct ecram *ecram,
-+ const struct model_config *model, bool state)
-+{
-+ u8 val = state ? MINIFANCUVE_ON_COOL_ON : MINIFANCUVE_ON_COOL_OFF;
-+
-+ ecram_write(ecram, model->registers->MINIFANCURVE_ON_COOL, val);
-+ return 0;
-+}
-+
+/* ============================= */
+/* Data model for fan curve */
-+/* ============================ */
++/* ============================= */
+
+struct fancurve_point {
+ // rpm1 devided by 100
@@ -1182,34 +1922,14 @@ index 000000000..6edfeffcc
+ size_t current_point_i;
+};
+
-+// calculate derived values
-+
-+int fancurve_get_cpu_deltahyst(struct fancurve_point *point)
-+{
-+ return ((int)point->cpu_max_temp_celsius) -
-+ ((int)point->cpu_min_temp_celsius);
-+}
-+
-+int fancurve_get_gpu_deltahyst(struct fancurve_point *point)
-+{
-+ return ((int)point->gpu_max_temp_celsius) -
-+ ((int)point->gpu_min_temp_celsius);
-+}
-+
-+int fancurve_get_ic_deltahyst(struct fancurve_point *point)
-+{
-+ return ((int)point->ic_max_temp_celsius) -
-+ ((int)point->ic_min_temp_celsius);
-+}
-+
+// validation functions
+
-+bool fancurve_is_valid_min_temp(int min_temp)
++static bool fancurve_is_valid_min_temp(int min_temp)
+{
+ return min_temp >= 0 && min_temp <= 127;
+}
+
-+bool fancurve_is_valid_max_temp(int max_temp)
++static bool fancurve_is_valid_max_temp(int max_temp)
+{
+ return max_temp >= 0 && max_temp <= 127;
+}
@@ -1218,7 +1938,7 @@ index 000000000..6edfeffcc
+// - make hwmon implementation easier
+// - keep fancurve valid, otherwise EC will not properly control fan
+
-+bool fancurve_set_rpm1(struct fancurve *fancurve, int point_id, int rpm)
++static bool fancurve_set_rpm1(struct fancurve *fancurve, int point_id, int rpm)
+{
+ bool valid = point_id == 0 ? rpm == 0 : (rpm >= 0 && rpm <= 4500);
+
@@ -1227,7 +1947,7 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_rpm2(struct fancurve *fancurve, int point_id, int rpm)
++static bool fancurve_set_rpm2(struct fancurve *fancurve, int point_id, int rpm)
+{
+ bool valid = point_id == 0 ? rpm == 0 : (rpm >= 0 && rpm <= 4500);
+
@@ -1238,7 +1958,8 @@ index 000000000..6edfeffcc
+
+// TODO: remove { ... } from single line if body
+
-+bool fancurve_set_accel(struct fancurve *fancurve, int point_id, int accel)
++static bool fancurve_set_accel(struct fancurve *fancurve, int point_id,
++ int accel)
+{
+ bool valid = accel >= 2 && accel <= 5;
+
@@ -1247,7 +1968,8 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_decel(struct fancurve *fancurve, int point_id, int decel)
++static bool fancurve_set_decel(struct fancurve *fancurve, int point_id,
++ int decel)
+{
+ bool valid = decel >= 2 && decel <= 5;
+
@@ -1256,8 +1978,8 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_cpu_temp_max(struct fancurve *fancurve, int point_id,
-+ int value)
++static bool fancurve_set_cpu_temp_max(struct fancurve *fancurve, int point_id,
++ int value)
+{
+ bool valid = fancurve_is_valid_max_temp(value);
+
@@ -1267,8 +1989,8 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_gpu_temp_max(struct fancurve *fancurve, int point_id,
-+ int value)
++static bool fancurve_set_gpu_temp_max(struct fancurve *fancurve, int point_id,
++ int value)
+{
+ bool valid = fancurve_is_valid_max_temp(value);
+
@@ -1277,8 +1999,8 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_ic_temp_max(struct fancurve *fancurve, int point_id,
-+ int value)
++static bool fancurve_set_ic_temp_max(struct fancurve *fancurve, int point_id,
++ int value)
+{
+ bool valid = fancurve_is_valid_max_temp(value);
+
@@ -1287,8 +2009,8 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_cpu_temp_min(struct fancurve *fancurve, int point_id,
-+ int value)
++static bool fancurve_set_cpu_temp_min(struct fancurve *fancurve, int point_id,
++ int value)
+{
+ bool valid = fancurve_is_valid_max_temp(value);
+
@@ -1297,59 +2019,28 @@ index 000000000..6edfeffcc
+ return valid;
+}
+
-+bool fancurve_set_gpu_temp_min(struct fancurve *fancurve, int point_id,
-+ int value)
++static bool fancurve_set_gpu_temp_min(struct fancurve *fancurve, int point_id,
++ int value)
+{
-+ bool valid = fancurve_is_valid_max_temp(value);
++ bool valid = fancurve_is_valid_min_temp(value);
+
+ if (valid)
+ fancurve->points[point_id].gpu_min_temp_celsius = value;
+ return valid;
+}
+
-+bool fancurve_set_ic_temp_min(struct fancurve *fancurve, int point_id,
-+ int value)
++static bool fancurve_set_ic_temp_min(struct fancurve *fancurve, int point_id,
++ int value)
+{
-+ bool valid = fancurve_is_valid_max_temp(value);
++ bool valid = fancurve_is_valid_min_temp(value);
+
+ if (valid)
+ fancurve->points[point_id].ic_min_temp_celsius = value;
+ return valid;
+}
+
-+//TODO: remove this if meaning of hyst in fan curve is clear!
-+//
-+//bool fancurve_set_cpu_deltahyst(struct fancurve* fancurve, int point_id, int hyst_value)
-+//{
-+// int max_temp = fancurve->points[point_id].cpu_max_temp_celsius;
-+// bool valid = hyst_value < max_temp;
-+// if(valid){
-+// fancurve->points[point_id].cpu_min_temp_celsius = max_temp - hyst_value;
-+// }
-+// return valid;
-+//}
-+
-+//bool fancurve_set_gpu_deltahyst(struct fancurve* fancurve, int point_id, int hyst_value)
-+//{
-+// int max_temp = fancurve->points[point_id].gpu_max_temp_celsius;
-+// bool valid = hyst_value < max_temp;
-+// if(valid){
-+// fancurve->points[point_id].gpu_min_temp_celsius = max_temp - hyst_value;
-+// }
-+// return valid;
-+//}
-+
-+//bool fancurve_set_ic_deltahyst(struct fancurve* fancurve, int point_id, int hyst_value)
-+//{
-+// int max_temp = fancurve->points[point_id].ic_max_temp_celsius;
-+// bool valid = hyst_value < max_temp;
-+// if(valid){
-+// fancurve->points[point_id].ic_min_temp_celsius = max_temp - hyst_value;
-+// }
-+// return valid;
-+//}
-+
-+bool fancurve_set_size(struct fancurve *fancurve, int size, bool init_values)
++static bool fancurve_set_size(struct fancurve *fancurve, int size,
++ bool init_values)
+{
+ bool valid = size >= 1 && size <= MAXFANCURVESIZE;
+
@@ -1358,7 +2049,6 @@ index 000000000..6edfeffcc
+ if (init_values && size < fancurve->size) {
+ // fancurve size is decreased, but last etnry alwasy needs 127 temperatures
+ // Note: size >=1
-+ // TODO: remove this comment
+ fancurve->points[size - 1].cpu_max_temp_celsius = 127;
+ fancurve->points[size - 1].ic_max_temp_celsius = 127;
+ fancurve->points[size - 1].gpu_max_temp_celsius = 127;
@@ -1374,6 +2064,612 @@ index 000000000..6edfeffcc
+ return true;
+}
+
++static ssize_t fancurve_print_seqfile(const struct fancurve *fancurve,
++ struct seq_file *s)
++{
++ int i;
++
++ seq_printf(
++ s,
++ "rpm1|rpm2|acceleration|deceleration|cpu_min_temp|cpu_max_temp|gpu_min_temp|gpu_max_temp|ic_min_temp|ic_max_temp\n");
++ for (i = 0; i < fancurve->size; ++i) {
++ const struct fancurve_point *point = &fancurve->points[i];
++
++ seq_printf(
++ s, "%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\n",
++ point->rpm1_raw * 100, point->rpm2_raw * 100,
++ point->accel, point->decel, point->cpu_min_temp_celsius,
++ point->cpu_max_temp_celsius,
++ point->gpu_min_temp_celsius,
++ point->gpu_max_temp_celsius, point->ic_min_temp_celsius,
++ point->ic_max_temp_celsius);
++ }
++ return 0;
++}
++
++struct light {
++ bool initialized;
++ struct led_classdev led;
++ unsigned int last_brightness;
++ u8 light_id;
++ unsigned int lower_limit;
++ unsigned int upper_limit;
++};
++
++/* ============================= */
++/* 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)
++struct legion_private {
++ struct platform_device *platform_device;
++ // TODO: remove or keep? init?
++ struct acpi_device *adev;
++
++ // Method to access ECRAM
++ struct ecram ecram;
++ // Configuration with registers an ECRAM access method
++ const struct model_config *conf;
++
++ // TODO: maybe refactor an keep only local to each function
++ // last known fan curve
++ struct fancurve fancurve;
++ // configured fan curve from user space
++ struct fancurve fancurve_configured;
++
++ // update lock, when partial values of fancurve are changed
++ struct mutex fancurve_mutex;
++
++ //interfaces
++ struct dentry *debugfs_dir;
++ struct device *hwmon_dev;
++ struct platform_profile_handler platform_profile_handler;
++
++ struct light kbd_bl;
++ struct light ylogo_light;
++ struct light iport_light;
++
++ // TODO: remove?
++ bool loaded;
++
++ // TODO: remove, only for reverse enginnering
++ struct ecram_memoryio ec_memoryio;
++};
++
++// shared between different drivers: WMI, platform and proteced by mutex
++static struct legion_private *legion_shared;
++static struct legion_private _priv;
++static DEFINE_MUTEX(legion_shared_mutex);
++
++static int legion_shared_init(struct legion_private *priv)
++{
++ int ret;
++
++ mutex_lock(&legion_shared_mutex);
++
++ if (!legion_shared) {
++ legion_shared = priv;
++ mutex_init(&legion_shared->fancurve_mutex);
++ ret = 0;
++ } else {
++ pr_warn("Found multiple platform devices\n");
++ ret = -EINVAL;
++ }
++
++ priv->loaded = true;
++ mutex_unlock(&legion_shared_mutex);
++
++ return ret;
++}
++
++static void legion_shared_exit(struct legion_private *priv)
++{
++ pr_info("Unloading legion shared\n");
++ mutex_lock(&legion_shared_mutex);
++
++ if (legion_shared == priv)
++ legion_shared = NULL;
++
++ mutex_unlock(&legion_shared_mutex);
++ pr_info("Unloading legion shared done\n");
++}
++
++static int get_simple_wmi_attribute(struct legion_private *priv,
++ const char *guid, u8 instance,
++ u32 method_id, bool invert,
++ unsigned long scale, unsigned long *value)
++{
++ unsigned long state = 0;
++ int err;
++
++ if (scale == 0) {
++ pr_info("Scale cannot be 0\n");
++ return -EINVAL;
++ }
++ err = wmi_exec_noarg_int(guid, instance, method_id, &state);
++ if (err)
++ return -EINVAL;
++
++ // TODO: remove later
++ pr_info("%swith raw value: %ld\n", __func__, state);
++
++ state = state * scale;
++
++ if (invert)
++ state = !state;
++ *value = state;
++ return 0;
++}
++
++static int get_simple_wmi_attribute_bool(struct legion_private *priv,
++ const char *guid, u8 instance,
++ u32 method_id, bool invert,
++ unsigned long scale, bool *value)
++{
++ unsigned long int_val = *value;
++ int err = get_simple_wmi_attribute(priv, guid, instance, method_id,
++ invert, scale, &int_val);
++ *value = int_val;
++ return err;
++}
++
++static int set_simple_wmi_attribute(struct legion_private *priv,
++ const char *guid, u8 instance,
++ u32 method_id, bool invert, int scale,
++ int state)
++{
++ int err;
++ u8 in_param;
++
++ if (scale == 0) {
++ pr_info("Scale cannot be 0\n");
++ return -EINVAL;
++ }
++
++ if (invert)
++ state = !state;
++
++ in_param = state / scale;
++
++ err = wmi_exec_arg(guid, instance, method_id, &in_param,
++ sizeof(in_param));
++ return err;
++}
++
++/* ============================= */
++/* Sensor values reading/writing */
++/* ============================= */
++
++static int ec_read_sensor_values(struct ecram *ecram,
++ const struct model_config *model,
++ struct sensor_values *values)
++{
++ values->fan1_target_rpm =
++ 100 * ecram_read(ecram, model->registers->EXT_FAN1_TARGET_RPM);
++ values->fan2_target_rpm =
++ 100 * ecram_read(ecram, model->registers->EXT_FAN2_TARGET_RPM);
++
++ values->fan1_rpm =
++ ecram_read(ecram, model->registers->EXT_FAN1_RPM_LSB) +
++ (((int)ecram_read(ecram, model->registers->EXT_FAN1_RPM_MSB))
++ << 8);
++ values->fan2_rpm =
++ ecram_read(ecram, model->registers->EXT_FAN2_RPM_LSB) +
++ (((int)ecram_read(ecram, model->registers->EXT_FAN2_RPM_MSB))
++ << 8);
++
++ values->cpu_temp_celsius =
++ ecram_read(ecram, model->registers->EXT_CPU_TEMP_INPUT);
++ values->gpu_temp_celsius =
++ ecram_read(ecram, model->registers->EXT_GPU_TEMP_INPUT);
++ values->ic_temp_celsius =
++ ecram_read(ecram, model->registers->EXT_IC_TEMP_INPUT);
++
++ values->cpu_temp_celsius = ecram_read(ecram, 0xC5E6);
++ values->gpu_temp_celsius = ecram_read(ecram, 0xC5E7);
++ values->ic_temp_celsius = ecram_read(ecram, 0xC5E8);
++
++ return 0;
++}
++
++static ssize_t ec_read_temperature(struct ecram *ecram,
++ const struct model_config *model,
++ int sensor_id, int *temperature)
++{
++ int err = 0;
++ unsigned long res;
++
++ if (sensor_id == 0) {
++ res = ecram_read(ecram, 0xC5E6);
++ } else if (sensor_id == 1) {
++ res = ecram_read(ecram, 0xC5E7);
++ } else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++ if (!err)
++ *temperature = res;
++ return err;
++}
++
++static ssize_t ec_read_fanspeed(struct ecram *ecram,
++ const struct model_config *model, int fan_id,
++ int *fanspeed_rpm)
++{
++ int err = 0;
++ unsigned long res;
++
++ if (fan_id == 0) {
++ res = ecram_read(ecram, model->registers->EXT_FAN1_RPM_LSB) +
++ (((int)ecram_read(ecram,
++ model->registers->EXT_FAN1_RPM_MSB))
++ << 8);
++ } else if (fan_id == 1) {
++ res = ecram_read(ecram, model->registers->EXT_FAN2_RPM_LSB) +
++ (((int)ecram_read(ecram,
++ model->registers->EXT_FAN2_RPM_MSB))
++ << 8);
++ } else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++ if (!err)
++ *fanspeed_rpm = res;
++ return err;
++}
++
++// '\_SB.PCI0.LPC0.EC0.FANS
++#define ACPI_PATH_FAN_SPEED1 "FANS"
++// '\_SB.PCI0.LPC0.EC0.FA2S
++#define ACPI_PATH_FAN_SPEED2 "FA2S"
++
++static ssize_t acpi_read_fanspeed(struct legion_private *priv, int fan_id,
++ int *value)
++{
++ int err;
++ unsigned long acpi_value;
++ const char *acpi_path;
++
++ if (fan_id == 0) {
++ acpi_path = ACPI_PATH_FAN_SPEED1;
++ } else if (fan_id == 1) {
++ acpi_path = ACPI_PATH_FAN_SPEED2;
++ } else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++ err = eval_int(priv->adev->handle, acpi_path, &acpi_value);
++ if (!err)
++ *value = (int)acpi_value * 100;
++ return err;
++}
++
++// '\_SB.PCI0.LPC0.EC0.CPUT
++#define ACPI_PATH_CPU_TEMP "CPUT"
++// '\_SB.PCI0.LPC0.EC0.GPUT
++#define ACPI_PATH_GPU_TEMP "GPUT"
++
++static ssize_t acpi_read_temperature(struct legion_private *priv, int fan_id,
++ int *value)
++{
++ int err;
++ unsigned long acpi_value;
++ const char *acpi_path;
++
++ if (fan_id == 0) {
++ acpi_path = ACPI_PATH_CPU_TEMP;
++ } else if (fan_id == 1) {
++ acpi_path = ACPI_PATH_GPU_TEMP;
++ } else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++ err = eval_int(priv->adev->handle, acpi_path, &acpi_value);
++ if (!err)
++ *value = (int)acpi_value;
++ return err;
++}
++
++// fan_id: 0 or 1
++static ssize_t wmi_read_fanspeed(int fan_id, int *fanspeed_rpm)
++{
++ int err;
++ unsigned long res;
++ struct acpi_buffer params;
++
++ params.length = 1;
++ params.pointer = &fan_id;
++
++ err = wmi_exec_int(WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_GETCURRENTFANSPEED, &params, &res);
++
++ if (!err)
++ *fanspeed_rpm = res;
++ return err;
++}
++
++//sensor_id: cpu = 0, gpu = 1
++static ssize_t wmi_read_temperature(int sensor_id, int *temperature)
++{
++ int err;
++ unsigned long res;
++ struct acpi_buffer params;
++
++ if (sensor_id == 0)
++ sensor_id = 0x03;
++ else if (sensor_id == 1)
++ sensor_id = 0x04;
++ else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++
++ params.length = 1;
++ params.pointer = &sensor_id;
++
++ err = wmi_exec_int(WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_GETCURRENTSENSORTEMPERATURE,
++ &params, &res);
++
++ if (!err)
++ *temperature = res;
++ return err;
++}
++
++// fan_id: 0 or 1
++static ssize_t wmi_read_fanspeed_gz(int fan_id, int *fanspeed_rpm)
++{
++ int err;
++ u32 method_id;
++ unsigned long res;
++
++ if (fan_id == 0)
++ method_id = WMI_METHOD_ID_GETFAN1SPEED;
++ else if (fan_id == 1)
++ method_id = WMI_METHOD_ID_GETFAN2SPEED;
++ else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0, method_id, &res);
++
++ if (!err)
++ *fanspeed_rpm = res;
++ return err;
++}
++
++//sensor_id: cpu = 0, gpu = 1
++static ssize_t wmi_read_temperature_gz(int sensor_id, int *temperature)
++{
++ int err;
++ u32 method_id;
++ unsigned long res;
++
++ if (sensor_id == 0)
++ method_id = WMI_METHOD_ID_GETCPUTEMP;
++ else if (sensor_id == 1)
++ method_id = WMI_METHOD_ID_GETGPUTEMP;
++ else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++
++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0, method_id, &res);
++
++ if (!err)
++ *temperature = res;
++ return err;
++}
++
++// fan_id: 0 or 1
++static ssize_t wmi_read_fanspeed_other(int fan_id, int *fanspeed_rpm)
++{
++ int err;
++ enum OtherMethodFeature featured_id;
++ int res;
++
++ if (fan_id == 0)
++ featured_id = OtherMethodFeature_FAN_SPEED_1;
++ else if (fan_id == 1)
++ featured_id = OtherMethodFeature_FAN_SPEED_2;
++ else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++
++ err = wmi_other_method_get_value(featured_id, &res);
++
++ if (!err)
++ *fanspeed_rpm = res;
++ return err;
++}
++
++//sensor_id: cpu = 0, gpu = 1
++static ssize_t wmi_read_temperature_other(int sensor_id, int *temperature)
++{
++ int err;
++ enum OtherMethodFeature featured_id;
++ int res;
++
++ if (sensor_id == 0)
++ featured_id = OtherMethodFeature_TEMP_CPU;
++ else if (sensor_id == 1)
++ featured_id = OtherMethodFeature_TEMP_GPU;
++ else {
++ // TODO: use all correct error codes
++ return -EEXIST;
++ }
++
++ err = wmi_other_method_get_value(featured_id, &res);
++ if (!err)
++ *temperature = res;
++ return err;
++}
++
++static ssize_t read_fanspeed(struct legion_private *priv, int fan_id,
++ int *speed_rpm)
++{
++ // TODO: use enums or function pointers?
++ switch (priv->conf->access_method_fanspeed) {
++ case ACCESS_METHOD_EC:
++ return ec_read_fanspeed(&priv->ecram, priv->conf, fan_id,
++ speed_rpm);
++ case ACCESS_METHOD_ACPI:
++ return acpi_read_fanspeed(priv, fan_id, speed_rpm);
++ case ACCESS_METHOD_WMI:
++ return wmi_read_fanspeed_gz(fan_id, speed_rpm);
++ case ACCESS_METHOD_WMI2:
++ return wmi_read_fanspeed(fan_id, speed_rpm);
++ case ACCESS_METHOD_WMI3:
++ return wmi_read_fanspeed_other(fan_id, speed_rpm);
++ default:
++ pr_info("No access method for fanspeed: %d\n",
++ priv->conf->access_method_fanspeed);
++ return -EINVAL;
++ }
++}
++
++static ssize_t read_temperature(struct legion_private *priv, int sensor_id,
++ int *temperature)
++{
++ // TODO: use enums or function pointers?
++ switch (priv->conf->access_method_temperature) {
++ case ACCESS_METHOD_EC:
++ return ec_read_temperature(&priv->ecram, priv->conf, sensor_id,
++ temperature);
++ case ACCESS_METHOD_ACPI:
++ return acpi_read_temperature(priv, sensor_id, temperature);
++ case ACCESS_METHOD_WMI:
++ return wmi_read_temperature_gz(sensor_id, temperature);
++ case ACCESS_METHOD_WMI2:
++ return wmi_read_temperature(sensor_id, temperature);
++ case ACCESS_METHOD_WMI3:
++ return wmi_read_temperature_other(sensor_id, temperature);
++ default:
++ pr_info("No access method for temperature: %d\n",
++ priv->conf->access_method_temperature);
++ return -EINVAL;
++ }
++}
++
++/* ============================= */
++/* Fancurve reading/writing */
++/* ============================= */
++
++/* Fancurve from WMI
++ * This allows changing fewer parameters.
++ * It is only available on newer models.
++ */
++
++struct WMIFanTable {
++ u8 FSTM; //FSMD
++ u8 FSID;
++ u32 FSTL; //FSST
++ u16 FSS0;
++ u16 FSS1;
++ u16 FSS2;
++ u16 FSS3;
++ u16 FSS4;
++ u16 FSS5;
++ u16 FSS6;
++ u16 FSS7;
++ u16 FSS8;
++ u16 FSS9;
++} __packed;
++
++struct WMIFanTableRead {
++ u32 FSFL;
++ u32 FSS0;
++ u32 FSS1;
++ u32 FSS2;
++ u32 FSS3;
++ u32 FSS4;
++ u32 FSS5;
++ u32 FSS6;
++ u32 FSS7;
++ u32 FSS8;
++ u32 FSS9;
++ u32 FSSA;
++} __packed;
++
++static ssize_t wmi_read_fancurve_custom(const struct model_config *model,
++ struct fancurve *fancurve)
++{
++ u8 buffer[88];
++ int err;
++
++ // The output buffer from the ACPI call is 88 bytes and larger
++ // than the returned object
++ pr_info("Size of object: %lu\n", sizeof(struct WMIFanTableRead));
++ err = wmi_exec_noarg_ints(WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_GET_TABLE, buffer,
++ sizeof(buffer));
++ print_hex_dump(KERN_INFO, "legion_laptop fan table wmi buffer",
++ DUMP_PREFIX_ADDRESS, 16, 1, buffer, sizeof(buffer),
++ true);
++ if (!err) {
++ struct WMIFanTableRead *fantable =
++ (struct WMIFanTableRead *)&buffer[0];
++ fancurve->current_point_i = 0;
++ fancurve->size = 10;
++ fancurve->points[0].rpm1_raw = fantable->FSS0;
++ fancurve->points[1].rpm1_raw = fantable->FSS1;
++ fancurve->points[2].rpm1_raw = fantable->FSS2;
++ fancurve->points[3].rpm1_raw = fantable->FSS3;
++ fancurve->points[4].rpm1_raw = fantable->FSS4;
++ fancurve->points[5].rpm1_raw = fantable->FSS5;
++ fancurve->points[6].rpm1_raw = fantable->FSS6;
++ fancurve->points[7].rpm1_raw = fantable->FSS7;
++ fancurve->points[8].rpm1_raw = fantable->FSS8;
++ fancurve->points[9].rpm1_raw = fantable->FSS9;
++ //fancurve->points[10].rpm1_raw = fantable->FSSA;
++ }
++ return err;
++}
++
++static ssize_t wmi_write_fancurve_custom(const struct model_config *model,
++ const struct fancurve *fancurve)
++{
++ u8 buffer[0x20];
++ int err;
++
++ // The buffer is read like this in ACPI firmware
++ //
++ // CreateByteField (Arg2, Zero, FSTM)
++ // CreateByteField (Arg2, One, FSID)
++ // CreateDWordField (Arg2, 0x02, FSTL)
++ // CreateByteField (Arg2, 0x06, FSS0)
++ // CreateByteField (Arg2, 0x08, FSS1)
++ // CreateByteField (Arg2, 0x0A, FSS2)
++ // CreateByteField (Arg2, 0x0C, FSS3)
++ // CreateByteField (Arg2, 0x0E, FSS4)
++ // CreateByteField (Arg2, 0x10, FSS5)
++ // CreateByteField (Arg2, 0x12, FSS6)
++ // CreateByteField (Arg2, 0x14, FSS7)
++ // CreateByteField (Arg2, 0x16, FSS8)
++ // CreateByteField (Arg2, 0x18, FSS9)
++
++ memset(buffer, 0, sizeof(buffer));
++ buffer[0x06] = fancurve->points[0].rpm1_raw;
++ buffer[0x08] = fancurve->points[1].rpm1_raw;
++ buffer[0x0A] = fancurve->points[2].rpm1_raw;
++ buffer[0x0C] = fancurve->points[3].rpm1_raw;
++ buffer[0x0E] = fancurve->points[4].rpm1_raw;
++ buffer[0x10] = fancurve->points[5].rpm1_raw;
++ buffer[0x12] = fancurve->points[6].rpm1_raw;
++ buffer[0x14] = fancurve->points[7].rpm1_raw;
++ buffer[0x16] = fancurve->points[8].rpm1_raw;
++ buffer[0x18] = fancurve->points[9].rpm1_raw;
++
++ print_hex_dump(KERN_INFO, "legion_laptop fan table wmi write buffer",
++ DUMP_PREFIX_ADDRESS, 16, 1, buffer, sizeof(buffer),
++ true);
++ err = wmi_exec_arg(WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_SET_TABLE, buffer, sizeof(buffer));
++ return err;
++}
++
+/* Read the fan curve from the EC.
+ *
+ * In newer models (>=2022) there is an ACPI/WMI to read fan curve as
@@ -1383,8 +2679,9 @@ index 000000000..6edfeffcc
+ * It reads all points from EC memory, even if stored fancurve is smaller, so
+ * it can contain 0 entries.
+ */
-+static int read_fancurve(struct ecram *ecram, const struct model_config *model,
-+ struct fancurve *fancurve)
++static int ec_read_fancurve_legion(struct ecram *ecram,
++ const struct model_config *model,
++ struct fancurve *fancurve)
+{
+ size_t i = 0;
+
@@ -1392,44 +2689,51 @@ index 000000000..6edfeffcc
+ struct fancurve_point *point = &fancurve->points[i];
+
+ point->rpm1_raw =
-+ ecram_read(ecram, model->registers->FAN1_BASE + i);
++ ecram_read(ecram, model->registers->EXT_FAN1_BASE + i);
+ point->rpm2_raw =
-+ ecram_read(ecram, model->registers->FAN2_BASE + i);
++ ecram_read(ecram, model->registers->EXT_FAN2_BASE + i);
+
-+ point->accel =
-+ ecram_read(ecram, model->registers->FAN_ACC_BASE + i);
-+ point->decel =
-+ ecram_read(ecram, model->registers->FAN_DEC_BASE + i);
++ point->accel = ecram_read(
++ ecram, model->registers->EXT_FAN_ACC_BASE + i);
++ point->decel = ecram_read(
++ ecram, model->registers->EXT_FAN_DEC_BASE + i);
+ point->cpu_max_temp_celsius =
-+ ecram_read(ecram, model->registers->CPU_TEMP + i);
-+ point->cpu_min_temp_celsius =
-+ ecram_read(ecram, model->registers->CPU_TEMP_HYST + i);
++ ecram_read(ecram, model->registers->EXT_CPU_TEMP + i);
++ point->cpu_min_temp_celsius = ecram_read(
++ ecram, model->registers->EXT_CPU_TEMP_HYST + i);
+ point->gpu_max_temp_celsius =
-+ ecram_read(ecram, model->registers->GPU_TEMP + i);
-+ point->gpu_min_temp_celsius =
-+ ecram_read(ecram, model->registers->GPU_TEMP_HYST + i);
++ ecram_read(ecram, model->registers->EXT_GPU_TEMP + i);
++ point->gpu_min_temp_celsius = ecram_read(
++ ecram, model->registers->EXT_GPU_TEMP_HYST + i);
+ point->ic_max_temp_celsius =
-+ ecram_read(ecram, model->registers->VRM_TEMP + i);
-+ point->ic_min_temp_celsius =
-+ ecram_read(ecram, model->registers->VRM_TEMP_HYST + i);
++ ecram_read(ecram, model->registers->EXT_VRM_TEMP + i);
++ point->ic_min_temp_celsius = ecram_read(
++ ecram, model->registers->EXT_VRM_TEMP_HYST + i);
+ }
+
+ // Do not trust that hardware; It might suddendly report
+ // a larger size, so clamp it.
-+ fancurve->size = ecram_read(ecram, model->registers->FAN_POINTS_SIZE);
++ fancurve->size =
++ ecram_read(ecram, model->registers->EXT_FAN_POINTS_SIZE);
+ fancurve->size =
+ min(fancurve->size, (typeof(fancurve->size))(MAXFANCURVESIZE));
+ fancurve->current_point_i =
-+ ecram_read(ecram, model->registers->FAN_CUR_POINT);
++ ecram_read(ecram, model->registers->EXT_FAN_CUR_POINT);
+ fancurve->current_point_i =
+ min(fancurve->current_point_i, fancurve->size);
+ return 0;
+}
+
-+static int write_fancurve(struct ecram *ecram, const struct model_config *model,
-+ const struct fancurve *fancurve, bool write_size)
++static int ec_write_fancurve_legion(struct ecram *ecram,
++ const struct model_config *model,
++ const struct fancurve *fancurve,
++ bool write_size)
+{
+ 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);
@@ -1440,39 +2744,39 @@ index 000000000..6edfeffcc
+ i < fancurve->size ? &fancurve->points[i] :
+ &fancurve_point_zero;
+
-+ ecram_write(ecram, model->registers->FAN1_BASE + i,
++ ecram_write(ecram, model->registers->EXT_FAN1_BASE + i,
+ point->rpm1_raw);
-+ ecram_write(ecram, model->registers->FAN2_BASE + i,
++ ecram_write(ecram, model->registers->EXT_FAN2_BASE + i,
+ point->rpm2_raw);
+
-+ ecram_write(ecram, model->registers->FAN_ACC_BASE + i,
++ ecram_write(ecram, model->registers->EXT_FAN_ACC_BASE + i,
+ point->accel);
-+ ecram_write(ecram, model->registers->FAN_DEC_BASE + i,
++ ecram_write(ecram, model->registers->EXT_FAN_DEC_BASE + i,
+ point->decel);
+
-+ ecram_write(ecram, model->registers->CPU_TEMP + i,
++ ecram_write(ecram, model->registers->EXT_CPU_TEMP + i,
+ point->cpu_max_temp_celsius);
-+ ecram_write(ecram, model->registers->CPU_TEMP_HYST + i,
++ ecram_write(ecram, model->registers->EXT_CPU_TEMP_HYST + i,
+ point->cpu_min_temp_celsius);
-+ ecram_write(ecram, model->registers->GPU_TEMP + i,
++ ecram_write(ecram, model->registers->EXT_GPU_TEMP + i,
+ point->gpu_max_temp_celsius);
-+ ecram_write(ecram, model->registers->GPU_TEMP_HYST + i,
++ ecram_write(ecram, model->registers->EXT_GPU_TEMP_HYST + i,
+ point->gpu_min_temp_celsius);
-+ ecram_write(ecram, model->registers->VRM_TEMP + i,
++ ecram_write(ecram, model->registers->EXT_VRM_TEMP + i,
+ point->ic_max_temp_celsius);
-+ ecram_write(ecram, model->registers->VRM_TEMP_HYST + i,
++ ecram_write(ecram, model->registers->EXT_VRM_TEMP_HYST + i,
+ point->ic_min_temp_celsius);
+ }
+
+ if (write_size) {
-+ ecram_write(ecram, model->registers->FAN_POINTS_SIZE,
++ ecram_write(ecram, model->registers->EXT_FAN_POINTS_SIZE,
+ fancurve->size);
+ }
+
+ // Reset current fan level to 0, so algorithm in EC
+ // selects fan curve point again and resetting hysterisis
+ // effects
-+ ecram_write(ecram, model->registers->FAN_CUR_POINT, 0);
++ ecram_write(ecram, model->registers->EXT_FAN_CUR_POINT, 0);
+
+ // Reset internal fan levels
+ ecram_write(ecram, 0xC634, 0); // CPU
@@ -1482,132 +2786,604 @@ index 000000000..6edfeffcc
+ return 0;
+}
+
-+//TODO: still needed?
-+//static ssize_t fancurve_print(const struct fancurve* fancurve, char *buf)
-+//{
-+// ssize_t output_char_count = 0;
-+// size_t i;
-+// for (i = 0; i < fancurve->size; ++i) {
-+// const struct fancurve_point * point = &fancurve->points[i];
-+// int res = sprintf(buf, "%d %d %d %d %d %d %d %d %d %d\n",
-+// point->rpm1_raw*100, point->rpm2_raw*100,
-+// point->accel, point->decel,
-+// point->cpu_min_temp_celsius,
-+// point->cpu_max_temp_celsius,
-+// point->gpu_min_temp_celsius,
-+// point->gpu_max_temp_celsius,
-+// point->ic_min_temp_celsius,
-+// point->ic_max_temp_celsius);
-+// if (res > 0) {
-+// output_char_count += res;
-+// } else {
-+// pr_debug(
-+// "Error writing to buffer for output of fanCurveStructure\n");
-+// return 0;
-+// }
-+// // go forward in buffer to append next print output
-+// buf += res;
-+// }
-+// return output_char_count;
-+//}
++#define FANCURVESIZE_IDEAPDAD 8
+
-+static ssize_t fancurve_print_seqfile(const struct fancurve *fancurve,
-+ struct seq_file *s)
++static int ec_read_fancurve_ideapad(struct ecram *ecram,
++ const struct model_config *model,
++ struct fancurve *fancurve)
+{
-+ int i;
++ size_t i = 0;
+
-+ seq_printf(
-+ s,
-+ "rpm1|rpm2|acceleration|deceleration|cpu_min_temp|cpu_max_temp|gpu_min_temp|gpu_max_temp|ic_min_temp|ic_max_temp\n");
-+ for (i = 0; i < fancurve->size; ++i) {
++ for (i = 0; i < FANCURVESIZE_IDEAPDAD; ++i) {
++ struct fancurve_point *point = &fancurve->points[i];
++
++ point->rpm1_raw =
++ ecram_read(ecram, model->registers->EXT_FAN1_BASE + i);
++ point->rpm2_raw =
++ ecram_read(ecram, model->registers->EXT_FAN2_BASE + i);
++
++ point->accel = 0;
++ point->decel = 0;
++ point->cpu_max_temp_celsius =
++ ecram_read(ecram, model->registers->EXT_CPU_TEMP + i);
++ point->cpu_min_temp_celsius = ecram_read(
++ ecram, model->registers->EXT_CPU_TEMP_HYST + i);
++ point->gpu_max_temp_celsius =
++ ecram_read(ecram, model->registers->EXT_GPU_TEMP + i);
++ point->gpu_min_temp_celsius = ecram_read(
++ ecram, model->registers->EXT_GPU_TEMP_HYST + i);
++ point->ic_max_temp_celsius = 0;
++ point->ic_min_temp_celsius = 0;
++ }
++
++ // Do not trust that hardware; It might suddendly report
++ // a larger size, so clamp it.
++ fancurve->size = FANCURVESIZE_IDEAPDAD;
++ 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_ideapad(struct ecram *ecram,
++ const struct model_config *model,
++ const struct fancurve *fancurve)
++{
++ size_t i;
++ int valr1;
++ int valr2;
++
++ // add this later: maybe other addresses needed
++ // therefore, fan curve might not be effective immediatley but
++ // only after temp change
++ // Reset fan update counters (try to avoid any race conditions)
++ ecram_write(ecram, 0xC5FE, 0);
++ ecram_write(ecram, 0xC5FF, 0);
++ for (i = 0; i < FANCURVESIZE_IDEAPDAD; ++i) {
+ const struct fancurve_point *point = &fancurve->points[i];
+
-+ seq_printf(
-+ s, "%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\n",
-+ point->rpm1_raw * 100, point->rpm2_raw * 100,
-+ point->accel, point->decel, point->cpu_min_temp_celsius,
-+ point->cpu_max_temp_celsius,
-+ point->gpu_min_temp_celsius,
-+ point->gpu_max_temp_celsius, point->ic_min_temp_celsius,
-+ point->ic_max_temp_celsius);
++ ecram_write(ecram, model->registers->EXT_FAN1_BASE + i,
++ point->rpm1_raw);
++ valr1 = ecram_read(ecram, model->registers->EXT_FAN1_BASE + i);
++ ecram_write(ecram, model->registers->EXT_FAN2_BASE + i,
++ point->rpm2_raw);
++ valr2 = ecram_read(ecram, model->registers->EXT_FAN2_BASE + i);
++ 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,
++ point->cpu_max_temp_celsius);
++ ecram_write(ecram, model->registers->EXT_CPU_TEMP + 8 + i,
++ point->cpu_max_temp_celsius);
++ // write to memory and repeat 8 bytes later again
++ ecram_write(ecram, model->registers->EXT_CPU_TEMP_HYST + i,
++ point->cpu_min_temp_celsius);
++ ecram_write(ecram, model->registers->EXT_CPU_TEMP_HYST + 8 + i,
++ point->cpu_min_temp_celsius);
++ // write to memory and repeat 8 bytes later again
++ ecram_write(ecram, model->registers->EXT_GPU_TEMP + i,
++ point->gpu_max_temp_celsius);
++ ecram_write(ecram, model->registers->EXT_GPU_TEMP + 8 + i,
++ point->gpu_max_temp_celsius);
++ // write to memory and repeat 8 bytes later again
++ ecram_write(ecram, model->registers->EXT_GPU_TEMP_HYST + i,
++ point->gpu_min_temp_celsius);
++ ecram_write(ecram, model->registers->EXT_GPU_TEMP_HYST + 8 + i,
++ point->gpu_min_temp_celsius);
+ }
++
++ // add this later: maybe other addresses needed
++ // therefore, fan curve might not be effective immediatley but
++ // only after temp change
++ // // Reset current fan level to 0, so algorithm in EC
++ // // selects fan curve point again and resetting hysterisis
++ // // effects
++ // ecram_write(ecram, model->registers->EXT_FAN_CUR_POINT, 0);
++
++ // // Reset internal fan levels
++ // ecram_write(ecram, 0xC634, 0); // CPU
++ // ecram_write(ecram, 0xC635, 0); // GPU
++ // ecram_write(ecram, 0xC636, 0); // SENSOR
++
+ return 0;
+}
+
-+/* ============================= */
-+/* Global and shared data between */
-+/* all calls to this module */
-+/* ============================ */
-+// Implemented like ideapad-laptop.c but currenlty still
-+// wihtout dynamic memory allocation (instaed global _priv)
++static int read_fancurve(struct legion_private *priv, struct fancurve *fancurve)
++{
++ // TODO: use enums or function pointers?
++ switch (priv->conf->access_method_fancurve) {
++ case ACCESS_METHOD_EC:
++ return ec_read_fancurve_legion(&priv->ecram, priv->conf,
++ fancurve);
++ case ACCESS_METHOD_EC2:
++ return ec_read_fancurve_ideapad(&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",
++ priv->conf->access_method_fancurve);
++ return -EINVAL;
++ }
++}
+
-+struct legion_private {
-+ struct platform_device *platform_device;
-+ // TODO: remove or keep? init?
-+ // struct acpi_device *adev;
++static int write_fancurve(struct legion_private *priv,
++ const struct fancurve *fancurve, bool write_size)
++{
++ // TODO: use enums or function pointers?
++ switch (priv->conf->access_method_fancurve) {
++ case ACCESS_METHOD_EC:
++ return ec_write_fancurve_legion(&priv->ecram, priv->conf,
++ fancurve, write_size);
++ case ACCESS_METHOD_EC2:
++ return ec_write_fancurve_ideapad(&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",
++ priv->conf->access_method_fancurve);
++ return -EINVAL;
++ }
++}
+
-+ // Method to access ECRAM
-+ struct ecram ecram;
-+ // Configuration with registers an ECRAM access method
-+ const struct model_config *conf;
++#define MINIFANCUVE_ON_COOL_ON 0x04
++#define MINIFANCUVE_ON_COOL_OFF 0xA0
+
-+ // TODO: maybe refactor an keep only local to each function
-+ // last known fan curve
-+ struct fancurve fancurve;
-+ // configured fan curve from user space
-+ struct fancurve fancurve_configured;
++static int ec_read_minifancurve(struct ecram *ecram,
++ const struct model_config *model, bool *state)
++{
++ int value =
++ ecram_read(ecram, model->registers->EXT_MINIFANCURVE_ON_COOL);
+
-+ // update lock, when partial values of fancurve are changed
-+ struct mutex fancurve_mutex;
++ switch (value) {
++ case MINIFANCUVE_ON_COOL_ON:
++ *state = true;
++ break;
++ case MINIFANCUVE_ON_COOL_OFF:
++ *state = false;
++ break;
++ default:
++ pr_info("Unexpected value in MINIFANCURVE register:%d\n",
++ value);
++ return -1;
++ }
++ return 0;
++}
+
-+ //interfaces
-+ struct dentry *debugfs_dir;
-+ struct device *hwmon_dev;
-+ struct platform_profile_handler platform_profile_handler;
++static ssize_t ec_write_minifancurve(struct ecram *ecram,
++ const struct model_config *model,
++ bool state)
++{
++ u8 val = state ? MINIFANCUVE_ON_COOL_ON : MINIFANCUVE_ON_COOL_OFF;
+
-+ // EC enables mini fancurve if long enough on low temp
-+ bool has_minifancurve_on_cool;
++ ecram_write(ecram, model->registers->EXT_MINIFANCURVE_ON_COOL, val);
++ return 0;
++}
+
-+ // TODO: remove?
-+ bool loaded;
++#define EC_LOCKFANCONTROLLER_ON 8
++#define EC_LOCKFANCONTROLLER_OFF 0
++
++static ssize_t ec_write_lockfancontroller(struct ecram *ecram,
++ const struct model_config *model,
++ bool state)
++{
++ u8 val = state ? EC_LOCKFANCONTROLLER_ON : EC_LOCKFANCONTROLLER_OFF;
++
++ ecram_write(ecram, model->registers->EXT_LOCKFANCONTROLLER, val);
++ return 0;
++}
++
++static int ec_read_lockfancontroller(struct ecram *ecram,
++ const struct model_config *model,
++ bool *state)
++{
++ int value = ecram_read(ecram, model->registers->EXT_LOCKFANCONTROLLER);
++
++ switch (value) {
++ case EC_LOCKFANCONTROLLER_ON:
++ *state = true;
++ break;
++ case EC_LOCKFANCONTROLLER_OFF:
++ *state = false;
++ break;
++ default:
++ pr_info("Unexpected value in lockfanspeed register:%d\n",
++ value);
++ return -1;
++ }
++ return 0;
++}
++
++#define EC_FANFULLSPEED_ON 0x40
++#define EC_FANFULLSPEED_OFF 0x00
++
++static int ec_read_fanfullspeed(struct ecram *ecram,
++ const struct model_config *model, bool *state)
++{
++ int value = ecram_read(ecram, model->registers->EXT_MAXIMUMFANSPEED);
++
++ switch (value) {
++ case EC_FANFULLSPEED_ON:
++ *state = true;
++ break;
++ case EC_FANFULLSPEED_OFF:
++ *state = false;
++ break;
++ default:
++ pr_info("Unexpected value in maximumfanspeed register:%d\n",
++ value);
++ return -1;
++ }
++ return 0;
++}
++
++static ssize_t ec_write_fanfullspeed(struct ecram *ecram,
++ const struct model_config *model,
++ bool state)
++{
++ u8 val = state ? EC_FANFULLSPEED_ON : EC_FANFULLSPEED_OFF;
++
++ ecram_write(ecram, model->registers->EXT_MAXIMUMFANSPEED, val);
++ return 0;
++}
++
++static ssize_t wmi_read_fanfullspeed(struct legion_private *priv, bool *state)
++{
++ return get_simple_wmi_attribute_bool(priv, WMI_GUID_LENOVO_FAN_METHOD,
++ 0, WMI_METHOD_ID_FAN_GET_FULLSPEED,
++ false, 1, state);
++}
++
++static ssize_t wmi_write_fanfullspeed(struct legion_private *priv, bool state)
++{
++ return set_simple_wmi_attribute(priv, WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_SET_FULLSPEED, false,
++ 1, state);
++}
++
++static ssize_t read_fanfullspeed(struct legion_private *priv, bool *state)
++{
++ // TODO: use enums or function pointers?
++ switch (priv->conf->access_method_fanfullspeed) {
++ case ACCESS_METHOD_EC:
++ return ec_read_fanfullspeed(&priv->ecram, priv->conf, state);
++ case ACCESS_METHOD_WMI:
++ return wmi_read_fanfullspeed(priv, state);
++ default:
++ pr_info("No access method for fan full speed: %d\n",
++ priv->conf->access_method_fanfullspeed);
++ return -EINVAL;
++ }
++}
++
++static ssize_t write_fanfullspeed(struct legion_private *priv, bool state)
++{
++ ssize_t res;
++
++ switch (priv->conf->access_method_fanfullspeed) {
++ case ACCESS_METHOD_EC:
++ res = ec_write_fanfullspeed(&priv->ecram, priv->conf, state);
++ return res;
++ case ACCESS_METHOD_WMI:
++ return wmi_write_fanfullspeed(priv, state);
++ default:
++ pr_info("No access method for fan full speed:%d\n",
++ priv->conf->access_method_fanfullspeed);
++ return -EINVAL;
++ }
++}
++
++/* ============================= */
++/* Power mode reading/writing */
++/* ============================= */
++
++enum legion_ec_powermode {
++ LEGION_EC_POWERMODE_QUIET = 2,
++ LEGION_EC_POWERMODE_BALANCED = 0,
++ LEGION_EC_POWERMODE_PERFORMANCE = 1,
++ LEGION_EC_POWERMODE_CUSTOM = 3
+};
+
-+// shared between different drivers: WMI, platform and proteced by mutex
-+static struct legion_private *legion_shared;
-+static struct legion_private _priv;
-+static DEFINE_MUTEX(legion_shared_mutex);
++enum legion_wmi_powermode {
++ LEGION_WMI_POWERMODE_QUIET = 1,
++ LEGION_WMI_POWERMODE_BALANCED = 2,
++ LEGION_WMI_POWERMODE_PERFORMANCE = 3,
++ LEGION_WMI_POWERMODE_CUSTOM = 255
++};
+
-+static int legion_shared_init(struct legion_private *priv)
++enum legion_wmi_powermode ec_to_wmi_powermode(int ec_mode)
+{
-+ int ret;
++ switch (ec_mode) {
++ case LEGION_EC_POWERMODE_QUIET:
++ return LEGION_WMI_POWERMODE_QUIET;
++ case LEGION_EC_POWERMODE_BALANCED:
++ return LEGION_WMI_POWERMODE_BALANCED;
++ case LEGION_EC_POWERMODE_PERFORMANCE:
++ return LEGION_WMI_POWERMODE_PERFORMANCE;
++ case LEGION_EC_POWERMODE_CUSTOM:
++ return LEGION_WMI_POWERMODE_CUSTOM;
++ default:
++ return LEGION_WMI_POWERMODE_BALANCED;
++ }
++}
+
-+ mutex_lock(&legion_shared_mutex);
++enum legion_ec_powermode wmi_to_ec_powermode(enum legion_wmi_powermode wmi_mode)
++{
++ switch (wmi_mode) {
++ case LEGION_WMI_POWERMODE_QUIET:
++ return LEGION_EC_POWERMODE_QUIET;
++ case LEGION_WMI_POWERMODE_BALANCED:
++ return LEGION_EC_POWERMODE_BALANCED;
++ case LEGION_WMI_POWERMODE_PERFORMANCE:
++ return LEGION_EC_POWERMODE_PERFORMANCE;
++ case LEGION_WMI_POWERMODE_CUSTOM:
++ return LEGION_EC_POWERMODE_CUSTOM;
++ default:
++ return LEGION_EC_POWERMODE_BALANCED;
++ }
++}
+
-+ if (!legion_shared) {
-+ legion_shared = priv;
-+ mutex_init(&legion_shared->fancurve_mutex);
-+ ret = 0;
-+ } else {
-+ pr_warn("Found multiple platform devices\n");
-+ ret = -EINVAL;
++static ssize_t ec_read_powermode(struct legion_private *priv, int *powermode)
++{
++ *powermode =
++ ecram_read(&priv->ecram, priv->conf->registers->EXT_POWERMODE);
++ return 0;
++}
++
++static ssize_t ec_write_powermode(struct legion_private *priv, u8 value)
++{
++ if (!((value >= 0 && value <= 2) || value == 255)) {
++ pr_info("Unexpected power mode value ignored: %d\n", value);
++ return -ENOMEM;
++ }
++ ecram_write(&priv->ecram, priv->conf->registers->EXT_POWERMODE, value);
++ return 0;
++}
++
++static ssize_t acpi_read_powermode(struct legion_private *priv, int *powermode)
++{
++ unsigned long acpi_powermode;
++ int err;
++
++ // spmo method not alwasy available
++ // \_SB.PCI0.LPC0.EC0.SPMO
++ err = eval_spmo(priv->adev->handle, &acpi_powermode);
++ *powermode = (int)acpi_powermode;
++ return err;
++}
++
++static ssize_t wmi_read_powermode(int *powermode)
++{
++ int err;
++ unsigned long res;
++
++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETSMARTFANMODE, &res);
++
++ if (!err)
++ *powermode = res;
++ return err;
++}
++
++static ssize_t wmi_write_powermode(u8 value)
++{
++ if (!((value >= LEGION_WMI_POWERMODE_QUIET &&
++ value <= LEGION_WMI_POWERMODE_PERFORMANCE) ||
++ value == LEGION_WMI_POWERMODE_CUSTOM)) {
++ pr_info("Unexpected power mode value ignored: %d\n", value);
++ return -ENOMEM;
+ }
++ return wmi_exec_arg(LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_SETSMARTFANMODE, &value,
++ sizeof(value));
++}
+
-+ priv->loaded = true;
-+ mutex_unlock(&legion_shared_mutex);
++static ssize_t read_powermode(struct legion_private *priv, int *powermode)
++{
++ ssize_t res;
++
++ switch (priv->conf->access_method_powermode) {
++ case ACCESS_METHOD_EC:
++ res = ec_read_powermode(priv, powermode);
++ *powermode = ec_to_wmi_powermode(*powermode);
++ return res;
++ case ACCESS_METHOD_ACPI:
++ return acpi_read_powermode(priv, powermode);
++ case ACCESS_METHOD_WMI:
++ return wmi_read_powermode(powermode);
++ default:
++ pr_info("No access method for powermode:%d\n",
++ priv->conf->access_method_powermode);
++ return -EINVAL;
++ }
++}
+
-+ return ret;
++static ssize_t write_powermode(struct legion_private *priv,
++ enum legion_wmi_powermode value)
++{
++ ssize_t res;
++
++ //TODO: remove again
++ pr_info("Set powermode\n");
++
++ switch (priv->conf->access_method_powermode) {
++ case ACCESS_METHOD_EC:
++ res = ec_write_powermode(priv, wmi_to_ec_powermode(value));
++ return res;
++ case ACCESS_METHOD_WMI:
++ return wmi_write_powermode(value);
++ default:
++ pr_info("No access method for powermode:%d\n",
++ priv->conf->access_method_powermode);
++ return -EINVAL;
++ }
+}
+
-+static void legion_shared_exit(struct legion_private *priv)
++/**
++ * Shortly toggle powermode to a different mode
++ * and switch back, e.g. to reset fan curve.
++ */
++static void toggle_powermode(struct legion_private *priv)
+{
-+ pr_info("Unloading legion shared\n");
-+ mutex_lock(&legion_shared_mutex);
++ int old_powermode;
++ int next_powermode;
+
-+ if (legion_shared == priv)
-+ legion_shared = NULL;
++ read_powermode(priv, &old_powermode);
++ next_powermode = old_powermode == 0 ? 1 : 0;
+
-+ mutex_unlock(&legion_shared_mutex);
-+ pr_info("Unloading legion shared done\n");
++ write_powermode(priv, next_powermode);
++ mdelay(1500);
++ write_powermode(priv, old_powermode);
++}
++
++/* ============================= */
++/* Charging mode reading/writing */
++/* ============================- */
++
++#define FCT_RAPID_CHARGE_ON 0x07
++#define FCT_RAPID_CHARGE_OFF 0x08
++#define RAPID_CHARGE_ON 0x0
++#define RAPID_CHARGE_OFF 0x1
++
++static int acpi_read_rapidcharge(struct acpi_device *adev, bool *state)
++{
++ unsigned long result;
++ int err;
++
++ //also works? what is better?
++ /*
++ * err = eval_qcho(adev->handle, &result);
++ * if (err)
++ * return err;
++ * state = result;
++ * return 0;
++ */
++
++ err = eval_gbmd(adev->handle, &result);
++ if (err)
++ return err;
++
++ *state = result & 0x04;
++ return 0;
++}
++
++static int acpi_write_rapidcharge(struct acpi_device *adev, bool state)
++{
++ int err;
++ unsigned long fct_nr = state > 0 ? FCT_RAPID_CHARGE_ON :
++ FCT_RAPID_CHARGE_OFF;
++
++ err = exec_sbmc(adev->handle, fct_nr);
++ pr_info("Set rapidcharge to %d by calling %lu: result: %d\n", state,
++ fct_nr, err);
++ return err;
++}
++
++/* ============================= */
++/* Keyboard backlight read/write */
++/* ============================= */
++
++static ssize_t legion_kbd_bl2_brightness_get(struct legion_private *priv)
++{
++ unsigned long state = 0;
++ int err;
++
++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETKEYBOARDLIGHT, &state);
++ if (err)
++ return -EINVAL;
++
++ return state;
++}
++
++//static int legion_kbd_bl2_brightness_set(struct legion_private *priv,
++// unsigned int brightness)
++//{
++// u8 in_param = brightness;
++
++// return wmi_exec_arg(LEGION_WMI_GAMEZONE_GUID, 0,
++// WMI_METHOD_ID_SETKEYBOARDLIGHT, &in_param,
++// sizeof(in_param));
++//}
++
++//min: 1, max: 3
++#define LIGHT_ID_KEYBOARD 0x00
++//min: 0, max: 1
++#define LIGHT_ID_YLOGO 0x03
++//min: 1, max: 2
++#define LIGHT_ID_IOPORT 0x05
++
++static int legion_wmi_light_get(struct legion_private *priv, u8 light_id,
++ unsigned int min_value, unsigned int max_value)
++{
++ struct acpi_buffer params;
++ u8 in;
++ u8 result[2];
++ u8 value;
++ int err;
++
++ params.length = 1;
++ params.pointer = &in;
++ in = light_id;
++ err = wmi_exec_ints(LEGION_WMI_KBBACKLIGHT_GUID, 0,
++ WMI_METHOD_ID_KBBACKLIGHTGET, &params, result,
++ ARRAY_SIZE(result));
++ if (err) {
++ pr_info("Error for WMI method call to get brightness\n");
++ return -EIO;
++ }
++
++ value = result[1];
++ if (!(value >= min_value && value <= max_value)) {
++ pr_info("Error WMI call for reading brightness: expected a value between %u and %u, but got %d\n",
++ min_value, max_value, value);
++ return -EFAULT;
++ }
++
++ return value - min_value;
++}
++
++static int legion_wmi_light_set(struct legion_private *priv, u8 light_id,
++ unsigned int min_value, unsigned int max_value,
++ unsigned int brightness)
++{
++ struct acpi_buffer buffer;
++ u8 in_buffer_param[8];
++ unsigned long result;
++ int err;
++
++ buffer.length = 3;
++ buffer.pointer = &in_buffer_param[0];
++ in_buffer_param[0] = light_id;
++ in_buffer_param[1] = 0x01;
++ in_buffer_param[2] =
++ clamp(brightness + min_value, min_value, max_value);
++
++ err = wmi_exec_int(LEGION_WMI_KBBACKLIGHT_GUID, 0,
++ WMI_METHOD_ID_KBBACKLIGHTSET, &buffer, &result);
++ if (err) {
++ pr_info("Error for WMI method call to set brightness on light: %d\n",
++ light_id);
++ return -EIO;
++ }
++
++ return 0;
++}
++
++static int legion_kbd_bl_brightness_get(struct legion_private *priv)
++{
++ return legion_wmi_light_get(priv, LIGHT_ID_KEYBOARD, 1, 3);
++}
++
++static int legion_kbd_bl_brightness_set(struct legion_private *priv,
++ unsigned int brightness)
++{
++ return legion_wmi_light_set(priv, LIGHT_ID_KEYBOARD, 1, 3, brightness);
+}
+
+/* ============================= */
@@ -1620,7 +3396,9 @@ index 000000000..6edfeffcc
+ size_t offset;
+
+ for (offset = 0; offset < priv->conf->memoryio_size; ++offset) {
-+ char value = ecram_read(&priv->ecram, priv->conf->memoryio_physical_ec_start + offset);
++ char value = ecram_read(&priv->ecram,
++ priv->conf->memoryio_physical_ec_start +
++ offset);
+
+ seq_write(s, &value, 1);
+ }
@@ -1629,47 +3407,180 @@ index 000000000..6edfeffcc
+
+DEFINE_SHOW_ATTRIBUTE(debugfs_ecmemory);
+
++static int debugfs_ecmemoryram_show(struct seq_file *s, void *unused)
++{
++ struct legion_private *priv = s->private;
++ size_t offset;
++ ssize_t err;
++ u8 value;
++
++ for (offset = 0; offset < priv->conf->ramio_size; ++offset) {
++ err = ecram_memoryio_read(&priv->ec_memoryio, offset, &value);
++ if (!err)
++ seq_write(s, &value, 1);
++ else
++ return -EACCES;
++ }
++ return 0;
++}
++
++DEFINE_SHOW_ATTRIBUTE(debugfs_ecmemoryram);
++
++//TODO: make (almost) all methods static
++
++static void seq_file_print_with_error(struct seq_file *s, const char *name,
++ ssize_t err, int value)
++{
++ seq_printf(s, "%s error: %ld\n", name, err);
++ seq_printf(s, "%s: %d\n", name, value);
++}
++
+static int debugfs_fancurve_show(struct seq_file *s, void *unused)
+{
+ struct legion_private *priv = s->private;
+ bool is_minifancurve;
+ bool is_lockfancontroller;
+ bool is_maximumfanspeed;
++ bool is_rapidcharge = false;
++ int powermode;
++ int temperature;
++ int fanspeed;
+ int err;
++ unsigned long cfg;
++ struct fancurve wmi_fancurve;
++ //int kb_backlight;
++
++ mutex_lock(&priv->fancurve_mutex);
+
+ seq_printf(s, "EC Chip ID: %x\n", read_ec_id(&priv->ecram, priv->conf));
+ seq_printf(s, "EC Chip Version: %x\n",
+ read_ec_version(&priv->ecram, priv->conf));
-+ // TODO: remove this
-+ seq_printf(s, "legion_laptop version: %s\n", MODULEVERSION);
+ seq_printf(s, "legion_laptop features: %s\n", LEGIONFEATURES);
+ seq_printf(s, "legion_laptop ec_readonly: %d\n", ec_readonly);
-+ read_fancurve(&priv->ecram, priv->conf, &priv->fancurve);
+
-+ err = read_minifancurve(&priv->ecram, priv->conf, &is_minifancurve);
-+ seq_printf(s, "minifancurve on cool: %s\n",
++ err = eval_int(priv->adev->handle, "VPC0._CFG", &cfg);
++ seq_printf(s, "ACPI CFG error: %d\n", err);
++ seq_printf(s, "ACPI CFG: %lu\n", cfg);
++
++ seq_printf(s, "temperature access method: %d\n",
++ priv->conf->access_method_temperature);
++ err = read_temperature(priv, 0, &temperature);
++ seq_file_print_with_error(s, "CPU temperature", err, temperature);
++ err = ec_read_temperature(&priv->ecram, priv->conf, 0, &temperature);
++ seq_file_print_with_error(s, "CPU temperature EC", err, temperature);
++ err = acpi_read_temperature(priv, 0, &temperature);
++ seq_file_print_with_error(s, "CPU temperature ACPI", err, temperature);
++ err = wmi_read_temperature_gz(0, &temperature);
++ seq_file_print_with_error(s, "CPU temperature WMI", err, temperature);
++ err = wmi_read_temperature(0, &temperature);
++ seq_file_print_with_error(s, "CPU temperature WMI2", err, temperature);
++ err = wmi_read_temperature_other(0, &temperature);
++ seq_file_print_with_error(s, "CPU temperature WMI3", err, temperature);
++
++ err = read_temperature(priv, 1, &temperature);
++ seq_file_print_with_error(s, "GPU temperature", err, temperature);
++ err = ec_read_temperature(&priv->ecram, priv->conf, 1, &temperature);
++ seq_file_print_with_error(s, "GPU temperature EC", err, temperature);
++ err = acpi_read_temperature(priv, 1, &temperature);
++ seq_file_print_with_error(s, "GPU temperature ACPI", err, temperature);
++ err = wmi_read_temperature_gz(1, &temperature);
++ seq_file_print_with_error(s, "GPU temperature WMI", err, temperature);
++ err = wmi_read_temperature(1, &temperature);
++ seq_file_print_with_error(s, "GPU temperature WMI2", err, temperature);
++ err = wmi_read_temperature_other(1, &temperature);
++ seq_file_print_with_error(s, "GPU temperature WMI3", err, temperature);
++
++ seq_printf(s, "fan speed access method: %d\n",
++ priv->conf->access_method_fanspeed);
++ err = read_fanspeed(priv, 0, &fanspeed);
++ seq_file_print_with_error(s, "1 fanspeed", err, fanspeed);
++ err = ec_read_fanspeed(&priv->ecram, priv->conf, 0, &fanspeed);
++ seq_file_print_with_error(s, "1 fanspeed EC", err, fanspeed);
++ err = acpi_read_fanspeed(priv, 0, &fanspeed);
++ seq_file_print_with_error(s, "1 fanspeed ACPI", err, fanspeed);
++ err = wmi_read_fanspeed_gz(0, &fanspeed);
++ seq_file_print_with_error(s, "1 fanspeed WMI", err, fanspeed);
++ err = wmi_read_fanspeed(0, &fanspeed);
++ seq_file_print_with_error(s, "1 fanspeed WMI2", err, fanspeed);
++ err = wmi_read_fanspeed_other(0, &fanspeed);
++ seq_file_print_with_error(s, "1 fanspeed WMI3", err, fanspeed);
++
++ err = read_fanspeed(priv, 1, &fanspeed);
++ seq_file_print_with_error(s, "2 fanspeed", err, fanspeed);
++ err = ec_read_fanspeed(&priv->ecram, priv->conf, 1, &fanspeed);
++ seq_file_print_with_error(s, "2 fanspeed EC", err, fanspeed);
++ err = acpi_read_fanspeed(priv, 1, &fanspeed);
++ seq_file_print_with_error(s, "2 fanspeed ACPI", err, fanspeed);
++ err = wmi_read_fanspeed_gz(1, &fanspeed);
++ seq_file_print_with_error(s, "2 fanspeed WMI", err, fanspeed);
++ err = wmi_read_fanspeed(1, &fanspeed);
++ seq_file_print_with_error(s, "2 fanspeed WMI2", err, fanspeed);
++ err = wmi_read_fanspeed_other(1, &fanspeed);
++ seq_file_print_with_error(s, "2 fanspeed WMI3", err, fanspeed);
++
++ seq_printf(s, "powermode access method: %d\n",
++ priv->conf->access_method_powermode);
++ err = read_powermode(priv, &powermode);
++ seq_file_print_with_error(s, "powermode", err, powermode);
++ err = ec_read_powermode(priv, &powermode);
++ seq_file_print_with_error(s, "powermode EC", err, powermode);
++ err = acpi_read_powermode(priv, &powermode);
++ seq_file_print_with_error(s, "powermode ACPI", err, powermode);
++ err = wmi_read_powermode(&powermode);
++ seq_file_print_with_error(s, "powermode WMI", err, powermode);
++ seq_printf(s, "has custom powermode: %d\n",
++ priv->conf->has_custom_powermode);
++
++ err = acpi_read_rapidcharge(priv->adev, &is_rapidcharge);
++ seq_printf(s, "ACPI rapidcharge error: %d\n", err);
++ seq_printf(s, "ACPI rapidcharge: %d\n", is_rapidcharge);
++
++ seq_printf(s, "WMI backlight 2 state: %ld\n",
++ legion_kbd_bl2_brightness_get(priv));
++ seq_printf(s, "WMI backlight 3 state: %d\n",
++ legion_kbd_bl_brightness_get(priv));
++
++ 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",
++ legion_wmi_light_get(priv, LIGHT_ID_YLOGO, 0, 4));
++
++ seq_printf(s, "EC minifancurve feature enabled: %d\n",
++ priv->conf->has_minifancurve);
++ err = ec_read_minifancurve(&priv->ecram, priv->conf, &is_minifancurve);
++ seq_printf(s, "EC minifancurve on cool: %s\n",
+ err ? "error" : (is_minifancurve ? "true" : "false"));
-+ err = read_lockfancontroller(&priv->ecram, priv->conf,
-+ &is_lockfancontroller);
-+ seq_printf(s, "lock fan controller: %s\n",
++
++ err = ec_read_lockfancontroller(&priv->ecram, priv->conf,
++ &is_lockfancontroller);
++ seq_printf(s, "EC lockfancontroller error: %d\n", err);
++ seq_printf(s, "EC lockfancontroller: %s\n",
+ err ? "error" : (is_lockfancontroller ? "true" : "false"));
-+ err = read_maximumfanspeed(&priv->ecram, priv->conf,
++
++ err = read_fanfullspeed(priv, &is_maximumfanspeed);
++ seq_file_print_with_error(s, "fanfullspeed", err, is_maximumfanspeed);
++
++ err = ec_read_fanfullspeed(&priv->ecram, priv->conf,
+ &is_maximumfanspeed);
-+ seq_printf(s, "enable maximumfanspeed: %s\n",
-+ err ? "error" : (is_maximumfanspeed ? "true" : "false"));
-+ seq_printf(s, "enable maximumfanspeed status: %d\n", err);
++ seq_file_print_with_error(s, "fanfullspeed EC", err,
++ is_maximumfanspeed);
+
-+ seq_printf(s, "fan curve current point id: %ld\n",
++ read_fancurve(priv, &priv->fancurve);
++ seq_printf(s, "EC fan curve current point id: %ld\n",
+ priv->fancurve.current_point_i);
-+ seq_printf(s, "fan curve points size: %ld\n", priv->fancurve.size);
++ seq_printf(s, "EC fan curve points size: %ld\n", priv->fancurve.size);
+
-+ seq_puts(s, "Current fan curve in hardware (embedded controller):\n");
++ seq_puts(s, "Current fan curve in hardware:\n");
+ fancurve_print_seqfile(&priv->fancurve, s);
+ seq_puts(s, "=====================\n");
++ mutex_unlock(&priv->fancurve_mutex);
+
-+ // TODO: decide what to do with it, still needed?
-+ // seq_puts(s, "Configured fan curve.\n");
-+ // fancurve_print_seqfile(&priv->fancurve_configured, s);
++ seq_puts(s, "Current fan curve in hardware (WMI; might be empty)\n");
++ wmi_fancurve.size = 0;
++ err = wmi_read_fancurve_custom(priv->conf, &wmi_fancurve);
++ fancurve_print_seqfile(&wmi_fancurve, s);
++ seq_puts(s, "=====================\n");
+ return 0;
+}
+
@@ -1691,6 +3602,8 @@ index 000000000..6edfeffcc
+ &debugfs_fancurve_fops);
+ debugfs_create_file("ecmemory", 0444, dir, priv,
+ &debugfs_ecmemory_fops);
++ debugfs_create_file("ecmemoryram", 0444, dir, priv,
++ &debugfs_ecmemoryram_fops);
+
+ priv->debugfs_dir = dir;
+}
@@ -1698,8 +3611,7 @@ index 000000000..6edfeffcc
+static void legion_debugfs_exit(struct legion_private *priv)
+{
+ pr_info("Unloading legion dubugfs\n");
-+ // TODO: remove this note
-+ // Note: does nothing if null
++ // The following is does nothing if pointer is NULL
+ debugfs_remove_recursive(priv->debugfs_dir);
+ priv->debugfs_dir = NULL;
+ pr_info("Unloading legion dubugfs done\n");
@@ -1709,43 +3621,78 @@ index 000000000..6edfeffcc
+/* sysfs interface */
+/* ============================ */
+
-+static ssize_t powermode_show(struct device *dev, struct device_attribute *attr,
-+ char *buf)
++static int show_simple_wmi_attribute(struct device *dev,
++ struct device_attribute *attr, char *buf,
++ const char *guid, u8 instance,
++ u32 method_id, bool invert,
++ unsigned long scale)
+{
++ unsigned long state = 0;
++ int err;
+ struct legion_private *priv = dev_get_drvdata(dev);
-+ int power_mode = read_powermode(&priv->ecram, priv->conf);
+
-+ return sysfs_emit(buf, "%d\n", power_mode);
++ mutex_lock(&priv->fancurve_mutex);
++ err = get_simple_wmi_attribute(priv, guid, instance, method_id, invert,
++ scale, &state);
++ mutex_unlock(&priv->fancurve_mutex);
++
++ if (err)
++ return -EINVAL;
++
++ return sysfs_emit(buf, "%lu\n", state);
+}
+
-+static ssize_t powermode_store(struct device *dev,
-+ struct device_attribute *attr, const char *buf,
-+ size_t count)
++static int show_simple_wmi_attribute_from_buffer(struct device *dev,
++ struct device_attribute *attr,
++ char *buf, const char *guid,
++ u8 instance, u32 method_id,
++ size_t ressize, size_t i,
++ int scale)
+{
-+ struct legion_private *priv = dev_get_drvdata(dev);
-+ int powermode;
++ u8 res[16];
+ int err;
++ int out;
++ struct legion_private *priv = dev_get_drvdata(dev);
+
-+ err = kstrtouint(buf, 0, &powermode);
-+ if (err)
-+ return err;
++ if (ressize > ARRAY_SIZE(res)) {
++ pr_info("Buffer to small for WMI result\n");
++ return -EINVAL;
++ }
++ if (i >= ressize) {
++ pr_info("Index not within buffer size\n");
++ return -EINVAL;
++ }
+
-+ err = write_powermode(&priv->ecram, priv->conf, powermode);
++ mutex_lock(&priv->fancurve_mutex);
++ err = wmi_exec_noarg_ints(guid, instance, method_id, res, ressize);
++ mutex_unlock(&priv->fancurve_mutex);
+ if (err)
+ return -EINVAL;
+
-+ // TODO: better?
-+ // we have to wait a bit before change is done in hardware and
-+ // readback done after notifying returns correct value, otherwise
-+ // the notified reader will read old value
-+ msleep(500);
-+ platform_profile_notify();
++ out = scale * res[i];
++ return sysfs_emit(buf, "%d\n", out);
++}
++
++static int store_simple_wmi_attribute(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count,
++ const char *guid, u8 instance,
++ u32 method_id, bool invert, int scale)
++{
++ int state;
++ int err;
++ struct legion_private *priv = dev_get_drvdata(dev);
+
++ err = kstrtouint(buf, 0, &state);
++ if (err)
++ return err;
++ err = set_simple_wmi_attribute(priv, guid, instance, method_id, invert,
++ scale, state);
++ if (err)
++ return err;
+ return count;
+}
+
-+static DEVICE_ATTR_RW(powermode);
-+
+static ssize_t lockfancontroller_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
@@ -1754,8 +3701,8 @@ index 000000000..6edfeffcc
+ int err;
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = read_lockfancontroller(&priv->ecram, priv->conf,
-+ &is_lockfancontroller);
++ err = ec_read_lockfancontroller(&priv->ecram, priv->conf,
++ &is_lockfancontroller);
+ mutex_unlock(&priv->fancurve_mutex);
+ if (err)
+ return -EINVAL;
@@ -1776,8 +3723,8 @@ index 000000000..6edfeffcc
+ return err;
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = write_lockfancontroller(&priv->ecram, priv->conf,
-+ is_lockfancontroller);
++ err = ec_write_lockfancontroller(&priv->ecram, priv->conf,
++ is_lockfancontroller);
+ mutex_unlock(&priv->fancurve_mutex);
+ if (err)
+ return -EINVAL;
@@ -1787,8 +3734,599 @@ index 000000000..6edfeffcc
+
+static DEVICE_ATTR_RW(lockfancontroller);
+
++static ssize_t rapidcharge_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ bool state = false;
++ int err;
++ struct legion_private *priv = dev_get_drvdata(dev);
++
++ mutex_lock(&priv->fancurve_mutex);
++ err = acpi_read_rapidcharge(priv->adev, &state);
++ mutex_unlock(&priv->fancurve_mutex);
++ if (err)
++ return -EINVAL;
++
++ return sysfs_emit(buf, "%d\n", state);
++}
++
++static ssize_t rapidcharge_store(struct device *dev,
++ struct device_attribute *attr, const char *buf,
++ size_t count)
++{
++ struct legion_private *priv = dev_get_drvdata(dev);
++ int state;
++ int err;
++
++ err = kstrtouint(buf, 0, &state);
++ if (err)
++ return err;
++
++ mutex_lock(&priv->fancurve_mutex);
++ err = acpi_write_rapidcharge(priv->adev, state);
++ mutex_unlock(&priv->fancurve_mutex);
++ if (err)
++ return -EINVAL;
++
++ return count;
++}
++
++static DEVICE_ATTR_RW(rapidcharge);
++
++static ssize_t issupportgpuoc_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_ISSUPPORTGPUOC, false,
++ 1);
++}
++
++static DEVICE_ATTR_RO(issupportgpuoc);
++
++static ssize_t aslcodeversion_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETVERSION, false, 1);
++}
++
++static DEVICE_ATTR_RO(aslcodeversion);
++
++static ssize_t issupportcpuoc_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_ISSUPPORTCPUOC, false,
++ 1);
++}
++
++static DEVICE_ATTR_RO(issupportcpuoc);
++
++static ssize_t winkey_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETWINKEYSTATUS, true,
++ 1);
++}
++
++static ssize_t winkey_store(struct device *dev, struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_SETWINKEYSTATUS, true,
++ 1);
++}
++
++static DEVICE_ATTR_RW(winkey);
++
++// on newer models the touchpad feature in ideapad does not work anymore, so
++// we need this
++static ssize_t touchpad_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETTPSTATUS, true, 1);
++}
++
++static ssize_t touchpad_store(struct device *dev, struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_SETTPSTATUS, true, 1);
++}
++
++static DEVICE_ATTR_RW(touchpad);
++
++static ssize_t gsync_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETGSYNCSTATUS, true, 1);
++}
++
++static ssize_t gsync_store(struct device *dev, struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_SETGSYNCSTATUS, true,
++ 1);
++}
++
++static DEVICE_ATTR_RW(gsync);
++
++static ssize_t powerchargemode_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETPOWERCHARGEMODE,
++ false, 1);
++}
++static DEVICE_ATTR_RO(powerchargemode);
++
++static ssize_t overdrive_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETODSTATUS, false, 1);
++}
++
++static ssize_t overdrive_store(struct device *dev,
++ struct device_attribute *attr, const char *buf,
++ size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_SETODSTATUS, false, 1);
++}
++
++static DEVICE_ATTR_RW(overdrive);
++
++static ssize_t thermalmode_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETTHERMALMODE, false,
++ 1);
++}
++static DEVICE_ATTR_RO(thermalmode);
++
++// TOOD: probably remove again because provided by other means; only useful for overclocking
++static ssize_t cpumaxfrequency_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETCPUMAXFREQUENCY,
++ false, 1);
++}
++static DEVICE_ATTR_RO(cpumaxfrequency);
++
++static ssize_t isacfitforoc_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_ISACFITFOROC, false, 1);
++}
++static DEVICE_ATTR_RO(isacfitforoc);
++
++static ssize_t igpumode_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_GETIGPUMODESTATUS, false,
++ 1);
++}
++
++static ssize_t igpumode_store(struct device *dev, struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ LEGION_WMI_GAMEZONE_GUID, 0,
++ WMI_METHOD_ID_SETIGPUMODESTATUS,
++ false, 1);
++}
++
++static DEVICE_ATTR_RW(igpumode);
++
++static ssize_t cpu_oc_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute_from_buffer(
++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_OC_STATUS, 16, 0, 1);
++}
++
++static ssize_t cpu_oc_store(struct device *dev, struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_SET_OC_STATUS,
++ false, 1);
++}
++
++static DEVICE_ATTR_RW(cpu_oc);
++
++static ssize_t cpu_shortterm_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute_from_buffer(
++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_SHORTTERM_POWERLIMIT, 16, 0, 1);
++}
++
++static ssize_t cpu_shortterm_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(
++ dev, attr, buf, count, WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_SET_SHORTTERM_POWERLIMIT, false, 1);
++}
++
++static DEVICE_ATTR_RW(cpu_shortterm_powerlimit);
++
++static ssize_t cpu_longterm_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute_from_buffer(
++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_LONGTERM_POWERLIMIT, 16, 0, 1);
++}
++
++static ssize_t cpu_longterm_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(
++ dev, attr, buf, count, WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_SET_LONGTERM_POWERLIMIT, false, 1);
++}
++
++static DEVICE_ATTR_RW(cpu_longterm_powerlimit);
++
++static ssize_t cpu_default_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(
++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_DEFAULT_POWERLIMIT, false, 1);
++}
++
++static DEVICE_ATTR_RO(cpu_default_powerlimit);
++
++static ssize_t cpu_peak_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_PEAK_POWERLIMIT,
++ false, 1);
++}
++
++static ssize_t cpu_peak_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_SET_PEAK_POWERLIMIT,
++ false, 1);
++}
++
++static DEVICE_ATTR_RW(cpu_peak_powerlimit);
++
++static ssize_t cpu_apu_sppt_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_APU_SPPT_POWERLIMIT, false, 1);
++}
++
++static ssize_t cpu_apu_sppt_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(
++ dev, attr, buf, count, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_SET_APU_SPPT_POWERLIMIT, false, 1);
++}
++
++static DEVICE_ATTR_RW(cpu_apu_sppt_powerlimit);
++
++static ssize_t cpu_cross_loading_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_GET_CROSS_LOADING_POWERLIMIT, false, 1);
++}
++
++static ssize_t cpu_cross_loading_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(
++ dev, attr, buf, count, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_CPU_SET_CROSS_LOADING_POWERLIMIT, false, 1);
++}
++
++static DEVICE_ATTR_RW(cpu_cross_loading_powerlimit);
++
++static ssize_t gpu_oc_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_OC_STATUS, false,
++ 1);
++}
++
++static ssize_t gpu_oc_store(struct device *dev, struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_SET_OC_STATUS,
++ false, 1);
++}
++
++static DEVICE_ATTR_RW(gpu_oc);
++
++static ssize_t gpu_ppab_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute_from_buffer(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_PPAB_POWERLIMIT, 16, 0, 1);
++}
++
++static ssize_t gpu_ppab_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_SET_PPAB_POWERLIMIT,
++ false, 1);
++}
++
++static DEVICE_ATTR_RW(gpu_ppab_powerlimit);
++
++static ssize_t gpu_ctgp_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute_from_buffer(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_CTGP_POWERLIMIT, 16, 0, 1);
++}
++
++static ssize_t gpu_ctgp_powerlimit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_SET_CTGP_POWERLIMIT,
++ false, 1);
++}
++
++static DEVICE_ATTR_RW(gpu_ctgp_powerlimit);
++
++static ssize_t gpu_ctgp2_powerlimit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute_from_buffer(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_CTGP_POWERLIMIT, 16, 0x0C, 1);
++}
++
++static DEVICE_ATTR_RO(gpu_ctgp2_powerlimit);
++
++// TOOD: probably remove again because provided by other means; only useful for overclocking
++static ssize_t
++gpu_default_ppab_ctrgp_powerlimit_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_DEFAULT_PPAB_CTGP_POWERLIMIT, false, 1);
++}
++static DEVICE_ATTR_RO(gpu_default_ppab_ctrgp_powerlimit);
++
++static ssize_t gpu_temperature_limit_show(struct device *dev,
++ struct device_attribute *attr,
++ char *buf)
++{
++ return show_simple_wmi_attribute(
++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_TEMPERATURE_LIMIT, false, 1);
++}
++
++static ssize_t gpu_temperature_limit_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(
++ dev, attr, buf, count, WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_SET_TEMPERATURE_LIMIT, false, 1);
++}
++
++static DEVICE_ATTR_RW(gpu_temperature_limit);
++
++// TOOD: probably remove again because provided by other means; only useful for overclocking
++static ssize_t gpu_boost_clock_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ WMI_GUID_LENOVO_GPU_METHOD, 0,
++ WMI_METHOD_ID_GPU_GET_BOOST_CLOCK,
++ false, 1);
++}
++static DEVICE_ATTR_RO(gpu_boost_clock);
++
++static ssize_t fan_fullspeed_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ bool state = false;
++ int err;
++ struct legion_private *priv = dev_get_drvdata(dev);
++
++ mutex_lock(&priv->fancurve_mutex);
++ err = read_fanfullspeed(priv, &state);
++ mutex_unlock(&priv->fancurve_mutex);
++ if (err)
++ return -EINVAL;
++
++ return sysfs_emit(buf, "%d\n", state);
++}
++
++static ssize_t fan_fullspeed_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ struct legion_private *priv = dev_get_drvdata(dev);
++ int state;
++ int err;
++
++ err = kstrtouint(buf, 0, &state);
++ if (err)
++ return err;
++
++ mutex_lock(&priv->fancurve_mutex);
++ err = write_fanfullspeed(priv, state);
++ mutex_unlock(&priv->fancurve_mutex);
++ if (err)
++ return -EINVAL;
++
++ return count;
++}
++
++static DEVICE_ATTR_RW(fan_fullspeed);
++
++static ssize_t fan_maxspeed_show(struct device *dev,
++ struct device_attribute *attr, char *buf)
++{
++ return show_simple_wmi_attribute(dev, attr, buf,
++ WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_GET_MAXSPEED, false,
++ 1);
++}
++
++static ssize_t fan_maxspeed_store(struct device *dev,
++ struct device_attribute *attr,
++ const char *buf, size_t count)
++{
++ return store_simple_wmi_attribute(dev, attr, buf, count,
++ WMI_GUID_LENOVO_FAN_METHOD, 0,
++ WMI_METHOD_ID_FAN_SET_MAXSPEED, false,
++ 1);
++}
++
++static DEVICE_ATTR_RW(fan_maxspeed);
++
++static ssize_t powermode_show(struct device *dev, struct device_attribute *attr,
++ char *buf)
++{
++ struct legion_private *priv = dev_get_drvdata(dev);
++ int power_mode;
++
++ mutex_lock(&priv->fancurve_mutex);
++ read_powermode(priv, &power_mode);
++ mutex_unlock(&priv->fancurve_mutex);
++ return sysfs_emit(buf, "%d\n", power_mode);
++}
++
++static void legion_platform_profile_notify(void);
++
++static ssize_t powermode_store(struct device *dev,
++ struct device_attribute *attr, const char *buf,
++ size_t count)
++{
++ struct legion_private *priv = dev_get_drvdata(dev);
++ int powermode;
++ int err;
++
++ err = kstrtouint(buf, 0, &powermode);
++ if (err)
++ return err;
++
++ mutex_lock(&priv->fancurve_mutex);
++ err = write_powermode(priv, powermode);
++ mutex_unlock(&priv->fancurve_mutex);
++ if (err)
++ return -EINVAL;
++
++ // TODO: better?
++ // we have to wait a bit before change is done in hardware and
++ // readback done after notifying returns correct value, otherwise
++ // the notified reader will read old value
++ msleep(500);
++ legion_platform_profile_notify();
++
++ return count;
++}
++
++static DEVICE_ATTR_RW(powermode);
++
+static struct attribute *legion_sysfs_attributes[] = {
-+ &dev_attr_powermode.attr, &dev_attr_lockfancontroller.attr, NULL
++ &dev_attr_powermode.attr,
++ &dev_attr_lockfancontroller.attr,
++ &dev_attr_rapidcharge.attr,
++ &dev_attr_winkey.attr,
++ &dev_attr_touchpad.attr,
++ &dev_attr_gsync.attr,
++ &dev_attr_powerchargemode.attr,
++ &dev_attr_overdrive.attr,
++ &dev_attr_cpumaxfrequency.attr,
++ &dev_attr_isacfitforoc.attr,
++ &dev_attr_cpu_oc.attr,
++ &dev_attr_cpu_shortterm_powerlimit.attr,
++ &dev_attr_cpu_longterm_powerlimit.attr,
++ &dev_attr_cpu_apu_sppt_powerlimit.attr,
++ &dev_attr_cpu_default_powerlimit.attr,
++ &dev_attr_cpu_peak_powerlimit.attr,
++ &dev_attr_cpu_cross_loading_powerlimit.attr,
++ &dev_attr_gpu_oc.attr,
++ &dev_attr_gpu_ppab_powerlimit.attr,
++ &dev_attr_gpu_ctgp_powerlimit.attr,
++ &dev_attr_gpu_ctgp2_powerlimit.attr,
++ &dev_attr_gpu_default_ppab_ctrgp_powerlimit.attr,
++ &dev_attr_gpu_temperature_limit.attr,
++ &dev_attr_gpu_boost_clock.attr,
++ &dev_attr_fan_fullspeed.attr,
++ &dev_attr_fan_maxspeed.attr,
++ &dev_attr_thermalmode.attr,
++ &dev_attr_issupportcpuoc.attr,
++ &dev_attr_issupportgpuoc.attr,
++ &dev_attr_aslcodeversion.attr,
++ &dev_attr_igpumode.attr,
++ NULL
+};
+
+static const struct attribute_group legion_attribute_group = {
@@ -1853,7 +4391,7 @@ index 000000000..6edfeffcc
+ pr_info("Fan event: legion type: %d; acpi type: %d (%d=integer)",
+ wpriv->event, data->type, ACPI_TYPE_INTEGER);
+ // TODO: here it is too early (first unlock mutext, then wait a bit)
-+ //platform_profile_notify();
++ //legion_platform_profile_notify();
+ break;
+ default:
+ pr_info("Event: legion type: %d; acpi type: %d (%d=integer)",
@@ -1867,7 +4405,7 @@ index 000000000..6edfeffcc
+ // 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
+ msleep(500);
-+ platform_profile_notify();
++ legion_platform_profile_notify();
+}
+
+static int legion_wmi_probe(struct wmi_device *wdev, const void *context)
@@ -1907,9 +4445,6 @@ index 000000000..6edfeffcc
+ .event = LEGION_EVENT_F
+};
+
-+// check if really a method
-+#define LEGION_WMI_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
-+
+#define LEGION_WMI_GUID_FAN_EVENT "D320289E-8FEA-41E0-86F9-611D83151B5F"
+#define LEGION_WMI_GUID_FAN2_EVENT "bc72a435-e8c1-4275-b3e2-d8b8074aba59"
+#define LEGION_WMI_GUID_GAMEZONE_KEY_EVENT \
@@ -1970,11 +4505,13 @@ index 000000000..6edfeffcc
+/* Platform profile */
+/* ============================ */
+
-+enum LEGION_POWERMODE {
-+ LEGION_POWERMODE_BALANCED = 0,
-+ LEGION_POWERMODE_PERFORMANCE = 1,
-+ LEGION_POWERMODE_QUIET = 2,
-+};
++static void legion_platform_profile_notify(void)
++{
++ if (!enable_platformprofile)
++ pr_info("Skipping platform_profile_notify because enable_platformprofile is false\n");
++
++ platform_profile_notify();
++}
+
+static int legion_platform_profile_get(struct platform_profile_handler *pprof,
+ enum platform_profile_option *profile)
@@ -1984,18 +4521,21 @@ index 000000000..6edfeffcc
+
+ priv = container_of(pprof, struct legion_private,
+ platform_profile_handler);
-+ powermode = read_powermode(&priv->ecram, priv->conf);
++ read_powermode(priv, &powermode);
+
+ switch (powermode) {
-+ case LEGION_POWERMODE_BALANCED:
++ case LEGION_WMI_POWERMODE_BALANCED:
+ *profile = PLATFORM_PROFILE_BALANCED;
+ break;
-+ case LEGION_POWERMODE_PERFORMANCE:
++ case LEGION_WMI_POWERMODE_PERFORMANCE:
+ *profile = PLATFORM_PROFILE_PERFORMANCE;
+ break;
-+ case LEGION_POWERMODE_QUIET:
++ case LEGION_WMI_POWERMODE_QUIET:
+ *profile = PLATFORM_PROFILE_QUIET;
+ break;
++ case LEGION_WMI_POWERMODE_CUSTOM:
++ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
++ break;
+ default:
+ return -EINVAL;
+ }
@@ -2013,25 +4553,33 @@ index 000000000..6edfeffcc
+
+ switch (profile) {
+ case PLATFORM_PROFILE_BALANCED:
-+ powermode = LEGION_POWERMODE_BALANCED;
++ powermode = LEGION_WMI_POWERMODE_BALANCED;
+ break;
+ case PLATFORM_PROFILE_PERFORMANCE:
-+ powermode = LEGION_POWERMODE_PERFORMANCE;
++ powermode = LEGION_WMI_POWERMODE_PERFORMANCE;
+ break;
+ case PLATFORM_PROFILE_QUIET:
-+ powermode = LEGION_POWERMODE_QUIET;
++ powermode = LEGION_WMI_POWERMODE_QUIET;
++ break;
++ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
++ powermode = LEGION_WMI_POWERMODE_CUSTOM;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
-+ return write_powermode(&priv->ecram, priv->conf, powermode);
++ return write_powermode(priv, powermode);
+}
+
+static int legion_platform_profile_init(struct legion_private *priv)
+{
+ int err;
+
++ if (!enable_platformprofile) {
++ pr_info("Skipping creating platform profile support because enable_platformprofile is false\n");
++ return 0;
++ }
++
+ priv->platform_profile_handler.profile_get =
+ legion_platform_profile_get;
+ priv->platform_profile_handler.profile_set =
@@ -2042,6 +4590,11 @@ index 000000000..6edfeffcc
+ priv->platform_profile_handler.choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE,
+ priv->platform_profile_handler.choices);
++ if (priv->conf->has_custom_powermode &&
++ priv->conf->access_method_powermode == ACCESS_METHOD_WMI) {
++ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE,
++ priv->platform_profile_handler.choices);
++ }
+
+ err = platform_profile_register(&priv->platform_profile_handler);
+ if (err)
@@ -2052,6 +4605,10 @@ index 000000000..6edfeffcc
+
+static void legion_platform_profile_exit(struct legion_private *priv)
+{
++ if (!enable_platformprofile) {
++ pr_info("Skipping unloading platform profile support because enable_platformprofile is false\n");
++ return;
++ }
+ pr_info("Unloading legion platform profile\n");
+ platform_profile_remove();
+ pr_info("Unloading legion platform profile done\n");
@@ -2109,35 +4666,44 @@ index 000000000..6edfeffcc
+ int sensor_id = (to_sensor_dev_attr(devattr))->index;
+ struct sensor_values values;
+ int outval;
-+
-+ read_sensor_values(&priv->ecram, priv->conf, &values);
++ int err = -EIO;
+
+ switch (sensor_id) {
+ case SENSOR_CPU_TEMP_ID:
-+ outval = 1000 * values.cpu_temp_celsius;
++ err = read_temperature(priv, 0, &outval);
++ outval *= 1000;
+ break;
+ case SENSOR_GPU_TEMP_ID:
-+ outval = 1000 * values.gpu_temp_celsius;
++ err = read_temperature(priv, 1, &outval);
++ outval *= 1000;
+ break;
+ case SENSOR_IC_TEMP_ID:
++ ec_read_sensor_values(&priv->ecram, priv->conf, &values);
+ outval = 1000 * values.ic_temp_celsius;
++ err = 0;
+ break;
+ case SENSOR_FAN1_RPM_ID:
-+ outval = values.fan1_rpm;
++ err = read_fanspeed(priv, 0, &outval);
+ break;
+ case SENSOR_FAN2_RPM_ID:
-+ outval = values.fan2_rpm;
++ err = read_fanspeed(priv, 1, &outval);
+ break;
+ case SENSOR_FAN1_TARGET_RPM_ID:
++ ec_read_sensor_values(&priv->ecram, priv->conf, &values);
+ outval = values.fan1_target_rpm;
++ err = 0;
+ break;
+ case SENSOR_FAN2_TARGET_RPM_ID:
++ ec_read_sensor_values(&priv->ecram, priv->conf, &values);
+ outval = values.fan2_target_rpm;
++ err = 0;
+ break;
+ default:
+ pr_info("Error reading sensor value with id %d\n", sensor_id);
+ return -EOPNOTSUPP;
+ }
++ if (err)
++ return err;
+
+ return sprintf(buf, "%d\n", outval);
+}
@@ -2182,7 +4748,7 @@ index 000000000..6edfeffcc
+ int point_id = to_sensor_dev_attr_2(devattr)->index;
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = read_fancurve(&priv->ecram, priv->conf, &fancurve);
++ err = read_fancurve(priv, &fancurve);
+ mutex_unlock(&priv->fancurve_mutex);
+
+ if (err) {
@@ -2249,6 +4815,7 @@ index 000000000..6edfeffcc
+ struct legion_private *priv = dev_get_drvdata(dev);
+ int fancurve_attr_id = to_sensor_dev_attr_2(devattr)->nr;
+ int point_id = to_sensor_dev_attr_2(devattr)->index;
++ bool write_fancurve_size = false;
+
+ if (!(point_id >= 0 && point_id < MAXFANCURVESIZE)) {
+ pr_info("Reading fancurve failed due to wrong point id: %d\n",
@@ -2265,7 +4832,7 @@ index 000000000..6edfeffcc
+ }
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = read_fancurve(&priv->ecram, priv->conf, &fancurve);
++ err = read_fancurve(priv, &fancurve);
+
+ if (err) {
+ pr_info("Reading fancurve failed\n");
@@ -2276,13 +4843,9 @@ index 000000000..6edfeffcc
+ switch (fancurve_attr_id) {
+ case FANCURVE_ATTR_PWM1:
+ valid = fancurve_set_rpm1(&fancurve, point_id, value);
-+ // TODO: remove
-+ //valid = valid && fancurve_set_rpm2(&fancurve, point_id, value);
+ break;
+ case FANCURVE_ATTR_PWM2:
+ valid = fancurve_set_rpm2(&fancurve, point_id, value);
-+ // TODO: remove
-+ //valid = valid && fancurve_set_rpm2(&fancurve, point_id, value);
+ break;
+ case FANCURVE_ATTR_CPU_TEMP:
+ valid = fancurve_set_cpu_temp_max(&fancurve, point_id, value);
@@ -2310,6 +4873,7 @@ index 000000000..6edfeffcc
+ break;
+ case FANCURVE_SIZE:
+ valid = fancurve_set_size(&fancurve, value, true);
++ write_fancurve_size = true;
+ break;
+ default:
+ pr_info("Writing fancurve failed due to wrong attribute id: %d\n",
@@ -2325,7 +4889,7 @@ index 000000000..6edfeffcc
+ goto error_mutex;
+ }
+
-+ err = write_fancurve(&priv->ecram, priv->conf, &fancurve, false);
++ err = write_fancurve(priv, &fancurve, write_fancurve_size);
+ if (err) {
+ pr_info("Writing fancurve failed for accessing hwmon at point_id: %d\n",
+ point_id);
@@ -2563,7 +5127,7 @@ index 000000000..6edfeffcc
+ struct legion_private *priv = dev_get_drvdata(dev);
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = read_minifancurve(&priv->ecram, priv->conf, &value);
++ err = ec_read_minifancurve(&priv->ecram, priv->conf, &value);
+ if (err) {
+ err = -1;
+ pr_info("Reading minifancurve not succesful\n");
@@ -2594,7 +5158,7 @@ index 000000000..6edfeffcc
+ }
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = write_minifancurve(&priv->ecram, priv->conf, value);
++ err = ec_write_minifancurve(&priv->ecram, priv->conf, value);
+ if (err) {
+ err = -1;
+ pr_info("Writing minifancurve not succesful\n");
@@ -2619,7 +5183,7 @@ index 000000000..6edfeffcc
+ struct legion_private *priv = dev_get_drvdata(dev);
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = read_maximumfanspeed(&priv->ecram, priv->conf, &value);
++ err = ec_read_fanfullspeed(&priv->ecram, priv->conf, &value);
+ if (err) {
+ err = -1;
+ pr_info("Reading pwm1_mode/maximumfanspeed not succesful\n");
@@ -2633,6 +5197,7 @@ index 000000000..6edfeffcc
+ return -1;
+}
+
++// TODO: remove? or use WMI method?
+static ssize_t pwm1_mode_store(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
@@ -2652,7 +5217,7 @@ index 000000000..6edfeffcc
+ is_maximumfanspeed = value == 0;
+
+ mutex_lock(&priv->fancurve_mutex);
-+ err = write_maximumfanspeed(&priv->ecram, priv->conf,
++ err = ec_write_fanfullspeed(&priv->ecram, priv->conf,
+ is_maximumfanspeed);
+ if (err) {
+ err = -1;
@@ -2777,21 +5342,37 @@ index 000000000..6edfeffcc
+ &sensor_dev_attr_pwm1_mode.dev_attr.attr, NULL
+};
+
++static umode_t legion_hwmon_is_visible(struct kobject *kobj,
++ struct attribute *attr, int idx)
++{
++ bool supported = true;
++ struct device *dev = kobj_to_dev(kobj);
++ struct legion_private *priv = dev_get_drvdata(dev);
++
++ if (attr == &sensor_dev_attr_minifancurve.dev_attr.attr)
++ supported = priv->conf->has_minifancurve;
++
++ supported = supported && (priv->conf->access_method_fancurve !=
++ ACCESS_METHOD_NO_ACCESS);
++
++ return supported ? attr->mode : 0;
++}
++
+static const struct attribute_group legion_hwmon_sensor_group = {
+ .attrs = sensor_hwmon_attributes,
-+ .is_visible = NULL // use modes from attributes
++ .is_visible = NULL
+};
+
+static const struct attribute_group legion_hwmon_fancurve_group = {
+ .attrs = fancurve_hwmon_attributes,
-+ .is_visible = NULL // use modes from attributes
++ .is_visible = legion_hwmon_is_visible,
+};
+
+static const struct attribute_group *legion_hwmon_groups[] = {
+ &legion_hwmon_sensor_group, &legion_hwmon_fancurve_group, NULL
+};
+
-+ssize_t legion_hwmon_init(struct legion_private *priv)
++static ssize_t legion_hwmon_init(struct legion_private *priv)
+{
+ //TODO: use hwmon_device_register_with_groups or
+ // hwmon_device_register_with_info (latter means all hwmon functions have to be
@@ -2800,7 +5381,7 @@ index 000000000..6edfeffcc
+ // TODO: Use devm_hwmon_device_register_with_groups ?
+ // some laptop drivers use this, some
+ struct device *hwmon_dev = hwmon_device_register_with_groups(
-+ &priv->platform_device->dev, "legion_hwmon", NULL,
++ &priv->platform_device->dev, "legion_hwmon", priv,
+ legion_hwmon_groups);
+ if (IS_ERR_OR_NULL(hwmon_dev)) {
+ pr_err("hwmon_device_register failed!\n");
@@ -2811,7 +5392,7 @@ index 000000000..6edfeffcc
+ return 0;
+}
+
-+void legion_hwmon_exit(struct legion_private *priv)
++static void legion_hwmon_exit(struct legion_private *priv)
+{
+ pr_info("Unloading legion hwon\n");
+ if (priv->hwmon_dev) {
@@ -2821,23 +5402,236 @@ index 000000000..6edfeffcc
+ pr_info("Unloading legion hwon done\n");
+}
+
++/* ACPI*/
++
++static int acpi_init(struct legion_private *priv, struct acpi_device *adev)
++{
++ int err;
++ unsigned long cfg;
++ bool skip_acpi_sta_check;
++ struct device *dev = &priv->platform_device->dev;
++
++ priv->adev = adev;
++ if (!priv->adev) {
++ dev_info(dev, "Could not get ACPI handle\n");
++ goto err_acpi_init;
++ }
++
++ skip_acpi_sta_check = force || (!priv->conf->acpi_check_dev);
++ if (!skip_acpi_sta_check) {
++ err = eval_int(priv->adev->handle, "_STA", &cfg);
++ if (err) {
++ dev_info(dev, "Could not evaluate ACPI _STA\n");
++ goto err_acpi_init;
++ }
++
++ err = eval_int(priv->adev->handle, "VPC0._CFG", &cfg);
++ if (err) {
++ dev_info(dev, "Could not evaluate ACPI _CFG\n");
++ goto err_acpi_init;
++ }
++ dev_info(dev, "ACPI CFG: %lu\n", cfg);
++ } else {
++ dev_info(dev, "Skipping ACPI _STA check");
++ }
++
++ return 0;
++
++err_acpi_init:
++ return err;
++}
++
++/* ============================= */
++/* White Keyboard Backlight */
++/* ============================ */
++// In style of ideapad-driver and with code modified from ideapad-driver.
++
++static enum led_brightness
++legion_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev)
++{
++ struct legion_private *priv =
++ container_of(led_cdev, struct legion_private, kbd_bl.led);
++
++ return legion_kbd_bl_brightness_get(priv);
++}
++
++static int legion_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev,
++ enum led_brightness brightness)
++{
++ struct legion_private *priv =
++ container_of(led_cdev, struct legion_private, kbd_bl.led);
++
++ return legion_kbd_bl_brightness_set(priv, brightness);
++}
++
++static int legion_kbd_bl_init(struct legion_private *priv)
++{
++ int brightness, err;
++
++ if (WARN_ON(priv->kbd_bl.initialized)) {
++ pr_info("Keyboard backlight already initialized\n");
++ return -EEXIST;
++ }
++
++ if (priv->conf->access_method_keyboard == ACCESS_METHOD_NO_ACCESS) {
++ pr_info("Keyboard backlight handling disabled by this driver\n");
++ return -ENODEV;
++ }
++
++ brightness = legion_kbd_bl_brightness_get(priv);
++ if (brightness < 0) {
++ pr_info("Error reading keyboard brighntess\n");
++ return brightness;
++ }
++
++ priv->kbd_bl.last_brightness = brightness;
++
++ // will be renamed to "platform::kbd_backlight_1" if it exists already
++ priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT;
++ priv->kbd_bl.led.max_brightness = 2;
++ priv->kbd_bl.led.brightness_get = legion_kbd_bl_led_cdev_brightness_get;
++ priv->kbd_bl.led.brightness_set_blocking =
++ legion_kbd_bl_led_cdev_brightness_set;
++ priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED;
++
++ err = led_classdev_register(&priv->platform_device->dev,
++ &priv->kbd_bl.led);
++ if (err)
++ return err;
++
++ priv->kbd_bl.initialized = true;
++
++ return 0;
++}
++
++/**
++ * Deinit keyboard backlight.
++ *
++ * Can also be called if init was not successful.
++ *
++ */
++static void legion_kbd_bl_exit(struct legion_private *priv)
++{
++ if (!priv->kbd_bl.initialized)
++ return;
++
++ priv->kbd_bl.initialized = false;
++
++ led_classdev_unregister(&priv->kbd_bl.led);
++}
++
++/* ============================= */
++/* Additional light driver */
++/* ============================ */
++
++static enum led_brightness
++legion_wmi_cdev_brightness_get(struct led_classdev *led_cdev)
++{
++ struct legion_private *priv =
++ container_of(led_cdev, struct legion_private, kbd_bl.led);
++ struct light *light_ins = container_of(led_cdev, struct light, led);
++
++ return legion_wmi_light_get(priv, light_ins->light_id,
++ light_ins->lower_limit,
++ light_ins->upper_limit);
++}
++
++static int legion_wmi_cdev_brightness_set(struct led_classdev *led_cdev,
++ enum led_brightness brightness)
++{
++ struct legion_private *priv =
++ container_of(led_cdev, struct legion_private, kbd_bl.led);
++ struct light *light_ins = container_of(led_cdev, struct light, led);
++
++ return legion_wmi_light_set(priv, light_ins->light_id,
++ light_ins->lower_limit,
++ light_ins->upper_limit, brightness);
++}
++
++static int legion_light_init(struct legion_private *priv,
++ struct light *light_ins, u8 light_id,
++ u8 lower_limit, u8 upper_limit, const char *name)
++{
++ int brightness, err;
++
++ if (WARN_ON(light_ins->initialized)) {
++ pr_info("Light already initialized for light: %u\n",
++ light_ins->light_id);
++ return -EEXIST;
++ }
++
++ light_ins->light_id = light_id;
++ light_ins->lower_limit = lower_limit;
++ light_ins->upper_limit = upper_limit;
++
++ brightness = legion_wmi_light_get(priv, light_ins->light_id,
++ light_ins->lower_limit,
++ light_ins->upper_limit);
++ if (brightness < 0) {
++ pr_info("Error reading brighntess for light: %u\n",
++ light_ins->light_id);
++ return brightness;
++ }
++
++ light_ins->led.name = name;
++ light_ins->led.max_brightness =
++ light_ins->upper_limit - light_ins->lower_limit;
++ light_ins->led.brightness_get = legion_wmi_cdev_brightness_get;
++ light_ins->led.brightness_set_blocking = legion_wmi_cdev_brightness_set;
++ light_ins->led.flags = LED_BRIGHT_HW_CHANGED;
++
++ err = led_classdev_register(&priv->platform_device->dev,
++ &light_ins->led);
++ if (err)
++ return err;
++
++ light_ins->initialized = true;
++
++ return 0;
++}
++
++/**
++ * Deinit light.
++ *
++ * Can also be called if init was not successful.
++ *
++ */
++static void legion_light_exit(struct legion_private *priv,
++ struct light *light_ins)
++{
++ if (!light_ins->initialized)
++ return;
++
++ light_ins->initialized = false;
++
++ led_classdev_unregister(&light_ins->led);
++}
++
+/* ============================= */
+/* Platform driver */
+/* ============================ */
+
-+int legion_add(struct platform_device *pdev)
++static int legion_add(struct platform_device *pdev)
+{
+ struct legion_private *priv;
+ const struct dmi_system_id *dmi_sys;
+ int err;
+ u16 ec_read_id;
++ bool skip_ec_id_check;
++ bool is_ec_id_valid;
+ bool is_denied = true;
+ bool is_allowed = false;
+ bool do_load_by_list = false;
+ bool do_load = false;
+ //struct legion_private *priv = dev_get_drvdata(&pdev->dev);
-+ dev_info(&pdev->dev, "legion_laptop platform driver %s probing\n",
-+ MODULEVERSION);
++ dev_info(&pdev->dev, "legion_laptop platform driver probing\n");
++
++ dev_info(
++ &pdev->dev,
++ "Read identifying information: DMI_SYS_VENDOR: %s; DMI_PRODUCT_NAME: %s; DMI_BIOS_VERSION:%s\n",
++ dmi_get_system_info(DMI_SYS_VENDOR),
++ dmi_get_system_info(DMI_PRODUCT_NAME),
++ dmi_get_system_info(DMI_BIOS_VERSION));
+
+ // TODO: allocate?
+ priv = &_priv;
@@ -2891,26 +5685,46 @@ index 000000000..6edfeffcc
+
+ priv->conf = dmi_sys->driver_data;
+
-+ err = ecram_init(&priv->ecram, priv->conf->ecram_access_method,
-+ priv->conf->memoryio_physical_start,
-+ priv->conf->memoryio_physical_ec_start,
++ err = acpi_init(priv, ACPI_COMPANION(&pdev->dev));
++ if (err) {
++ dev_info(&pdev->dev, "Could not init ACPI access: %d\n", err);
++ goto err_acpi_init;
++ }
++
++ // TODO: remove; only used for reverse engineering
++ pr_info("Creating RAM access to embedded controller\n");
++ err = ecram_memoryio_init(&priv->ec_memoryio,
++ priv->conf->ramio_physical_start, 0,
++ priv->conf->ramio_size);
++ if (err) {
++ dev_info(
++ &pdev->dev,
++ "Could not init RAM access to embedded controller: %d\n",
++ err);
++ goto err_ecram_memoryio_init;
++ }
++
++ err = ecram_init(&priv->ecram, priv->conf->memoryio_physical_ec_start,
+ priv->conf->memoryio_size);
+ if (err) {
+ dev_info(&pdev->dev,
-+ "Could not init access to embedded controller\n");
++ "Could not init access to embedded controller: %d\n",
++ err);
+ goto err_ecram_init;
+ }
+
+ ec_read_id = read_ec_id(&priv->ecram, priv->conf);
+ dev_info(&pdev->dev, "Read embedded controller ID 0x%x\n", ec_read_id);
-+ if (priv->conf->check_embedded_controller_id &&
-+ !(ec_read_id == priv->conf->embedded_controller_id)) {
++ skip_ec_id_check = force || (!priv->conf->check_embedded_controller_id);
++ is_ec_id_valid = skip_ec_id_check ||
++ (ec_read_id == priv->conf->embedded_controller_id);
++ if (!is_ec_id_valid) {
+ err = -ENOMEM;
+ dev_info(&pdev->dev, "Expected EC chip id 0x%x but read 0x%x\n",
+ priv->conf->embedded_controller_id, ec_read_id);
+ goto err_ecram_id;
+ }
-+ if (!priv->conf->check_embedded_controller_id) {
++ if (skip_ec_id_check) {
+ dev_info(&pdev->dev,
+ "Skipped checking embedded controller id\n");
+ }
@@ -2921,33 +5735,65 @@ index 000000000..6edfeffcc
+ pr_info("Creating sysfs inteface\n");
+ err = legion_sysfs_init(priv);
+ if (err) {
-+ dev_info(&pdev->dev, "Creating sysfs interface failed\n");
++ dev_info(&pdev->dev, "Creating sysfs interface failed: %d\n",
++ err);
+ goto err_sysfs_init;
+ }
+
+ pr_info("Creating hwmon interface");
+ err = legion_hwmon_init(priv);
-+ if (err)
++ if (err) {
++ dev_info(&pdev->dev, "Creating hwmon interface failed: %d\n",
++ err);
+ goto err_hwmon_init;
++ }
+
+ pr_info("Creating platform profile support\n");
+ err = legion_platform_profile_init(priv);
+ if (err) {
-+ dev_info(&pdev->dev, "Creating platform profile failed\n");
++ dev_info(&pdev->dev, "Creating platform profile failed: %d\n",
++ err);
+ goto err_platform_profile;
+ }
+
+ pr_info("Init WMI driver support\n");
+ err = legion_wmi_init();
+ if (err) {
-+ dev_info(&pdev->dev, "Init WMI driver failed\n");
++ dev_info(&pdev->dev, "Init WMI driver failed: %d\n", err);
+ goto err_wmi;
+ }
+
++ pr_info("Init keyboard backlight LED driver\n");
++ err = legion_kbd_bl_init(priv);
++ if (err) {
++ dev_info(
++ &pdev->dev,
++ "Init keyboard backlight LED driver failed. Skipping ...\n");
++ }
++
++ pr_info("Init Y-Logo LED driver\n");
++ err = legion_light_init(priv, &priv->ylogo_light, LIGHT_ID_YLOGO, 0, 1,
++ "platform::ylogo");
++ if (err) {
++ dev_info(&pdev->dev,
++ "Init Y-Logo LED driver failed. Skipping ...\n");
++ }
++
++ pr_info("Init IO-Port LED driver\n");
++ err = legion_light_init(priv, &priv->iport_light, LIGHT_ID_IOPORT, 1, 2,
++ "platform::ioport");
++ if (err) {
++ dev_info(&pdev->dev,
++ "Init IO-Port LED driver failed. Skipping ...\n");
++ }
++
+ dev_info(&pdev->dev, "legion_laptop loaded for this device\n");
+ return 0;
+
+ // TODO: remove eventually
++ legion_light_exit(priv, &priv->iport_light);
++ legion_light_exit(priv, &priv->ylogo_light);
++ legion_kbd_bl_exit(priv);
+ legion_wmi_exit();
+err_wmi:
+ legion_platform_profile_exit(priv);
@@ -2960,6 +5806,9 @@ index 000000000..6edfeffcc
+err_ecram_id:
+ ecram_exit(&priv->ecram);
+err_ecram_init:
++ ecram_memoryio_exit(&priv->ec_memoryio);
++err_ecram_memoryio_init:
++err_acpi_init:
+ legion_shared_exit(priv);
+err_legion_shared_init:
+err_model_mismtach:
@@ -2967,15 +5816,17 @@ index 000000000..6edfeffcc
+ return err;
+}
+
-+int legion_remove(struct platform_device *pdev)
++static int legion_remove(struct platform_device *pdev)
+{
+ struct legion_private *priv = dev_get_drvdata(&pdev->dev);
-+ // TODO: remove this
-+ pr_info("Unloading legion\n");
++
+ mutex_lock(&legion_shared_mutex);
+ priv->loaded = false;
+ mutex_unlock(&legion_shared_mutex);
+
++ legion_light_exit(priv, &priv->iport_light);
++ legion_light_exit(priv, &priv->ylogo_light);
++ legion_kbd_bl_exit(priv);
+ // first unregister wmi, so toggling powermode does not
+ // generate events anymore that even might be delayed
+ legion_wmi_exit();
@@ -2983,19 +5834,20 @@ index 000000000..6edfeffcc
+
+ // toggle power mode to load default setting from embedded controller
+ // again
-+ toggle_powermode(&priv->ecram, priv->conf);
++ toggle_powermode(priv);
+
+ legion_hwmon_exit(priv);
+ legion_sysfs_exit(priv);
+ legion_debugfs_exit(priv);
+ ecram_exit(&priv->ecram);
++ ecram_memoryio_exit(&priv->ec_memoryio);
+ legion_shared_exit(priv);
+
+ pr_info("Legion platform unloaded\n");
+ return 0;
+}
+
-+int legion_resume(struct platform_device *pdev)
++static int legion_resume(struct platform_device *pdev)
+{
+ //struct legion_private *priv = dev_get_drvdata(&pdev->dev);
+ dev_info(&pdev->dev, "Resumed in legion-laptop\n");
@@ -3016,7 +5868,8 @@ index 000000000..6edfeffcc
+
+// same as ideapad
+static const struct acpi_device_id legion_device_ids[] = {
-+ { "PNP0C09", 0 }, // todo: change to "VPC2004"
++ // todo: change to "VPC2004", and also ACPI paths
++ { "PNP0C09", 0 },
+ { "", 0 },
+};
+MODULE_DEVICE_TABLE(acpi, legion_device_ids);
@@ -3032,53 +5885,29 @@ index 000000000..6edfeffcc
+ },
+};
+
-+int __init legion_init(void)
++static int __init legion_init(void)
+{
+ int err;
-+ // TODO: remove version
-+ pr_info("legion_laptop %s starts loading\n", MODULEVERSION);
-+
-+ // TODO: remove this, make a comment
-+ if (!(MAXFANCURVESIZE <= 10)) {
-+ pr_debug("Fan curve size too large\n");
-+ err = -ENOMEM;
-+ goto error_assert;
-+ }
-+
-+ // TODO: remove this
-+ pr_info("Read identifying information: DMI_SYS_VENDOR: %s; DMI_PRODUCT_NAME: %s; DMI_BIOS_VERSION:%s\n",
-+ dmi_get_system_info(DMI_SYS_VENDOR),
-+ dmi_get_system_info(DMI_PRODUCT_NAME),
-+ dmi_get_system_info(DMI_BIOS_VERSION));
+
++ pr_info("legion_laptop starts loading\n");
+ err = platform_driver_register(&legion_driver);
+ if (err) {
+ pr_info("legion_laptop: platform_driver_register failed\n");
-+ goto error_platform_driver;
++ return err;
+ }
+
-+ // TODO: remove version
-+ // pr_info("legion_laptop %s loading done\n", MODULEVERSION);
-+
+ return 0;
-+
-+error_platform_driver:
-+error_assert:
-+ return err;
+}
+
+module_init(legion_init);
+
-+void __exit legion_exit(void)
++static void __exit legion_exit(void)
+{
-+ // TODO: remove this
-+ pr_info("legion_laptop %s starts unloading\n", MODULEVERSION);
+ platform_driver_unregister(&legion_driver);
-+ // TODO: remove this
-+ pr_info("legion_laptop %s unloaded\n", MODULEVERSION);
++ pr_info("legion_laptop exit\n");
+}
+
+module_exit(legion_exit);
--
-2.39.1
+2.41.0