summaryrefslogtreecommitdiff
path: root/src/ec/clevo
diff options
context:
space:
mode:
Diffstat (limited to 'src/ec/clevo')
-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 */
+}