Re: [PATCH v12] hwmon: (yogafan) Add support for Lenovo Laptops fan monitoring,
From: Guenter Roeck
Date: Fri Apr 03 2026 - 14:36:13 EST
On 4/3/26 09:15, Sergio Melas wrote:
hwmon: (yogafan) Add support for Lenovo Yoga/Legion and others, now support 95% of
lenovo portfolio.
Duplicate subject, and the subject itself is misleading.
The support is already there, this patch extends it.
This driver provides fan speed monitoring for Lenovo Yoga, Legion, and
IdeaPad laptops by interfacing with the Embedded Controller (EC) via ACPI.
To address low-resolution sampling in Lenovo EC firmware, a Rate-Limited
Lag (RLLag) filter is implemented. The filter ensures a consistent physical
curve regardless of userspace polling frequency.
Hardware identification is performed via DMI-based quirk tables, which
map specific ACPI object paths and register widths (8-bit vs 16-bit)
deterministically.
This update introduces a Linear Estimation engine to support older Lenovo
Yoga and IdeaPad models where the Embedded Controller (EC) reports
discrete PWM steps instead of raw tachometer data.
This to support discrete RPM EC architectures. By mapping these steps (Nmax)
to physical fan characteristics (Rmax),
the driver now provides consistent RPM monitoring for legacy hardware
that previously reported only "levels" or zero-readings. That expanded
greatly the supported model list:
* YOGA & SLIM SERIES (8-bit / Discrete Logic)
- Yoga 14cACN, 14s, 13 (including Aura Edition)
- Yoga Slim 7, 7i, 7 Pro, 7 Carbon
- Yoga Pro 7, 9 (83E2, 83DN)
- Yoga 710, 720, 510 (Discrete Step Logic)
- Yoga 3 14, 11s, Yoga 2 13 (Discrete Step Logic)
- Xiaoxin Pro, Air, 14, 16 (All PRC/Chinese Variants)
* LEGION, LOQ & G-SERIES (16-bit High-Precision Raw)
- Legion 5, 5i, 5 Pro (AMD & Intel 82JW/82JU)
- Legion 7, 7i, 7 Slim (82WQ)
- LOQ 15, 16 (82XV, 83DV)
- GeekPro G5000, G6000 (PRC Gaming Series)
* IDEAPAD & FLEX SERIES (8-bit / Discrete Logic)
- IdeaPad 5, 5i, 5 Pro (81YM, 82FG)
- IdeaPad 3, 3i (Modern 8-bit variants)
- IdeaPad 500S, U31-70 (Discrete Step Logic)
- Flex 5, 5i (81X1)
* THINKBOOK, V-SERIES & LEGACY (Discrete Logic)
- ThinkBook G6, G7 (83AK)
- V330-15IKB, V580
- Legacy U-Series (U330p, U430p)
This update expands the support from 3 to 12 distinct hardware families,
covering over 450 unique models.
It now accounts for 95% of Lenovo's consumer and ultra-portable portfolio
released between 2011 and 2026 through a unified hardware abstraction layer.
The driver exposes the RLLag physical filter parameters (time constant and slew-rate limit)
in SI units (seconds), dynamically synchronizing them with the specific model's maximum RPM
to ensure a consistent physical response across the entire Lenovo product stack.
Signed-off-by: Sergio Melas <sergiomelas@xxxxxxxxx>
---
I am submitting this v12 at -rc6 as I realize we are late in the cycle;
however, this version significantly expands hardware compatibility to
nearly the entire Lenovo consumer line. If you feel it is too late for
this window, please let me know and I will gladly resubmit it at the
opening of the next cycle.
Given the AI feedback:
https://sashiko.dev/#/patchset/20260403161540.894955-1-sergiomelas%40gmail.com
and my own concerns (inline, not a complete review) this will definitely have to wait.
Thanks,
Guenter
v12: Expanded Architecture & Universal Coverage
- Implemented Discrete Level Architecture (Linear Estimation) using the formula
raw_RPM = (Rmax * IN) / Nmax to support legacy and ultra-portable models
reporting fixed PWM steps.
- Added specific DMI-based quirks for Yoga 710, 720, 510, IdeaPad 500S, U31-70,
and Yoga 2/3 series to utilize the new estimation logic.
- Expanded ACPI path probing to include "FAN0", "FA2S", and "FANS" handles,
ensuring out-of-the-box compatibility for ThinkBook G6 and LOQ series.
- Integrated the RLLag filter with discrete steps, mathematically smoothing
abrupt level jumps into a continuous physical RPM curve.
- Refactored driver to store filter constants (Tau, Slew) per-device,
enabling dynamic synchronization with model-specific maximum RPMs.
- Updated Documentation/hwmon/yogafan.rst with the validated Master
HAL Reference Database (2026).
- Expanded support from 3 to 12 distinct hardware families, covering over
450 unique models and 95% of Lenovo's consumer portfolio (2011–2026).
- Fixed Documentation formatting, now table appear correctly.
V11: Multirate Filter & Autoreset Logic
- Mapped ACPI paths directly via DMI quirks.
- Fixed Documentation formatting (0-day robot warnings).
- Implemented 100ms MIN_SAMPLING to address rapid polling concerns.
- Removed redundant platform_set_drvdata() in probe.
- Already Supported Models: Yoga 14c, Slim 7, Pro 7, Pro 9, Legion 5, Legion 7i, LOQ.
v9/10:RLLag V1 Physics & Multiplier Fix
- Implement ACPI handle resolution during probe for better performance (O(1) read).
- Add MODULE_DEVICE_TABLE(dmi, ...) to enable module autoloading.
- Refine RLLag filter documentation and suspend/resume logic.
- Include comprehensive EC architecture research database (8-bit vs 16-bit).
- Validated efficiency on kernels 6.18, 6.19, and 7.0-rc5: 'perf top'
confirms negligible CPU overhead (<0.01%) during active polling.
V08: ACPI Handle Discovery & Initial Probe
- Replaced heuristic multiplier with deterministic DMI Quirk Table.
- Added 'depends on DMI' to Kconfig.
- Verified FOPTD model (1000ms TAU / 1500 RPM/s slew) against hardware traces.
- Increased filter precision to 12-bit fixed-point.
V07: DMI Quirk Table & Device Identification
- Fixed Kconfig: Removed non-existent 'select MATH64'.
- Fixed unused macro: Utilized RPM_FLOOR_LIMIT to implement an
immediate 0-RPM bypass in the filter.
- Clarification: Previous "unified structure" comment meant that all
6 files (driver, docs, metadata) are now in this single atomic patch.
V06: Dual-Fan Support & ACPI Handle Eval
- Unified patch structure (6 files changed).
- Verified FOPTD (First-Order Plus Time Delay) model against hardware
traces (Yoga 14c) to ensure physical accuracy of the 1000ms time constant.
- Fixed a rounding stall: added a +/- 1 RPM floor to the step calculation
to ensure convergence even at high polling frequencies.
- Set MAX_SLEW_RPM_S to 1500 to match physical motor inertia.
- Documentation: Updated to clarify 100-RPM hardware step resolution.
- 32-bit safety: Implemented div64_s64 for coefficient precision.
V05: Raw EC Register Offset Validation
- Fixed 32-bit build failures by using div64_s64 for 64-bit division.
- Extracted magic numbers into constants (RPM_UNIT_THRESHOLD, etc.).
- Fixed filter stall by ensuring a minimum slew limit (limit = 1).
- Refined RPM floor logic to trigger only when hardware reports 0 RPM.
- Resolved 255/256 unit-jump bug by adjusting heuristic thresholds.
v04: Initial HWMON Sysfs Implementation
- Rebased on groeck/hwmon-next branch for clean application.
- Corrected alphabetical sorting in Kconfig and Makefile.
- Technical Validation & FOPTD Verification:
- Implemented RLLag (Rate-Limited Lag) first-order modeling.
- Used 10-bit fixed-point math for alpha calculation to avoid
floating point overhead in the kernel.
- Added 5000ms filter reset for resume/long-polling sanitation.
V03: DSDT Analysis & ACPI Path Mapping
- Added MAINTAINERS entry and full Documentation/hwmon/yogafan.rst.
- Fixed integer overflow in filter math.
- Added support for secondary fan paths (FA2S) for Legion laptops.
V02: Proof-of-Concept Embedded Controller Reads
- Migrated from background worker to passive multirate filtering.
- Implemented dt-based scaling to maximize CPU sleep states.
- Restricted driver to Lenovo hardware via DMI matching.
V01: Initial Module Skeleton & Kbuild Setup
- Initial submission with basic ACPI fan path support.
---
Documentation/hwmon/yogafan.rst | 301 ++++++++++++++++++++++----
drivers/hwmon/Kconfig | 2 +-
drivers/hwmon/yogafan.c | 366 ++++++++++++++++++++++++++++----
3 files changed, 583 insertions(+), 86 deletions(-)
diff --git a/Documentation/hwmon/yogafan.rst b/Documentation/hwmon/yogafan.rst
index c0a449aa8..16ca16edc 100644
--- a/Documentation/hwmon/yogafan.rst
+++ b/Documentation/hwmon/yogafan.rst
@@ -1,56 +1,191 @@
.. SPDX-License-Identifier: GPL-2.0-only
+
===============================================================================================
-Kernel driver yogafan
+Kernel driver yogafan V12
Completely irrelevant.
===============================================================================================
Likely to trigger doc warnings since the next line is longer.
+The yogafan driver provides fan speed monitoring for Lenovo consumer laptops (Yoga, Legion, IdeaPad)
+by interfacing with the Embedded Controller (EC) via ACPI, implementing a Rate-Limited Lag (RLLag)
+filter to ensure smooth and physically accurate RPM telemetry.
+
+
Supported chips:
+----------------
+
+ * YOGA & SLIM SERIES (8-bit / Discrete Logic)
+ - Yoga 14cACN, 14s, 13 (including Aura Edition)
+ - Yoga Slim 7, 7i, 7 Pro, 7 Carbon
+ - Yoga Pro 7, 9 (83E2, 83DN)
+ - Yoga 710, 720, 510 (Discrete Step Logic)
+ - Yoga 3 14, 11s, Yoga 2 13 (Discrete Step Logic)
+ - Xiaoxin Pro, Air, 14, 16 (All PRC/Chinese Variants)
+
+ * LEGION, LOQ & G-SERIES (16-bit High-Precision Raw)
+ - Legion 5, 5i, 5 Pro (AMD & Intel 82JW/82JU)
+ - Legion 7, 7i, 7 Slim (82WQ)
+ - LOQ 15, 16 (82XV, 83DV)
+ - GeekPro G5000, G6000 (PRC Gaming Series)
+
+ * IDEAPAD & FLEX SERIES (8-bit / Discrete Logic)
+ - IdeaPad 5, 5i, 5 Pro (81YM, 82FG)
+ - IdeaPad 3, 3i (Modern 8-bit variants)
+ - IdeaPad 500S, U31-70 (Discrete Step Logic)
+ - Flex 5, 5i (81X1)
+
+ * THINKBOOK, V-SERIES & LEGACY (Discrete Logic)
+ - ThinkBook G6, G7 (83AK)
+ - V330-15IKB, V580
+ - Legacy U-Series (U330p, U430p)
- * Lenovo Yoga, Legion, IdeaPad, Slim, Flex, and LOQ Embedded Controllers
Prefix: 'yogafan'
- Addresses: ACPI handle (See Database Below)
+
+ Addresses: ACPI handle (DMI Quirk Table Fallback)
+
+ Datasheet: Not available; based on ACPI DSDT and EC reverse engineering.
Author: Sergio Melas <sergiomelas@xxxxxxxxx>
Description
-----------
-This driver provides fan speed monitoring for modern Lenovo consumer laptops.
-Most Lenovo laptops do not provide fan tachometer data through standard
-ISA/LPC hardware monitoring chips. Instead, the data is stored in the
-Embedded Controller (EC) and exposed via ACPI.
+This driver provides fan speed monitoring for a wide range of Lenovo consumer
+laptops. Unlike standard ThinkPads, these models do not use the 'thinkpad_acpi'
+interface for fan speed but instead store fan telemetry in the Embedded
+Controller (EC).
+
+The driver interfaces with the ACPI namespace to locate the fan tachometer
+objects. If the ACPI path is not standard, it falls back to a machine-specific
+quirk table based on DMI information.
+
+This driver covers over 95% of Lenovo's consumer and ultra-portable laptop portfolio
+released between 2011 and 2026, providing a unified hardware abstraction layer for diverse
+Embedded Controller (EC) architectures.
+
+The driver exposes the RLLag physical filter parameters (time constant and slew-rate limit) in SI units (seconds),
+dynamically synchronizing them with the specific model's maximum RPM to ensure a consistent physical response
+across the entire Lenovo product stack.
+
Line length.
+Filter Physics (RLLag )
+--------------------------
+
+To address low-resolution tachometer sampling in the Embedded Controller,
+the driver implements a passive discrete-time first-order lag filter
+with slew-rate limiting.
+
+* Multirate Filtering: The filter adapts to the sampling time (dt) of the
+ userspace request.
+* Discrete Logic: For older models (e.g., Yoga 710), it estimates RPM based
+ on discrete duty-cycle steps.
+* Continuous Logic: For modern models (e.g., Legion), it maps raw high-precision
+ units to RPM.
The driver implements a **Rate-Limited Lag (RLLag)** filter to handle
-the low-resolution and jittery sampling found in Lenovo EC firmware.
+low-resolution sampling in Lenovo EC firmware. The update equation is:
+
+ **RPM_state[t+1] = RPM_state[t] + Clamp(Alpha * (raw_RPM[t] - RPM_state[t]), -limit[t], limit[t])**
+
+ Where:
+
+* Time delta between reads:
+
+ **Ts[t] = Sys_time[t+1] - Sys_time[t]**
+
+* Low-pass smoothing factor
+
+ **Alpha = 1 - exp(-Ts[t] / Tau)**
+
+* Time-normalized slew limit
+
+ **limit[t] = MAX_SLEW_RPM_S * Ts[t]**
+
+To avoid expensive floating-point exponential calculations in the kernel,
+we use a first-order Taylor/Bilinear approximation:
+
+
+ **Alpha = Ts / (Tau + Ts)**
+
+Implementing this in the driver state machine:
+
+* Next step filtered RPM:
+ **RPM_state[t+1] = RPM_new**
+* Current step filtered RPM:
+ **RPM_state[t] = RPM_old**
+* Time step Calculation:
+ **Ts = current_time - last_sample_time**
+* Alpha Calculation:
+ **Alpha = Ts / (Tau + Ts)**
+* RPM step Calculation:
+ **step = Alpha * (raw_RPM - RPM_old)**
+* Limit step Calculation:
+ **limit = MAX_SLEW_RPM_S * Ts**
+* RPM physical step Calculation:
+ **step_clamped = clamp(step, -limit, limit)**
+* Update of RPM
+ **RPM_new = RPM_old + step_clamped**
+* Update internal state
+ **RPM_old = RPM_new**
+
+
+The input of the filter (raw_RPM) is derived from the EC using the logic defined in the
+HAL section below.
+
+The driver exposes the RLLag physical filter parameters (time constant and slew-rate limit)
+in SI units (seconds), dynamically synchronizing them with the specific model's maximum RPM
+to ensure a consistent physical response across the entire Lenovo product stack.
+
+This approach inshures that the RLLag filter is a passive discrete-time first-order lag model:
+ - **Smoothing:** Low-resolution step increments are smoothed into 1-RPM increments.
+ - **Slew-Rate Limiting:** Prevents unrealistic readings by capping the change
+ to 1500 RPM/s, matching physical fan inertia.
+ - **Polling Independence:** The filter math scales based on the time delta
+ between userspace reads, ensuring a consistent physical curve regardless
+ of polling frequency.
The above should really be a comment in the driver.
Hardware Identification and Multiplier LogicToo many empty lines.
--------------------------------------------
-The driver supports two distinct EC architectures. Differentiation is handled
+The driver supports three distinct EC architectures. Differentiation is handled
deterministically via a DMI Product Family quirk table during the probe phase,
eliminating the need for runtime heuristics.
+Continuous RPM Reads
+~~~~~~~~~~~~~~~~~~~~
+
1. 8-bit EC Architecture (Multiplier: 100)
- - **Families:** Yoga, IdeaPad, Slim, Flex.
+ - **Families:** Yoga, IdeaPad, Slim, Flex, Xiaoxin.
- **Technical Detail:** These models allocate a single 8-bit register for
tachometer data. Since 8-bit fields are limited to a value of 255, the
BIOS stores fan speed in units of 100 RPM (e.g., 42 = 4200 RPM).
2. 16-bit EC Architecture (Multiplier: 1)
- - **Families:** Legion, LOQ.
+ - **Families:** Legion, LOQ, GeekPro.
- **Technical Detail:** High-performance gaming models require greater
precision for fans exceeding 6000 RPM. These use a 16-bit word (2 bytes)
storing the raw RPM value directly.
-Filter Details:
----------------
+Discrete RPM Reads
+~~~~~~~~~~~~~~~~~~
+
+3. Discrete Level Architecture (Linear Estimation)
+ - **Families:** Yoga 710/510/13, IdeaPad 500S, Legacy U-Series.
+ - **Technical Detail:** Older or ultra-portable EC firmware does not store
+ a real-time tachometer value. Instead, it operates on a fixed number of
+ discrete PWM states (Nmax). The driver translates these levels into an
+ estimated physical RPM using the following linear mapping:
+
+ raw_RPM = (Rmax * IN) / Nmax
+
+ Where:
+ - IN: Current discrete level read from the EC.
+ - Nmax: Maximum number of steps defined in the BIOS (e.g., 59, 255).
+ - Rmax: Maximum physical RPM of the fan motor at full duty cycle.
+
+ - **Filter Interaction:** Because these hardware reads jump abruptly
+ between levels (e.g., from level 4 to 5), the RLLag filter is essential
+ here to simulate mechanical acceleration, smoothing the transition
+ for the final fanX_input attribute.
+
-The RLLag filter is a passive discrete-time first-order lag model that ensures:
- - **Smoothing:** Low-resolution step increments are smoothed into 1-RPM increments.
- - **Slew-Rate Limiting:** Prevents unrealistic readings by capping the change
- to 1500 RPM/s, matching physical fan inertia.
- - **Polling Independence:** The filter math scales based on the time delta
- between userspace reads, ensuring a consistent physical curve regardless
- of polling frequency.
Suspend and Resume
------------------
@@ -72,27 +207,8 @@ fanX_input Filtered fan speed in RPM.
Note: If the hardware reports 0 RPM, the filter is bypassed and 0 is reported
immediately to ensure the user knows the fan has stopped.
-
-====================================================================================================
- LENOVO FAN CONTROLLER: MASTER REFERENCE DATABASE (2026)
-====================================================================================================
-
-MODEL (DMI PN) | FAMILY / SERIES | EC OFFSET | FULL ACPI OBJECT PATH | WIDTH | MULTiplier
-----------------------------------------------------------------------------------------------------
-82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
-80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
-83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
-82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 100
-81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
-82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1
-82JW / 82JU | Legion 5 (AMD) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1
-82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 1
-82WQ | Legion 7i (Int) | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 1
-82XV / 83DV | LOQ 15/16 | 0xFE/0xFF | \_SB.PCI0.LPC0.EC0.FANS /FA2S | 16-bit | 1
-83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
-81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100
-*Legacy* | Pre-2020 Models | 0x06 | \_SB.PCI0.LPC.EC.FAN0 | 8-bit | 100
-----------------------------------------------------------------------------------------------------
+Lenovo Fan HAL
+--------------
METHODOLOGY & IDENTIFICATION:
@@ -110,6 +226,109 @@ METHODOLOGY & IDENTIFICATION:
- 16-bit (Multiplier 1): Standard for Legion/LOQ. Two registers (0xFE/0xFF).
+================================================================================================================
+ LENOVO FAN CONTROLLER: MASTER REFERENCE DATABASE (2026) Hardware Abstraction Layer
+================================================================================================================
+
+Hardware Abstraction Layer: Master Database
+-------------------------------------------
+
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| MODEL | FAMILY / SERIES | OFFSET | FULL ACPI OBJECT PATH | WIDTH | NMAX | RMAX | MULT |
++=============+===================+=========+================================+========+=======+=======+======+
+| 82N7 | Yoga 14cACN | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 0 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80V2 / 81C3 | Yoga 710/720 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 59 | 4500 | 0 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 83E2 / 83DN | Yoga Pro 7/9 | 0xFE | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 0 | 6000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82A2 / 82A3 | Yoga Slim 7 | 0x06 | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 0 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 81YM / 82FG | IdeaPad 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 0 | 4500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80S7 | Yoga 510 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 41 | 4500 | 0 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 81AX | V330-15IKB | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 116 | 4200 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82JW / 82JU | Legion 5 (AMD) | 0xFE/FF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 0 | 6500 | 1 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82JW / 82JU | Legion 5 (AMD) | 0xFE/FF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 0 | 6500 | 1 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82WQ | Legion 7i (Int) | 0xFE/FF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 0 | 8000 | 1 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82WQ | Legion 7i (Int) | 0xFE/FF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 0 | 8000 | 1 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82XV / 83DV | LOQ 15/16 | 0xFE/FF | \_SB.PCI0.LPC0.EC0.FANS (Fan1) | 16-bit | 0 | 6500 | 1 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 82XV / 83DV | LOQ 15/16 | 0xFE/FF | \_SB.PCI0.LPC0.EC0.FA2S (Fan2) | 16-bit | 0 | 6500 | 1 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 83AK | ThinkBook G6 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 0 | 5400 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 81X1 | Flex 5 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 0 | 4500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80SR / 80SX | IdeaPad 500S-13 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 44 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80S1 | IdeaPad 500S-14 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 116 | 5000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80TK | IdeaPad 510S | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 41 | 5100 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80S9 | IdeaPad 710S | 0x95/98 | \_SB.PCI0.LPC0.EC0.FAN1/2 | 8-bit | 72 | 5200 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80KU | U31-70 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 44 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80S1 | U41-70 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 116 | 5000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 80JH | Yoga 3 14 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 80 | 5000 | 0 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 20344 | Yoga 2 13 | 0xAB | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 8 | 4200 | 0 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 2191 / 20191| Yoga 13 | 0xF2/F3 | \_SB.PCI0.LPC0.EC0.FAN1/2 | 8-bit | 255 | 5000 | 0 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| Legacy | Yoga 11s | 0x56 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 80 | 4500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 20GJ / 20GK | ThinkPad 13 | 0x85 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 7 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 1143 | ThinkPad E520 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 | 4200 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 3698 | ThinkPad Helix | 0x2F | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 4500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 20M7 / 20M8 | ThinkPad L380 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN1 | 8-bit | 52 | 4600 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 20NR / 20NS | ThinkPad L390 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 64 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 2464 / 2468 | ThinkPad L530 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 75 | 4400 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 2356 | ThinkPad T430s | 0x2F | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 5000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 20AQ / 20AR | ThinkPad T440s | 0x4E | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 5200 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 20BE / 20BF | ThinkPad T540p | 0x2F | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 5500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 3051 | ThinkPad x121e | 0x2F | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 4500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 4290 | ThinkPad x220i | 0x2F | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 5000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| 2324 / 2325 | ThinkPad x230 | 0x2F | \_SB.PCI0.LPC0.EC0.FANS | 8-bit | 7 | 5000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| Legacy | IdeaPad Y580 | 0x06 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 95 | 5200 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| Legacy | IdeaPad V580 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 100 | 5000 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| Legacy | U160 | 0x95 | \_SB.PCI0.LPC0.EC0.FAN0 | 8-bit | 64 | 4500 | 100 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+| Legacy | U330p/U430p | 0x92 | \_SB.PCI0.LPC0.EC0.FAN0 | 16-bit | 768 | 5000 | 0 |
++-------------+-------------------+---------+--------------------------------+--------+-------+-------+------+
+
+Note for the raw_RPM we have 2 caases :
+
+* Discrete Level Estimation
+ **Nmax > 0 then raw_RPM = (Rmax * IN) / Nmax**
+
+* Continuous Unit Mapping
+ **Nmax = 0 then raw_RPM = IN * Multiplier**
+
+
+
References
----------
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 0081dd097..1b905f8df 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2661,6 +2661,7 @@ config SENSORS_XGENE
If you say yes here you get support for the temperature
and power sensors for APM X-Gene SoC.
+
Unnecessary and unacceptable whitespace change.
config SENSORS_YOGAFAN
tristate "Lenovo Yoga Fan Hardware Monitoring"
depends on ACPI && HWMON && DMI
@@ -2673,7 +2674,6 @@ config SENSORS_YOGAFAN
This driver can also be built as a module. If so, the module
will be called yogafan.
-
Guess you got me there. I am dropping that from the version queued for upstream.
But that is not a reason to introduce another double empty line above.
config SENSORS_INTEL_M10_BMC_HWMON
tristate "Intel MAX10 BMC Hardware Monitoring"
depends on MFD_INTEL_M10_BMC_CORE
diff --git a/drivers/hwmon/yogafan.c b/drivers/hwmon/yogafan.c
index 605cc928f..b557785e0 100644
--- a/drivers/hwmon/yogafan.c
+++ b/drivers/hwmon/yogafan.c
@@ -24,6 +24,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/math64.h>
+#include <linux/hwmon-sysfs.h>
/* Driver Configuration Constants */
#define DRVNAME "yogafan"
@@ -34,43 +35,156 @@
#define MAX_SLEW_RPM_S 1500 /* Maximum allowed change in RPM per second */
#define MAX_SAMPLING 5000 /* Maximum allowed Ts for reset (ms) */
#define MIN_SAMPLING 100 /* Minimum interval between filter updates (ms) */
+#define MIN_TAU_MS 10 /* Minimum value accepted for the time constant (ms) */
+#define MIN_THRESHOLD_RPM 10 /* Minimum value accepted for the RPM threshold (rpm) */
spaces -> tabs
/* RPM Sanitation Constants */
#define RPM_FLOOR_LIMIT 50 /* Snap filtered value to 0 if raw is 0 */
+static uint tau_s = 1;
+module_param(tau_s, uint, 0644);
+MODULE_PARM_DESC(tau_s, "Filter time constant in SECONDS");
+
+static uint slew_time_s = 4;
+module_param(slew_time_s, uint, 0644);
+MODULE_PARM_DESC(slew_time_s, "Seconds to reach Max RPM from zero");
+
+static uint stop_threshold_rpm = 50;
+module_param(stop_threshold_rpm, uint, 0644);
Rationale ? Module parameters are discouraged.
+
struct yogafan_config {
- int multiplier;
- int fan_count;
+ int multiplier; /* Used if n_max == 0 */
+ int fan_count; /* 1 or 2 */
+ int n_max; /* Discrete steps (0 = Continuous) */
+ int r_max; /* Max physical RPM for estimation */
const char *paths[2];
};
struct yoga_fan_data {
acpi_handle active_handles[MAX_FANS];
long filtered_val[MAX_FANS];
+ long raw_val[MAX_FANS];
ktime_t last_sample[MAX_FANS];
- int multiplier;
+ const struct yogafan_config *config;
int fan_count;
+ /* Per-device physics constants */
+ unsigned int internal_tau_ms;
+ unsigned int internal_max_slew_rpm_s;
+ unsigned int device_max_rpm;
};
/* Specific configurations mapped via DMI */
-static const struct yogafan_config yoga_8bit_fans_cfg = {
- .multiplier = 100,
- .fan_count = 1,
- .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", NULL }
+//* --- CONTINUOUS PROFILES (Nmax = 0) --- */
//* ?
+
+/* Standard 8-bit Yoga/IdeaPad (Covers 82N7, Slim 7, etc.) */
+static const struct yogafan_config yoga_continuous_8bit_cfg = {
+ .multiplier = 100, .fan_count = 1, .n_max = 0,
+ .r_max = 5500, /* Verified 14cACN peak */
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FAN0" }
+};
+
+/* Legion / LOQ Gaming (2 Fans, Raw RPM 16-bit) */
+static const struct yogafan_config legion_continuous_16bit_cfg = {
+ .multiplier = 1, .fan_count = 2, .n_max = 0,
+ .r_max = 6500, /* Standard Legion/LOQ peak */
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" }
+};
+
+/* --- DISCRETE ESTIMATION PROFILES (NMAX > 0) --- */
+
+/* Yoga 710/720 (N=59) */
+static const struct yogafan_config yoga_710_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 59, .r_max = 4500,
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
The NULL here and elsewhere is unnecessary.
+};
+
+/* Yoga 510 / Ideapad 510s (N=41) */
+static const struct yogafan_config yoga_510_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 41, .r_max = 4500,
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
+};
+
+/* Ideapad 500S / U31-70 (N=44) */
+static const struct yogafan_config ideapad_500s_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 44, .r_max = 5500,
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
+};
+
+/* Yoga 3 14 / Yoga 11s (N=80) */
+static const struct yogafan_config yoga3_14_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 80, .r_max = 5000,
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", "\\_SB.PCI0.LPC0.EC0.FANS" }
I didn't check all of them, but this contradicts the table in the documentation
which doesn't mention "\\_SB.PCI0.LPC0.EC0.FANS" for this system.
Actually, the same seems to apply to the other entries with one fan but two
table entries as well. This warrants an explanation.
};
-static const struct yogafan_config ideapad_8bit_fan0_cfg = {
- .multiplier = 100,
- .fan_count = 1,
+/* Yoga 2 13 (N=8) */
+static const struct yogafan_config yoga2_13_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 8, .r_max = 4200,
.paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
};
-static const struct yogafan_config legion_16bit_dual_cfg = {
- .multiplier = 1,
- .fan_count = 2,
+/* Yoga 13 (N=255) - Dual Fan */
+static const struct yogafan_config yoga13_discrete_cfg = {
+ .multiplier = 0, .fan_count = 2, .n_max = 255, .r_max = 5000,
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN1", "\\_SB.PCI0.LPC0.EC0.FAN2" }
+};
+
+/* Legacy U330p/U430p (N=768) */
+static const struct yogafan_config legacy_u_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 768, .r_max = 5000,
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", NULL }
+};
+
+/* ThinkPad 13 / Helix / T-Series (Strict Discrete) */
+static const struct yogafan_config thinkpad_discrete_cfg = {
+ .multiplier = 0, .fan_count = 1, .n_max = 7,
+ .r_max = 5500, /* Matching table peak for T540p/TP13 */
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", "\\_SB.PCI0.LPC0.EC0.FANS" }
+};
+
+/* ThinkPad L-Series / V580 (Continuous 8-bit) */
+static const struct yogafan_config thinkpad_l_cfg = {
+ .multiplier = 100, .fan_count = 1, .n_max = 100,
+ .r_max = 5500, /* Matching table peak for L390 */
+ .paths = { "\\_SB.PCI0.LPC0.EC0.FAN0", "\\_SB.PCI0.LPC0.EC0.FAN1" }
+};
+
+/* High Performance (Strict Continuous) */
+static const struct yogafan_config legion_high_perf_cfg = {
+ .multiplier = 1, .fan_count = 2, .n_max = 0,
+ .r_max = 8000, /* Peak for Legion 7i / Yoga Pro 9 */
.paths = { "\\_SB.PCI0.LPC0.EC0.FANS", "\\_SB.PCI0.LPC0.EC0.FA2S" }
};
+/* --- Custom Sysfs Interface for Companion App --- */
+static ssize_t show_custom_rpm(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr);
+ struct yoga_fan_data *data = dev_get_drvdata(dev);
+ int nr = sensor_attr->index;
+
+ /* Using index to differentiate between raw (0,1) and max (2,3) */
+ if (strstr(attr->attr.name, "_max"))
+ return sysfs_emit(buf, "%u\n", data->device_max_rpm);
+
+ return sysfs_emit(buf, "%ld\n", data->raw_val[nr]);
+}
+
+static SENSOR_DEVICE_ATTR(fan1_raw, 0444, show_custom_rpm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_max, 0444, show_custom_rpm, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan2_raw, 0444, show_custom_rpm, NULL, 1);
+static SENSOR_DEVICE_ATTR(fan2_max, 0444, show_custom_rpm, NULL, 1);
Those are a no-go. The _raw attributes are nowhere documented. Document them and
make it debugfs attributes if they are really needed. The _max attributes are
standard sysfs attributes, also not documented, and if they mean anything but
the maximum supported fan speed unacceptable as sysfs attributes.
On top of that, it seels like there are multiple sets of changes in this patch.
Since we are beyond the initial version, the "one logical change per patch" rule
now applies. That also includes the introduction of module parameters.
+
+static struct attribute *yogafan_attrs[] = {
+ &sensor_dev_attr_fan1_raw.dev_attr.attr,
+ &sensor_dev_attr_fan1_max.dev_attr.attr,
+ &sensor_dev_attr_fan2_raw.dev_attr.attr,
+ &sensor_dev_attr_fan2_max.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group yogafan_group = { .attrs = yogafan_attrs };
+static const struct attribute_group *yogafan_groups[] = { &yogafan_group, NULL };
+
static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm)
{
ktime_t now = ktime_get_boottime();
@@ -78,12 +192,21 @@ static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm
long delta, step, limit, alpha;
s64 temp_num;
- if (raw_rpm < RPM_FLOOR_LIMIT) {
+ /* 1. PHYSICAL CLAMP & TELEMETRY: Use per-device device_max_rpm */
+ if (raw_rpm > (long)data->device_max_rpm)
+ raw_rpm = (long)data->device_max_rpm;
+
+ data->raw_val[idx] = raw_rpm;
+
+ /* 2. Threshold logic */
+ if (raw_rpm < (long)(stop_threshold_rpm < MIN_THRESHOLD_RPM
+ ? MIN_THRESHOLD_RPM : stop_threshold_rpm)) {
data->filtered_val[idx] = 0;
data->last_sample[idx] = now;
return;
}
+ /* 3. Auto-reset logic */
if (data->last_sample[idx] == 0 || dt_ms > MAX_SAMPLING) {
data->filtered_val[idx] = raw_rpm;
data->last_sample[idx] = now;
@@ -99,14 +222,16 @@ static void apply_rllag_filter(struct yoga_fan_data *data, int idx, long raw_rpm
return;
}
+ /* 4. V12 PHYSICS: Use per-device internal_tau_ms */
temp_num = dt_ms << 12;
- alpha = (long)div64_s64(temp_num, (s64)(TAU_MS + dt_ms));
+ alpha = (long)div64_s64(temp_num, (s64)(data->internal_tau_ms + dt_ms));
step = (delta * alpha) >> 12;
if (step == 0 && delta != 0)
step = (delta > 0) ? 1 : -1;
- limit = (MAX_SLEW_RPM_S * (long)dt_ms) / 1000;
+ /* 5. V12 SLEW: Use per-device internal_max_slew_rpm_s */
+ limit = ((long)data->internal_max_slew_rpm_s * (long)dt_ms) / 1000;
if (limit < 1)
limit = 1;
@@ -123,7 +248,9 @@ 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);
+ const struct yogafan_config *cfg = data->config;
unsigned long long raw_acpi;
+ long rpm_raw;
acpi_status status;
if (type != hwmon_fan || attr != hwmon_fan_input)
@@ -133,10 +260,29 @@ static int yoga_fan_read(struct device *dev, enum hwmon_sensor_types type,
if (ACPI_FAILURE(status))
return -EIO;
- apply_rllag_filter(data, channel, (long)raw_acpi * data->multiplier);
- *val = data->filtered_val[channel];
+ /* Select Calculation Path */
+ if (cfg->n_max > 0) {
+ /* Formula: (RMAX * IN) / NMAX */
+ rpm_raw = (long)div64_s64((s64)cfg->r_max * raw_acpi, cfg->n_max);
+ } else {
+ /* Formula: IN * Multiplier */
+ rpm_raw = (long)raw_acpi * cfg->multiplier;
+ }
- return 0;
+ /* Smooth via RLLag filter */
+ apply_rllag_filter(data, channel, rpm_raw);
+
+ if (attr == hwmon_fan_input) {
+ *val = data->filtered_val[channel];
+ return 0;
+ }
+ /* This to support KDE auto-scaling */
+ if (attr == hwmon_fan_max) {
+ *val = (long)data->device_max_rpm; /* Use the struct member */
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
}
static umode_t yoga_fan_is_visible(const void *data, enum hwmon_sensor_types type,
@@ -157,10 +303,10 @@ static const struct hwmon_ops yoga_fan_hwmon_ops = {
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),
+ HWMON_F_INPUT | HWMON_F_MAX, HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX, HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX, HWMON_F_INPUT | HWMON_F_MAX,
+ HWMON_F_INPUT | HWMON_F_MAX, HWMON_F_INPUT | HWMON_F_MAX),
Does this even instantiate ? This introduces standard fanX_max attributes and non-standard
Ah, I see: Support is declared here, but the is_visible function still returns 0 for
its visibility. No, that is not how this is supposed to be implemented.
NULL
};
@@ -170,32 +316,149 @@ static const struct hwmon_chip_info yoga_fan_chip_info = {
};
static const struct dmi_system_id yogafan_quirks[] = {
+ /* --- DISCRETE OVERRIDES (Specific matches MUST come first) --- */
{
- .ident = "Lenovo Yoga",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga"),
- },
- .driver_data = (void *)&yoga_8bit_fans_cfg,
+ .ident = "Lenovo Yoga 710",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Yoga 710") },
+ .driver_data = (void *)&yoga_710_discrete_cfg,
I don't think (void *) type casts are needed.
+ },
+ {
+ .ident = "Lenovo Yoga 510",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Yoga 510") },
+ .driver_data = (void *)&yoga_510_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo Ideapad 510s",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Ideapad 510s") },
+ .driver_data = (void *)&yoga_510_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo Ideapad 500S",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Ideapad 500S") },
+ .driver_data = (void *)&ideapad_500s_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo U31-70",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "U31-70") },
+ .driver_data = (void *)&ideapad_500s_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo Yoga 3 14",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "80JH") },
+ .driver_data = (void *)&yoga3_14_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo Yoga 2 13",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "20344") },
+ .driver_data = (void *)&yoga2_13_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo Yoga 13",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "20191") },
+ .driver_data = (void *)&yoga13_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo U330p/U430p",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo u330p") },
+ .driver_data = (void *)&legacy_u_discrete_cfg,
+ },
+ {
+ .ident = "ThinkPad 13",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "ThinkPad 13") },
+ .driver_data = (void *)&thinkpad_discrete_cfg,
+ },
+ {
+ .ident = "ThinkPad Helix",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "3698") },
+ .driver_data = (void *)&thinkpad_discrete_cfg,
+ },
+ {
+ .ident = "ThinkPad X-Series",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "ThinkPad X") },
+ .driver_data = (void *)&thinkpad_discrete_cfg,
+ },
+ {
+ .ident = "ThinkPad T-Series",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "ThinkPad T") },
+ .driver_data = (void *)&thinkpad_discrete_cfg,
+ },
+ {
+ .ident = "Lenovo V330",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "81AX") },
+ .driver_data = (void *)&thinkpad_l_cfg,
+ },
+
+ /* --- SPECIAL PROFILES (Must precede general fallbacks) --- */
+ {
+ .ident = "Lenovo Yoga Pro",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Yoga Pro") },
+ .driver_data = (void *)&legion_high_perf_cfg,
},
+ {
+ .ident = "Lenovo Legion Pro",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Legion P") },
+ .driver_data = (void *)&legion_high_perf_cfg,
+ },
+ {
+ .ident = "Lenovo ThinkPad L",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "ThinkPad L") },
+ .driver_data = (void *)&thinkpad_l_cfg,
+ },
+
+ /* --- CONTINUOUS FALLBACKS (Family matches last) --- */
{
.ident = "Lenovo Legion",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion"),
- },
- .driver_data = (void *)&legion_16bit_dual_cfg,
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "Legion") },
+ .driver_data = (void *)&legion_continuous_16bit_cfg,
+ },
+ {
+ .ident = "Lenovo LOQ",
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "LOQ") },
+ .driver_data = (void *)&legion_continuous_16bit_cfg,
+ },
+ {
+ .ident = "Lenovo Yoga",
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "Yoga") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
},
{
.ident = "Lenovo IdeaPad",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad"),
- },
- .driver_data = (void *)&ideapad_8bit_fan0_cfg,
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "IdeaPad") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
+ },
+ {
+ .ident = "Lenovo Xiaoxin",
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "Xiaoxin") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
+ },
+ {
+ .ident = "Lenovo GeekPro",
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "GeekPro") },
+ .driver_data = (void *)&legion_continuous_16bit_cfg,
+ },
+ {
+ .ident = "Lenovo ThinkBook",
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "ThinkBook") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
+ },
+ {
+ .ident = "Lenovo Slim",
+ .matches = { DMI_MATCH(DMI_PRODUCT_FAMILY, "Slim") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
+ },
+ {
+ .ident = "Lenovo V-Series",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo V") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
+ },
+ {
+ .ident = "Lenovo Aura Edition",
+ .matches = { DMI_MATCH(DMI_PRODUCT_NAME, "Aura") },
+ .driver_data = (void *)&yoga_continuous_8bit_cfg,
},
{ }
};
+
Please refrain from such unnecessary whitespace changes.
MODULE_DEVICE_TABLE(dmi, yogafan_quirks);
static int yoga_fan_probe(struct platform_device *pdev)
@@ -204,6 +467,7 @@ static int yoga_fan_probe(struct platform_device *pdev)
const struct yogafan_config *cfg;
struct yoga_fan_data *data;
struct device *hwmon_dev;
+ acpi_status status;
int i;
dmi_id = dmi_first_match(yogafan_quirks);
@@ -215,22 +479,36 @@ static int yoga_fan_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
- data->multiplier = cfg->multiplier;
+ data->config = cfg;
+
+ /* Global sync for V12 Physics - Now stored per-device */
+ data->device_max_rpm = cfg->r_max ? cfg->r_max : 5000;
+ data->internal_tau_ms = tau_s * 1000;
+ if (data->device_max_rpm == 0)
+ data->device_max_rpm = 5000;
+ data->internal_max_slew_rpm_s = data->device_max_rpm / (slew_time_s ? slew_time_s : 1);
- for (i = 0; i < cfg->fan_count; i++) {
- acpi_status status;
+ for (i = 0; i < 2; i++) {
+ if (!cfg->paths[i])
+ continue;
Why continue ?
for (i = 0; i < 2 && cfg->paths[i]; i++) {
would seem much more straightforward, and I hope there are no plans
to leave the _first_ element of the array in the configuration data empty.
status = acpi_get_handle(NULL, (char *)cfg->paths[i],
&data->active_handles[data->fan_count]);
- if (ACPI_SUCCESS(status))
+
+ if (ACPI_SUCCESS(status)) {
data->fan_count++;
+ if (data->fan_count >= cfg->fan_count)
+ break;
Does this even make sense ? Why would ACPI ever return data for the second
(non-existing) fan ?
+ }
}
if (data->fan_count == 0)
return -ENODEV;
+ /* V12 Update: Pass 'yogafan_groups' to expose raw/max attributes to sysfs */
Just like in the documentation, such version references are unacceptable in the code.
hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, DRVNAME,
- data, &yoga_fan_chip_info, NULL);
+ data, &yoga_fan_chip_info,
+ yogafan_groups);
return PTR_ERR_OR_ZERO(hwmon_dev);
}