From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B821639023F for ; Thu, 19 Mar 2026 13:29:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773926985; cv=none; b=SS5x4E5fF62BGYEEz1F1fjJ8j+z/GtoUr40xk43Z2RGwohKdBbIjPnlTGeNC5snqa0ltXv4krK5BmmPufCoQcMSKRHAbJxfo3UNtYjCIVEDzhn0UQICN3skQj6rMoErjsbekZ1PukBjPtgZVOvpxOwlbNQcBvhZOr8+AgQWgKa4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773926985; c=relaxed/simple; bh=tYIWjxUGjfNKOqEwbymIthAPsZ1FZcs6udQywmZ8BL0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aq0VI15ZaYH4+7a8hyrKYKz/2gQ8Iv7QdbLIfvBrzQdW9J1r9ogOvpe10byioxH4jSm+GjlxoJMELyk9glqDUevUJXWjZgItIvlfg01He9y2nMPMNoMwdMw3PFNXXFKtYyGjjTBnKeND8jcBYK6UC8FX6BNhHoa02xiOsKQAC4c= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=RwW0FYFX; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="RwW0FYFX" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-485410a0a8aso8429685e9.2 for ; Thu, 19 Mar 2026 06:29:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773926982; x=1774531782; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ZSos5A2cxMur7uXRdTUr1JZpSXMZ6e+vR3GpO0nzsGg=; b=RwW0FYFXrg4YylGNWr5NdWx6WPFVTqX58DnsjtuBMa/tlvlAf6mFxQG3+PYk/T/BSH F85N7XgOHukTUy/WOhqoHwh7+Ia/zywlcJxGnrGBPh0Tjx32IiuJqWzcVaISNWW8ADMF eWOC4nxsu9c/8YaNQeFMNP2RJvPh3kz+TX0heWlYSrv/zMlbM4/wP7tNyTDtjMgz8xyK /L6GMl/FNWKEWL53TlaXUNTu9v8spzhm6tkZXVgGZ1Kkhj8rE44FNCZbo1zBm4Z22GuY 4BAIiXWo8u78IBlZXGYD53G9uycw3toifkQyAadEYFN1/+tXmkQtM23QOIVHyUn/u52U gNQQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773926982; x=1774531782; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=ZSos5A2cxMur7uXRdTUr1JZpSXMZ6e+vR3GpO0nzsGg=; b=E3u+aQBhmfPRyxi4NOdarFJjMIqzoGMrFogl1T13Bu/TD/TZhNB9jKmVIvwlsvGptz aBDjztU65PB7ux6W/BiGO73cFwl6n3Qm05w/o5r7XIUgE5G7tabFdLuyvSD4qz52tBoI EWsYpXs6OOzFu+SsVaWUEmzwjprhQ8+AWd+ZMGcTbqailuCao32RD01qo/hfHoegiE7m /mrl2M93ffDIk37udy4BlBdyh/LclU5C7nOZVEAQXlkJL98mZp8uHmLOULxGzIlktoqv 57AsnV+OHLqQLoixP1Qa7B8moCzthzrttuq8oOk3D7YCzrZFLMLhwDZJ7aOfkXvA3XCT eUtg== X-Gm-Message-State: AOJu0YwipImmvy+cphP4l0HUCN3w+hLOUxx6Q5Xv2n8QRd9yiHTwlDP+ 2BXU9r/H22TAwljgb2GyMOYAg+W+U2rTq+2Fl8eYvABsPqUFr3pgeh59uCSWXVgT3pCISgey X-Gm-Gg: ATEYQzxI2dCNaCdO42njJD3WNoV5MpCYRh6afMK8sC2SjeqBPWgGFz7Ik0Y25z5gdid +nfaw/2B0pFUByxWXISmMwCF2iTzNVd0uQIxWYgyZ7bnZUv2YJPPEh2l0N0UjMrObARuHO7kUME Ns97hQ8y2fZwYm3ZE2gPKlySwkNF4wb/ZEDN9fCUSTJJOUAHMnaZsWA670Ny4etKuekve+NIgjd GBHWtXknHtQFRM7DocfYYwtmO6y90DOxTdekd0Yvyco2yQoT1P51meCQIYqKQfgT/3faFLztl7Y wzNYbTkRmejIwjUeCtH6Ngp3OoxJkJruAK/7CdkhyJw0jbCetoArspMQSf5++5Di6GyocQoE0Ed LRf6mec3C3UFCH14jy7xz+8ZFruhldZ+5ccNYPxg6/MEzvSDbQp36pOt44JKHCCO/srvyBbRDSv ytEBoj1Znowvom4MSaOhddENsZLU3liKdMO6MAJTo= X-Received: by 2002:a05:600c:c8f:b0:483:1403:c47f with SMTP id 5b1f17b1804b1-486f4422060mr132654755e9.6.1773926981559; Thu, 19 Mar 2026 06:29:41 -0700 (PDT) Received: from sergio-82n7.fritz.box ([2a01:b600:83d8:1:eddb:b262:41cc:374a]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-486f4b74abdsm70405415e9.5.2026.03.19.06.29.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 06:29:41 -0700 (PDT) From: Sergio Melas To: linux-hwmon@vger.kernel.org, groeck@gmail.com, sergiomelas@gmail.com Cc: platform-driver-x86@vger.kernel.org, jdelvare@suse.com Subject: [PATCH 1/1] hwmon: (yogafan) Add universal Lenovo Yoga/Legion fan driver Date: Thu, 19 Mar 2026 14:29:20 +0100 Message-ID: <20260319132920.275755-2-sergiomelas@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260319132920.275755-1-sergiomelas@gmail.com> References: <20260319132920.275755-1-sergiomelas@gmail.com> Precedence: bulk X-Mailing-List: linux-hwmon@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit - Support universal ACPI fan monitoring for up to 8 fans. - Support various ACPI path naming conventions (FANS, FA2S, FANX). - Implement a passive RLLag (Rate Limited Lag) filter for jumpy EC data. - Use dt-based scaling for physical consistency across read intervals. - Filter parameters verified via tachometer and FOPTD identification. - Use 10-bit fixed-point integer math to avoid floating-point registers. - Support KDE 6 Plasma Sensor compatibility and stable S3 sleep cycles. Signed-off-by: Sergio Melas --- Documentation/hwmon/yoga_fan.rst | 36 +++++ auto_compile_rust_lenovo_drivers.sh | 196 ----------------------- drivers/hwmon/Kconfig | 14 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/yoga_fan.c | 237 ++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 196 deletions(-) create mode 100644 Documentation/hwmon/yoga_fan.rst delete mode 100755 auto_compile_rust_lenovo_drivers.sh create mode 100644 drivers/hwmon/yoga_fan.c diff --git a/Documentation/hwmon/yoga_fan.rst b/Documentation/hwmon/yoga_fan.rst new file mode 100644 index 000000000..37eec7647 --- /dev/null +++ b/Documentation/hwmon/yoga_fan.rst @@ -0,0 +1,36 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver yogafan +===================== + +Supported chips: + * Lenovo Yoga / Legion / IdeaPad Embedded Controllers + Prefix: 'yogafan' + Addresses: ACPI (Dynamic probing of FANS, FA2S, FANX, etc.) + +Description +----------- + +This driver provides fan speed monitoring for modern Lenovo laptops. +It interfaces with the Lenovo Embedded Controller (EC) via ACPI to +retrieve fan tachometer data. + +Many Lenovo ECs report RPM values that oscillate rapidly due to +low-resolution internal sampling. To provide a stable reading in +userspace (e.g., KDE Plasma, MangoHud), this driver implements a +"Passive RLLag" (Rate Limited Lag) filter. + +Filter Logic: +The filter is "passive," meaning it performs no background work. +It calculates the state transition based on the ktime delta (dt) +between read requests. This ensures physical model consistency +while maximizing CPU sleep states and battery life. + +Probing: +The driver dynamically searches for common Lenovo ACPI fan handles. +It does not assume a fixed number of fans, making it compatible +across various Yoga and Legion generations. + +Usage Note: +If your device shows more fans than physically present, the EC is likely +exposing a virtual or secondary hardware channel. diff --git a/auto_compile_rust_lenovo_drivers.sh b/auto_compile_rust_lenovo_drivers.sh deleted file mode 100755 index a3df04f65..000000000 --- a/auto_compile_rust_lenovo_drivers.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/bin/bash -# auto_compile_rust_lenovo_drivers.sh - -##################################################################" -# #" -# Kernel Compile Script #" -# Developed by Sergio Melas 2021-26 #" -# #" -# Email: sergiomelas@gmail.com #" -# Released under GPL V2.0 #" -# #" -##################################################################" - - - -# Fix for dpkg-source email warning -export DEBFULLNAME="Sergio Melas" -export DEBEMAIL="sergiomelas@gmail.com" - - -# Your kernel personalization string -postfix="yoga" - -# ANSI Color Codes -CYAN='\033[0;36m' -GOLD='\033[1;33m' -GREEN='\033[0;32m' -BLUE='\033[1;34m' -NC='\033[0m' - -echo -e "${GOLD} " -echo -e " ##################################################################" -echo -e " # #" -echo -e " # ${CYAN} Kernel Compile Script ${GOLD} #" -echo -e " # ${CYAN} Developed by Sergio Melas 2021-26 ${GOLD} #" -echo -e " # #" -echo -e " # ${BLUE} Email: ${GREEN}sergiomelas@gmail.com ${GOLD} #" -echo -e " # ${BLUE} Released under GPL V2.0 ${GOLD} #" -echo -e " # #" -echo -e " ##################################################################" -echo -e " ${NC}" - - - -# 1. Change to local directory -VAR=$0 -DIR="$(dirname "${VAR}")" -cd "${DIR}" - -# 2. Automated Architecture Detection -ARCH_TYPE=$(uname -m) -case "$ARCH_TYPE" in - x86_64) ARCH_SUFFIX="amd64" ;; - aarch64) ARCH_SUFFIX="arm64" ;; - armv7l) ARCH_SUFFIX="armhf" ;; - i386|i686) ARCH_SUFFIX="i386" ;; - *) ARCH_SUFFIX="$ARCH_TYPE" ;; -esac - -# 3. Combine for the final string -full_postfix="${postfix}-${ARCH_SUFFIX}" - - -# Admin login -sudo ls >/dev/null - -# Install libs -sudo apt-get install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev dwarves debhelper rustc rust-src bindgen rustfmt rust-clippy clang libdw-dev:native bc - -# Configure kernel base -LATEST_CONFIG=$(ls -v /boot/config-* 2>/dev/null | grep -v "$postfix" | tail -n 1) -if [ -n "$LATEST_CONFIG" ]; then - cp -v "$LATEST_CONFIG" .config -else - cp -v /boot/config-$(uname -r) .config -fi - -# --- DRIVER INJECTION (Yoga Fan v4.3) --- -echo -e "${BLUE}Injecting Yoga Fan Driver v4.3...${NC}" -SOURCE_CODE="../../Lenovo Drivers/yoga_fan.c" -TARGET_FILE="./drivers/hwmon/yoga_fan.c" - -if [ -f "$SOURCE_CODE" ]; then - cp "$SOURCE_CODE" "$TARGET_FILE" -else - echo -e "${GOLD}Error: Source not found!${NC}" - exit 1 -fi - -if ! grep -q "yoga_fan.o" ./drivers/hwmon/Makefile; then - echo "obj-\$(CONFIG_SENSORS_YOGA_FAN) += yoga_fan.o" >> ./drivers/hwmon/Makefile -fi - -if ! grep -q "config SENSORS_YOGA_FAN" ./drivers/hwmon/Kconfig; then - cat <> ./drivers/hwmon/Kconfig -config SENSORS_YOGA_FAN - tristate "Lenovo Yoga Fan Hardware Monitoring" - depends on ACPI && HWMON - help - Support for fan RPM on modern Lenovo laptops. -EOF -fi - -# --- START OF OPTIMIZED MODULE CONFIGURATION (YOGA 14c ACN) --- - -# 1. AMD Zen 3 & Power (For Ryzen 5800U) -scripts/config --set-val CONFIG_MZEN3 y # Zen 3 microarchitecture optimization -scripts/config --enable CONFIG_X86_AMD_PSTATE # Modern AMD P-State driver -scripts/config --set-val CONFIG_X86_AMD_PSTATE_DEFAULT_MODE 3 # "Active" mode for performance/watt balance -scripts/config --enable CONFIG_AMD_PMC # Vital for s2idle (Modern Standby) sleep support -scripts/config --enable CONFIG_SENSORS_K10TEMP # Accurate CPU temperature monitoring -scripts/config --enable CONFIG_PINCTRL_AMD # Crucial for Touchpad/GPIO interrupts - -# 2. Graphics (AMD Radeon Vega/Cezanne) -scripts/config --enable CONFIG_DRM_AMDGPU # Main Radeon driver -scripts/config --enable CONFIG_DRM_AMDGPU_USERPTR # Support for OpenCL/ROCm -scripts/config --enable CONFIG_DRM_DISPLAY_HDMI_HELPER # Essential for HDMI/HDR -scripts/config --enable CONFIG_DRM_DISPLAY_DP_HELPER # Essential for DisplayPort/USB-C Alt Mode - -# 3. Lenovo Yoga 14c Hardware Logic (The Fan & Sensor Fix) -scripts/config --set-val CONFIG_HWMON y # REQUIRED: Base framework for all sensors -scripts/config --set-val CONFIG_ACPI_WMI y # REQUIRED: Bridge for Lenovo BIOS -scripts/config --enable CONFIG_WMI_BMOF # REQUIRED: Interpret ACPI binary data -scripts/config --set-val CONFIG_SENSORS_YOGA_FAN m # Compiles Yoga Fan driver as module -scripts/config --enable CONFIG_LEDS_CLASS # Needed for status LEDs -scripts/config --enable CONFIG_LEDS_TRIGGERS # Allows hardware events to trigger LEDs -scripts/config --set-val CONFIG_IDEAPAD_LAPTOP y # Main driver for Yoga Fn keys -scripts/config --enable CONFIG_LENOVO_YMC # Yoga Mode Control (Tablet mode) -scripts/config --enable CONFIG_ACPI_PLATFORM_PROFILE # Enables Fn+Q power modes -scripts/config --enable CONFIG_AMD_SFH_HID # Needed for screen auto-rotation -scripts/config --enable CONFIG_HID_WACOM # Supports the internal stylus -scripts/config --enable CONFIG_HID_MULTITOUCH # Enables 10-point touch screen - -# 4. Enable Rust (2026 Toolchain) -scripts/config --set-val CONFIG_RUST y # Enables Rust infrastructure -scripts/config --set-val MODVERSIONS n # Required for Rust compatibility -scripts/config --set-val GENDWARFKSYMS y # Safe Rust module loading -scripts/config --set-val RANDSTRUCT n # Prevents C-to-Rust memory mismatches -scripts/config --set-val DEBUG_INFO_BTF n # Prevents Rust symbol length conflicts - -# 5. Rust-Powered "Blue Screen" (DRM Panic) -scripts/config --enable CONFIG_DRM_PANIC # Graphical panic core -scripts/config --enable CONFIG_DRM_PANIC_SCREEN_USER # Blue background -scripts/config --enable CONFIG_DRM_PANIC_SCREEN_QR_CODE # Rust-generated scannable QR code - -# 6. Build Tweaks & Strict Debug Stripping -scripts/config --set-str CONFIG_LOCALVERSION "-$full_postfix" # Identifies as -yoga-amd64 -scripts/config --set-val CONFIG_LOCALVERSION_AUTO n # Cleaner versioning -scripts/config --undefine CONFIG_DEBUG_INFO # Strip primary debug symbols -scripts/config --undefine CONFIG_DEBUG_INFO_BTF # Disable BPF Type Format bloat -scripts/config --set-val CONFIG_DEBUG_INFO_NONE y # Explicitly select 'None' -scripts/config --disable CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT # Kills bloated symbols -scripts/config --disable CONFIG_DEBUG_INFO_DWARF4 # Disables old DWARF v4 -scripts/config --disable CONFIG_DEBUG_INFO_DWARF5 # Disables heavy DWARF v5 -scripts/config --disable CONFIG_GDB_SCRIPTS # No Python helpers - -# --- END OF OPTIMIZED MODULE CONFIGURATION --- - -make olddefconfig - -# Get kernel version (e.g., 7.0.0-rc4) -VERSION_BASE=$(make kernelversion) -FULL_VER="${VERSION_BASE}-${full_postfix}" -PKG_VER="${VERSION_BASE}-${full_postfix}" - -echo -e "${BLUE}Starting Kernel Build (uname -r: ${FULL_VER})${NC}" - -# Compile using your naming logic -make -j$(nproc) bindeb-pkg \ - KDEB_PKGVERSION="${PKG_VER}" \ - KDEB_SOURCENAME=linux-upstream \ - DEBUG_INFO=n \ - NO_VMLINUX_DEBUG=1 - -# Change to parent directory -cd ../ - -echo -e "${BLUE}Post-Processing: Cleaning up filenames...${NC}" - -# Perform the exact renaming to remove the redundant version_arch string -# e.g., linux-image-VER_VER_amd64.deb -> linux-image-VER.deb -mv "linux-image-${FULL_VER}_${PKG_VER}_${ARCH_SUFFIX}.deb" "linux-image-${FULL_VER}.deb" -mv "linux-headers-${FULL_VER}_${PKG_VER}_${ARCH_SUFFIX}.deb" "linux-headers-${FULL_VER}.deb" -mv "linux-libc-dev_${PKG_VER}_${ARCH_SUFFIX}.deb" "linux-libc-dev_${FULL_VER}.deb" - -# Clean up -rm -f *.buildinfo *.changes - -# Install the cleaned packages -sudo apt-mark unhold linux-libc-dev -sudo dpkg -i "linux-image-${FULL_VER}.deb" \ - "linux-headers-${FULL_VER}.deb" \ - "linux-libc-dev_${FULL_VER}.deb" -sudo apt-mark hold linux-libc-dev - -echo -e "${CYAN}Success! kernel ${FULL_VER} installed.${NC}" diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 342aa97bf..7d938308d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2454,6 +2454,20 @@ config SENSORS_VEXPRESS the ARM Ltd's Versatile Express platform. It can provide wide range of information like temperature, power, energy. +config SENSORS_YOGAFAN + tristate "Lenovo Yoga/Legion Fan Monitor" + depends on ACPI && DMI + help + If you say yes here you get support for the fan sensors on + modern Lenovo Yoga, Legion, and IdeaPad laptops. + + This driver implements a passive RLLag filter to smooth out + oscillating RPM data reported by the Embedded Controller without + the power overhead of a background worker. + + This driver can also be built as a module. If so, the module + will be called yoga_fan. + config SENSORS_VIA_CPUTEMP tristate "VIA CPU temperature sensor" depends on X86 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 7ac7711f9..d8d4ff834 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -252,3 +252,4 @@ obj-$(CONFIG_PMBUS) += pmbus/ ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG obj-$(CONFIG_SENSORS_YOGA_FAN) += yoga_fan.o + diff --git a/drivers/hwmon/yoga_fan.c b/drivers/hwmon/yoga_fan.c new file mode 100644 index 000000000..31e657146 --- /dev/null +++ b/drivers/hwmon/yoga_fan.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/** + * yoga_fan.c - Lenovo Yoga/Legion Fan Hardware Monitoring Driver + * + * Provides fan speed monitoring for Lenovo Yoga, Legion, and IdeaPad + * laptops by interfacing with the Embedded Controller (EC) via ACPI. + * + * The driver implements a passive discrete-time first-order lag filter + * with slew-rate limiting (RLLag). This filter addresses low-resolution + * tachometer sampling in the EC by smoothing RPM readings based on + * the time delta (dt) between userspace requests, ensuring physical + * consistency without background task overhead. + * + * Copyright (C) 2021-2026 Sergio Melas + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "yogafan" +#define MAX_FANS 8 + +/* Filter Configuration Constants */ +#define TAU_MS 3000 /* Time constant for the first-order lag (ms) */ +#define MAX_SLEW_RPM_S 100 /* Maximum allowed change in RPM per second */ + +struct yoga_fan_data { + const char *active_paths[MAX_FANS]; + long filtered_val[MAX_FANS]; + ktime_t last_update[MAX_FANS]; + int fan_count; +}; + +/** + * apply_rllag_filter - Discrete-time filter update (Passive) + * @data: pointer to driver data + * @idx: fan index + * @raw_rpm: new raw value from ACPI + */ +static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm) +{ + ktime_t now = ktime_get(); + s64 dt_ms; + long delta, step, limit, alpha; + + if (data->last_update[idx] == 0) { + data->filtered_val[idx] = raw_rpm; + data->last_update[idx] = now; + return; + } + + dt_ms = ktime_to_ms(ktime_sub(now, data->last_update[idx])); + + if (dt_ms > 5000) { + data->filtered_val[idx] = raw_rpm; + data->last_update[idx] = now; + return; + } + + if (dt_ms < 1) + return; + + delta = raw_rpm - data->filtered_val[idx]; + + /* Alpha = dt / (Tau + dt) using 10-bit fixed point math */ + alpha = (dt_ms << 10) / (TAU_MS + dt_ms); + step = (delta * alpha) >> 10; + + /* Slew Limit = (MaxSlew * dt) / 1000 */ + limit = (MAX_SLEW_RPM_S * (long)dt_ms) / 1000; + + if (step > limit) + step = limit; + else if (step < -limit) + step = -limit; + + data->filtered_val[idx] += step; + + if (data->filtered_val[idx] < 50) + data->filtered_val[idx] = 0; + + data->last_update[idx] = now; +} + +static int yoga_fan_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct yoga_fan_data *data = dev_get_drvdata(dev); + unsigned long long raw_acpi; + acpi_status status; + long rpm; + + if (type != hwmon_fan || attr != hwmon_fan_input) + return -EOPNOTSUPP; + + status = acpi_evaluate_integer(NULL, (char *)data->active_paths[channel], NULL, &raw_acpi); + if (ACPI_FAILURE(status)) + return -EIO; + + rpm = (raw_acpi > 0 && raw_acpi <= 255) ? ((long)raw_acpi * 100) : (long)raw_acpi; + + apply_rllag_filter(data, channel, rpm); + + *val = data->filtered_val[channel]; + return 0; +} + +static umode_t yoga_fan_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + const struct yoga_fan_data *fan_data = data; + + if (type == hwmon_fan && channel < fan_data->fan_count) + return 0444; + + return 0; +} + +static const struct hwmon_ops yoga_fan_hwmon_ops = { + .is_visible = yoga_fan_is_visible, + .read = yoga_fan_read, +}; + +static const struct hwmon_channel_info *yoga_fan_info[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT, + HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT, HWMON_F_INPUT), + NULL +}; + +static const struct hwmon_chip_info yoga_fan_chip_info = { + .ops = &yoga_fan_hwmon_ops, + .info = yoga_fan_info, +}; + +static int yoga_fan_probe(struct platform_device *pdev) +{ + struct yoga_fan_data *data; + struct device *hwmon_dev; + acpi_handle handle; + int i; + + static const char * const fan_paths[] = { + "\\_SB.PCI0.LPC0.EC0.FANS", // Primary Fan (Yoga 14c) + "\\_SB.PCI0.LPC0.EC0.FA2S", // Secondary Fan (Legion) + "\\_SB.PCI0.LPC0.EC0.FAN0", // IdeaPad / Slim + "\\_SB.PCI0.LPC.EC.FAN0", // Legacy + "\\_SB.PCI0.LPC0.EC.FAN0", // Alternate + }; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->fan_count = 0; + + for (i = 0; i < ARRAY_SIZE(fan_paths); i++) { + if (ACPI_SUCCESS(acpi_get_handle(NULL, (char *)fan_paths[i], &handle))) { + data->active_paths[data->fan_count] = fan_paths[i]; + data->fan_count++; + + if (data->fan_count >= MAX_FANS) + break; + } + } + + if (data->fan_count == 0) + return -ENODEV; + + dev_set_drvdata(&pdev->dev, data); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, DRVNAME, + data, &yoga_fan_chip_info, NULL); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + + +static struct platform_driver yoga_fan_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = yoga_fan_probe, +}; + +static struct platform_device *yoga_fan_device; + +static const struct dmi_system_id yoga_dmi_table[] __initconst = { + { + .ident = "Lenovo", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, yoga_dmi_table); + +static int __init yoga_fan_init(void) +{ + int ret; + + if (!dmi_check_system(yoga_dmi_table)) + return -ENODEV; + + ret = platform_driver_register(&yoga_fan_driver); + if (ret) + return ret; + + yoga_fan_device = platform_device_register_simple(DRVNAME, 0, NULL, 0); + if (IS_ERR(yoga_fan_device)) { + platform_driver_unregister(&yoga_fan_driver); + return PTR_ERR(yoga_fan_device); + } + + return 0; +} + +static void __exit yoga_fan_exit(void) +{ + platform_device_unregister(yoga_fan_device); + platform_driver_unregister(&yoga_fan_driver); +} + +module_init(yoga_fan_init); +module_exit(yoga_fan_exit); + +MODULE_AUTHOR("Sergio Melas "); +MODULE_DESCRIPTION("Lenovo Yoga/Legion Fan Monitor Driver"); +MODULE_LICENSE("GPL"); -- 2.53.0