summaryrefslogtreecommitdiff
path: root/src/ec/clevo/it5570e
diff options
context:
space:
mode:
authorMichael Niewöhner <foss@mniewoehner.de>2021-12-01 19:09:13 +0100
committerMichael Niewöhner <foss@mniewoehner.de>2022-11-11 22:42:46 +0000
commite1e65cb0f1cad6117d48503f2f78d115caecc55b (patch)
treeb6d42b3968fd2e6d57062031a48ac65123bb03ca /src/ec/clevo/it5570e
parent064c6ced40a0acd7c4127414ee04c90b3a458751 (diff)
ec/clevo/it5570e: add driver for EC used on various Clevo laptops
This adds a driver for the ITE IT5570E EC in combination with Clevo vendor EC firmware. The interface is mostly identical on various laptop models. Thus, we have implemented one common driver to support them all. The following features were implemented: - Basics like battery, ac, etc. - Suspend/hibernate support: S0ix, S3*, S4/S5 - Save/restore of keyboard backlight level during S0ix without the need for Clevo vendor software (ControlCenter) - Flexicharger - Fn keys (backlight, volume, airplane etc.) - Various configuration options via Kconfig / CMOS options * Note: S3 support works at least on L140CU (Cometlake), but it's not enabled for this board because S0ix is used. Not implemented, yet: - Type-C UCSI: the EC firmware seems to be buggy (with vendor fw, too) - dGPU support is WIP An example of how this driver can be hooked up by a board can be seen in in change CB:59850, where support for the L140MU is added. Known issues: - Touchpad toggle: The touchpad toggle (Fn-F1) has two modes, Ctrl-Alt-F9 mode and keycodes 0xf7/0xf8 mode. Ctrl-Alt-F9 is the native touchpad toggle shortcut on Windows. On Linux this would switch to virtual console 9, if enabled. Thus, one should use the keycodes mode and add udev rules as specified in [1]. If VT9 is disabled, Ctrl-Alt-F9 mode could be used to set up a keyboard shortcut command toggling the touchpad. - Multi-fan systems The Clevo NV41MZ (w/o dGPU) has two fans that should be in-sync. However, the second fan does not spin. This needs further investigation. [1] https://docs.dasharo.com/variants/clevo_nv41/post_install/ Testing the various functionalities of this EC driver was done in the changes hooking up this driver for the boards. Change-Id: Ic8c0bee9002ad9edcd10c83b775fc723744caaa0 Co-authored-by: Michał Kopeć <michal.kopec@3mdeb.com> Co-authored-by: Michał Żygowski <michal.zygowski@3mdeb.com> Co-authored-by: Michael Niewöhner <foss@mniewoehner.de> Signed-off-by: Michał Kopeć <michal.kopec@3mdeb.com> Signed-off-by: Michał Żygowski <michal.zygowski@3mdeb.com> Signed-off-by: Michael Niewöhner <foss@mniewoehner.de> Reviewed-on: https://review.coreboot.org/c/coreboot/+/68791 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Diffstat (limited to 'src/ec/clevo/it5570e')
-rw-r--r--src/ec/clevo/it5570e/Kconfig126
-rw-r--r--src/ec/clevo/it5570e/Makefile.inc16
-rw-r--r--src/ec/clevo/it5570e/acpi/ac.asl12
-rw-r--r--src/ec/clevo/it5570e/acpi/battery.asl116
-rw-r--r--src/ec/clevo/it5570e/acpi/buttons.asl13
-rw-r--r--src/ec/clevo/it5570e/acpi/common.asl28
-rw-r--r--src/ec/clevo/it5570e/acpi/ec.asl176
-rw-r--r--src/ec/clevo/it5570e/acpi/ec_queries.asl387
-rw-r--r--src/ec/clevo/it5570e/acpi/ec_ram.asl265
-rw-r--r--src/ec/clevo/it5570e/acpi/hid.asl109
-rw-r--r--src/ec/clevo/it5570e/acpi/lid.asl21
-rw-r--r--src/ec/clevo/it5570e/chip.h27
-rw-r--r--src/ec/clevo/it5570e/commands.c168
-rw-r--r--src/ec/clevo/it5570e/commands.h77
-rw-r--r--src/ec/clevo/it5570e/early_init.c24
-rw-r--r--src/ec/clevo/it5570e/early_init.h8
-rw-r--r--src/ec/clevo/it5570e/ec.c128
-rw-r--r--src/ec/clevo/it5570e/ec.h32
-rw-r--r--src/ec/clevo/it5570e/i2ec.c50
-rw-r--r--src/ec/clevo/it5570e/i2ec.h13
-rw-r--r--src/ec/clevo/it5570e/smbios.c17
-rw-r--r--src/ec/clevo/it5570e/smihandler.c37
-rw-r--r--src/ec/clevo/it5570e/smm.h10
-rw-r--r--src/ec/clevo/it5570e/ssdt.c152
24 files changed, 2012 insertions, 0 deletions
diff --git a/src/ec/clevo/it5570e/Kconfig b/src/ec/clevo/it5570e/Kconfig
new file mode 100644
index 0000000000..8620eb1233
--- /dev/null
+++ b/src/ec/clevo/it5570e/Kconfig
@@ -0,0 +1,126 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config EC_CLEVO_IT5570E
+ bool
+ select EC_ACPI
+ help
+ IT5570E embedded controller in Clevo notebooks
+
+if EC_CLEVO_IT5570E
+
+config EC_CLEVO_IT5570E_MEM_BASE
+ hex
+ help
+ Memory address for the EC memory region mapped via LGMR
+
+config EC_CLEVO_IT5570E_AC_FAN_ALWAYS_ON
+ bool "Fan always on with AC attached"
+ default n
+ help
+ Never turn the fan fully off when AC is attached.
+
+ This setting can be overridden by cmos option `ac_fan_always_on`.
+
+config EC_CLEVO_IT5570E_KBLED_BOOTEFFECT
+ bool "Keyboard boot effect"
+ default n
+ help
+ Enable the "breathing" boot effect of the LED keyboard.
+
+ This setting can be overridden by cmos option `kbled_booteffect`.
+
+config EC_CLEVO_IT5570E_KBLED_TIMEOUT
+ int "Keyboard backlight timeout"
+ default 15
+ help
+ Keyboard backlight timeout in seconds. 0 keeps the backlight
+ always on.
+
+ This setting can be overridden by cmos option `kbled_timeout`.
+
+config EC_CLEVO_IT5570E_FN_WIN_SWAP
+ bool "Swap Fn/Windows keys"
+ default n
+ help
+ Swap the Fn and Windows key.
+
+ This setting can be overridden by cmos option `fn_win_swap`.
+
+config EC_CLEVO_IT5570E_FLEXICHARGER
+ bool "Flexicharger"
+ default n
+ help
+ Enable the Flexicharger functionality.
+
+ This setting can be overridden by cmos option `flexicharger`.
+
+if EC_CLEVO_IT5570E_FLEXICHARGER
+
+config EC_CLEVO_IT5570E_FLEXICHG_START
+ int "Start charge threshold"
+ default 95
+ help
+ Start charge threshold in percent.
+
+ This setting can be overridden by cmos option `flexicharger_start`.
+
+config EC_CLEVO_IT5570E_FLEXICHG_STOP
+ int "Stop charge threshold"
+ default 100
+ help
+ Stop charge threshold in percent.
+
+ This setting can be overridden by cmos option `flexicharger_stop`.
+
+endif
+
+choice
+ prompt "Camera default state"
+ default EC_CLEVO_IT5570E_CAM_BOOT_STATE_KEEP
+ help
+ Camera default state.
+
+ This setting can be overridden by cmos option `camera_boot_state`.
+
+config EC_CLEVO_IT5570E_CAM_BOOT_STATE_KEEP
+ bool "Keep previous state"
+
+config EC_CLEVO_IT5570E_CAM_BOOT_STATE_DISABLE
+ bool "Disable"
+
+config EC_CLEVO_IT5570E_CAM_BOOT_STATE_ENABLE
+ bool "Enable"
+
+endchoice
+
+config EC_CLEVO_IT5570E_CAM_BOOT_STATE
+ int
+ default 0 if EC_CLEVO_IT5570E_CAM_BOOT_STATE_DISABLE
+ default 1 if EC_CLEVO_IT5570E_CAM_BOOT_STATE_ENABLE
+ default 2
+
+choice
+ prompt "Touchpad toggle mode"
+ default EC_CLEVO_IT5570E_TP_TOGGLE_MODE_CTRLALTF9
+ help
+ There are two modes for the touchpad toggle (Fn-F1):
+ - Ctrl-Alt-F9 mode sends the windows-native touchpad toggle keyboard shortcut.
+ - Keycode mode sends special key codes f7/f8 which can be configured in udev
+ to be handled as touchpad toggle.
+
+ This setting can be overridden by cmos option `tp_toggle_mode`.
+
+config EC_CLEVO_IT5570E_TP_TOGGLE_MODE_CTRLALTF9
+ bool "Ctrl-Alt-F9"
+
+config EC_CLEVO_IT5570E_TP_TOGGLE_MODE_KEYOCDE_F7F8
+ bool "Keycode f7/f8"
+
+endchoice
+
+config EC_CLEVO_IT5570E_TP_TOGGLE_MODE
+ int
+ default 0 if EC_CLEVO_IT5570E_TP_TOGGLE_MODE_CTRLALTF9
+ default 1 if EC_CLEVO_IT5570E_TP_TOGGLE_MODE_KEYOCDE_F7F8
+
+endif
diff --git a/src/ec/clevo/it5570e/Makefile.inc b/src/ec/clevo/it5570e/Makefile.inc
new file mode 100644
index 0000000000..46d97d2dc6
--- /dev/null
+++ b/src/ec/clevo/it5570e/Makefile.inc
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+ifeq ($(CONFIG_EC_CLEVO_IT5570E),y)
+
+bootblock-y += early_init.c
+bootblock-y += i2ec.c
+
+ramstage-y += ec.c
+ramstage-y += commands.c
+ramstage-y += smbios.c
+ramstage-y += ssdt.c
+
+smm-y += commands.c
+smm-y += smihandler.c
+
+endif
diff --git a/src/ec/clevo/it5570e/acpi/ac.asl b/src/ec/clevo/it5570e/acpi/ac.asl
new file mode 100644
index 0000000000..49f4c26f83
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/ac.asl
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+Device (AC)
+{
+ Name (_HID, "ACPI0003")
+ Name (_PCL, Package () { \_SB })
+
+ Method (_PSR)
+ {
+ Return (\_SB.PCI0.LPCB.EC0.ADP)
+ }
+}
diff --git a/src/ec/clevo/it5570e/acpi/battery.asl b/src/ec/clevo/it5570e/acpi/battery.asl
new file mode 100644
index 0000000000..7b29a2ef9b
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/battery.asl
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#define PBST_STATE 0
+#define PBST_PRESENT_RATE 1
+#define PBST_REMAINING_CAP 2
+#define PBST_PRESENT_VOLT 3
+
+#define PBIX_DESIGN_CAP 2
+#define PBIX_LAST_FULL_CHG_CAP 3
+#define PBIX_DESIGN_VOLT 5
+#define PBIX_DESIGN_CAP_WARN 6
+#define PBIX_DESIGN_CAP_LOW 7
+#define PBIX_CYCLE_COUNT 8
+#define PBIX_MODEL 16
+#define PBIX_SERIAL 17
+#define PBIX_TYPE 18
+#define PBIX_OEM_INFO 19
+
+Device (BAT0)
+{
+ Name (_HID, EisaId ("PNP0C0A"))
+ Name (_UID, 0)
+ Name (_PCL, Package () { \_SB })
+
+ Name (BFCC, 0) /* Full Charge Capacity */
+
+ Name (PBST, Package () {
+ 0, // Battery State
+ // [0] : Discharging
+ // [1] : Charging
+ // [2] : Critical Low
+ 0, // Battery Present Rate
+ 0, // Battery Remaining Capacity
+ 0, // Battery Present Voltage
+ })
+
+ Name (PBIX, Package () {
+ 1, // Revision
+ 1, // Power Unit (1 = mA(h))
+ 0, // Design Capacity
+ 0, // Last Full Charge Capacity
+ 1, // Battery Technology (1 = rechargeable)
+ 0, // Design Voltage
+ 0, // Design Capacity of Warning
+ 0, // Design Capacity of Low
+ 0, // Cycle Count
+ 95000, // Measurement Accuracy (95 %)
+ 0, // Max Sampling Time
+ 0, // Min Sampling Time
+ 0, // Max Averaging Interval
+ 0, // Min Averaging Interval
+ 1, // Battery Capacity Granularity 1 (low < warning)
+ 1, // Battery Capacity Granularity 2 (warning < full)
+ " ", // Model Number
+ " ", // Serial Number
+ " ", // Battery Type
+ " ", // OEM Information
+ 0, // Battery Swapping Capability (0 = not swappable)
+ })
+
+ Method (_STA)
+ {
+ Local0 = 0x0f
+
+ If (\_SB.PCI0.LPCB.EC0.BAT0)
+ {
+ Local0 |= 0x10 /* battery present */
+ }
+
+ Return (Local0)
+ }
+
+ Method (_BST)
+ {
+ /*
+ * Trigger update of static info update when
+ * the last full charge capacity changes.
+ * (This is what the vendor does.)
+ */
+ If (BFCC != ToInteger (\_SB.PCI0.LPCB.EC0.BFC0))
+ {
+ Notify (BAT0, 0x81) /* information change */
+ }
+
+ /* Convert signed current to absolute value */
+ Local0 = ToInteger (\_SB.PCI0.LPCB.EC0.BPR0)
+ If (Local0 & 0x8000)
+ {
+ Local0 = (~Local0 & 0xffff) + 1
+ }
+
+ PBST [PBST_STATE] = ToInteger (\_SB.PCI0.LPCB.EC0.BST0)
+ PBST [PBST_PRESENT_RATE] = Local0
+ PBST [PBST_REMAINING_CAP] = ToInteger (\_SB.PCI0.LPCB.EC0.BRC0)
+ PBST [PBST_PRESENT_VOLT] = ToInteger (\_SB.PCI0.LPCB.EC0.BPV0)
+
+ Return (PBST)
+ }
+
+ Method (_BIX)
+ {
+ BFCC = ToInteger (\_SB.PCI0.LPCB.EC0.BFC0)
+ PBIX [PBIX_DESIGN_CAP] = ToInteger (\_SB.PCI0.LPCB.EC0.BDC0)
+ PBIX [PBIX_LAST_FULL_CHG_CAP] = ToInteger (\_SB.PCI0.LPCB.EC0.BFC0)
+ PBIX [PBIX_DESIGN_VOLT] = ToInteger (\_SB.PCI0.LPCB.EC0.BDV0)
+ PBIX [PBIX_DESIGN_CAP_WARN] = ToInteger (\_SB.PCI0.LPCB.EC0.BCW0)
+ PBIX [PBIX_DESIGN_CAP_LOW] = ToInteger (\_SB.PCI0.LPCB.EC0.BCL0)
+ PBIX [PBIX_CYCLE_COUNT] = ToInteger (\_SB.PCI0.LPCB.EC0.CYC0)
+ PBIX [PBIX_MODEL] = ToBuffer (\_SB.PCI0.LPCB.EC0.BMO0)
+ PBIX [PBIX_SERIAL] = ToHexString (\_SB.PCI0.LPCB.EC0.BSN0)
+ PBIX [PBIX_TYPE] = ToBuffer (\_SB.PCI0.LPCB.EC0.BTY0)
+ PBIX [PBIX_OEM_INFO] = ToBuffer (\_SB.PCI0.LPCB.EC0.BIF0)
+
+ Return (PBIX)
+ }
+}
diff --git a/src/ec/clevo/it5570e/acpi/buttons.asl b/src/ec/clevo/it5570e/acpi/buttons.asl
new file mode 100644
index 0000000000..3f5dd12973
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/buttons.asl
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+Device (PWRB)
+{
+ Name (_HID, "PNP0C0C")
+ Name (_PRW, Package () { EC_GPE_PWRB, 4 })
+}
+
+Device (SLPB)
+{
+ Name (_HID, "PNP0C0E")
+ Name (_PRW, Package () { EC_GPE_SLPB, 3 })
+}
diff --git a/src/ec/clevo/it5570e/acpi/common.asl b/src/ec/clevo/it5570e/acpi/common.asl
new file mode 100644
index 0000000000..21ddbd9816
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/common.asl
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_GPE_SCI
+#error EC_GPE_SCI must be defined by mainboard.
+#endif
+#ifndef EC_GPE_PWRB
+#error EC_GPE_PWRB must be defined by mainboard.
+#endif
+#ifndef EC_GPE_SLPB
+#error EC_GPE_PWRB must be defined by mainboard.
+#endif
+#ifndef EC_GPE_LID
+#error EC_GPE_LID must be defined by mainboard.
+#endif
+
+Scope (\_SB)
+{
+ #include "ac.asl"
+ #include "battery.asl"
+ #include "buttons.asl"
+ #include "lid.asl"
+ #include "hid.asl"
+
+ Scope (PCI0.LPCB)
+ {
+ #include "ec.asl"
+ }
+}
diff --git a/src/ec/clevo/it5570e/acpi/ec.asl b/src/ec/clevo/it5570e/acpi/ec.asl
new file mode 100644
index 0000000000..a4b282c4cc
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/ec.asl
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <ec/clevo/it5570e/commands.h>
+
+#define EC_DATA_IO 0x62
+#define EC_SC_IO 0x66
+
+Device (EC0)
+{
+ Name (_HID, "PNP0C09")
+ Name (_GPE, EC_GPE_SCI)
+
+ Name (_CRS, ResourceTemplate ()
+ {
+ IO (Decode16, 0x62, 0x62, 0, 1)
+ IO (Decode16, 0x66, 0x66, 0, 1)
+ })
+
+ #include <ec/acpi/ec.asl>
+
+ OperationRegion (PMS0, SystemMemory, 0xfe001b1c, 4)
+ Field (PMS0, DWordAcc, NoLock, Preserve)
+ {
+ , 15,
+ GS0E, 1, /* Global SLP_S0# enable */
+ }
+
+ #include "ec_ram.asl"
+
+ External (SFCV, MethodObj) /* Generated in SSDT */
+
+ Method (INIT)
+ {
+ Printf ("EC: INIT")
+
+ ECOS = 2 /* ACPI with driver */
+ BLCT = 1 /* Enable ACPI brightness control */
+ CAMK = 1 /* Enable camera hotkey */
+
+ SFCV () /* Apply custom fan curve */
+
+ PNOT ()
+ }
+
+ Method (_INI)
+ {
+ Printf ("EC: _INI")
+
+ INIT ()
+ }
+
+ /* Send FCMD */
+ Method (SFCC, 1, Serialized)
+ {
+ FCMD = Arg0
+ SEND_EC_COMMAND(0)
+
+ /* EC sets FCMD = 0x00 on completion (FCMD = 0xfa on some commands) */
+ Local0 = 50
+ While (Local0--)
+ {
+ Stall (1)
+ If (FCMD == 0x00 || FCMD == 0xfa)
+ {
+ Printf("EC: FCMD 0x%o completed after %o ms",
+ ToHexString(Arg0), ToDecimalString(50 - Local0))
+ Return (1)
+ }
+ }
+ Printf("EC: FCMD 0x%o timed out", ToHexString(Arg0))
+ Return (0)
+ }
+
+ /*
+ * Method called from _PTS prior to system sleep state entry
+ */
+ Method (PTS, 1, Serialized)
+ {
+ Printf ("EC: PTS: Arg0=%o", ToDecimalString(Arg0))
+
+ WFNO = 0 /* Clear wake cause */
+ }
+
+ /*
+ * Method called from _WAK prior to system sleep state wakeup
+ */
+ Method (WAK, 1, Serialized)
+ {
+ Printf ("EC: WAK: Arg0=%o, WFNO=%o", ToDecimalString(Arg0), ToHexString (WFNO))
+
+ INIT ()
+
+ /* update battery */
+ Notify (\_SB.BAT0, 0x00) /* bus check */
+ Notify (\_SB.BAT0, 0x80) /* state change */
+ Notify (\_SB.BAT0, 0x81) /* information change */
+
+ /* update AC */
+ Notify (\_SB.AC, 0x00) /* bus check */
+ Notify (\_SB.AC, 0x80) /* state change */
+
+ If (Arg0 == 0x03 || Arg0 == 0x04) {
+ Notify (\_SB.PWRB, 0x02) /* Wake */
+ }
+ }
+
+ /*
+ * Display On/Off Notifications
+ * Called from \_SB.PEPD._DSM
+ */
+ Name (KBLV, 0)
+ Method (EDSX, 1, Serialized)
+ {
+ Printf ("EC: PEP display hook, state=%o", ToDecimalString (Arg0))
+
+ If (S5FG)
+ {
+ Return ()
+ }
+
+ /* Display off */
+ If (!Arg0)
+ {
+ /* Store current keyboard backlight level */
+ FDAT = FDAT_KBLED_WHITE_GET_LEVEL
+ SFCC (FCMD_KLED)
+ KBLV = FBUF
+
+ /* Turn off keyboard backlight */
+ FDAT = FDAT_KBLED_WHITE_SET_LEVEL
+ FBUF = 0x00
+ SFCC (FCMD_KLED)
+ }
+
+ /* Display on */
+ Else
+ {
+ /* Restore keyboard backlight level */
+ FDAT = FDAT_KBLED_WHITE_SET_LEVEL
+ FBUF = KBLV
+ SFCC (FCMD_KLED)
+ }
+ }
+
+ /*
+ * S0ix Entry/Exit Notifications
+ * Called from \_SB.PEPD._DSM
+ */
+ Method (S0IX, 1, Serialized)
+ {
+ If (S5FG)
+ {
+ Return ()
+ }
+
+ Printf ("EC: S0ix change, state=%o", ToDecimalString (Arg0))
+
+ /* S0ix entry */
+ If (Arg0)
+ {
+ MSFG = 1 /* Notify EC */
+ }
+
+ /* S0ix exit */
+ Else
+ {
+ GS0E = 0 /* Block SLP_S0# assertion during wakeup */
+ MSFG = 0 /* Notfiy EC */
+ Sleep (150) /* wait for EC to become ready */
+ SFCV () /* Apply custom fan curve */
+ GS0E = 1 /* Unblock SLP_S0# */
+ }
+ }
+
+ #include "ec_queries.asl"
+}
diff --git a/src/ec/clevo/it5570e/acpi/ec_queries.asl b/src/ec/clevo/it5570e/acpi/ec_queries.asl
new file mode 100644
index 0000000000..ff9cd06470
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/ec_queries.asl
@@ -0,0 +1,387 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/*
+ * Note:
+ * The code contains all queries/events that are known to be implemented by the EC firmware.
+ * Not all events are known yet, though.
+ */
+
+Method (_Q0A)
+{
+ Printf ("EC: _Q0A: Toggle touchpad, SCIE=0x%o, state=%o",
+ ToHexString (SCIE), ToDecimalString(SCIE & 1))
+}
+
+Method (_Q0B)
+{
+ Printf ("EC: _Q0B: LCD off, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q0C)
+{
+ Printf ("EC: _Q0C: Toggle mute, SCIE=0x%o, state=%o",
+ ToHexString (SCIE), ToDecimalString(SCIE & 1))
+}
+
+Method (_Q0E)
+{
+ Printf ("EC: _Q0E: Decrease volume")
+}
+
+Method (_Q0F)
+{
+ Printf ("EC: _Q0F: Increase volume")
+}
+
+Method (_Q10)
+{
+ Printf ("EC: _Q10, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q11)
+{
+ Printf ("EC: _Q11: Decrease brightness")
+
+ If (CondRefOf (\_SB.PCI0.GFX0.LCD0)) {
+ Notify (\_SB.PCI0.GFX0.LCD0, 0x87)
+ }
+}
+
+Method (_Q12)
+{
+ Printf ("EC: _Q12: Increase brightness")
+
+ If (CondRefOf (\_SB.PCI0.GFX0.LCD0)) {
+ Notify (\_SB.PCI0.GFX0.LCD0, 0x86)
+ }
+}
+
+Method (_Q13)
+{
+ Printf ("EC: _Q13: Toggle camera, SCIE=0x%o, state=%o",
+ ToHexString (SCIE), ToDecimalString(SCIE & 1))
+}
+
+Method (_Q14)
+{
+ Printf ("EC: _Q14: Toggle airplane mode, SCIE=0x%o", ToHexString (SCIE))
+
+ \_SB.HIDD.HPEM (8)
+}
+
+Method (_Q15)
+{
+ Printf ("EC: _Q15: Sleep button")
+
+ Notify (\_SB.SLPB, 0x80)
+}
+
+Method (_Q16)
+{
+ Printf ("EC: _Q16: Power event (AC/BAT0)")
+
+ Notify (AC, 0x80) /* status change */
+ If (BAT0)
+ {
+ Notify (\_SB.BAT0, 0x80) /* status change */
+ Notify (\_SB.BAT0, 0x81) /* information change */
+ }
+}
+
+Method (_Q17)
+{
+ Printf ("EC: _Q17: Battery presence change, state=%o", ToDecimalString (BAT0))
+
+ Notify (\_SB.BAT0, 0x81) /* information change */
+}
+
+Method (_Q19)
+{
+ Printf ("EC: _Q19: Battery critical")
+
+ Notify (\_SB.BAT0, 0x80) /* status change */
+}
+
+Method (_Q1A)
+{
+ Printf ("EC: _Q1A: Wake event, WFNO=0x%o", ToHexString (WFNO))
+
+ Switch (ToInteger (WFNO))
+ {
+ Case (0x01)
+ {
+ Printf ("EC: Wake reason: Lid")
+ Notify (\_SB.LID, 0x02) /* wake */
+ }
+
+ Case (0x04)
+ {
+ Printf ("EC: Wake reason: Sleep button")
+ Notify (\_SB.SLPB, 0x02) /* wake */
+ }
+
+ Case (0x05)
+ {
+ Printf ("EC: Wake reason: Timer")
+ Notify (\_SB.PWRB, 0x02) /* wake */
+ }
+
+ Case (0x10)
+ {
+ Printf ("EC: Wake reason: Battery low")
+ Notify (\_SB.BAT0, 0x02) /* wake */
+ }
+
+ Default
+ {
+ Printf ("EC: Wake reason: other")
+ Notify (\_SB.PWRB, 0x02) /* wake */
+ }
+ }
+}
+
+Method (_Q1B)
+{
+ Printf ("EC: _Q1B: Lid state change, state=%o", ToDecimalString (LSTE))
+
+ Notify (\_SB.LID, 0x80)
+}
+
+Method (_Q1D)
+{
+ Printf ("EC: _Q1D: Power button")
+
+ Notify (\_SB.PWRB, 0x80)
+}
+
+Method (_Q1E)
+{
+ Printf ("EC: _Q1E: Battery low")
+}
+
+Method (_Q32)
+{
+ Printf ("EC: _Q32: Battery thermal trip")
+}
+
+Method (_Q35)
+{
+ Printf ("EC: _Q35: Silent fan mode change, state=%o", ToDecimalString (SLFG))
+}
+
+Method (_Q37)
+{
+ Printf ("EC: _Q37: B15C flag change, B15C=%o", ToHexString (B15C))
+}
+
+Method (_Q42)
+{
+ Printf ("EC: _Q42, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q46)
+{
+ Printf ("EC: _Q46, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q4A)
+{
+ Printf ("EC: _Q4A: KBC beep on/off, SCIE=0x%o, state=%o",
+ ToHexString (SCIE), ToDecimalString(SCIE & 1))
+}
+
+Method (_Q50)
+{
+ Printf ("EC: _Q50: SCI event, SCIE=0x%o", ToHexString (SCIE))
+
+ Switch (ToInteger (SCIE))
+ {
+ Case (0x68) // L140MU only
+ {
+ }
+
+ Case (0x69) // L140MU only
+ {
+ }
+
+ Case (0x6a)
+ {
+ Printf ("EC: Fan mode: MaxQ")
+ }
+
+ Case (0x6c)
+ {
+ Printf ("EC: Fan mode: custom")
+ }
+
+ Case (0x7a)
+ {
+ }
+
+ Case (0x7b)
+ {
+ Printf ("EC: Fn + Backspace pressed")
+ }
+
+ Case (0x7c)
+ {
+ Printf ("EC: Screen rotate (Fn + R)")
+ }
+
+ Case (0x80)
+ {
+ Printf ("EC: Color keyboard color change")
+ }
+
+ Case (0x81)
+ {
+ Printf ("EC: Color keyboard brightness down")
+ }
+
+ Case (0x82)
+ {
+ Printf ("EC: Color keyboard brightness up")
+ }
+
+ Case (0x8a)
+ {
+ Printf ("EC: White keyboard backlight toggle")
+ }
+
+ Case (0x9f)
+ {
+ Printf ("EC: Color keyboard backlight toggle")
+ }
+
+ Case (0xa0)
+ {
+ }
+
+ Case (0xa8)
+ {
+ Printf ("EC: Fn + ESC pressed")
+ }
+
+ Case (0xae)
+ {
+ Printf ("EC: airplane mode LED off")
+ }
+
+ Case (0xaf)
+ {
+ Printf ("EC: airplane mode LED on")
+ }
+
+ Case (0xb0)
+ {
+ }
+
+ Case (0xc7)
+ {
+ Printf ("EC: NumLock off")
+ }
+
+ Case (0xc8)
+ {
+ Printf ("EC: NumLock on")
+ }
+
+ Case (0xc9)
+ {
+ Printf ("EC: CapsLock off")
+ }
+
+ Case (0xca)
+ {
+ Printf ("EC: CapsLock on")
+ }
+
+ Case (0xcf)
+ {
+ Printf ("EC: ScrollLock off")
+ }
+
+ Case (0xd0)
+ {
+ Printf ("EC: ScrollLock on")
+ }
+
+ Case (0xf0)
+ {
+ }
+
+ Case (0xf1)
+ {
+ }
+
+ Case (0xf2)
+ {
+ Printf ("EC: Fan mode: auto")
+ }
+
+ Case (0xf3)
+ {
+ Printf ("EC: Fan mode: turbo")
+ }
+ }
+}
+
+Method (_Q51)
+{
+ Printf ("EC: _Q51, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q52)
+{
+ Printf ("EC: _Q52, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q53)
+{
+ Printf ("EC: _Q53, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q56)
+{
+ Printf ("EC: _Q56, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q57)
+{
+ Printf ("EC: _Q57, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q58)
+{
+ Printf ("EC: _Q58, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q59)
+{
+ Printf ("EC: _Q59, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q5A)
+{
+ Printf ("EC: _Q5A, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q5D)
+{
+ Printf ("EC: _Q5D: Performance profile hotkey pressed (Fn + 3)")
+}
+
+Method (_Q5E)
+{
+ Printf ("EC: _Q5E, SCIE=0x%o", ToHexString (SCIE))
+}
+
+Method (_Q61)
+{
+ Printf ("EC: _Q61: Board thermal trip")
+}
+
+Method (_Q62)
+{
+ Printf ("EC: _Q62: UCSI event")
+}
diff --git a/src/ec/clevo/it5570e/acpi/ec_ram.asl b/src/ec/clevo/it5570e/acpi/ec_ram.asl
new file mode 100644
index 0000000000..0f4b39baa4
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/ec_ram.asl
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/*
+ * Labeling:
+ * - just offset bits
+ * ? unknown / not fully understood, used by vendor fw / ec fw
+ */
+
+/*
+ * Note: not all fields are used by all mainboards. Also some bits/fields are
+ * still unknown and will be amended as soon as more details are known.
+ * Naming differs from vendor firmware, since there were many completely
+ * wrong/misleading names. Fields unused by the vendor EC fw have been
+ * dropped, since they just were present due to vendor doing copy-pasting.
+ */
+
+OperationRegion (RAM1, SystemMemory, CONFIG_EC_CLEVO_IT5570E_MEM_BASE + 0x100, 0x400)
+Field (RAM1, ByteAcc, Lock, Preserve)
+{
+ Offset (0x03),
+ LSTE, 1, // Lid open 3h.0
+ , 1, // - 3h.1
+ LWKE, 1, // Lid wake enable 3h.2
+
+ Offset (0x04),
+ AC0, 8, // Active cooling temp 0 4h
+ PSV, 8, // Passive cooling temp 5h
+ CRT, 8, // Critical temp 6h
+ TMP, 8, // CPU temp read from PECI 7h
+ AC1, 8, // Active cooling temp 1 8h
+ BBST, 8, // ? dGPU related (BatteryBooST?) 9h
+ BTMP, 8, // Board temperature ah
+
+ Offset (0x10),
+ ADP, 1, // AC connected 10h.0
+ , 1, // - 10h.1
+ BAT0, 1, // BAT0 connected 10h.2
+
+ Offset (0x11),
+ WFNO, 8, // Wake cause 11h
+ // 0x01 = lid
+ // 0x04 = sleep button
+ // 0x05 = timer
+ // 0x10 = battery low
+
+ Offset (0x16),
+ BDC0, 32, // BAT0 design capacity 16h-19h
+ BFC0, 32, // BAT0 last full charge capacity 1ah-1dh
+
+ Offset (0x22),
+ BDV0, 32, // BAT0 design voltage 22h-25h
+ BST0, 8, // BAT0 status 26h
+ // BST0[0] : discharging
+ // BST0[1] : charging
+ // BST0[2] : critical low
+
+ Offset (0x2a),
+ BPR0, 32, // BAT0 present current 2ah-2dh
+ BRC0, 32, // BAT0 remaining capacity 2eh-31h
+ BPV0, 32, // BAT0 present voltage 32h-35h
+
+ Offset (0x38),
+ BRS0, 8, // BAT0 relative charge 38h
+
+ Offset (0x3a),
+ BCW0, 32, // Design capacity of warning 3ah-3dh
+ BCL0, 32, // Design capacity of low 3eh-41h
+
+ Offset (0x4a),
+ BMO0, 64, // Model 4ah-51h
+ BIF0, 64, // Vendor 52h-59h
+ BSN0, 32, // Serial number 5ah-5dh
+ BTY0, 64, // Type 5eh-65h
+
+ Offset (0x68),
+ ECOS, 8, // ACPI OS support 68h
+ // 0 = no ACPI
+ // 1 = ACPI w/o driver
+ // 2 = ACPI w/ driver
+
+ Offset (0x78),
+ /*
+ * PECI
+ * Maybe usable for debugging. Must never be written directly!
+ */
+ PCAD, 8, // PECI address 78h
+ PEWL, 8, // PECI write length 79h
+ PWRL, 8, // PECI read length 7ah
+ PECD, 8, // PECI command 7bh
+ PEHI, 8, // PECI host ID 7ch
+ PECI, 8, // PECI index 7dh
+ PEPL, 8, // PECI LSB 7eh
+ PEPM, 8, // PECI MSB 7fh
+ PWFC, 8, // ? 80h
+ PECC, 8, // PECI completion code 81h
+ PDT0, 8, // PECI data 82h
+ PDT1, 8, // PECI data 83h
+ PDT2, 8, // PECI data 84h
+ PDT3, 8, // PECI data 85h
+
+ Offset (0x92),
+ BMD0, 16, // BAT0 manufacturing date 92h-93h
+ // BMD0[4:0] : day
+ // BMD0[8:5] : month
+ // BMD0[15:9] : year - 1980
+ CYC0, 16, // BAT0 cycle count 94h-95h
+
+ Offset (0xc7),
+ VOFF, 8, // VGA fan base offset c7h
+ FANC, 8, // FAN count (FANC == FANQ) c8h
+ BLVL, 8, // Legacy display brightness level (unused) c9h
+
+ Offset (0xca),
+ , 1, // - cah.0
+ , 1, // ? cah.1
+ CAMK, 1, // Enable webcam hotkey cah.2
+ , 2, // - cah.3-4
+ WWAN, 1, // WWAN/3G/LTE present (enables WWAN) cah.5
+
+ Offset (0xcb),
+ , 5, // - cbh.0-4
+ B15C, 1, // ? cbh.5
+ , 1, // - cbh.6
+ SLFG, 1, // silent fan mode flag cbh.7
+
+ Offset (0xcc),
+ SCIE, 8, // SCI extra value cch
+
+ Offset (0xce),
+ DUT1, 8, // Fan 1 duty ceh
+ DUT2, 8, // Fan 2 duty cfh
+
+ Offset (0xd0),
+ RPM1, 16, // Fan 1 RPM d0h-d1h
+ RPM2, 16, // Fan 2 RPM d2h-d3h
+ RPM4, 16, // Fan 4 RPM d4h-d5h
+
+ Offset (0xd7),
+ DTHL, 8, // ? d7h
+ DTBP, 8, // ? d8h
+ , 1, // - d9h.0
+ WOLD, 1, // ? Disable Wake-on-LAN d9h.1
+ PWRM, 2, // current performance profile d9h.2-3
+ // 0 = entertainment
+ // 1 = performance
+ // 2 = quiet
+ // 3 = powersave
+ FN3E, 1, // Fn + 3 enable (power profile toggle) d9h.4
+ , 1, // ? d9h.5
+ AIRP, 1, // airplane mode status (in non-ACPI mode) d9h.6
+ GPUP, 1, // dGPU power status d9h.7
+
+ Offset (0xda),
+ BLCT, 1, // ACPI backlight control dah.0
+ DBGP, 1, // 3IN1 debug card present flag dah.1
+ , 1, // WINF[2] ? dah.2
+ MEUL, 1, // ME/IFD unlock (ACPI usage unclear) dah.3
+
+ Offset (0xdb),
+ RINF, 8, // dbh
+ // RINF[0] : set when EC cmd A8 was called
+ // RINF[1] : -
+ // RINF[2] : ? TP
+ // RINF[3] : ?
+ // RINF[4] : I2C TP SupportSandTPScanCode
+ // RINF[5] : ?
+ // RINF[6] : set on first airplane mode activation
+ // RINF[7] : ?
+ DBG, 8, // P80 + 3in1 debug dch
+
+ Offset (0xdd),
+ , 1, // ddh.0
+ , 1, // INF2[1] : ? ddh.1
+ , 4, // - ddh.2-5
+ BWKE, 1, // S3 wake on low battery ddh.6
+ FF2D, 1, // Fn + F2 (LCD off) disable ddh.7
+ EID2, 8, // EC CHIPID LSB deh
+ BWKT, 8, // threshold for S3 wake on low battery dfh
+
+ Offset (0xe0),
+ RPM3, 16, // Fan 3 RPM e0h-e1h
+
+ Offset (0xe2),
+ , 3, // - e2h.0-2
+ SWFN, 1, // swap Fn and left Win key e2h.3
+ LWIN, 1, // enable left Win key e2h.4
+ , 2, // - e2h.5-6
+ AIRK, 1, // enable airplane hotkey support e2h.7
+
+ Offset (0xe4),
+ , 1, // ? e4h.0
+ , 2, // - e4h.1-2
+ , 1, // ? e4h.3
+ , 1, // - e4h.4
+ EP12, 1, // ? (gpu related) e4h.5
+ FN_G, 1, // Fn + G pressed (GPU reset on vendor fw) e4h.6
+ , 1, // ? e4h.7
+
+ Offset (0xe5),
+ ECSZ, 8, // EC eFlash size e5h
+
+ Offset (0xe6),
+ , 2, // - e6h.0-1
+ G3FG, 1, // Enter G3 (all power off) in S4/S5 e6h.2
+ , 3, // - e6h.3-5
+ FOAC, 1, // Fan always on when AC connected e6h.6
+
+ Offset (0xe7),
+ FOFF, 8, // Fan base offset e7h
+
+ Offset (0xe8),
+ , 1, // ? static 1; vendor: if 1: eccmd c6, val 0 e8h.0
+ CNVI, 1, // CNVI card present e8h.1
+ , 3, // - e8h.2-4
+ FN_D, 1, // Fn + D pressed (CMOS reset on vendor fw) e8h.5
+ , 1, // ? fan related e8h.6
+
+ Offset (0xe9),
+ KBBO, 1, // KB LED supports boot effect override e9h.0
+
+ Offset (0xea),
+ , 3, // - eah.0-2
+ PDFG, 1, // Power supplied via USB-C eah.3
+ MSFG, 1, // Modern standby flag eah.4
+ RCHG, 1, // ? eah.5
+ ACOT, 1, // ? eah.6
+ S5FG, 1, // ? eah.7
+
+ Offset (0xeb),
+ , 1, // ? (unknown keypress status, NV4x only) ebh.0
+ , 1, // ? (unknown keypress status, NV4x only) ebh.1
+ , 1, // - ebh.2
+ DGPT, 1, // ? ebh.3
+ TOPN, 1, // ? ebh.4
+ , 1, // ? (kbc beep related?) ebh.5
+ , 1, // ? (kbc beep related?) ebh.6
+ APRD, 1, // AP ready ebh.7
+
+ Offset (0xf0),
+ PL2B, 16, // Power Limit 2 set when on battery f0h-f1h
+ PL2T, 16, // Power Limit 2 (note: never enabled by EC!) f2h-f3h
+ TAUT, 8, // Tau (for PL1, not effective) f4h
+
+ /* FCMD interface */
+ Offset (0xf8),
+ FCMD, 8, // Command f8h
+ FDAT, 8, // Data f9h
+ FBUF, 8, // Buffer[0] fah
+ FBF1, 8, // Buffer[1] fbh
+ FBF2, 8, // Buffer[2] fch
+ FBF3, 8, // Buffer[3] fdh
+
+ Offset (0xff),
+ , 8, // ? static, l14xcu/mu: 0xe0, nv4x 0x22 ffh
+
+ Offset (0x28a),
+ FANQ, 8, // FAN count (FANC == FANQ) 28ah
+ KBTP, 8, // Keyboard backlight type 28bh
+ // 0x00 = none
+ // 0x01 = white
+ // 0x02 = RGB
+ // 0x*3 = per-key RGB
+ // 0x06 = RGB15Color
+ // 0x16 = RGB15ColorCustom
+}
diff --git a/src/ec/clevo/it5570e/acpi/hid.asl b/src/ec/clevo/it5570e/acpi/hid.asl
new file mode 100644
index 0000000000..10d23f5330
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/hid.asl
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/* Minimal implementation of HID event filter device for airplane hotkey support */
+
+Device (HIDD)
+{
+ Name (_HID, "INTC1051")
+
+ Name (HRDY, 0)
+ Name (HBSY, 0)
+ Name (HIDX, 0)
+
+ /*
+ * Workaround:
+ * There was a bug in Linux' HID driver, making evaluation of function 2 fail.
+ * The driver falls back to legacy mode and evaluates integers instead of _DSM.
+ * A bugfix for this was merged to mainline and stable but not LTS-branches. Thus,
+ * let's keep this for some time.
+ * https://lore.kernel.org/r/66f813f5bcc724a0f6dd5adefe6a9728dbe509e3.camel@mniewoehner.de
+ */
+ Name (HDMM, 0)
+
+ Method (_STA)
+ {
+ Return (0x0f)
+ }
+
+ Method (_DSM, 4, Serialized)
+ {
+ If (Arg0 == ToUUID ("eeec56b3-4442-408f-a792-4edd4d758054"))
+ {
+ If (Arg1 == 1) /* Revision 1 */
+ {
+ Printf ("HIDD: _DSM called, fn=%o", ToDecimalString(Arg2))
+
+ Switch (ToInteger (Arg2))
+ {
+ Case (0)
+ {
+ /* Supported functions: 0, 2, 3, 4, 7 */
+ Return (Buffer () {0x9d, 0x00})
+ }
+ Case (2)
+ {
+ /* Simple mode */
+ Return (0)
+ }
+ Case (3)
+ {
+ /* Driver status */
+ HRDY = DeRefOf (Index (Arg3, 0))
+ }
+ Case (4)
+ {
+ /* HID driver calls this to get event */
+ HBSY = 0
+ Return (HIDX)
+ }
+ Case (7)
+ {
+ /* Only airplane mode button implemented */
+ Return (1 << 1)
+ }
+ }
+ }
+ }
+
+ Return (Buffer () {0})
+ }
+
+ /*
+ * HID Platform Event Method
+ * Called to trigger HID event.
+ */
+ Method (HPEM, 1, Serialized)
+ {
+ Printf ("HIDD: HPEM called, event=%o", ToHexString (Arg0))
+
+ If (!HRDY)
+ {
+ Printf ("HIDD: HID driver not ready. Ignoring event.")
+ }
+
+ HBSY = 1
+ HIDX = Arg0
+
+ Notify (HIDD, 0xc0)
+
+ /* Wait max. 1 second for HID driver */
+ Local0 = 0
+ While ((Local0 < 250) && HBSY)
+ {
+ Sleep (4)
+ Local0++
+ }
+
+ If (HBSY)
+ {
+ Printf ("HIDD: HPEM timeout")
+
+ HBSY = 0
+ HIDX = 0
+
+ Return (1) /* Timeout */
+ }
+
+ Return (0)
+ }
+}
diff --git a/src/ec/clevo/it5570e/acpi/lid.asl b/src/ec/clevo/it5570e/acpi/lid.asl
new file mode 100644
index 0000000000..3a1ab1750e
--- /dev/null
+++ b/src/ec/clevo/it5570e/acpi/lid.asl
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+Device (LID)
+{
+ Name (_HID, "PNP0C0D")
+ Name (_PRW, Package () { EC_GPE_LID, 3 })
+
+ Method (_LID)
+ {
+ Printf ("LID: _LID: state=%o", ToDecimalString (\_SB.PCI0.LPCB.EC0.LSTE))
+
+ Return (\_SB.PCI0.LPCB.EC0.LSTE)
+ }
+
+ Method (_PSW, 1)
+ {
+ Printf ("LID: _PSW: set lid wake enable=%o", ToDecimalString (Arg0))
+
+ \_SB.PCI0.LPCB.EC0.LWKE = Arg0
+ }
+}
diff --git a/src/ec/clevo/it5570e/chip.h b/src/ec/clevo/it5570e/chip.h
new file mode 100644
index 0000000000..a9ff3b1644
--- /dev/null
+++ b/src/ec/clevo/it5570e/chip.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_CLEVO_IT5570E_CHIP_H
+#define EC_CLEVO_IT5570E_CHIP_H
+
+#define IT5570E_FAN_CURVE_LEN 4 /* Number of fan curve points */
+#define IT5570E_MAX_FAN_CNT 4 /* Maximum number of configurable fans */
+
+enum ec_clevo_it5570e_fan_mode {
+ FAN_MODE_AUTO = 0,
+ FAN_MODE_CUSTOM,
+};
+
+struct ec_clevo_it5570e_fan_curve {
+ uint8_t temperature[IT5570E_FAN_CURVE_LEN];
+ uint8_t speed[IT5570E_FAN_CURVE_LEN];
+};
+
+struct ec_clevo_it5570e_config {
+ uint8_t pl2_on_battery;
+ enum ec_clevo_it5570e_fan_mode fan_mode;
+ struct ec_clevo_it5570e_fan_curve fan_curves[IT5570E_MAX_FAN_CNT];
+};
+
+typedef struct ec_clevo_it5570e_config ec_config_t;
+
+#endif /* EC_CLEVO_IT5570E_CHIP_H */
diff --git a/src/ec/clevo/it5570e/commands.c b/src/ec/clevo/it5570e/commands.c
new file mode 100644
index 0000000000..7a85b8f769
--- /dev/null
+++ b/src/ec/clevo/it5570e/commands.c
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <console/console.h>
+#include <delay.h>
+#include <device/mmio.h>
+#include <ec/acpi/ec.h>
+#include <swab.h>
+#include <timer.h>
+#include <types.h>
+
+#include "chip.h"
+#include "commands.h"
+#include "ec.h"
+
+#define ec_cmd send_ec_command
+#define ec_dat send_ec_data
+
+static void ec_fcmd(uint8_t fcmd)
+{
+ write8p(ECRAM + FCMD, fcmd);
+ ec_cmd(ECCMD_NOP);
+
+ /* EC sets FCMD = 0x00 on completion (FCMD = 0xfa on some commands) */
+ int time = wait_us(50000, read8p(ECRAM + FCMD) == 0x00 || read8p(ECRAM + FCMD) == 0xfa);
+ if (time)
+ printk(BIOS_DEBUG, "EC: FCMD 0x%02x completed after %d us\n", fcmd, time);
+ else
+ printk(BIOS_ERR, "EC: FCMD 0x%02x timed out\n", fcmd);
+}
+
+static void ec_recv_str(char *buf, size_t size)
+{
+ while (size--) {
+ *buf = recv_ec_data();
+ if (*buf == '$') { /* end mark */
+ *buf = '\0';
+ return;
+ }
+ buf++;
+ }
+
+ /* Truncate and discard the rest */
+ *--buf = '\0';
+ do {} while (recv_ec_data() != '$');
+ printk(BIOS_ERR, "EC: Received string longer than buffer. Data truncated.\n");
+}
+
+char *ec_read_model(void)
+{
+ static char model[10];
+
+ ec_cmd(ECCMD_READ_MODEL);
+ ec_recv_str(model, sizeof(model));
+
+ return model;
+}
+
+char *ec_read_fw_version(void)
+{
+ static char version[10] = "1.";
+
+ ec_cmd(ECCMD_READ_FW_VER);
+ ec_recv_str(version + 2, sizeof(version) - 2);
+
+ return version;
+}
+
+void ec_set_acpi_mode(bool state)
+{
+ ec_cmd(state ? ECCMD_ENABLE_ACPI_MODE : ECCMD_DISABLE_ACPI_MODE);
+ if (state)
+ ec_cmd(ECCMD_ENABLE_HOTKEYS);
+}
+
+void ec_set_enter_g3_in_s4s5(bool state)
+{
+ clrsetbits8p(ECRAM + 0x1e6, 1 << G3FG, state << G3FG);
+}
+
+void ec_set_aprd(void)
+{
+ setbits8p(ECRAM + 0x1eb, 1 << APRD);
+}
+
+/* To be called by a graphics driver, when detecting a dGPU */
+void ec_set_dgpu_present(bool state)
+{
+ clrsetbits8p(ECRAM + 0x1eb, 1 << DGPT, state << DGPT);
+}
+
+void ec_set_fn_win_swap(bool state)
+{
+ clrsetbits8p(ECRAM + ECKS, 1 << SWFN, state << SWFN);
+}
+
+void ec_set_ac_fan_always_on(bool state)
+{
+ clrsetbits8p(ECRAM + 0x1e6, 1 << FOAC, state << FOAC);
+}
+
+void ec_set_kbled_timeout(uint16_t timeout)
+{
+ printk(BIOS_DEBUG, "EC: set keyboard backlight timeout to %us\n", timeout);
+
+ write8p(ECRAM + FDAT, timeout ? 0xff : 0x00);
+ write16p(ECRAM + FBUF, swab16(timeout));
+ ec_fcmd(FCMD_SET_KBLED_TIMEOUT);
+}
+
+void ec_set_flexicharger(bool state, uint8_t start, uint8_t stop)
+{
+ printk(BIOS_DEBUG, "EC: set flexicharger: enabled=%d, start=%u%%, stop=%u%%\n",
+ state, start, stop);
+
+ if (!state) {
+ start = 0xff;
+ stop = 0xff;
+
+ } else if (start > 100 || stop > 100) {
+ printk(BIOS_ERR, "EC: invalid flexicharger settings: start/stop > 100%%\n");
+ return;
+
+ } else if (start >= stop) {
+ printk(BIOS_ERR, "EC: invalid flexicharger settings: start >= stop\n");
+ return;
+ }
+
+ write8p(ECRAM + FBF1, state << 1);
+ write8p(ECRAM + FBUF, start);
+ write8p(ECRAM + FDAT, stop);
+ ec_fcmd(FCMD_FLEXICHARGER);
+}
+
+void ec_set_camera_boot_state(enum camera_state state)
+{
+ if (state > CAMERA_STATE_KEEP) {
+ printk(BIOS_ERR,
+ "EC: invalid camera boot state %u. Keeping previous state.\n", state);
+ state = CAMERA_STATE_KEEP;
+ }
+
+ if (state == CAMERA_STATE_KEEP) {
+ /*
+ * The EC maintains the camera's state in RAM. However, it doesn't sync the GPIO
+ * on a concurrent boot. Thus, read the previous state from the EC and set the
+ * state and the GPIO by sending the state command even in the keep-case.
+ */
+ ec_cmd(ECCMD_GET_DEVICES_STATE);
+ state = recv_ec_data() & 1;
+ }
+
+ printk(BIOS_DEBUG, "EC: set camera: enabled=%u\n", state);
+
+ ec_dat(DEVICE_CAMERA | DEVICE_STATE(state));
+ ec_cmd(ECCMD_SET_INV_DEVICE_STATE);
+}
+
+void ec_set_tp_toggle_mode(uint8_t mode)
+{
+ switch (mode) {
+ case 0: /* CtrlAltF9 */
+ setbits8p(ECRAM + RINF, TP_TOGGLE_CTRLALTF9);
+ break;
+ case 1: /* KeycodeF7F8*/
+ clrbits8p(ECRAM + RINF, TP_TOGGLE_CTRLALTF9);
+ break;
+ }
+}
diff --git a/src/ec/clevo/it5570e/commands.h b/src/ec/clevo/it5570e/commands.h
new file mode 100644
index 0000000000..7593117b1b
--- /dev/null
+++ b/src/ec/clevo/it5570e/commands.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_CLEVO_IT5570E_COMMANDS_H
+#define EC_CLEVO_IT5570E_COMMANDS_H
+
+#ifndef __ACPI__
+#define ECRAM CONFIG_EC_CLEVO_IT5570E_MEM_BASE
+
+/* EC RAM fields and bits */
+#define FANC 0x1c8
+#define RINF 0x1db
+#define TP_TOGGLE_CTRLALTF9 (BIT(4) | BIT(2))
+#define ECKS 0x1e2
+#define SWFN 3
+// #### 0x1e6
+#define G3FG 2
+#define FOAC 6
+#define KBBO 0x1e9
+// #### 0x1eb
+#define DGPT 3
+#define APRD 7
+#define PL2B 0x1f0
+#define PL2T 0x1f2
+#define TAUT 0x1f4
+#define FCMD 0x1f8
+#define FDAT 0x1f9
+#define FBUF 0x1fa
+#define FBF1 0x1fb
+#define FBF2 0x1fc
+#define FBF3 0x1fd
+#endif // __ACPI__
+
+/* EC commands */
+#define ECCMD_NOP 0x00 /* dummy, triggers FCMDs */
+#define ECCMD_ENABLE_ACPI_MODE 0x90
+#define ECCMD_DISABLE_ACPI_MODE 0x91
+#define ECCMD_READ_MODEL 0x92
+#define ECCMD_READ_FW_VER 0x93
+#define ECCMD_ENABLE_HOTKEYS 0x98
+#define ECCMD_GET_DEVICES_STATE 0x9a
+#define ECCMD_SET_INV_DEVICE_STATE 0x9c
+#define DEVICE_CAMERA 2
+#define DEVICE_STATE(state) (!(state) << 7)
+#define ECCMD_SET_BATLOW_ALARM 0x9d
+#define ECCMD_SETUP_DEVICES 0xa8
+
+/* FCMD commands */
+#define FCMD_DEVICES 0xb8
+#define FDAT_DEVICE_SET_INV_STATE 0xc2 /* inverted! en=0xc2|0, dis=0xc2|1 */
+#define FCMD_KLED 0xca
+#define FDAT_KBLED_WHITE_SET_LEVEL 0x00
+#define FDAT_KBLED_WHITE_GET_LEVEL 0x01
+#define FCMD_FLEXICHARGER 0xcb
+#define FCMD_SET_KBLED_TIMEOUT 0xd4
+
+#ifndef __ACPI__
+enum camera_state {
+ CAMERA_STATE_DISABLE,
+ CAMERA_STATE_ENABLE,
+ CAMERA_STATE_KEEP,
+};
+
+char *ec_read_model(void);
+char *ec_read_fw_version(void);
+void ec_set_acpi_mode(bool state);
+void ec_set_aprd(void);
+void ec_set_enter_g3_in_s4s5(bool state);
+void ec_set_dgpu_present(bool state);
+void ec_set_fn_win_swap(bool state);
+void ec_set_ac_fan_always_on(bool state);
+void ec_set_kbled_timeout(uint16_t timeout);
+void ec_set_flexicharger(bool state, uint8_t start, uint8_t stop);
+void ec_set_camera_boot_state(enum camera_state state);
+void ec_set_tp_toggle_mode(uint8_t mode);
+#endif // __ACPI__
+
+#endif /* EC_CLEVO_IT5570E_COMMANDS_H */
diff --git a/src/ec/clevo/it5570e/early_init.c b/src/ec/clevo/it5570e/early_init.c
new file mode 100644
index 0000000000..864d6ecaa4
--- /dev/null
+++ b/src/ec/clevo/it5570e/early_init.c
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <console/console.h>
+#include <option.h>
+
+#include "early_init.h"
+#include "i2ec.h"
+
+#define XRAM_BOOTEFFECT_DISABLE 0x47a
+#define XRAM_BOOTEFFECT_SUPPORT 0x1e9
+
+void ec_configure_kbled_booteffect(void)
+{
+ if (!ec_d2i2ec_read(XRAM_BOOTEFFECT_SUPPORT)) {
+ printk(BIOS_INFO, "EC: boot effect override not supported by ec firmware\n");
+ return;
+ }
+
+ bool enable = get_uint_option("kbled_booteffect",
+ CONFIG(EC_CLEVO_IT5570E_KBLED_BOOTEFFECT));
+
+ printk(BIOS_DEBUG, "EC: set booteffect enable=%i\n", enable);
+ ec_d2i2ec_write(XRAM_BOOTEFFECT_DISABLE, !enable);
+}
diff --git a/src/ec/clevo/it5570e/early_init.h b/src/ec/clevo/it5570e/early_init.h
new file mode 100644
index 0000000000..7822d0d768
--- /dev/null
+++ b/src/ec/clevo/it5570e/early_init.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_CLEVO_IT5570E_EARLY_INIT_H
+#define EC_CLEVO_IT5570E_EARLY_INIT_H
+
+void ec_configure_kbled_booteffect(void);
+
+#endif /* EC_CLEVO_IT5570E_EARLY_INIT_H */
diff --git a/src/ec/clevo/it5570e/ec.c b/src/ec/clevo/it5570e/ec.c
new file mode 100644
index 0000000000..4f10750b51
--- /dev/null
+++ b/src/ec/clevo/it5570e/ec.c
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <console/console.h>
+#include <cpu/x86/msr.h>
+#include <device/device.h>
+#include <device/mmio.h>
+#include <device/pnp.h>
+#include <ec/acpi/ec.h>
+#include <option.h>
+#include <pc80/keyboard.h>
+#include <soc/msr.h>
+#include <superio/conf_mode.h>
+
+#include "chip.h"
+#include "commands.h"
+#include "ec.h"
+
+static void pnp_configure_smfi(void)
+{
+ if (!CONFIG_EC_CLEVO_IT5570E_MEM_BASE) {
+ printk(BIOS_ERR, "EC: no LGMR base address configured. Check your config!\n");
+ return;
+ }
+
+ /* Check for valid address (0xfeXXX000/0xffXXX000) */
+ if ((CONFIG_EC_CLEVO_IT5570E_MEM_BASE & 0xfe000fff) != 0xfe000000) {
+ printk(BIOS_ERR, "EC: LGMR base address 0x%08x invalid. Check your config!\n",
+ CONFIG_EC_CLEVO_IT5570E_MEM_BASE);
+ return;
+ }
+
+ struct device dev = {
+ .path.type = DEVICE_PATH_PNP,
+ .path.pnp.port = 0x2e,
+ .path.pnp.device = IT5570E_SMFI,
+ };
+ dev.ops->ops_pnp_mode = &pnp_conf_mode_870155_aa;
+
+ /* Configure SMFI for LGMR */
+ pnp_enter_conf_mode(&dev);
+ pnp_set_logical_device(&dev);
+ pnp_set_enable(&dev, 1);
+ pnp_write_config(&dev, HLPCRAMBA_24, CONFIG_EC_CLEVO_IT5570E_MEM_BASE >> 24 & 0x01);
+ pnp_write_config(&dev, HLPCRAMBA_23_16, CONFIG_EC_CLEVO_IT5570E_MEM_BASE >> 16 & 0xff);
+ pnp_write_config(&dev, HLPCRAMBA_15_12, CONFIG_EC_CLEVO_IT5570E_MEM_BASE >> 8 & 0xf0);
+ pnp_exit_conf_mode(&dev);
+}
+
+static void ec_init(struct device *dev)
+{
+ if (!dev->enabled)
+ return;
+
+ const ec_config_t *config = config_of(dev);
+ printk(BIOS_DEBUG, "%s init.\n", dev->chip_ops->name);
+
+ const char *const model = ec_read_model();
+ const char *const version = ec_read_fw_version();
+ printk(BIOS_DEBUG, "EC FW: model %s, version %s\n", model, version);
+
+ pnp_configure_smfi();
+
+ ec_set_ac_fan_always_on(
+ get_uint_option("ac_fan_always_on", CONFIG(EC_CLEVO_IT5570E_AC_FAN_ALWAYS_ON)));
+
+ ec_set_kbled_timeout(
+ get_uint_option("kbled_timeout", CONFIG_EC_CLEVO_IT5570E_KBLED_TIMEOUT));
+
+ ec_set_fn_win_swap(
+ get_uint_option("fn_win_swap", CONFIG(EC_CLEVO_IT5570E_FN_WIN_SWAP)));
+
+ ec_set_flexicharger(
+ get_uint_option("flexicharger", CONFIG(EC_CLEVO_IT5570E_FLEXICHARGER)),
+ get_uint_option("flexicharger_start", CONFIG_EC_CLEVO_IT5570E_FLEXICHG_START),
+ get_uint_option("flexicharger_stop", CONFIG_EC_CLEVO_IT5570E_FLEXICHG_STOP));
+
+ ec_set_camera_boot_state(
+ get_uint_option("camera_boot_state", CONFIG_EC_CLEVO_IT5570E_CAM_BOOT_STATE));
+
+ ec_set_tp_toggle_mode(
+ get_uint_option("tp_toggle_mode", CONFIG_EC_CLEVO_IT5570E_TP_TOGGLE_MODE));
+
+ /*
+ * The vendor abuses the field PL2B (originally named PL1T) to set PL2 via PECI on
+ * battery-only. With AC attached, PL2B (PL1T) gets set as PL1 and PL2T as PL2, but
+ * both are never enabled (bit 15). Since PL1 is never enabled, Tau isn't either.
+ * Thus, set PL2T, TAUT to zero, so the EC doesn't write these non-effective values.
+ */
+ const uint16_t power_unit = 1 << (msr_read(MSR_PKG_POWER_SKU_UNIT) & 0xf);
+ write16p(ECRAM + PL2B, config->pl2_on_battery * power_unit);
+ write16p(ECRAM + PL2T, 0);
+ write16p(ECRAM + TAUT, 0);
+
+ ec_set_aprd();
+
+ pc_keyboard_init(NO_AUX_DEVICE);
+}
+
+static const char *ec_acpi_name(const struct device *dev)
+{
+ return "EC0";
+}
+
+static void ec_fill_ssdt_generator(const struct device *dev)
+{
+ ec_fan_curve_fill_ssdt(dev);
+}
+
+static struct device_operations ec_ops = {
+ .init = ec_init,
+ .read_resources = noop_read_resources,
+ .set_resources = noop_set_resources,
+ .acpi_fill_ssdt = ec_fill_ssdt_generator,
+ .acpi_name = ec_acpi_name,
+};
+
+static void enable_dev(struct device *dev)
+{
+ if (dev->path.type == DEVICE_PATH_GENERIC && dev->path.generic.id == 0)
+ dev->ops = &ec_ops;
+ else
+ printk(BIOS_ERR, "EC: Unknown device. Check your devicetree!\n");
+}
+
+struct chip_operations ec_clevo_it5570e_ops = {
+ CHIP_NAME("Clevo IT5570E EC")
+ .enable_dev = enable_dev,
+};
diff --git a/src/ec/clevo/it5570e/ec.h b/src/ec/clevo/it5570e/ec.h
new file mode 100644
index 0000000000..fee976a781
--- /dev/null
+++ b/src/ec/clevo/it5570e/ec.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_CLEVO_IT5570E_EC_H
+#define EC_CLEVO_IT5570E_EC_H
+
+#include <device/device.h>
+
+/* PNP devices */
+#define IT5570E_UART1 0x01 /* UART1 */
+#define IT5570E_UART2 0x02 /* UART2 */
+#define IT5570E_SWUC 0x04 /* System Wake-Up Control */
+#define IT5570E_KBCM 0x05 /* PS/2 KBC Mouse */
+#define IT5570E_KBCK 0x06 /* PS/2 KBC Keyboard */
+#define IT5570E_CIR 0x0a /* Consumer IR */
+#define IT5570E_SMFI 0x0f /* Shared Memory/Flash Interface */
+#define IT5570E_RTCT 0x10 /* RTC-like Timer */
+#define IT5570E_PM1 0x11 /* Power Management Channel 1 */
+#define IT5570E_PM2 0x12 /* Power Management Channel 2 */
+#define IT5570E_SSPI 0x13 /* Serial Peripheral Interface */
+#define IT5570E_PECI 0x14 /* Platform Environment Control Interface */
+#define IT5570E_PM3 0x17 /* Power Management Channel 3 */
+#define IT5570E_PM4 0x18 /* Power Management Channel 4 */
+#define IT5570E_PM5 0x19 /* Power Management Channel 5 */
+
+/* SMFI registers */
+#define HLPCRAMBA_15_12 0xf5
+#define HLPCRAMBA_23_16 0xf6
+#define HLPCRAMBA_24 0xfc
+
+void ec_fan_curve_fill_ssdt(const struct device *dev);
+
+#endif /* EC_CLEVO_IT5570E_EC_H */
diff --git a/src/ec/clevo/it5570e/i2ec.c b/src/ec/clevo/it5570e/i2ec.c
new file mode 100644
index 0000000000..2d7dd6c37c
--- /dev/null
+++ b/src/ec/clevo/it5570e/i2ec.c
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <device/pnp_ops.h>
+
+#include "i2ec.h"
+
+#define SIO_DEV PNP_DEV(0x2e, 0)
+
+/* SIO depth 2 index/data pair */
+#define D2ADR 0x2e
+#define D2DAT 0x2f
+
+/* SIO depth 2 address space */
+#define I2EC_ADDR_L 0x10
+#define I2EC_ADDR_H 0x11
+#define I2EC_DATA 0x12
+
+/*
+ * Read/write SIO "depth 2" registers
+ */
+
+static uint8_t sio_d2_read(uint8_t addr)
+{
+ pnp_write_config(SIO_DEV, D2ADR, addr);
+ return pnp_read_config(SIO_DEV, D2DAT);
+}
+
+static void sio_d2_write(uint8_t addr, uint8_t val)
+{
+ pnp_write_config(SIO_DEV, D2ADR, addr);
+ pnp_write_config(SIO_DEV, D2DAT, val);
+}
+
+/*
+ * Read/write I2EC registers through SIO "depth 2" address space
+ */
+
+uint8_t ec_d2i2ec_read(uint16_t addr)
+{
+ sio_d2_write(I2EC_ADDR_H, addr >> 8 & 0xff);
+ sio_d2_write(I2EC_ADDR_L, addr & 0xff);
+ return sio_d2_read(I2EC_DATA);
+}
+
+void ec_d2i2ec_write(uint16_t addr, uint8_t val)
+{
+ sio_d2_write(I2EC_ADDR_H, addr >> 8 & 0xff);
+ sio_d2_write(I2EC_ADDR_L, addr & 0xff);
+ sio_d2_write(I2EC_DATA, val);
+}
diff --git a/src/ec/clevo/it5570e/i2ec.h b/src/ec/clevo/it5570e/i2ec.h
new file mode 100644
index 0000000000..8b8b0f62d7
--- /dev/null
+++ b/src/ec/clevo/it5570e/i2ec.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_CLEVO_IT5570E_I2EC_H
+#define EC_CLEVO_IT5570E_I2EC_H
+
+/*
+ * Read/write I2EC registers through SIO "depth 2" address space
+ */
+
+uint8_t ec_d2i2ec_read(uint16_t addr);
+void ec_d2i2ec_write(uint16_t addr, uint8_t val);
+
+#endif /* EC_CLEVO_IT5570E_I2EC_H */
diff --git a/src/ec/clevo/it5570e/smbios.c b/src/ec/clevo/it5570e/smbios.c
new file mode 100644
index 0000000000..20fa2ce897
--- /dev/null
+++ b/src/ec/clevo/it5570e/smbios.c
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <string.h>
+#include <smbios.h>
+#include "commands.h"
+
+void smbios_ec_revision(uint8_t *ec_major_revision, uint8_t *ec_minor_revision)
+{
+ char *version, *major, *minor;
+
+ version = ec_read_fw_version(); /* 1.XX.YY */
+ major = &version[2];
+ minor = &version[5];
+
+ *ec_major_revision = skip_atoi(&major);
+ *ec_minor_revision = skip_atoi(&minor);
+}
diff --git a/src/ec/clevo/it5570e/smihandler.c b/src/ec/clevo/it5570e/smihandler.c
new file mode 100644
index 0000000000..fedac06e34
--- /dev/null
+++ b/src/ec/clevo/it5570e/smihandler.c
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpi.h>
+#include <cpu/x86/smm.h>
+
+#include "commands.h"
+#include "smm.h"
+
+void ec_smi_apmc(uint8_t apmc)
+{
+ printk(BIOS_DEBUG, "EC SMI APMC handler\n");
+
+ switch (apmc) {
+ case APM_CNT_ACPI_ENABLE:
+ ec_set_acpi_mode(true);
+ break;
+ case APM_CNT_ACPI_DISABLE:
+ ec_set_acpi_mode(false);
+ break;
+ default:
+ break;
+ }
+}
+
+void ec_smi_sleep(uint8_t slp_type)
+{
+ printk(BIOS_DEBUG, "EC SMI sleep handler\n");
+
+ switch (slp_type) {
+ case ACPI_S4:
+ case ACPI_S5:
+ ec_set_enter_g3_in_s4s5(true);
+ __fallthrough;
+ default:
+ break;
+ }
+}
diff --git a/src/ec/clevo/it5570e/smm.h b/src/ec/clevo/it5570e/smm.h
new file mode 100644
index 0000000000..fc05c32f19
--- /dev/null
+++ b/src/ec/clevo/it5570e/smm.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef EC_CLEVO_IT5570E_SMM_H
+#define EC_CLEVO_IT5570E_SMM_H
+
+/* SMI handler */
+void ec_smi_apmc(uint8_t apmc);
+void ec_smi_sleep(uint8_t slp_type);
+
+#endif /* EC_CLEVO_IT5570E_SMM_H */
diff --git a/src/ec/clevo/it5570e/ssdt.c b/src/ec/clevo/it5570e/ssdt.c
new file mode 100644
index 0000000000..a0d746ac00
--- /dev/null
+++ b/src/ec/clevo/it5570e/ssdt.c
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <acpi/acpigen.h>
+#include <console/console.h>
+#include <device/device.h>
+#include <device/mmio.h>
+
+#include "chip.h"
+#include "ec.h"
+#include "commands.h"
+
+static bool is_curve_valid(struct ec_clevo_it5570e_fan_curve curve)
+{
+ /*
+ * Fan curve speeds have to be non-decreasing.
+ * Fan curve temperatures have to be increasing (to avoid division by 0).
+ * This also covers the case when the curve is all zeroes (i.e. not configured).
+ */
+
+ for (int i = 1; i < IT5570E_FAN_CURVE_LEN; i++) {
+ if (curve.speed[i] < curve.speed[i - 1] ||
+ curve.temperature[i] <= curve.temperature[i - 1])
+ return false;
+ }
+
+ return true;
+}
+
+static void write_fan_curve(struct ec_clevo_it5570e_fan_curve curve, int fan)
+{
+ uint16_t ramp;
+ char fieldname[5];
+
+ /* Curve points */
+ for (int i = 0; i < IT5570E_FAN_CURVE_LEN; i++) {
+ snprintf(fieldname, 5, "F%dT%d", fan + 1, i + 1);
+ acpigen_write_store_int_to_namestr(curve.temperature[i], fieldname);
+ snprintf(fieldname, 5, "F%dD%d", fan + 1, i + 1);
+ acpigen_write_store_int_to_namestr(curve.speed[i] * 255 / 100, fieldname);
+ }
+
+ /* Ramps */
+ for (int i = 0; i < (IT5570E_FAN_CURVE_LEN - 1); i++) {
+ ramp = 255 * 16 *
+ (curve.speed[i + 1] - curve.speed[i]) /
+ (curve.temperature[i + 1] - curve.temperature[i]) /
+ 100;
+
+ snprintf(fieldname, 5, "F%dR%d", fan + 1, i + 1);
+ acpigen_write_store_int_to_namestr(ramp, fieldname);
+ }
+}
+
+static void write_fan_opregion(int fan_cnt)
+{
+ char fieldname[5];
+ uint8_t flags = FIELD_ANYACC | FIELD_LOCK | FIELD_PRESERVE;
+ struct opregion opreg = {
+ .name = "FNCV",
+ .regionspace = SYSTEMMEMORY,
+ .regionoffset = CONFIG_EC_CLEVO_IT5570E_MEM_BASE + 0x38c,
+ .regionlen = fan_cnt * 14,
+ };
+
+ acpigen_write_opregion(&opreg);
+ acpigen_emit_ext_op(FIELD_OP);
+ acpigen_write_len_f();
+ acpigen_emit_namestring(opreg.name);
+ acpigen_emit_byte(flags);
+
+ for (int fan = 1; fan <= fan_cnt; fan++) {
+ /* temps */
+ for (int i = 1; i <= IT5570E_FAN_CURVE_LEN; i++) {
+ snprintf(fieldname, 5, "F%dT%d", fan, i);
+ acpigen_write_field_name(fieldname, 8);
+ }
+
+ /* duties */
+ for (int i = 1; i <= IT5570E_FAN_CURVE_LEN; i++) {
+ snprintf(fieldname, 5, "F%dD%d", fan, i);
+ acpigen_write_field_name(fieldname, 8);
+ }
+
+ /* ramps */
+ for (int i = 1; i < IT5570E_FAN_CURVE_LEN; i++) {
+ snprintf(fieldname, 5, "F%dR%d", fan, i);
+ acpigen_write_field_name(fieldname, 16);
+ }
+ }
+
+ acpigen_pop_len(); /* Field */
+}
+
+/*
+ * Set Fan curve
+ * The function must exist even if the fan curve isn't enabled in devicetree.
+ */
+void ec_fan_curve_fill_ssdt(const struct device *dev)
+{
+ const ec_config_t *config = config_of(dev);
+ const int fan_cnt = read8p(ECRAM + FANC);
+
+ acpigen_write_scope(acpi_device_path(dev));
+ write_fan_opregion(fan_cnt);
+ acpigen_write_method("SFCV", 0);
+
+ if (config->fan_mode == FAN_MODE_CUSTOM) {
+ int curve_cnt = 0;
+
+ /* Check curve count against fan count from EC */
+ for (int i = 0; i < IT5570E_MAX_FAN_CNT; i++)
+ if (*config->fan_curves[i].speed && *config->fan_curves[i].temperature)
+ curve_cnt++;
+
+ if (curve_cnt != fan_cnt) {
+ printk(BIOS_WARNING,
+ "EC: Fan curve count (%d) does not match fan count (%d). "
+ "Check your devicetree!\n", curve_cnt, fan_cnt);
+ goto pop;
+ }
+
+ /*
+ * Check all curves.
+ * Custom mode can only be enabled for all fans or none. Thus, all
+ * custom curves must be valid before custom mode can be enabled.
+ */
+ bool error = false;
+ for (int i = 0; i < fan_cnt; i++) {
+ if (!is_curve_valid(config->fan_curves[i])) {
+ printk(BIOS_ERR,
+ "EC: Fan %d curve invalid. Check your devicetree!\n", i);
+ error = true;
+ }
+ }
+ if (error)
+ goto pop;
+
+ acpigen_write_debug_string("EC: Apply custom fan curve");
+
+ for (int i = 0; i < fan_cnt; i++)
+ write_fan_curve(config->fan_curves[i], i);
+
+ /* Enable custom fan mode */
+ acpigen_write_store_int_to_namestr(0x04, "FDAT");
+ acpigen_emit_namestring("SFCC");
+ acpigen_write_integer(0xd7);
+ }
+
+pop:
+ acpigen_pop_len(); /* Method */
+ acpigen_pop_len(); /* Scope */
+}